Files
fullstack-fuware/src/components/form/account/profile-form.tsx
Sam c2981ed7d8 added Model Box and Item
added Box function for admin
2026-03-09 10:04:38 +07:00

146 lines
4.4 KiB
TypeScript

import { Skeleton } from '@/components/ui/skeleton';
import { updateProfile } from '@/service/profile.api';
import { useAuth } from '@components/auth/auth-provider';
import AvatarUser, { AvatarUserType } from '@components/avatar/avatar-user';
import RoleBadge from '@components/avatar/role-badge';
import { useAppForm } from '@hooks/use-app-form';
import { m } from '@paraglide/messages';
import { UserCircleIcon } from '@phosphor-icons/react';
import { ProfileInput, profileUpdateSchema } from '@service/profile.schema';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Card, CardContent, CardHeader, CardTitle } from '@ui/card';
import { Field, FieldGroup, FieldLabel } from '@ui/field';
import { Input } from '@ui/input';
import { useRef } from 'react';
import { toast } from 'sonner';
const defaultValues: ProfileInput = {
name: '',
image: undefined,
};
const ProfileForm = () => {
const fileInputRef = useRef<HTMLInputElement>(null);
const { session, isPending } = useAuth();
const queryClient = useQueryClient();
const { mutate: updateProfileMutation, isPending: isRunning } = useMutation({
mutationFn: updateProfile,
onSuccess: () => {
form.reset();
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
queryClient.refetchQueries({
queryKey: ['auth', 'session'],
});
toast.success(m.profile_messages_update_success(), {
richColors: true,
});
},
onError: (error: ReturnError) => {
console.error(error);
const code = error.code as Parameters<
typeof m.backend_message
>[0]['code'];
toast.error(m.backend_message({ code }), {
richColors: true,
});
},
});
const form = useAppForm({
defaultValues: {
...defaultValues,
name: session?.user?.name || '',
},
validators: {
onSubmit: profileUpdateSchema,
onChange: profileUpdateSchema,
},
onSubmit: async ({ value }) => {
const formData = new FormData();
formData.set('name', value.name);
if (value.image) {
formData.set('file', value.image);
}
updateProfileMutation({ data: formData });
},
});
if (isPending || !session?.user?.name) {
return <Skeleton className="h-100 col-span-1" />;
}
return (
<Card className="@container/card col-span-1">
<CardHeader>
<CardTitle className="text-xl flex items-center gap-2">
<UserCircleIcon size={20} />
{m.profile_ui_title()}
</CardTitle>
</CardHeader>
<CardContent>
<form
id="profile-form"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<FieldGroup>
<div className="grid grid-cols-3 gap-3">
<AvatarUser
className="h-20 w-20"
textSize="2xl"
user={session.user as AvatarUserType}
/>
<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.AppField name="name">
{(field) => <field.TextField label={m.profile_form_name()} />}
</form.AppField>
<Field>
<FieldLabel htmlFor="name">{m.profile_form_email()}</FieldLabel>
<Input
id="email"
name="email"
value={session?.user?.email || ''}
disabled
className="disabled:opacity-80"
/>
</Field>
<Field>
<FieldLabel htmlFor="name">{m.profile_form_role()}</FieldLabel>
<div className="flex gap-2">
<RoleBadge type={session?.user?.role} />
</div>
</Field>
<Field>
<form.AppForm>
<form.SubscribeButton
label={m.ui_update_btn()}
disabled={isRunning}
/>
</form.AppForm>
</Field>
</FieldGroup>
</form>
</CardContent>
</Card>
);
};
export default ProfileForm;