change basic form to form context
This commit is contained in:
@@ -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
|
||||
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 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()}
|
||||
/>
|
||||
)}
|
||||
</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}
|
||||
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
|
||||
placeholder={m.users_page_ui_select_placeholder_role()}
|
||||
/>
|
||||
</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 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"
|
||||
/>
|
||||
)}
|
||||
</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' }) },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</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
|
||||
placeholder={m.users_page_ui_select_placeholder_role()}
|
||||
/>
|
||||
</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 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' }) },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</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}
|
||||
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}
|
||||
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 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"
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<form.AppField name="confirmPassword">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
label={m.change_password_form_confirm_password()}
|
||||
type="password"
|
||||
/>
|
||||
)}
|
||||
</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}
|
||||
accept=".jpg, .jpeg, .png, .webp"
|
||||
ref={fileInputRef}
|
||||
onChange={(e) =>
|
||||
field.handleChange(e.target.files?.[0])
|
||||
}
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.AppField name="image">
|
||||
{(field) => (
|
||||
<field.FileField
|
||||
label="Avatar"
|
||||
className="col-span-2"
|
||||
accept=".jpg, .jpeg, .png, .webp"
|
||||
onChange={(e) => field.handleChange(e.target.files?.[0])}
|
||||
/>
|
||||
)}
|
||||
</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} />
|
||||
)}
|
||||
</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 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()} />
|
||||
)}
|
||||
</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}
|
||||
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}
|
||||
type="password"
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<form.AppField name="email">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
label={m.login_page_form_email()}
|
||||
type="email"
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<form.AppField name="password">
|
||||
{(field) => (
|
||||
<field.TextField
|
||||
type="password"
|
||||
label={m.login_page_form_password()}
|
||||
/>
|
||||
)}
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user