Files
webapp/app/pages/clientarea/profile/index.vue
Ruslan Bakiev 5d1ce88927
Some checks failed
Build Docker Image / build (push) Failing after 1m20s
Migrate pages to topnav layout
2026-01-08 01:08:25 +07:00

159 lines
5.0 KiB
Vue

<template>
<Section variant="plain" paddingY="md">
<Stack gap="6">
<PageHeader
:title="$t('dashboard.profile')"
:actions="[{ label: t('clientProfile.actions.debugTokens'), icon: 'lucide:bug', to: localePath('/clientarea/profile/debug-tokens') }]"
/>
<Alert v-if="hasError" variant="error">
<Stack gap="1">
<Heading :level="4" weight="semibold">{{ $t('common.error') }}</Heading>
<Text tone="muted">{{ error }}</Text>
</Stack>
</Alert>
<Stack v-if="isLoading" align="center" justify="center" gap="3">
<Spinner />
<Text tone="muted">{{ t('clientProfile.states.loading') }}</Text>
</Stack>
<template v-else>
<Card padding="lg">
<Grid :cols="1" :lg="3" :gap="8">
<GridItem :lg="2">
<Stack gap="4">
<form @submit.prevent="updateProfile">
<Stack gap="4">
<Input
v-model="profileForm.firstName"
type="text"
:label="$t('profile.first_name')"
:placeholder="$t('profile.first_name_placeholder')"
/>
<Input
v-model="profileForm.lastName"
type="text"
:label="$t('profile.last_name')"
:placeholder="$t('profile.last_name_placeholder')"
/>
<Input
v-model="profileForm.phone"
type="tel"
:label="$t('profile.phone')"
:placeholder="$t('profile.phone_placeholder')"
/>
<Button type="submit" :full-width="true" :disabled="isUpdating">
<template v-if="isUpdating">{{ $t('profile.saving') }}...</template>
<template v-else>{{ $t('profile.save') }}</template>
</Button>
</Stack>
</form>
</Stack>
</GridItem>
<GridItem>
<Stack gap="6" align="center">
<Stack gap="3" align="center">
<Heading :level="3">{{ $t('profile.avatar') }}</Heading>
<UserAvatar
:userId="userData?.id"
:firstName="userData?.firstName"
:lastName="userData?.lastName"
:avatarId="userData?.avatarId"
@avatar-changed="handleAvatarChange"
/>
</Stack>
</Stack>
</GridItem>
</Grid>
</Card>
</template>
</Stack>
</Section>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'topnav',
middleware: ['auth-oidc']
})
const localePath = useLocalePath()
const { t } = useI18n()
const { mutate } = useGraphQL()
const userData = useState<{
id?: string
firstName?: string
lastName?: string
phone?: string | null
avatarId?: string | null
} | null>('me', () => null)
const isLoading = ref(true)
const hasError = ref(false)
const error = ref('')
const isUpdating = ref(false)
const avatarDraftId = ref<string | null>(null)
const profileForm = reactive({
firstName: '',
lastName: '',
phone: ''
})
const syncProfileForm = () => {
if (!userData.value) {
hasError.value = true
error.value = t('clientProfile.error.load')
isLoading.value = false
return
}
hasError.value = false
error.value = ''
profileForm.firstName = userData.value.firstName || ''
profileForm.lastName = userData.value.lastName || ''
profileForm.phone = userData.value.phone || ''
avatarDraftId.value = userData.value.avatarId || null
isLoading.value = false
}
const updateProfile = async () => {
try {
isUpdating.value = true
const { UpdateUserDocument } = await import('~/composables/graphql/user/teams-generated')
const result = await mutate(UpdateUserDocument, {
userId: userData.value.id,
input: {
firstName: profileForm.firstName,
lastName: profileForm.lastName,
phone: profileForm.phone,
avatarId: avatarDraftId.value || null
},
}, 'user', 'teams')
if (result?.updateUser?.user) {
userData.value = { ...(userData.value || {}), ...result.updateUser.user }
avatarDraftId.value = userData.value.avatarId || avatarDraftId.value
}
} catch (err) {
hasError.value = true
error.value = err?.message || t('clientProfile.error.save')
} finally {
isUpdating.value = false
}
}
const handleAvatarChange = async (newAvatarId?: string) => {
if (!newAvatarId) return
// Only stage avatar change; will be saved on form submit
avatarDraftId.value = newAvatarId
}
watch(userData, () => {
syncProfileForm()
}, { immediate: true })
</script>