change basic form to form context
This commit is contained in:
@@ -93,6 +93,7 @@
|
||||
"settings_form_description": "Description",
|
||||
"settings_form_keywords": "keywords",
|
||||
"settings_form_language": "Language",
|
||||
"settings_form_select_language": "Please select language",
|
||||
"settings_ui_title": "Settings",
|
||||
"settings_messages_update_success": "Updated settings successfully!",
|
||||
"settings_messages_update_fail": "Update fail!",
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
"settings_form_description": "Mô tả website",
|
||||
"settings_form_keywords": "Từ khóa",
|
||||
"settings_form_language": "Ngôn ngữ",
|
||||
"settings_form_select_language": "Hãy chọn ngôn ngữ",
|
||||
"settings_ui_title": "Cài đặt",
|
||||
"settings_messages_update_success": "Cập nhật cài đặt thành công!",
|
||||
"settings_messages_update_fail": "Cập nhật cài đặt thất bại!",
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { userBanSchema } from '@/service/user.schema';
|
||||
import { WarningIcon } from '@phosphor-icons/react';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { UserWithRole } from 'better-auth/plugins';
|
||||
import { Alert, AlertDescription, AlertTitle } from '../ui/alert';
|
||||
import { Button } from '../ui/button';
|
||||
import { DialogClose, DialogFooter } from '../ui/dialog';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '../ui/select';
|
||||
import { Textarea } from '../ui/textarea';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
import { useBanContext } from '../user/ban-user-dialog';
|
||||
|
||||
type FormProps = {
|
||||
@@ -25,7 +16,7 @@ type FormProps = {
|
||||
const BanUserForm = ({ data }: FormProps) => {
|
||||
const { setSubmitData, setOpen, setOpenConfirm } = useBanContext();
|
||||
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
id: data.id,
|
||||
banReason: '',
|
||||
@@ -59,99 +50,33 @@ const BanUserForm = ({ data }: FormProps) => {
|
||||
<AlertTitle>
|
||||
{m.profile_form_name()}: {data.name}
|
||||
</AlertTitle>
|
||||
<AlertDescription className="sr-only">adá</AlertDescription>
|
||||
<AlertDescription className="sr-only">{data.name}</AlertDescription>
|
||||
</Alert>
|
||||
<form.Field
|
||||
name="id"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<Input
|
||||
type="hidden"
|
||||
name={field.name}
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="banReason"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.users_page_ui_form_ban_reason()}:
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
rows={4}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="banExp"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.users_page_ui_form_ban_exp()}
|
||||
</FieldLabel>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={String(field.state.value)}
|
||||
onValueChange={(value) => field.handleChange(Number(value))}
|
||||
>
|
||||
<SelectTrigger aria-invalid={isInvalid}>
|
||||
<SelectValue
|
||||
<form.AppField name="id">
|
||||
{(field) => <field.HiddenField />}
|
||||
</form.AppField>
|
||||
<form.AppField name="banReason">
|
||||
{(field) => (
|
||||
<field.TextArea label={m.users_page_ui_form_ban_reason()} />
|
||||
)}
|
||||
</form.AppField>
|
||||
<form.AppField name="banExp">
|
||||
{(field) => (
|
||||
<field.SelectNumber
|
||||
label={m.users_page_ui_form_ban_exp()}
|
||||
values={[
|
||||
{ label: m.exp_time({ time: '1' }), value: '1' },
|
||||
{ label: m.exp_time({ time: '7' }), value: '7' },
|
||||
{ label: m.exp_time({ time: '15' }), value: '15' },
|
||||
{ label: m.exp_time({ time: '30' }), value: '30' },
|
||||
{ label: m.exp_time({ time: '180' }), value: '180' },
|
||||
{ label: m.exp_time({ time: '365' }), value: '365' },
|
||||
{ label: m.exp_time({ time: '99999' }), value: '99999' },
|
||||
]}
|
||||
placeholder={m.users_page_ui_select_placeholder_ban_exp()}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">
|
||||
{m.exp_time({ time: '1' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="7">
|
||||
{m.exp_time({ time: '7' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="15">
|
||||
{m.exp_time({ time: '15' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="30">
|
||||
{m.exp_time({ time: '30' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="180">
|
||||
{m.exp_time({ time: '180' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="365">
|
||||
{m.exp_time({ time: '365' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="99999">
|
||||
{m.exp_time({ time: '99999' })}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
@@ -159,9 +84,12 @@ const BanUserForm = ({ data }: FormProps) => {
|
||||
{m.ui_cancel_btn()}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" variant="destructive">
|
||||
{m.ui_ban_btn()}
|
||||
</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton
|
||||
label={m.ui_ban_btn()}
|
||||
variant="destructive"
|
||||
/>
|
||||
</form.AppForm>
|
||||
</DialogFooter>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { usersQueries } from '@/service/queries';
|
||||
import { createUser } from '@/service/user.api';
|
||||
import { RoleEnum, userCreateSchema } from '@/service/user.schema';
|
||||
import { userCreateSchema } from '@/service/user.schema';
|
||||
import { ReturnError } from '@/types/common';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '../ui/button';
|
||||
import { DialogClose, DialogFooter } from '../ui/dialog';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '../ui/select';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
|
||||
type FormProps = {
|
||||
onSubmit: (open: boolean) => void;
|
||||
@@ -44,7 +36,7 @@ const AdminCreateUserForm = ({ onSubmit }: FormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
password: '',
|
||||
@@ -70,114 +62,33 @@ const AdminCreateUserForm = ({ onSubmit }: FormProps) => {
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="email"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.login_page_form_email()}:
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
type="email"
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="password"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.login_page_form_password()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
<form.AppField name="email">
|
||||
{(field) => <field.TextField label={m.login_page_form_email()} />}
|
||||
</form.AppField>
|
||||
<form.AppField name="password">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
label={m.login_page_form_password()}
|
||||
type="password"
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="name"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.profile_form_name()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
type="text"
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="role"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.profile_form_role()}
|
||||
</FieldLabel>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onValueChange={(value) =>
|
||||
field.handleChange(RoleEnum.parse(value))
|
||||
}
|
||||
>
|
||||
<SelectTrigger aria-invalid={isInvalid}>
|
||||
<SelectValue
|
||||
)}
|
||||
</form.AppField>
|
||||
<form.AppField name="name">
|
||||
{(field) => <field.TextField label={m.profile_form_name()} />}
|
||||
</form.AppField>
|
||||
<form.AppField name="role">
|
||||
{(field) => (
|
||||
<field.Select
|
||||
label={m.profile_form_role()}
|
||||
placeholder={m.users_page_ui_select_placeholder_role()}
|
||||
isRole
|
||||
values={[
|
||||
{ value: 'admin', label: m.role_tags({ role: 'admin' }) },
|
||||
{ value: 'user', label: m.role_tags({ role: 'user' }) },
|
||||
]}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">
|
||||
{m.role_tags({ role: 'admin' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="user">
|
||||
{m.role_tags({ role: 'user' })}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
@@ -185,9 +96,9 @@ const AdminCreateUserForm = ({ onSubmit }: FormProps) => {
|
||||
{m.ui_cancel_btn()}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" variant="destructive">
|
||||
{m.ui_signup_btn()}
|
||||
</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_signup_btn()} />
|
||||
</form.AppForm>
|
||||
</DialogFooter>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { usersQueries } from '@/service/queries';
|
||||
import { setUserPassword } from '@/service/user.api';
|
||||
import { userSetPasswordSchema } from '@/service/user.schema';
|
||||
import { ReturnError } from '@/types/common';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { UserWithRole } from 'better-auth/plugins';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '../ui/button';
|
||||
import { DialogClose, DialogFooter } from '../ui/dialog';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
|
||||
type FormProps = {
|
||||
data: UserWithRole;
|
||||
@@ -39,7 +38,7 @@ const AdminSetPasswordForm = ({ data, onSubmit }: FormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
id: data.id,
|
||||
password: '',
|
||||
@@ -62,49 +61,14 @@ const AdminSetPasswordForm = ({ data, onSubmit }: FormProps) => {
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="id"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<Input
|
||||
type="hidden"
|
||||
name={field.name}
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="password"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.change_password_form_new_password()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
type="password"
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.AppField name="id">
|
||||
{(field) => <field.HiddenField />}
|
||||
</form.AppField>
|
||||
<form.AppField name="password">
|
||||
{(field) => (
|
||||
<field.TextField label={m.change_password_form_new_password()} />
|
||||
)}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
@@ -112,7 +76,9 @@ const AdminSetPasswordForm = ({ data, onSubmit }: FormProps) => {
|
||||
{m.ui_cancel_btn()}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">{m.ui_save_btn()}</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_save_btn()} />
|
||||
</form.AppForm>
|
||||
</DialogFooter>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { usersQueries } from '@/service/queries';
|
||||
import { setUserRole } from '@/service/user.api';
|
||||
import { RoleEnum, userUpdateRoleSchema } from '@/service/user.schema';
|
||||
import { userUpdateRoleSchema } from '@/service/user.schema';
|
||||
import { ReturnError } from '@/types/common';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { UserWithRole } from 'better-auth/plugins';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '../ui/button';
|
||||
import { DialogClose, DialogFooter } from '../ui/dialog';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '../ui/select';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
|
||||
type SetRoleFormProps = {
|
||||
data: UserWithRole;
|
||||
@@ -51,7 +43,7 @@ const AdminSetUserRoleForm = ({ data, onSubmit }: SetRoleFormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues: userUpdateRoleSchema.parse(defaultFormValues),
|
||||
validators: {
|
||||
onChange: userUpdateRoleSchema,
|
||||
@@ -72,61 +64,21 @@ const AdminSetUserRoleForm = ({ data, onSubmit }: SetRoleFormProps) => {
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="id"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<Input
|
||||
type="hidden"
|
||||
name={field.name}
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="role"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.profile_form_role()}
|
||||
</FieldLabel>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onValueChange={(value) =>
|
||||
field.handleChange(RoleEnum.parse(value))
|
||||
}
|
||||
>
|
||||
<SelectTrigger aria-invalid={isInvalid}>
|
||||
<SelectValue
|
||||
<form.AppField name="id">
|
||||
{(field) => <field.HiddenField />}
|
||||
</form.AppField>
|
||||
<form.AppField name="role">
|
||||
{(field) => (
|
||||
<field.Select
|
||||
label={m.profile_form_role()}
|
||||
placeholder={m.users_page_ui_select_placeholder_role()}
|
||||
values={[
|
||||
{ value: 'admin', label: m.role_tags({ role: 'admin' }) },
|
||||
{ value: 'user', label: m.role_tags({ role: 'user' }) },
|
||||
]}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">
|
||||
{m.role_tags({ role: 'admin' })}
|
||||
</SelectItem>
|
||||
<SelectItem value="user">
|
||||
{m.role_tags({ role: 'user' })}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
@@ -134,7 +86,9 @@ const AdminSetUserRoleForm = ({ data, onSubmit }: SetRoleFormProps) => {
|
||||
{m.ui_cancel_btn()}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">{m.ui_save_btn()}</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_save_btn()} />
|
||||
</form.AppForm>
|
||||
</DialogFooter>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { usersQueries } from '@/service/queries';
|
||||
import { updateUserInformation } from '@/service/user.api';
|
||||
import { userUpdateInfoSchema } from '@/service/user.schema';
|
||||
import { ReturnError } from '@/types/common';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { UserWithRole } from 'better-auth/plugins';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '../ui/button';
|
||||
import { DialogClose, DialogFooter } from '../ui/dialog';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
|
||||
type UpdateUserFormProps = {
|
||||
data: UserWithRole;
|
||||
@@ -38,7 +37,7 @@ const AdminUpdateUserInfoForm = ({ data, onSubmit }: UpdateUserFormProps) => {
|
||||
});
|
||||
},
|
||||
});
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
@@ -61,49 +60,12 @@ const AdminUpdateUserInfoForm = ({ data, onSubmit }: UpdateUserFormProps) => {
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="id"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<Input
|
||||
type="hidden"
|
||||
name={field.name}
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="name"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.profile_form_name()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
type="text"
|
||||
/>
|
||||
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.AppField name="id">
|
||||
{(field) => <field.HiddenField />}
|
||||
</form.AppField>
|
||||
<form.AppField name="name">
|
||||
{(field) => <field.TextField label={m.profile_form_name()} />}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
@@ -111,7 +73,9 @@ const AdminUpdateUserInfoForm = ({ data, onSubmit }: UpdateUserFormProps) => {
|
||||
{m.ui_cancel_btn()}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">{m.ui_save_btn()}</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_save_btn()} />
|
||||
</form.AppForm>
|
||||
</DialogFooter>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
@@ -1,43 +1,14 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import {
|
||||
ChangePassword,
|
||||
ChangePasswordFormSchema,
|
||||
} from '@/service/user.schema';
|
||||
import { KeyIcon } from '@phosphor-icons/react';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { toast } from 'sonner';
|
||||
import z from 'zod';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
|
||||
const ChangePasswordFormSchema = z
|
||||
.object({
|
||||
currentPassword: z.string().nonempty(
|
||||
m.common_is_required({
|
||||
field: m.change_password_form_current_password(),
|
||||
}),
|
||||
),
|
||||
newPassword: z.string().nonempty(
|
||||
m.common_is_required({
|
||||
field: m.change_password_form_new_password(),
|
||||
}),
|
||||
),
|
||||
confirmPassword: z.string().nonempty(
|
||||
m.common_is_required({
|
||||
field: m.change_password_form_confirm_password(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.newPassword !== data.confirmPassword) {
|
||||
ctx.addIssue({
|
||||
path: ['confirmPassword'],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: m.change_password_messages_password_not_match(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type ChangePassword = z.infer<typeof ChangePasswordFormSchema>;
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
|
||||
const defaultValues: ChangePassword = {
|
||||
currentPassword: '',
|
||||
@@ -46,7 +17,7 @@ const defaultValues: ChangePassword = {
|
||||
};
|
||||
|
||||
const ChangePasswordForm = () => {
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues,
|
||||
validators: {
|
||||
onSubmit: ChangePasswordFormSchema,
|
||||
@@ -98,86 +69,33 @@ const ChangePasswordForm = () => {
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="currentPassword"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.change_password_form_current_password()}:
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
<form.AppField name="currentPassword">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
label={m.change_password_form_current_password()}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<form.AppField name="newPassword">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
label={m.change_password_form_new_password()}
|
||||
type="password"
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="newPassword"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.change_password_form_new_password()}:
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
</form.AppField>
|
||||
<form.AppField name="confirmPassword">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
label={m.change_password_form_confirm_password()}
|
||||
type="password"
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="confirmPassword"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.change_password_form_confirm_password()}:
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
type="password"
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<Button type="submit">{m.ui_change_password_btn()}</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_change_password_btn()} />
|
||||
</form.AppForm>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
|
||||
218
src/components/form/form-components.tsx
Normal file
218
src/components/form/form-components.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import { useFieldContext, useFormContext } from '@/hooks/use-app-form';
|
||||
import { RoleEnum } from '@/service/user.schema';
|
||||
import { useStore } from '@tanstack/react-form';
|
||||
import { type VariantProps } from 'class-variance-authority';
|
||||
import { Button, buttonVariants } from '../ui/button';
|
||||
import { Field, FieldError, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import * as ShadcnSelect from '../ui/select';
|
||||
import { Textarea } from '../ui/textarea';
|
||||
|
||||
export function SubscribeButton({
|
||||
label,
|
||||
variant = 'default',
|
||||
}: {
|
||||
label: string;
|
||||
} & VariantProps<typeof buttonVariants>) {
|
||||
const form = useFormContext();
|
||||
return (
|
||||
<form.Subscribe selector={(state) => state.isSubmitting}>
|
||||
{(isSubmitting) => (
|
||||
<Button type="submit" disabled={isSubmitting} variant={variant}>
|
||||
{label}
|
||||
</Button>
|
||||
)}
|
||||
</form.Subscribe>
|
||||
);
|
||||
}
|
||||
|
||||
export function HiddenField() {
|
||||
const field = useFieldContext<string>();
|
||||
const errors = useStore(field.store, (state) => state.meta.errors);
|
||||
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
aria-invalid={isInvalid}
|
||||
type="hidden"
|
||||
/>
|
||||
{isInvalid && <FieldError errors={errors} />}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
export function TextField({
|
||||
label,
|
||||
placeholder,
|
||||
type = 'text',
|
||||
}: {
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
}) {
|
||||
const field = useFieldContext<string>();
|
||||
const errors = useStore(field.store, (state) => state.meta.errors);
|
||||
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>{label}:</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
type={type}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={errors} />}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
export function FileField({
|
||||
label,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
label: string;
|
||||
className?: string;
|
||||
} & React.ComponentProps<'input'>) {
|
||||
const field = useFieldContext<string>();
|
||||
const errors = useStore(field.store, (state) => state.meta.errors);
|
||||
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className={className}>
|
||||
<FieldLabel htmlFor={field.name}>{label}:</FieldLabel>
|
||||
<Input
|
||||
type="file"
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
aria-invalid={isInvalid}
|
||||
{...props}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={errors} />}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
export function TextArea({
|
||||
label,
|
||||
rows = 4,
|
||||
}: {
|
||||
label: string;
|
||||
rows?: number;
|
||||
}) {
|
||||
const field = useFieldContext<string>();
|
||||
const errors = useStore(field.store, (state) => state.meta.errors);
|
||||
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>{label}:</FieldLabel>
|
||||
<Textarea
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
rows={rows}
|
||||
/>
|
||||
{isInvalid && <FieldError errors={errors} />}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
export function Select({
|
||||
label,
|
||||
values,
|
||||
placeholder,
|
||||
isRole = false,
|
||||
}: {
|
||||
label: string;
|
||||
values: Array<{ label: string; value: string }>;
|
||||
placeholder?: string;
|
||||
isRole?: boolean;
|
||||
}) {
|
||||
const field = useFieldContext<string>();
|
||||
const errors = useStore(field.store, (state) => state.meta.errors);
|
||||
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>{label}:</FieldLabel>
|
||||
<ShadcnSelect.Select
|
||||
name={field.name}
|
||||
value={String(field.state.value)}
|
||||
onValueChange={(value) =>
|
||||
isRole
|
||||
? field.handleChange(RoleEnum.parse(value))
|
||||
: field.handleChange(value)
|
||||
}
|
||||
>
|
||||
<ShadcnSelect.SelectTrigger aria-invalid={isInvalid}>
|
||||
<ShadcnSelect.SelectValue placeholder={placeholder} />
|
||||
</ShadcnSelect.SelectTrigger>
|
||||
<ShadcnSelect.SelectContent>
|
||||
<ShadcnSelect.SelectGroup>
|
||||
<ShadcnSelect.SelectLabel>{label}</ShadcnSelect.SelectLabel>
|
||||
{values.map((value) => (
|
||||
<ShadcnSelect.SelectItem key={value.value} value={value.value}>
|
||||
{value.label}
|
||||
</ShadcnSelect.SelectItem>
|
||||
))}
|
||||
</ShadcnSelect.SelectGroup>
|
||||
</ShadcnSelect.SelectContent>
|
||||
</ShadcnSelect.Select>
|
||||
{isInvalid && <FieldError errors={errors} />}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectNumber({
|
||||
label,
|
||||
values,
|
||||
placeholder,
|
||||
}: {
|
||||
label: string;
|
||||
values: Array<{ label: string; value: string }>;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const field = useFieldContext<number>();
|
||||
const errors = useStore(field.store, (state) => state.meta.errors);
|
||||
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>{label}:</FieldLabel>
|
||||
<ShadcnSelect.Select
|
||||
name={field.name}
|
||||
value={field.state.value === 0 ? String('') : String(field.state.value)}
|
||||
onValueChange={(value) => field.handleChange(Number(value))}
|
||||
>
|
||||
<ShadcnSelect.SelectTrigger aria-invalid={isInvalid}>
|
||||
<ShadcnSelect.SelectValue placeholder={placeholder} />
|
||||
</ShadcnSelect.SelectTrigger>
|
||||
<ShadcnSelect.SelectContent>
|
||||
<ShadcnSelect.SelectGroup>
|
||||
<ShadcnSelect.SelectLabel>{label}</ShadcnSelect.SelectLabel>
|
||||
{values.map((value) => (
|
||||
<ShadcnSelect.SelectItem key={value.value} value={value.value}>
|
||||
{value.label}
|
||||
</ShadcnSelect.SelectItem>
|
||||
))}
|
||||
</ShadcnSelect.SelectGroup>
|
||||
</ShadcnSelect.SelectContent>
|
||||
</ShadcnSelect.Select>
|
||||
{isInvalid && <FieldError errors={errors} />}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { uploadProfileImage } from '@/service/profile.api';
|
||||
import { ProfileInput, profileUpdateSchema } from '@/service/profile.schema';
|
||||
import { UserCircleIcon } from '@phosphor-icons/react';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useRef } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { useAuth } from '../auth/auth-provider';
|
||||
import AvatarUser from '../avatar/avatar-user';
|
||||
import RoleBadge from '../avatar/role-badge';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Field, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
|
||||
const defaultValues: ProfileInput = {
|
||||
@@ -25,7 +24,7 @@ const ProfileForm = () => {
|
||||
const { data: session, isPending } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
...defaultValues,
|
||||
name: session?.user?.name || '',
|
||||
@@ -102,58 +101,20 @@ const ProfileForm = () => {
|
||||
<FieldGroup>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<AvatarUser className="h-20 w-20" textSize="2xl" />
|
||||
<form.Field
|
||||
name="image"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>Avatar</FieldLabel>
|
||||
<Input
|
||||
type="file"
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
<form.AppField name="image">
|
||||
{(field) => (
|
||||
<field.FileField
|
||||
label="Avatar"
|
||||
className="col-span-2"
|
||||
accept=".jpg, .jpeg, .png, .webp"
|
||||
ref={fileInputRef}
|
||||
onChange={(e) =>
|
||||
field.handleChange(e.target.files?.[0])
|
||||
}
|
||||
aria-invalid={isInvalid}
|
||||
onChange={(e) => field.handleChange(e.target.files?.[0])}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</form.AppField>
|
||||
</div>
|
||||
<form.Field
|
||||
name="name"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.profile_form_name()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.AppField name="name">
|
||||
{(field) => <field.TextField label={m.profile_form_name()} />}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="name">{m.profile_form_email()}</FieldLabel>
|
||||
<Input
|
||||
@@ -171,7 +132,9 @@ const ProfileForm = () => {
|
||||
</div>
|
||||
</Field>
|
||||
<Field>
|
||||
<Button type="submit">{m.ui_update_btn()}</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_update_btn()} />
|
||||
</form.AppForm>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { settingQueries } from '@/service/queries';
|
||||
import { updateAdminSettings } from '@/service/setting.api';
|
||||
import { settingSchema, SettingsInput } from '@/service/setting.schema';
|
||||
import { ReturnError } from '@/types/common';
|
||||
import { GearIcon } from '@phosphor-icons/react';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
import { Skeleton } from '../ui/skeleton';
|
||||
import { Textarea } from '../ui/textarea';
|
||||
|
||||
const defaultValues: SettingsInput = {
|
||||
site_name: '',
|
||||
@@ -41,7 +38,7 @@ const SettingsForm = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
...defaultValues,
|
||||
site_name: settings?.site_name?.value || '',
|
||||
@@ -78,85 +75,21 @@ const SettingsForm = () => {
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="site_name"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.settings_form_name()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
<form.AppField name="site_name">
|
||||
{(field) => <field.TextField label={m.settings_form_name()} />}
|
||||
</form.AppField>
|
||||
<form.AppField name="site_description">
|
||||
{(field) => (
|
||||
<field.TextArea label={m.settings_form_description()} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="site_description"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.settings_form_description()}
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
rows={4}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="site_keywords"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.settings_form_keywords()}
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
rows={4}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</form.AppField>
|
||||
<form.AppField name="site_keywords">
|
||||
{(field) => <field.TextArea label={m.settings_form_keywords()} />}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<Button type="submit">{m.ui_update_btn()}</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_update_btn()} />
|
||||
</form.AppForm>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { createLink, useNavigate } from '@tanstack/react-router';
|
||||
import { toast } from 'sonner';
|
||||
import z from 'zod';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import { Input } from '../ui/input';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
|
||||
const SignInFormSchema = z.object({
|
||||
email: z
|
||||
@@ -27,7 +26,8 @@ const ButtonLink = createLink(Button);
|
||||
const SignInForm = () => {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const form = useForm({
|
||||
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
password: '',
|
||||
@@ -91,62 +91,26 @@ const SignInForm = () => {
|
||||
</Button>
|
||||
</Field>
|
||||
<FieldSeparator>Or continue with</FieldSeparator> */}
|
||||
<form.Field
|
||||
name="email"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.login_page_form_email()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
<form.AppField name="email">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
label={m.login_page_form_email()}
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
autoComplete="off"
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="password"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.login_page_form_password()}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
</form.AppField>
|
||||
<form.AppField name="password">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
type="password"
|
||||
label={m.login_page_form_password()}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<Button type="submit">{m.ui_login_btn()}</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_login_btn()} />
|
||||
</form.AppForm>
|
||||
<ButtonLink to="/" variant="outline">
|
||||
{m.ui_cancel_btn()}
|
||||
</ButtonLink>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { Locale, setLocale } from '@/paraglide/runtime';
|
||||
import { settingQueries } from '@/service/queries';
|
||||
@@ -5,20 +6,11 @@ import { updateUserSettings } from '@/service/setting.api';
|
||||
import { UserSettingInput, userSettingSchema } from '@/service/setting.schema';
|
||||
import { ReturnError } from '@/types/common';
|
||||
import { GearIcon } from '@phosphor-icons/react';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from '../ui/field';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '../ui/select';
|
||||
import { Field, FieldGroup } from '../ui/field';
|
||||
import { Skeleton } from '../ui/skeleton';
|
||||
|
||||
const defaultValues: UserSettingInput = {
|
||||
@@ -49,7 +41,7 @@ const UserSettingsForm = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
const form = useAppForm({
|
||||
defaultValues,
|
||||
validators: {
|
||||
onSubmit: userSettingSchema,
|
||||
@@ -68,6 +60,13 @@ const UserSettingsForm = () => {
|
||||
}
|
||||
}, [data, form]);
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div>
|
||||
<Skeleton className="h-40 w-full" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card className="@container/card col-span-1 @xl/main:col-span-2">
|
||||
<CardHeader>
|
||||
@@ -86,47 +85,22 @@ const UserSettingsForm = () => {
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
{isLoading ? (
|
||||
<div className="col-span-2 space-y-2">
|
||||
<Skeleton className="h-4 w-20" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
) : (
|
||||
<form.Field
|
||||
name="language"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid;
|
||||
return (
|
||||
<Field data-invalid={isInvalid} className="col-span-2">
|
||||
<FieldLabel htmlFor={field.name}>
|
||||
{m.settings_form_language()}
|
||||
</FieldLabel>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onValueChange={(value) => field.handleChange(value)}
|
||||
>
|
||||
<SelectTrigger aria-invalid={isInvalid}>
|
||||
<SelectValue placeholder="Select Language" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="en">English</SelectItem>
|
||||
<SelectItem value="vi">Vietnamese</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
<form.AppField name="language">
|
||||
{(field) => (
|
||||
<field.Select
|
||||
label={m.settings_form_language()}
|
||||
placeholder={m.settings_form_select_language()}
|
||||
values={[
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'vi', label: 'Tiếng Việt' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{m.ui_update_btn()}
|
||||
</Button>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_update_btn()} />
|
||||
</form.AppForm>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
|
||||
@@ -18,8 +18,8 @@ const AddNewUserButton = () => {
|
||||
const prevent = usePreventAutoFocus();
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Dialog open={_open} onOpenChange={_setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="button" variant="default">
|
||||
<PlusIcon />
|
||||
{m.nav_add_new()}
|
||||
@@ -39,7 +39,7 @@ const AddNewUserButton = () => {
|
||||
{m.nav_add_new()}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<AdminCreateUserForm />
|
||||
<AdminCreateUserForm onSubmit={_setOpen} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
29
src/hooks/use-app-form.ts
Normal file
29
src/hooks/use-app-form.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
FileField,
|
||||
HiddenField,
|
||||
Select,
|
||||
SelectNumber,
|
||||
SubscribeButton,
|
||||
TextArea,
|
||||
TextField,
|
||||
} from '@/components/form/form-components';
|
||||
import { createFormHook, createFormHookContexts } from '@tanstack/react-form';
|
||||
|
||||
export const { fieldContext, useFieldContext, formContext, useFormContext } =
|
||||
createFormHookContexts();
|
||||
|
||||
export const { useAppForm } = createFormHook({
|
||||
fieldComponents: {
|
||||
HiddenField,
|
||||
TextField,
|
||||
TextArea,
|
||||
Select,
|
||||
SelectNumber,
|
||||
FileField,
|
||||
},
|
||||
formComponents: {
|
||||
SubscribeButton,
|
||||
},
|
||||
fieldContext,
|
||||
formContext,
|
||||
});
|
||||
@@ -11,7 +11,6 @@
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as appRouteRouteImport } from './routes/(app)/route'
|
||||
import { Route as appIndexRouteImport } from './routes/(app)/index'
|
||||
import { Route as authSignUpRouteImport } from './routes/(auth)/sign-up'
|
||||
import { Route as authSignInRouteImport } from './routes/(auth)/sign-in'
|
||||
import { Route as appauthRouteRouteImport } from './routes/(app)/(auth)/route'
|
||||
import { Route as ApiAuthSplatRouteImport } from './routes/api.auth.$'
|
||||
@@ -36,11 +35,6 @@ const appIndexRoute = appIndexRouteImport.update({
|
||||
path: '/',
|
||||
getParentRoute: () => appRouteRoute,
|
||||
} as any)
|
||||
const authSignUpRoute = authSignUpRouteImport.update({
|
||||
id: '/(auth)/sign-up',
|
||||
path: '/sign-up',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const authSignInRoute = authSignInRouteImport.update({
|
||||
id: '/(auth)/sign-in',
|
||||
path: '/sign-in',
|
||||
@@ -114,7 +108,6 @@ const appauthAccountChangePasswordRoute =
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/sign-in': typeof authSignInRoute
|
||||
'/sign-up': typeof authSignUpRoute
|
||||
'/': typeof appIndexRoute
|
||||
'/account': typeof appauthAccountRouteRouteWithChildren
|
||||
'/kanri': typeof appauthKanriRouteRouteWithChildren
|
||||
@@ -131,7 +124,6 @@ export interface FileRoutesByFullPath {
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/sign-in': typeof authSignInRoute
|
||||
'/sign-up': typeof authSignUpRoute
|
||||
'/': typeof appIndexRoute
|
||||
'/dashboard': typeof appauthDashboardRoute
|
||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
||||
@@ -149,7 +141,6 @@ export interface FileRoutesById {
|
||||
'/(app)': typeof appRouteRouteWithChildren
|
||||
'/(app)/(auth)': typeof appauthRouteRouteWithChildren
|
||||
'/(auth)/sign-in': typeof authSignInRoute
|
||||
'/(auth)/sign-up': typeof authSignUpRoute
|
||||
'/(app)/': typeof appIndexRoute
|
||||
'/(app)/(auth)/account': typeof appauthAccountRouteRouteWithChildren
|
||||
'/(app)/(auth)/kanri': typeof appauthKanriRouteRouteWithChildren
|
||||
@@ -168,7 +159,6 @@ export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths:
|
||||
| '/sign-in'
|
||||
| '/sign-up'
|
||||
| '/'
|
||||
| '/account'
|
||||
| '/kanri'
|
||||
@@ -185,7 +175,6 @@ export interface FileRouteTypes {
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/sign-in'
|
||||
| '/sign-up'
|
||||
| '/'
|
||||
| '/dashboard'
|
||||
| '/api/auth/$'
|
||||
@@ -202,7 +191,6 @@ export interface FileRouteTypes {
|
||||
| '/(app)'
|
||||
| '/(app)/(auth)'
|
||||
| '/(auth)/sign-in'
|
||||
| '/(auth)/sign-up'
|
||||
| '/(app)/'
|
||||
| '/(app)/(auth)/account'
|
||||
| '/(app)/(auth)/kanri'
|
||||
@@ -221,7 +209,6 @@ export interface FileRouteTypes {
|
||||
export interface RootRouteChildren {
|
||||
appRouteRoute: typeof appRouteRouteWithChildren
|
||||
authSignInRoute: typeof authSignInRoute
|
||||
authSignUpRoute: typeof authSignUpRoute
|
||||
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
||||
}
|
||||
|
||||
@@ -241,13 +228,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof appIndexRouteImport
|
||||
parentRoute: typeof appRouteRoute
|
||||
}
|
||||
'/(auth)/sign-up': {
|
||||
id: '/(auth)/sign-up'
|
||||
path: '/sign-up'
|
||||
fullPath: '/sign-up'
|
||||
preLoaderRoute: typeof authSignUpRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/(auth)/sign-in': {
|
||||
id: '/(auth)/sign-in'
|
||||
path: '/sign-in'
|
||||
@@ -416,7 +396,6 @@ const appRouteRouteWithChildren = appRouteRoute._addFileChildren(
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
appRouteRoute: appRouteRouteWithChildren,
|
||||
authSignInRoute: authSignInRoute,
|
||||
authSignUpRoute: authSignUpRoute,
|
||||
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
|
||||
@@ -2,13 +2,25 @@ import { AuthProvider } from '@/components/auth/auth-provider';
|
||||
import Header from '@/components/Header';
|
||||
import AppSidebar from '@/components/sidebar/app-sidebar';
|
||||
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
||||
import { Locale, setLocale } from '@/paraglide/runtime';
|
||||
import { settingQueries } from '@/service/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { createFileRoute, Outlet } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/(app)')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: language } = useQuery(
|
||||
settingQueries.getCurrentUserLanguageSetting(),
|
||||
);
|
||||
useEffect(() => {
|
||||
if (language) {
|
||||
setLocale(language as Locale);
|
||||
}
|
||||
}, [language]);
|
||||
return (
|
||||
<AuthProvider>
|
||||
<SidebarProvider defaultOpen={false}>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import SignupForm from '@/components/form/signup-form'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(auth)/sign-up')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||
<div className="flex w-full max-w-sm flex-col gap-6">
|
||||
<h1 className="text-xl font-semibold flex items-center gap-2 self-center">
|
||||
<img src="/logo.svg" alt="Fuware Logo" className="h-8" />
|
||||
Fuware
|
||||
</h1>
|
||||
<SignupForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import { getSession } from '@/lib/auth/session';
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { getAllAudit } from './audit.api';
|
||||
import { getAdminSettings, getUserSettings } from './setting.api';
|
||||
import {
|
||||
getAdminSettings,
|
||||
getCurrentUserLanguage,
|
||||
getUserSettings,
|
||||
} from './setting.api';
|
||||
import { getAllUser } from './user.api';
|
||||
|
||||
export const sessionQueries = {
|
||||
@@ -27,6 +31,11 @@ export const settingQueries = {
|
||||
queryKey: [...settingQueries.all, 'listUser'],
|
||||
queryFn: () => getUserSettings(),
|
||||
}),
|
||||
getCurrentUserLanguageSetting: () =>
|
||||
queryOptions({
|
||||
queryKey: [...settingQueries.all, 'language'],
|
||||
queryFn: () => getCurrentUserLanguage(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const auditQueries = {
|
||||
|
||||
@@ -12,6 +12,24 @@ export const getAdminSettings = createServerFn({ method: 'GET' })
|
||||
return await getAllAdminSettings();
|
||||
});
|
||||
|
||||
export const getCurrentUserLanguage = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.handler(async ({ context }) => {
|
||||
try {
|
||||
const setting = await prisma.setting.findUniqueOrThrow({
|
||||
where: { key: context.user.id, relation: 'user' },
|
||||
select: { value: true },
|
||||
});
|
||||
|
||||
const value = JSON.parse(setting.value) as UserSetting;
|
||||
|
||||
return value.language;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
export const updateAdminSettings = createServerFn({ method: 'POST' })
|
||||
.inputValidator(settingSchema)
|
||||
.middleware([authMiddleware])
|
||||
@@ -84,6 +102,7 @@ export const getUserSettings = createServerFn({ method: 'GET' })
|
||||
value: JSON.parse(settings.value) as UserSetting,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -128,6 +147,7 @@ export const updateUserSettings = createServerFn({ method: 'POST' })
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,36 @@ export const baseUser = z.object({
|
||||
id: z.string().nonempty(m.users_page_message_user_not_found()),
|
||||
});
|
||||
|
||||
export const ChangePasswordFormSchema = z
|
||||
.object({
|
||||
currentPassword: z.string().nonempty(
|
||||
m.common_is_required({
|
||||
field: m.change_password_form_current_password(),
|
||||
}),
|
||||
),
|
||||
newPassword: z.string().nonempty(
|
||||
m.common_is_required({
|
||||
field: m.change_password_form_new_password(),
|
||||
}),
|
||||
),
|
||||
confirmPassword: z.string().nonempty(
|
||||
m.common_is_required({
|
||||
field: m.change_password_form_confirm_password(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.newPassword !== data.confirmPassword) {
|
||||
ctx.addIssue({
|
||||
path: ['confirmPassword'],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: m.change_password_messages_password_not_match(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type ChangePassword = z.infer<typeof ChangePasswordFormSchema>;
|
||||
|
||||
export const userListSchema = z.object({
|
||||
page: z.coerce.number().min(1).default(1),
|
||||
limit: z.coerce.number().min(10).max(100).default(10),
|
||||
|
||||
Reference in New Issue
Block a user