diff --git a/app/features/sendouq-settings/QSettingsRepository.server.ts b/app/features/sendouq-settings/QSettingsRepository.server.ts index ac0a06540..c44ae3f72 100644 --- a/app/features/sendouq-settings/QSettingsRepository.server.ts +++ b/app/features/sendouq-settings/QSettingsRepository.server.ts @@ -1,6 +1,7 @@ import { db } from "~/db/sql"; import type { Tables, UserMapModePreferences } from "~/db/tables"; import { modesShort, type MainWeaponId } from "~/modules/in-game-lists"; +import { COMMON_USER_FIELDS } from "~/utils/kysely.server"; export async function settingsByUserId(userId: number) { const preferences = await db @@ -100,3 +101,35 @@ export function updateNoScreen({ .where("User.id", "=", userId) .execute(); } + +export function currentTeamByUserId(userId: number) { + return db + .selectFrom("TeamMember") + .innerJoin("Team", "Team.id", "TeamMember.teamId") + .select(["Team.name"]) + .where("TeamMember.userId", "=", userId) + .executeTakeFirst(); +} + +export function findTrustedUsersByGiverId(trustGiverUserId: number) { + return db + .selectFrom("TrustRelationship") + .innerJoin("User", "User.id", "TrustRelationship.trustReceiverUserId") + .select(COMMON_USER_FIELDS) + .where("TrustRelationship.trustGiverUserId", "=", trustGiverUserId) + .execute(); +} + +export function deleteTrustedUser({ + trustGiverUserId, + trustReceiverUserId, +}: { + trustGiverUserId: number; + trustReceiverUserId: number; +}) { + return db + .deleteFrom("TrustRelationship") + .where("TrustRelationship.trustGiverUserId", "=", trustGiverUserId) + .where("TrustRelationship.trustReceiverUserId", "=", trustReceiverUserId) + .execute(); +} diff --git a/app/features/sendouq-settings/q-settings-schemas.server.ts b/app/features/sendouq-settings/q-settings-schemas.server.ts index a772cbd28..4179b21e3 100644 --- a/app/features/sendouq-settings/q-settings-schemas.server.ts +++ b/app/features/sendouq-settings/q-settings-schemas.server.ts @@ -3,6 +3,7 @@ import { languagesUnified } from "~/modules/i18n/config"; import { _action, checkboxValueToBoolean, + id, modeShort, noDuplicates, safeJSONParse, @@ -73,4 +74,8 @@ export const settingsActionSchema = z.union([ _action: _action("UPDATE_NO_SCREEN"), noScreen: z.preprocess(checkboxValueToBoolean, z.boolean()), }), + z.object({ + _action: _action("REMOVE_TRUST"), + userToRemoveTrustFromId: id, + }), ]); diff --git a/app/features/sendouq-settings/routes/q.settings.tsx b/app/features/sendouq-settings/routes/q.settings.tsx index 9f3b652d2..3134bcf0a 100644 --- a/app/features/sendouq-settings/routes/q.settings.tsx +++ b/app/features/sendouq-settings/routes/q.settings.tsx @@ -44,6 +44,11 @@ import { SENDOUQ_WEAPON_POOL_MAX_SIZE, } from "../q-settings-constants"; import { settingsActionSchema } from "../q-settings-schemas.server"; +import { FormMessage } from "~/components/FormMessage"; +import { UsersIcon } from "~/components/icons/Users"; +import { Avatar } from "~/components/Avatar"; +import { FormWithConfirm } from "~/components/FormWithConfirm"; +import { Trans } from "react-i18next"; import "../q-settings.css"; @@ -100,6 +105,13 @@ export const action = async ({ request }: ActionFunctionArgs) => { }); break; } + case "REMOVE_TRUST": { + await QSettingsRepository.deleteTrustedUser({ + trustGiverUserId: user.id, + trustReceiverUserId: data.userToRemoveTrustFromId, + }); + break; + } default: { assertUnreachable(data); } @@ -113,6 +125,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { return { settings: await QSettingsRepository.settingsByUserId(user.id), + trusted: await QSettingsRepository.findTrustedUsersByGiverId(user.id), + team: await QSettingsRepository.currentTeamByUserId(user.id), }; }; @@ -124,6 +138,7 @@ export default function SendouQSettingsPage() { + @@ -587,8 +602,10 @@ function Sounds() { {t("q:settings.sounds.header")} - {isMounted && } - {isMounted && } +
+ {isMounted && } + {isMounted && } +
); } @@ -689,6 +706,80 @@ function SoundSlider() { ); } +function TrustedUsers() { + const { t } = useTranslation(["q"]); + const data = useLoaderData(); + + return ( +
+ + {t("q:settings.trusted.header")} + +
+ {data.trusted.length > 0 ? ( + <> +
+ {data.trusted.map((trustedUser) => { + return ( +
+ +
+ {trustedUser.discordName} +
+ + + +
+ ); + })} + + {t("q:settings.trusted.trustedExplanation")} + +
+ + ) : ( + + {t("q:settings.trusted.noTrustedExplanation")} + + )} + {data.team ? ( + + + In addition to the users above, a member of your team{" "} + {data.team.name} can you add you directly. + + + ) : null} +
+
+ ); +} + function Misc() { const data = useLoaderData(); const [checked, setChecked] = React.useState(Boolean(data.settings.noScreen)); diff --git a/locales/en/q.json b/locales/en/q.json index befa4bb2d..e65dcebff 100644 --- a/locales/en/q.json +++ b/locales/en/q.json @@ -73,6 +73,11 @@ "settings.misc.header": "Misc", "settings.banned": "Banned", "settings.avoid.label": "Avoid {{special}}", + "settings.trusted.header": "Trusted users", + "settings.trusted.confirm": "Remove {{name}} from trusted users?", + "settings.trusted.trustedExplanation": "Trusted users can add you to groups and tournament teams directly. If removed from this list, in the future you need to join via a link they share.", + "settings.trusted.noTrustedExplanation": "You don't currently trust any users. Users can be trusted when you join their SendouQ group or tournament team via a link. Trusted users can add you to groups and tournament teams directly.", + "settings.trusted.teamExplanation": "In addition to the users above, a member of your team <2>{{name}} can you add you directly.", "looking.joiningGroupError": "Before joining another group, leave the current one", "looking.goToSettingsPrompt": "To help group finding set your weapon pool and voice chat status on the settings page",