mirror of
https://github.com/YunoHost/yunohost-portal.git
synced 2024-09-03 20:06:23 +02:00
edit: separate info and password forms
This commit is contained in:
parent
f8b33f803e
commit
8e177e6907
4 changed files with 248 additions and 219 deletions
113
components/UserInfoForm.vue
Normal file
113
components/UserInfoForm.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/yup'
|
||||
import * as yup from 'yup'
|
||||
import { pick } from '@/utils/common'
|
||||
import type { User } from '@/composables/states'
|
||||
import type { Feedback } from '@/composables/form'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const user = await useUser()
|
||||
const loading: Ref<boolean | null> = ref(false)
|
||||
const feedback: Ref<Feedback> = ref(null)
|
||||
|
||||
const { handleSubmit, setFieldError, resetForm, meta } = useForm({
|
||||
validationSchema: toTypedSchema(
|
||||
yup.object({
|
||||
fullname: yup.string().required().min(2),
|
||||
mailalias: yup.array().of(yup.string().email().required()).required(),
|
||||
mailforward: yup.array().of(yup.string().email().required()).required(),
|
||||
}),
|
||||
),
|
||||
initialValues: {
|
||||
...pick(user.value, 'fullname', 'mailalias', 'mailforward'),
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => meta.value.dirty,
|
||||
(value) => {
|
||||
// remove loading and feedback on edition
|
||||
if (value) {
|
||||
loading.value = null
|
||||
feedback.value = null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const onSubmit = handleSubmit(async (form) => {
|
||||
loading.value = true
|
||||
|
||||
const { error, data } = await useApi<
|
||||
Pick<User, 'fullname' | 'mailalias' | 'mailforward'>
|
||||
>('/update', {
|
||||
method: 'PUT',
|
||||
body: form,
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
// Reset form dirty state but keep previous values
|
||||
resetForm({ values: form })
|
||||
const errData = error.value.data
|
||||
let message
|
||||
|
||||
if (errData.path) {
|
||||
setFieldError(errData.path, errData.error)
|
||||
message = t('form_has_errors')
|
||||
} else {
|
||||
message = errData.error || errData
|
||||
}
|
||||
feedback.value = {
|
||||
variant: 'error',
|
||||
icon: 'alert',
|
||||
message,
|
||||
}
|
||||
} else if (data.value) {
|
||||
Object.assign(user.value, data)
|
||||
resetForm({
|
||||
values: data.value,
|
||||
})
|
||||
feedback.value = {
|
||||
variant: 'success',
|
||||
icon: 'thumb-up',
|
||||
message: t('user_profile_updated'),
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YForm :loading="loading" :feedback="feedback" @submit.prevent="onSubmit">
|
||||
<FormField name="fullname" :label="$t('fullname')" class="mb-10">
|
||||
<TextInput
|
||||
name="fullname"
|
||||
type="text"
|
||||
:placeholder="$t('fullname')"
|
||||
autocomplete="name"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<TextInputList
|
||||
name="mailalias"
|
||||
type="email"
|
||||
:label="$t('mail_addresses')"
|
||||
:input-label="$t('mail_address')"
|
||||
:button-label="$t('add_mail')"
|
||||
:placeholder="$t('new_mail')"
|
||||
class="mb-10"
|
||||
/>
|
||||
|
||||
<TextInputList
|
||||
name="mailforward"
|
||||
type="email"
|
||||
:label="$t('mail_forwards')"
|
||||
:input-label="$t('mail_forward')"
|
||||
:button-label="$t('add_forward')"
|
||||
:placeholder="$t('new_forward')"
|
||||
/>
|
||||
</YForm>
|
||||
</template>
|
119
components/UserPasswordForm.vue
Normal file
119
components/UserPasswordForm.vue
Normal file
|
@ -0,0 +1,119 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/yup'
|
||||
import * as yup from 'yup'
|
||||
import { exclude } from '@/utils/common'
|
||||
import type { Feedback } from '@/composables/form'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading: Ref<boolean | null> = ref(false)
|
||||
const feedback: Ref<Feedback> = ref(null)
|
||||
|
||||
const { handleSubmit, setFieldError, resetForm, meta } = useForm({
|
||||
validationSchema: toTypedSchema(
|
||||
yup.object({
|
||||
currentpassword: yup.string().required(),
|
||||
newpassword: yup
|
||||
.string()
|
||||
.matches(/.{8,}/, {
|
||||
excludeEmptyString: true,
|
||||
message: { key: 'v.string_too_short', values: { min: 8 } },
|
||||
})
|
||||
.required(),
|
||||
confirmpassword: yup
|
||||
.string()
|
||||
.oneOf([yup.ref('newpassword')], 'v.password_not_match')
|
||||
.required(),
|
||||
}),
|
||||
),
|
||||
initialValues: {
|
||||
currentpassword: '',
|
||||
newpassword: '',
|
||||
confirmpassword: '',
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => meta.value.dirty,
|
||||
(value) => {
|
||||
// remove loading and feedback on edition
|
||||
if (value) {
|
||||
loading.value = null
|
||||
feedback.value = null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const onSubmit = handleSubmit(async (form) => {
|
||||
loading.value = true
|
||||
|
||||
const { error, data } = await useApi('/update', {
|
||||
method: 'PUT',
|
||||
body: exclude(form, 'confirmpassword'),
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
// Reset form dirty state and remove previous entries
|
||||
resetForm({
|
||||
values: { currentpassword: '', newpassword: '', confirmpassword: '' },
|
||||
})
|
||||
const errData = error.value.data
|
||||
let message
|
||||
|
||||
if (errData.path) {
|
||||
setFieldError(errData.path, errData.error)
|
||||
message = t('form_has_errors')
|
||||
} else {
|
||||
message = errData.error || errData
|
||||
}
|
||||
feedback.value = {
|
||||
variant: 'error',
|
||||
icon: 'alert',
|
||||
message,
|
||||
}
|
||||
} else if (data.value) {
|
||||
// reset loggedin state and redirect to login
|
||||
// FIXME toast ok message
|
||||
useIsLoggedIn().value = false
|
||||
return navigateTo('/login')
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YForm :loading="loading" :feedback="feedback" @submit.prevent="onSubmit">
|
||||
<FormField
|
||||
name="currentpassword"
|
||||
:label="$t('current_password')"
|
||||
class="mb-3"
|
||||
>
|
||||
<TextInput
|
||||
name="currentpassword"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
name="newpassword"
|
||||
:label="$t('new_password')"
|
||||
:description="$t('good_practices_about_user_password')"
|
||||
class="mb-3"
|
||||
>
|
||||
<TextInput
|
||||
name="newpassword"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField name="confirmpassword" :label="$t('confirm_new_password')">
|
||||
<TextInput name="confirmpassword" type="password" class="w-full" />
|
||||
</FormField>
|
||||
</YForm>
|
||||
</template>
|
|
@ -47,6 +47,7 @@
|
|||
"footer": "Skip to footer"
|
||||
},
|
||||
"theme": "Theme",
|
||||
"user_profile_updated": "User personal information updated with success!",
|
||||
"username": "Username",
|
||||
"visitor": "Visitor",
|
||||
"v": {
|
||||
|
|
234
pages/edit.vue
234
pages/edit.vue
|
@ -1,19 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/yup'
|
||||
import * as yup from 'yup'
|
||||
import { pick, exclude } from '@/utils/common'
|
||||
import type { User } from '@/composables/states'
|
||||
|
||||
const { t, locale, locales } = useI18n()
|
||||
|
||||
useHead({
|
||||
title: t('footerlink_edit'),
|
||||
})
|
||||
|
||||
const user = await useUser()
|
||||
|
||||
// Browser
|
||||
const localesAsOptions = computed(() => {
|
||||
return locales.value.map((locale) => ({
|
||||
text: locale.name,
|
||||
|
@ -60,227 +51,32 @@ const themesAsOptions = [
|
|||
text: theme.charAt(0).toUpperCase() + theme.slice(1),
|
||||
value: theme,
|
||||
}))
|
||||
// Server
|
||||
const loading: Ref<boolean | null> = ref(false)
|
||||
const feedback: Ref<{
|
||||
variant: 'success' | 'warning' | 'error'
|
||||
icon: string
|
||||
message: string
|
||||
} | null> = ref(null)
|
||||
|
||||
const { handleSubmit, setFieldError, resetForm, meta } = useForm({
|
||||
validationSchema: toTypedSchema(
|
||||
yup.object({
|
||||
// username: yup.string().required(),
|
||||
fullname: yup.string().required().min(2),
|
||||
currentpassword: yup
|
||||
.string()
|
||||
.when('newpassword', ([newpassword], schema) => {
|
||||
return newpassword ? schema.required() : schema
|
||||
}),
|
||||
newpassword: yup.string().matches(/.{8,}/, {
|
||||
excludeEmptyString: true,
|
||||
message: { key: 'v.string_too_short', values: { min: 8 } },
|
||||
}),
|
||||
confirmpassword: yup
|
||||
.string()
|
||||
.when('newpassword', ([newpassword], schema) => {
|
||||
return newpassword
|
||||
? schema.oneOf([yup.ref('newpassword')], 'v.password_not_match')
|
||||
: schema
|
||||
}),
|
||||
mailalias: yup.array().of(yup.string().email().required()).required(),
|
||||
mailforward: yup.array().of(yup.string().email().required()).required(),
|
||||
}),
|
||||
),
|
||||
initialValues: {
|
||||
currentpassword: '',
|
||||
newpassword: '',
|
||||
confirmpassword: '',
|
||||
...pick(user.value, 'fullname', 'mailalias', 'mailforward'),
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => meta.value.dirty,
|
||||
(value) => {
|
||||
// remove loading and feedback on edition
|
||||
if (value) {
|
||||
loading.value = null
|
||||
feedback.value = null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const onSubmit = handleSubmit(async (form) => {
|
||||
loading.value = true
|
||||
|
||||
const { error, data } = await useApi<
|
||||
Pick<User, 'fullname' | 'mailalias' | 'mailforward'>
|
||||
>('/update', {
|
||||
method: 'PUT',
|
||||
body: exclude(form, 'confirmpassword'),
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
// Reset form dirty state but keep previous values
|
||||
resetForm({ values: form })
|
||||
const errData = error.value.data
|
||||
let message
|
||||
|
||||
if (errData.path) {
|
||||
setFieldError(errData.path, errData.error)
|
||||
message = t('form_has_errors')
|
||||
} else {
|
||||
message = errData.error || errData
|
||||
}
|
||||
feedback.value = {
|
||||
variant: 'error',
|
||||
icon: 'alert',
|
||||
message,
|
||||
}
|
||||
} else if (data.value) {
|
||||
// redirect on password change
|
||||
if (form.newpassword) {
|
||||
useIsLoggedIn().value = false
|
||||
return navigateTo('/login')
|
||||
}
|
||||
|
||||
Object.assign(user.value, data)
|
||||
resetForm({
|
||||
values: {
|
||||
...data.value,
|
||||
currentpassword: '',
|
||||
newpassword: '',
|
||||
confirmpassword: '',
|
||||
},
|
||||
})
|
||||
feedback.value = {
|
||||
variant: 'success',
|
||||
icon: 'thumb-up',
|
||||
message: t('user_profile_updated'),
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageTitle :text="$t('footerlink_edit')" />
|
||||
|
||||
<section>
|
||||
<h2 class="text-3xl">{{ t('edit_personal_settings') }}</h2>
|
||||
<div class="lg:flex lg:justify-between">
|
||||
<section
|
||||
class="lg:w-1/2 lg:me-20 h-full card card-body border border-neutral my-10"
|
||||
>
|
||||
<h2 class="text-3xl mb-3">{{ t('edit_personal_settings') }}</h2>
|
||||
|
||||
<form novalidate class="my-10" @submit="onSubmit">
|
||||
<div class="lg:flex lg:justify-between">
|
||||
<div class="lg:w-1/2 lg:me-20">
|
||||
<!-- <FormField name="username" :label="$t('username')" class="mb-3">
|
||||
<TextInput
|
||||
name="username"
|
||||
type="text"
|
||||
:placeholder="$t('username')"
|
||||
disabled
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField> -->
|
||||
<UserInfoForm />
|
||||
</section>
|
||||
|
||||
<FormField name="fullname" :label="$t('fullname')" class="mb-10">
|
||||
<TextInput
|
||||
name="fullname"
|
||||
type="text"
|
||||
:placeholder="$t('fullname')"
|
||||
autocomplete="name"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
<section class="lg:w-1/2 card card-body border border-neutral my-10">
|
||||
<h2 class="text-3xl mb-3">{{ $t('change_password') }}</h2>
|
||||
|
||||
<TextInputList
|
||||
name="mailalias"
|
||||
type="email"
|
||||
:label="$t('mail_addresses')"
|
||||
:input-label="$t('mail_address')"
|
||||
:button-label="$t('add_mail')"
|
||||
:placeholder="$t('new_mail')"
|
||||
class="mb-10"
|
||||
/>
|
||||
<UserPasswordForm />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<TextInputList
|
||||
name="mailforward"
|
||||
type="email"
|
||||
:label="$t('mail_forwards')"
|
||||
:input-label="$t('mail_forward')"
|
||||
:button-label="$t('add_forward')"
|
||||
:placeholder="$t('new_forward')"
|
||||
/>
|
||||
</div>
|
||||
<section class="card card-body border border-neutral my-10">
|
||||
<h2 class="text-3xl mb-3">{{ t('edit_browser_settings') }}</h2>
|
||||
|
||||
<fieldset class="basis-1/2 mt-10 lg:mt-0">
|
||||
<legend class="text-xl mb-3">{{ $t('change_password') }}</legend>
|
||||
|
||||
<FormField
|
||||
name="currentpassword"
|
||||
:label="$t('current_password')"
|
||||
class="mb-3"
|
||||
>
|
||||
<TextInput
|
||||
name="currentpassword"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
name="newpassword"
|
||||
:label="$t('new_password')"
|
||||
:description="$t('good_practices_about_user_password')"
|
||||
class="mb-3"
|
||||
>
|
||||
<TextInput
|
||||
name="newpassword"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
name="confirmpassword"
|
||||
:label="$t('confirm_new_password')"
|
||||
>
|
||||
<TextInput
|
||||
name="confirmpassword"
|
||||
type="password"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- Success + generic error announcement -->
|
||||
<BaseAlert v-show="feedback" v-bind="feedback" class="mb-10" />
|
||||
|
||||
<!-- SR "loading" announcement -->
|
||||
<BaseAlert
|
||||
:message="loading ? $t('api.processing') : ''"
|
||||
class="sr-only"
|
||||
assertive
|
||||
/>
|
||||
|
||||
<div class="flex mt-10">
|
||||
<NuxtLink to="/" class="btn ms-auto me-2">
|
||||
{{ $t('cancel') }}
|
||||
</NuxtLink>
|
||||
<SubmitButton :loading="loading" variant="success" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="my-10">
|
||||
<h2 class="text-4xl font-bold">{{ t('edit_browser_settings') }}</h2>
|
||||
|
||||
<form novalidate class="my-10" @submit.prevent>
|
||||
<form novalidate @submit.prevent>
|
||||
<div role="group" class="flex align mb-3">
|
||||
<!-- eslint-disable-next-line vuejs-accessibility/label-has-for -->
|
||||
<label for="language" class="label me-3">{{ t('language') }}</label>
|
||||
|
|
Loading…
Reference in a new issue