Update form to look better & add automatic lunch toggle
This commit is contained in:
@@ -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",
|
||||||
|
@@ -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
|
||||||
|
@@ -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>
|
||||||
|
@@ -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 };
|
||||||
|
@@ -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,
|
||||||
|
31
apps/next/src/components/ui/switch.tsx
Normal file
31
apps/next/src/components/ui/switch.tsx
Normal 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 };
|
1
bun.lock
1
bun.lock
@@ -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",
|
||||||
|
@@ -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 (
|
||||||
|
Reference in New Issue
Block a user