From de0eaee5219b351cd03614cac4745ff0e80a51bd Mon Sep 17 00:00:00 2001 From: Pascal Syma Date: Fri, 31 Jul 2026 01:48:31 +0200 Subject: [PATCH] feat(team): add team settings with edit, leave, and kick functionality --- app/components/TeamAddModal.vue | 2 +- app/components/TeamJoinModal.vue | 2 +- app/components/TeamSettingsModal.vue | 277 ++++++++++++++++++ app/lang/de.json | 18 ++ app/lang/en.json | 18 ++ app/pages/hunt/[huntSlug]/index.vue | 31 +- .../hunt/[huntId]/team/[teamId]/index.put.ts | 57 ++++ .../hunt/[huntId]/team/[teamId]/kick.post.ts | 60 ++++ server/api/hunt/[huntId]/team/index.get.ts | 65 ++++ server/api/hunt/[huntId]/team/leave.delete.ts | 58 ++++ 10 files changed, 579 insertions(+), 9 deletions(-) create mode 100644 app/components/TeamSettingsModal.vue create mode 100644 server/api/hunt/[huntId]/team/[teamId]/index.put.ts create mode 100644 server/api/hunt/[huntId]/team/[teamId]/kick.post.ts create mode 100644 server/api/hunt/[huntId]/team/index.get.ts create mode 100644 server/api/hunt/[huntId]/team/leave.delete.ts diff --git a/app/components/TeamAddModal.vue b/app/components/TeamAddModal.vue index dc52d2c..0b2bfde 100644 --- a/app/components/TeamAddModal.vue +++ b/app/components/TeamAddModal.vue @@ -55,7 +55,7 @@ async function submit() { t('comp.team_add.create_a_new_team') }} -

{{ t('comp.team_add.create_a_new_team') }}

+

{{ t('comp.team_add.create_a_new_team') }}

-

{{ t('comp.team_join.join_a_team') }}

+

{{ t('comp.team_join.join_a_team') }}

