Update form to look better & add automatic lunch toggle

This commit is contained in:
2025-09-17 21:13:02 -05:00
parent 3bff31c07a
commit 3d85e0c2e9
14 changed files with 231 additions and 165 deletions

View File

@@ -10,13 +10,14 @@ import {
} from 'convex/react';
import { api } from '~/convex/_generated/api';
import {
Avatar,
AvatarImage,
BasedAvatar,
Button,
CardContent,
ImageCrop,
ImageCropApply,
ImageCropContent,
ImageCropReset,
Input,
} from '@/components/ui';
import { toast } from 'sonner';
@@ -121,7 +122,7 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
<BasedAvatar
src={currentImageUrl ?? undefined}
fullName={user?.name}
className='h-32 w-32'
className='h-42 w-42'
fallbackProps={{ className: 'text-4xl font-semibold' }}
userIconProps={{ size: 100 }}
/>
@@ -173,7 +174,6 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
<ImageCropContent className='max-w-sm' />
<div className='flex items-center gap-2'>
<ImageCropApply />
<ImageCropReset />
<Button
onClick={handleReset}
size='icon'
@@ -190,19 +190,17 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
{/* Cropped preview + actions */}
{croppedImage && (
<div className='flex flex-col items-center gap-3'>
<Image
alt='Cropped preview'
className='overflow-hidden rounded-full'
height={128}
src={croppedImage}
unoptimized
width={128}
/>
<div className='flex items-center gap-2'>
<Avatar className='h-42 w-42'>
<AvatarImage
alt='Cropped preview'
src={croppedImage}
/>
</Avatar>
<div className='flex items-center gap-1'>
<Button
onClick={handleSave}
disabled={isUploading}
className='px-6'
className='px-4'
>
{isUploading ? (
<span className='inline-flex items-center gap-2'>
@@ -217,7 +215,10 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
onClick={handleReset}
size='icon'
type='button'
variant='ghost'
className='dark:bg-red-500/30 bg-red-400/80
hover:dark:text-red-300/60 hover:text-red-800/80
hover:dark:bg-accent'
variant='secondary'
>
<XIcon className='size-4' />
</Button>

View File

@@ -97,7 +97,7 @@ export const ResetPasswordForm = () => {
return (
<>
<CardHeader className='pb-5'>
<CardHeader>
<CardTitle className='text-2xl'>Change Password</CardTitle>
<CardDescription>
Update your password to keep your account secure

View File

@@ -7,6 +7,10 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import {
CardContent,
CardDescription,
CardHeader,
CardTitle,
Checkbox,
Form,
FormControl,
FormDescription,
@@ -35,7 +39,8 @@ const formSchema = z.object({
lunchTime: z
.string()
.trim()
.min(3, { message: 'Must be a valid 24-hour time. Example: 13:00'}),
.min(3, { message: 'Must be a valid 24-hour time. Example: 13:00' }),
automaticLunch: z.boolean(),
});
type UserInfoFormProps = {
@@ -49,13 +54,16 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
const updateUserName = useMutation(api.auth.updateUserName);
const updateUserEmail = useMutation(api.auth.updateUserEmail);
const updateUserLunchtime = useMutation(api.auth.updateUserLunchtime);
const updateUserAutomaticLunch = useMutation(api.auth.updateUserAutomaticLunch);
const initialValues = useMemo<z.infer<typeof formSchema>>(
() => ({
name: user?.name ?? '',
email: user?.email ?? '',
lunchTime: user?.lunchTime ?? '',
}), [user?.name, user?.email, user?.lunchTime]
lunchTime: user?.lunchTime ?? '12:00',
automaticLunch: user?.automaticLunch ?? false,
}),
[user?.name, user?.email, user?.lunchTime, user?.automaticLunch],
);
const form = useForm<z.infer<typeof formSchema>>({
@@ -68,10 +76,13 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
const name = values.name.trim();
const email = values.email.trim().toLowerCase();
const lunchTime = values.lunchTime.trim();
const automaticLunch = values.automaticLunch;
if (name !== (user?.name ?? '')) ops.push(updateUserName({ name }));
if (email !== (user?.email ?? '')) ops.push(updateUserEmail({ email }));
if (lunchTime !== (user?.lunchTime ?? ''))
ops.push(updateUserLunchtime({ lunchTime }))
ops.push(updateUserLunchtime({ lunchTime }));
if (automaticLunch !== (user?.automaticLunch))
ops.push(updateUserAutomaticLunch({ automaticLunch }));
if (ops.length === 0) return;
setLoading(true);
try {
@@ -87,65 +98,103 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
};
return (
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-6'>
<FormField
control={form.control}
name='name'
render={({ field }) => (
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<>
<CardHeader>
<CardTitle className='text-2xl'>Account Information</CardTitle>
<CardDescription>
Update your account information here.
</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-6'>
<FormField
control={form.control}
name='name'
render={({ field }) => (
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
Your email address associated with your account.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
Your email address associated with your account.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className='flex flex-row justify-center space-x-6'>
<FormField
control={form.control}
name='lunchTime'
render={({ field }) => (
<FormItem className='w-2/5'>
<FormLabel>Lunch Time</FormLabel>
<FormControl>
<Input
type='time'
className='max-w-26'
{...field}
/>
</FormControl>
<FormDescription>
Your regular lunch time.
</FormDescription>
<FormDescription className='dark:text-red-300/60 text-red-800/80'>
{!user?.lunchTime && 'Not currently set.'}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='automaticLunch'
render={({ field }) => (
<FormItem className='w-2/5'>
<div className='flex flex-row space-x-2 my-auto'>
<FormControl>
<Checkbox
className='border-solid border-primary'
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>Automatic Lunch</FormLabel>
</div>
<FormDescription>
Automatically take your lunch at the time you specify.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name='lunchTime'
render={({ field }) => (
<FormItem>
<FormLabel>Regular Lunch Time</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
Enter the time you take your lunch most often in 24 hour time.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className='flex justify-center'>
<SubmitButton disabled={loading} pendingText='Saving...'>
Save Changes
</SubmitButton>
</div>
</form>
</Form>
</CardContent>
<div className='flex justify-center'>
<SubmitButton disabled={loading} pendingText='Saving...'>
Save Changes
</SubmitButton>
</div>
</form>
</Form>
</CardContent>
</>
);
};