Add updatable profile
This commit is contained in:
+261
-1
@@ -2,10 +2,270 @@
|
||||
definePageMeta({
|
||||
middleware: ['auth']
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { loggedIn, fetch } = useUserSession();
|
||||
const localePath = useLocalePath();
|
||||
const validation = useValidation();
|
||||
watch(loggedIn, (isLoggedIn) => {
|
||||
if (!isLoggedIn) {
|
||||
router.push(localePath('/login'));
|
||||
}
|
||||
});
|
||||
const { data, refresh, pending } = await useFetch('/api/auth/profile');
|
||||
|
||||
const form = reactive({
|
||||
email: data.value!.email,
|
||||
name: data.value!.name
|
||||
});
|
||||
const uploading = ref(false);
|
||||
const changed = computed(
|
||||
() => form.name !== data.value?.name || form.email !== data.value?.email
|
||||
);
|
||||
|
||||
const profileError = ref('');
|
||||
async function submitProfile() {
|
||||
uploading.value = true;
|
||||
profileError.value = '';
|
||||
try {
|
||||
await $fetch(`/api/auth/profile`, {
|
||||
method: 'POST',
|
||||
body: form
|
||||
});
|
||||
|
||||
await refresh();
|
||||
form.email = data.value!.email;
|
||||
form.name = data.value!.name;
|
||||
await fetch();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && 'statusCode' in e && e.statusCode === 409) {
|
||||
// ToDo: i18n
|
||||
profileError.value = 'Email already exists!';
|
||||
} else {
|
||||
profileError.value = parseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
uploading.value = false;
|
||||
}
|
||||
|
||||
const password = reactive({
|
||||
password: '',
|
||||
old: ''
|
||||
});
|
||||
|
||||
const passwordError = ref('');
|
||||
async function submitPassword() {
|
||||
uploading.value = true;
|
||||
passwordError.value = '';
|
||||
try {
|
||||
await $fetch(`/api/auth/password`, {
|
||||
method: 'POST',
|
||||
body: password
|
||||
});
|
||||
|
||||
password.password = '';
|
||||
password.old = '';
|
||||
} catch (e) {
|
||||
if (e instanceof Error && 'statusCode' in e && e.statusCode === 404) {
|
||||
// ToDo: i18n
|
||||
passwordError.value = 'Old password incorrect!';
|
||||
} else {
|
||||
passwordError.value = parseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
uploading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>profile</h1>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<VaCard stripe stripe-color="success">
|
||||
<VaCardTitle>Public profile</VaCardTitle>
|
||||
<VaCardContent v-if="data">
|
||||
<div class="flex flex-col gap-4">
|
||||
<VaInput
|
||||
label="Name"
|
||||
v-model="form.name"
|
||||
clearable
|
||||
:clear-value="data.name"
|
||||
clearable-icon="replay"
|
||||
:disabled="uploading"
|
||||
counter
|
||||
max-length="265"
|
||||
min-length="3"
|
||||
:rules="[
|
||||
validation.required(t('auth.name')),
|
||||
validation.min(t('auth.name'), 3),
|
||||
validation.max(t('auth.name'), 256)
|
||||
]"
|
||||
/>
|
||||
<VaInput
|
||||
label="Email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
clearable
|
||||
:clear-value="data.email"
|
||||
clearable-icon="replay"
|
||||
:disabled="uploading"
|
||||
:rules="[
|
||||
validation.required(t('auth.email')),
|
||||
validation.min(t('auth.email'), 3)
|
||||
]"
|
||||
/>
|
||||
<VaAlert
|
||||
v-if="profileError.length > 0"
|
||||
color="danger"
|
||||
class="w-full text-center"
|
||||
>{{ t(profileError) }}</VaAlert
|
||||
>
|
||||
<VaButton
|
||||
:loading="pending || uploading"
|
||||
color="success"
|
||||
:disabled="!changed || pending || uploading"
|
||||
@click="submitProfile"
|
||||
>Update</VaButton
|
||||
>
|
||||
</div>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
<VaCard stripe stripe-color="danger">
|
||||
<VaCardTitle>Change password</VaCardTitle>
|
||||
<VaCardContent>
|
||||
<div class="flex flex-col gap-4">
|
||||
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
||||
<VaInput
|
||||
v-model="password.old"
|
||||
class="w-full"
|
||||
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||
label="Old password"
|
||||
:placeholder="t('auth.password')"
|
||||
autocomplete="current-password"
|
||||
:minlength="8"
|
||||
required
|
||||
>
|
||||
<template #prependInner>
|
||||
<VaIcon name="key" color="secondary" />
|
||||
</template>
|
||||
|
||||
<template #appendInner>
|
||||
<VaButton
|
||||
v-if="password.old"
|
||||
preset="plain"
|
||||
:title="
|
||||
t(
|
||||
isPasswordVisible.value
|
||||
? 'auth.hidePassword'
|
||||
: 'auth.showPassword'
|
||||
)
|
||||
"
|
||||
@click="isPasswordVisible.value = !isPasswordVisible.value"
|
||||
>
|
||||
<VaIcon
|
||||
:name="
|
||||
isPasswordVisible.value ? 'visibility_off' : 'visibility'
|
||||
"
|
||||
color="primary"
|
||||
/>
|
||||
</VaButton>
|
||||
</template>
|
||||
</VaInput>
|
||||
</VaValue>
|
||||
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
||||
<VaInput
|
||||
v-model="password.password"
|
||||
class="w-full"
|
||||
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||
label="New password"
|
||||
:placeholder="t('auth.password')"
|
||||
autocomplete="current-password"
|
||||
:minlength="8"
|
||||
required
|
||||
>
|
||||
<template #prependInner>
|
||||
<VaIcon name="key" color="secondary" />
|
||||
</template>
|
||||
|
||||
<template #appendInner>
|
||||
<VaButton
|
||||
v-if="password.password"
|
||||
preset="plain"
|
||||
:title="
|
||||
t(
|
||||
isPasswordVisible.value
|
||||
? 'auth.hidePassword'
|
||||
: 'auth.showPassword'
|
||||
)
|
||||
"
|
||||
@click="isPasswordVisible.value = !isPasswordVisible.value"
|
||||
>
|
||||
<VaIcon
|
||||
:name="
|
||||
isPasswordVisible.value ? 'visibility_off' : 'visibility'
|
||||
"
|
||||
color="primary"
|
||||
/>
|
||||
</VaButton>
|
||||
</template>
|
||||
</VaInput>
|
||||
</VaValue>
|
||||
<VaAlert
|
||||
v-if="passwordError.length > 0"
|
||||
color="danger"
|
||||
class="w-full text-center"
|
||||
>{{ t(profileError) }}</VaAlert
|
||||
>
|
||||
<VaButton
|
||||
:loading="pending || uploading"
|
||||
color="success"
|
||||
:disabled="
|
||||
password.password.length < 8 ||
|
||||
password.old.length < 8 ||
|
||||
pending ||
|
||||
uploading
|
||||
"
|
||||
@click="submitPassword"
|
||||
>Update</VaButton
|
||||
>
|
||||
</div>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
<VaCard stripe stripe-color="primary">
|
||||
<VaCardTitle>Uploaded pictures</VaCardTitle>
|
||||
<VaCardContent>
|
||||
<VaCarousel
|
||||
v-if="data"
|
||||
stateful
|
||||
:items="data.uploadedFiles"
|
||||
arrows
|
||||
indicators
|
||||
lazy
|
||||
height="50vh"
|
||||
indicator-trigger="hover"
|
||||
>
|
||||
<template #default="{ item }"
|
||||
><ClickableImage
|
||||
:src="item.url"
|
||||
:title="`${item.answer.quest.title} — ${item.answer.quest.hunt.name}`"
|
||||
/>
|
||||
<NuxtLink
|
||||
:to="
|
||||
$localePath(
|
||||
`/hunt/${sluggy(item.answer.quest.hunt)}/${sluggy(item.answer.quest)}`
|
||||
)
|
||||
"
|
||||
class="absolute top-2 mx-auto rounded bg-gray-200/75 px-2 py-1 text-center text-xl hover:underline"
|
||||
>
|
||||
{{ item.answer.quest.title }} —
|
||||
{{ item.answer.quest.hunt.name }}
|
||||
</NuxtLink></template
|
||||
>
|
||||
</VaCarousel>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user