From 92854382dbf211ca694426d2138324bd91d3d8cc Mon Sep 17 00:00:00 2001 From: gibbyb Date: Wed, 17 Sep 2025 15:56:59 -0500 Subject: [PATCH] Lunch time reminder is now actually functional --- apps/next/src/app/layout.tsx | 4 +-- .../components/layout/profile/user-info.tsx | 28 ++++++++++++++++++- .../components/providers/lunch-reminder.tsx | 15 ++++------ packages/backend/convex/auth.ts | 18 ++++++++++++ packages/backend/convex/schema.ts | 1 + 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/apps/next/src/app/layout.tsx b/apps/next/src/app/layout.tsx index 927925c..3dbd564 100644 --- a/apps/next/src/app/layout.tsx +++ b/apps/next/src/app/layout.tsx @@ -50,9 +50,7 @@ const RootLayout = async ({
{children} - + diff --git a/apps/next/src/components/layout/profile/user-info.tsx b/apps/next/src/components/layout/profile/user-info.tsx index b6787ff..c22b82a 100644 --- a/apps/next/src/components/layout/profile/user-info.tsx +++ b/apps/next/src/components/layout/profile/user-info.tsx @@ -32,6 +32,10 @@ const formSchema = z.object({ email: z.email({ message: 'Please enter a valid email address.', }), + lunchTime: z + .string() + .trim() + .min(3, { message: 'Must be a valid 24-hour time. Example: 13:00'}), }); type UserInfoFormProps = { @@ -44,12 +48,14 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => { const updateUserName = useMutation(api.auth.updateUserName); const updateUserEmail = useMutation(api.auth.updateUserEmail); + const updateUserLunchtime = useMutation(api.auth.updateUserLunchtime); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { name: user?.name ?? '', email: user?.email ?? '', + lunchTime: user?.lunchTime ?? '', }, }); @@ -57,13 +63,16 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => { const ops: Promise[] = []; const name = values.name.trim(); const email = values.email.trim().toLowerCase(); + const lunchTime = values.lunchTime.trim(); if (name !== (user?.name ?? '')) ops.push(updateUserName({ name })); if (email !== (user?.email ?? '')) ops.push(updateUserEmail({ email })); + if (lunchTime !== (user?.lunchTime ?? '')) + ops.push(updateUserLunchtime({ lunchTime })) if (ops.length === 0) return; setLoading(true); try { await Promise.all(ops); - form.reset({ name, email }); + form.reset({ name, email, lunchTime }); toast.success('Profile updated successfully.'); } catch (error) { console.error(error); @@ -109,6 +118,23 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => { )} /> + ( + + Regular Lunch Time + + + + + Enter the time you take your lunch most often in 24 hour time. + + + + )} + /> +
Save Changes diff --git a/apps/next/src/components/providers/lunch-reminder.tsx b/apps/next/src/components/providers/lunch-reminder.tsx index 2a96b0d..dafa37c 100644 --- a/apps/next/src/components/providers/lunch-reminder.tsx +++ b/apps/next/src/components/providers/lunch-reminder.tsx @@ -2,14 +2,9 @@ import { useEffect, useRef } from 'react'; import { toast } from 'sonner'; -import { useMutation } from 'convex/react'; +import { useMutation, useQuery } from 'convex/react'; import { api } from '~/convex/_generated/api'; -type Props = { - lunchTime: string; - enabled?: boolean; -}; - const nextOccurrenceMs = (hhmm: string, from = new Date()): number => { const [hStr, mStr] = hhmm.split(':'); const target = new Date(from); @@ -18,13 +13,13 @@ const nextOccurrenceMs = (hhmm: string, from = new Date()): number => { return target.getTime() - from.getTime(); }; -export const LunchReminder = ({ lunchTime, enabled = true }: Props) => { +export const LunchReminder = () => { const setStatus = useMutation(api.statuses.create); const timeoutRef = useRef(null); - console.log('LunchReminder is running!') + const user = useQuery(api.auth.getUser); + const lunchTime = user?.lunchTime ?? ''; useEffect(() => { - if (!enabled) return; const schedule = () => { const ms = nextOccurrenceMs(lunchTime); console.log('Ms = ', ms) @@ -66,7 +61,7 @@ export const LunchReminder = ({ lunchTime, enabled = true }: Props) => { return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }; - }, [enabled, lunchTime, setStatus]); + }, [lunchTime, setStatus]); return null; }; diff --git a/packages/backend/convex/auth.ts b/packages/backend/convex/auth.ts index 17076f8..ddc7333 100644 --- a/packages/backend/convex/auth.ts +++ b/packages/backend/convex/auth.ts @@ -33,6 +33,7 @@ export const getUser = query(async (ctx) => { email: user.email ?? null, name: user.name ?? null, image, + lunchTime: user.lunchTime ?? null, }; }); @@ -43,6 +44,7 @@ export const getAllUsers = query(async (ctx) => { email: u.email ?? null, name: u.name ?? null, image: u.image ?? null, + lunchTime: u.lunchTime ?? null, })); }); @@ -96,6 +98,22 @@ export const updateUserImage = mutation({ }, }); +export const updateUserLunchtime = mutation({ + args: { + lunchTime: v.string(), + }, + handler: async (ctx, { lunchTime }) => { + const userId = await getAuthUserId(ctx); + if (!userId) throw new ConvexError('Not authenticated.'); + const user = await ctx.db.get(userId); + if (!user) throw new ConvexError('User not found.'); + if (!lunchTime.includes(':')) + throw new ConvexError('Lunch time is invalid.'); + await ctx.db.patch(userId, { lunchTime }); + return { success: true }; + }, +}); + export const validatePassword = (password: string): boolean => { if ( password.length < 8 || diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 4b37535..f4fd1bc 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -12,6 +12,7 @@ export default defineSchema({ image: v.optional(v.string()), email: v.optional(v.string()), currentStatusId: v.optional(v.id('statuses')), + lunchTime: v.optional(v.string()), emailVerificationTime: v.optional(v.number()), phone: v.optional(v.string()), phoneVerificationTime: v.optional(v.number()),