Update form to look better & add automatic lunch toggle

This commit is contained in:
2025-09-17 21:22:16 -05:00
parent 3d85e0c2e9
commit 87c128f7c5
8 changed files with 79 additions and 46 deletions

View File

@@ -24,6 +24,7 @@
"@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@sentry/nextjs": "^10.12.0", "@sentry/nextjs": "^10.12.0",
"@t3-oss/env-nextjs": "^0.13.8", "@t3-oss/env-nextjs": "^0.13.8",

View File

@@ -191,10 +191,7 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
{croppedImage && ( {croppedImage && (
<div className='flex flex-col items-center gap-3'> <div className='flex flex-col items-center gap-3'>
<Avatar className='h-42 w-42'> <Avatar className='h-42 w-42'>
<AvatarImage <AvatarImage alt='Cropped preview' src={croppedImage} />
alt='Cropped preview'
src={croppedImage}
/>
</Avatar> </Avatar>
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>
<Button <Button

View File

@@ -10,7 +10,6 @@ import {
CardDescription, CardDescription,
CardHeader, CardHeader,
CardTitle, CardTitle,
Checkbox,
Form, Form,
FormControl, FormControl,
FormDescription, FormDescription,
@@ -20,6 +19,7 @@ import {
FormMessage, FormMessage,
Input, Input,
SubmitButton, SubmitButton,
Switch,
} from '@/components/ui'; } from '@/components/ui';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -54,7 +54,9 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
const updateUserName = useMutation(api.auth.updateUserName); const updateUserName = useMutation(api.auth.updateUserName);
const updateUserEmail = useMutation(api.auth.updateUserEmail); const updateUserEmail = useMutation(api.auth.updateUserEmail);
const updateUserLunchtime = useMutation(api.auth.updateUserLunchtime); const updateUserLunchtime = useMutation(api.auth.updateUserLunchtime);
const updateUserAutomaticLunch = useMutation(api.auth.updateUserAutomaticLunch); const updateUserAutomaticLunch = useMutation(
api.auth.updateUserAutomaticLunch,
);
const initialValues = useMemo<z.infer<typeof formSchema>>( const initialValues = useMemo<z.infer<typeof formSchema>>(
() => ({ () => ({
@@ -81,7 +83,7 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
if (email !== (user?.email ?? '')) ops.push(updateUserEmail({ email })); if (email !== (user?.email ?? '')) ops.push(updateUserEmail({ email }));
if (lunchTime !== (user?.lunchTime ?? '')) if (lunchTime !== (user?.lunchTime ?? ''))
ops.push(updateUserLunchtime({ lunchTime })); ops.push(updateUserLunchtime({ lunchTime }));
if (automaticLunch !== (user?.automaticLunch)) if (automaticLunch !== user?.automaticLunch)
ops.push(updateUserAutomaticLunch({ automaticLunch })); ops.push(updateUserAutomaticLunch({ automaticLunch }));
if (ops.length === 0) return; if (ops.length === 0) return;
setLoading(true); setLoading(true);
@@ -101,13 +103,14 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
<> <>
<CardHeader> <CardHeader>
<CardTitle className='text-2xl'>Account Information</CardTitle> <CardTitle className='text-2xl'>Account Information</CardTitle>
<CardDescription> <CardDescription>Update your account information here.</CardDescription>
Update your account information here.
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-6'> <form
onSubmit={form.handleSubmit(handleSubmit)}
className='space-y-6'
>
<FormField <FormField
control={form.control} control={form.control}
name='name' name='name'
@@ -139,23 +142,19 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
</FormItem> </FormItem>
)} )}
/> />
<div className='flex flex-row justify-center space-x-6'> <div className='flex flex-row justify-center space-x-10'>
<FormField <FormField
control={form.control} control={form.control}
name='lunchTime' name='lunchTime'
render={({ field }) => ( render={({ field }) => (
<FormItem className='w-2/5'> <FormItem className='sm:w-2/5'>
<FormLabel>Lunch Time</FormLabel> <div className='flex flex-row space-x-2 my-auto'>
<FormControl> <FormLabel>Lunch Time</FormLabel>
<Input <FormControl>
type='time' <Input type='time' className='w-28' {...field} />
className='max-w-26' </FormControl>
{...field} </div>
/> <FormDescription>Your regular lunch time.</FormDescription>
</FormControl>
<FormDescription>
Your regular lunch time.
</FormDescription>
<FormDescription className='dark:text-red-300/60 text-red-800/80'> <FormDescription className='dark:text-red-300/60 text-red-800/80'>
{!user?.lunchTime && 'Not currently set.'} {!user?.lunchTime && 'Not currently set.'}
</FormDescription> </FormDescription>
@@ -167,10 +166,10 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
control={form.control} control={form.control}
name='automaticLunch' name='automaticLunch'
render={({ field }) => ( render={({ field }) => (
<FormItem className='w-2/5'> <FormItem className='w-2/5 mt-2'>
<div className='flex flex-row space-x-2 my-auto'> <div className='flex flex-row space-x-2 my-auto'>
<FormControl> <FormControl>
<Checkbox <Switch
className='border-solid border-primary' className='border-solid border-primary'
checked={field.value} checked={field.value}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
@@ -187,8 +186,12 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
/> />
</div> </div>
<div className='flex justify-center'> <div className='flex justify-center mt-5'>
<SubmitButton disabled={loading} pendingText='Saving...'> <SubmitButton
className='lg:w-1/3 w-2/3 text-[1.0rem]'
disabled={loading}
pendingText='Saving...'
>
Save Changes Save Changes
</SubmitButton> </SubmitButton>
</div> </div>

View File

@@ -1,10 +1,10 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { CheckIcon } from "lucide-react" import { CheckIcon } from 'lucide-react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
function Checkbox({ function Checkbox({
className, className,
@@ -12,21 +12,21 @@ function Checkbox({
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) { }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return ( return (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" data-slot='checkbox'
className={cn( className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", 'peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className className,
)} )}
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
data-slot="checkbox-indicator" data-slot='checkbox-indicator'
className="flex items-center justify-center text-current transition-none" className='flex items-center justify-center text-current transition-none'
> >
<CheckIcon className="size-3.5" /> <CheckIcon className='size-3.5' />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
) );
} }
export { Checkbox } export { Checkbox };

View File

@@ -70,6 +70,7 @@ export { ScrollArea, ScrollBar } from './scroll-area';
export { Separator } from './separator'; export { Separator } from './separator';
export { StatusMessage } from './status-message'; export { StatusMessage } from './status-message';
export { SubmitButton } from './submit-button'; export { SubmitButton } from './submit-button';
export { Switch } from './switch';
export { export {
Table, Table,
TableHeader, TableHeader,

View File

@@ -0,0 +1,31 @@
'use client';
import * as React from 'react';
import * as SwitchPrimitive from '@radix-ui/react-switch';
import { cn } from '@/lib/utils';
function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot='switch'
className={cn(
'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot='switch-thumb'
className={cn(
'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',
)}
/>
</SwitchPrimitive.Root>
);
}
export { Switch };

View File

@@ -65,6 +65,7 @@
"@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@sentry/nextjs": "^10.12.0", "@sentry/nextjs": "^10.12.0",
"@t3-oss/env-nextjs": "^0.13.8", "@t3-oss/env-nextjs": "^0.13.8",

View File

@@ -34,7 +34,7 @@ export const getUser = query(async (ctx) => {
name: user.name ?? null, name: user.name ?? null,
image, image,
lunchTime: user.lunchTime ?? null, lunchTime: user.lunchTime ?? null,
automaticLunch: user.automaticLunch ?? false as boolean, automaticLunch: user.automaticLunch ?? (false as boolean),
}; };
}); });
@@ -46,7 +46,7 @@ export const getAllUsers = query(async (ctx) => {
name: u.name ?? null, name: u.name ?? null,
image: u.image ?? null, image: u.image ?? null,
lunchTime: u.lunchTime ?? null, lunchTime: u.lunchTime ?? null,
automaticLUnch: u.automaticLunch ?? false as boolean, automaticLUnch: u.automaticLunch ?? (false as boolean),
})); }));
}); });
@@ -123,12 +123,11 @@ export const updateUserAutomaticLunch = mutation({
if (!userId) throw new ConvexError('Not authenticated.'); if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.db.get(userId); const user = await ctx.db.get(userId);
if (!user) throw new ConvexError('User not found.'); if (!user) throw new ConvexError('User not found.');
if (user.automaticLunch === automaticLunch) if (user.automaticLunch === automaticLunch) return { success: true };
return { success: true };
await ctx.db.patch(userId, { automaticLunch }); await ctx.db.patch(userId, { automaticLunch });
return { success: true }; return { success: true };
}, },
}) });
export const validatePassword = (password: string): boolean => { export const validatePassword = (password: string): boolean => {
if ( if (