+import { parseError } from '#shared/utils/error'; + +const props = defineProps<{ + huntId: number; +}>(); + +const emit = defineEmits<{ + update: []; +}>(); + +const { t } = useI18n(); +const { init } = useToast(); +const { confirm } = useModal(); +const validation = useValidation(); +const { user } = useUserSession(); + +const isOpen = defineModel('modelValue', { default: false }); + +type TeamMember = { + member: { + id: number; + name: string; + }; +}; + +type TeamDetails = { + id: number; + name: string; + description: string; + password: string; + ownerId: number; + owner: { id: number; name: string }; + members: TeamMember[]; +}; + +const team = ref(null); +const loading = ref(false); +const pending = ref(false); +const error = ref(''); + +const form = reactive({ + name: '', + description: '', + password: '' +}); + +const isOwner = computed( + () => !!team.value && !!user.value && team.value.ownerId === user.value.id +); + +watch(isOpen, async (visible) => { + if (visible) { + await loadTeam(); + } +}); + +async function loadTeam() { + loading.value = true; + error.value = ''; + try { + team.value = await $fetch(`/api/hunt/${props.huntId}/team`); + form.name = team.value!.name; + form.description = team.value!.description; + form.password = team.value!.password; + } catch (e) { + error.value = parseError(e); + } + loading.value = false; +} + +async function save() { + if (!team.value || pending.value) { + return; + } + pending.value = true; + error.value = ''; + try { + await $fetch(`/api/hunt/${props.huntId}/team/${team.value.id}`, { + method: 'PUT', + body: { + name: form.name, + description: form.description, + password: form.password + } + }); + init({ message: t('comp.team_settings.saved'), color: 'success' }); + await loadTeam(); + emit('update'); + } catch (e) { + error.value = parseError(e); + } + pending.value = false; +} + +async function leaveTeam() { + const ok = await confirm({ + title: t('comp.team_settings.leave_title'), + message: t('comp.team_settings.leave_confirm'), + okText: t('auth.accept') + }); + if (!ok) { + return; + } + pending.value = true; + error.value = ''; + try { + await $fetch(`/api/hunt/${props.huntId}/team/leave`, { + method: 'DELETE' + }); + init({ message: t('comp.team_settings.left'), color: 'success' }); + isOpen.value = false; + emit('update'); + } catch (e) { + error.value = parseError(e); + } + pending.value = false; +} + +async function kickMember(member: TeamMember) { + const ok = await confirm({ + title: t('comp.team_settings.kick_title'), + message: t('comp.team_settings.kick_confirm', { + name: member.member.name + }), + okText: t('auth.accept') + }); + if (!ok) { + return; + } + pending.value = true; + error.value = ''; + try { + await $fetch(`/api/hunt/${props.huntId}/team/${team.value!.id}/kick`, { + method: 'POST', + body: { userId: member.member.id } + }); + init({ message: t('comp.team_settings.kicked'), color: 'success' }); + await loadTeam(); + emit('update'); + } catch (e) { + error.value = parseError(e); + } + pending.value = false; +} + + + + + diff --git a/app/lang/de.json b/app/lang/de.json index 37d6a61..1fe9106 100644 --- a/app/lang/de.json +++ b/app/lang/de.json @@ -32,6 +32,23 @@ "no_team_yet": "Es wurde noch kein Team erstellt, gründe das erste!", "select_a_team": "Wähle ein Team aus", "team_preview": "'{team}' von '{owner}', {members} Mitglieder" + }, + "team_settings": { + "description": "Beschreibung", + "invite_code": "Einladungscode", + "kick": "Entfernen", + "kick_confirm": "Möchtest du {name} wirklich aus dem Team entfernen?", + "kick_title": "Mitglied entfernen", + "kicked": "Mitglied entfernt!", + "leave": "Team verlassen", + "leave_confirm": "Möchtest du das Team wirklich verlassen?", + "leave_title": "Team verlassen", + "left": "Du hast das Team verlassen!", + "members": "Mitglieder", + "owner": "Eigentümer", + "saved": "Team aktualisiert!", + "team_name": "Teamname", + "title": "Team-Einstellungen" } }, "enums": { @@ -143,6 +160,7 @@ "showcase": "Showcase", "team_count": "Keine Teams beigetreten, so wie du! | Ein Team ist beigetreten, du noch nicht! | {count} Teams sind beigetreten, du noch nicht!", "team_count_joined": "Keine Teams beigetreten, so wie du! | Ein Team ist beigetreten, du auch! | {count} Teams sind beigetreten, du auch!", + "team_settings": "Team-Einstellungen", "teams": "Teams", "total_points": "Punktestand: {score}", "your_team": "Dein Team:" diff --git a/app/lang/en.json b/app/lang/en.json index 2144914..294fe8e 100644 --- a/app/lang/en.json +++ b/app/lang/en.json @@ -32,6 +32,23 @@ "no_team_yet": " No team has been created yet, be the first!", "select_a_team": "Select a team", "team_preview": "'{team}' of '{owner}', {members} members" + }, + "team_settings": { + "description": "Description", + "invite_code": "Invite code", + "kick": "Remove", + "kick_confirm": "Do you really want to remove {name} from the team?", + "kick_title": "Remove member", + "kicked": "Member removed!", + "leave": "Leave team", + "leave_confirm": "Do you really want to leave the team?", + "leave_title": "Leave team", + "left": "You left the team!", + "members": "Members", + "owner": "Owner", + "saved": "Team updated!", + "team_name": "Team name", + "title": "Team settings" } }, "enums": { @@ -143,6 +160,7 @@ "showcase": "Showcase", "team_count": "No teams joined, just like you! | One team joined, unlike you! | {count} teams joined, unlike you!", "team_count_joined": "No teams joined, just like you! | One team joined, just like you! | {count} teams joined, just like you!", + "team_settings": "Team settings", "teams": "Teams", "total_points": "Total points: {score}", "your_team": "Your team:" diff --git a/app/pages/hunt/[huntSlug]/index.vue b/app/pages/hunt/[huntSlug]/index.vue index edb8a9e..ea5a56b 100644 --- a/app/pages/hunt/[huntSlug]/index.vue +++ b/app/pages/hunt/[huntSlug]/index.vue @@ -1,6 +1,7 @@