Lunch time reminder is now actually functional
This commit is contained in:
@@ -50,9 +50,7 @@ const RootLayout = async ({
|
|||||||
<Header />
|
<Header />
|
||||||
{children}
|
{children}
|
||||||
<NotificationsPermission />
|
<NotificationsPermission />
|
||||||
<LunchReminder
|
<LunchReminder />
|
||||||
lunchTime='15:24'
|
|
||||||
/>
|
|
||||||
<Toaster richColors />
|
<Toaster richColors />
|
||||||
</TVModeProvider>
|
</TVModeProvider>
|
||||||
</ConvexClientProvider>
|
</ConvexClientProvider>
|
||||||
|
@@ -32,6 +32,10 @@ const formSchema = z.object({
|
|||||||
email: z.email({
|
email: z.email({
|
||||||
message: 'Please enter a valid email address.',
|
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 = {
|
type UserInfoFormProps = {
|
||||||
@@ -44,12 +48,14 @@ 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 form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: user?.name ?? '',
|
name: user?.name ?? '',
|
||||||
email: user?.email ?? '',
|
email: user?.email ?? '',
|
||||||
|
lunchTime: user?.lunchTime ?? '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,13 +63,16 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
|
|||||||
const ops: Promise<unknown>[] = [];
|
const ops: Promise<unknown>[] = [];
|
||||||
const name = values.name.trim();
|
const name = values.name.trim();
|
||||||
const email = values.email.trim().toLowerCase();
|
const email = values.email.trim().toLowerCase();
|
||||||
|
const lunchTime = values.lunchTime.trim();
|
||||||
if (name !== (user?.name ?? '')) ops.push(updateUserName({ name }));
|
if (name !== (user?.name ?? '')) ops.push(updateUserName({ name }));
|
||||||
if (email !== (user?.email ?? '')) ops.push(updateUserEmail({ email }));
|
if (email !== (user?.email ?? '')) ops.push(updateUserEmail({ email }));
|
||||||
|
if (lunchTime !== (user?.lunchTime ?? ''))
|
||||||
|
ops.push(updateUserLunchtime({ lunchTime }))
|
||||||
if (ops.length === 0) return;
|
if (ops.length === 0) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await Promise.all(ops);
|
await Promise.all(ops);
|
||||||
form.reset({ name, email });
|
form.reset({ name, email, lunchTime });
|
||||||
toast.success('Profile updated successfully.');
|
toast.success('Profile updated successfully.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -109,6 +118,23 @@ export const UserInfoForm = ({ preloadedUser }: UserInfoFormProps) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<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'>
|
<div className='flex justify-center'>
|
||||||
<SubmitButton disabled={loading} pendingText='Saving...'>
|
<SubmitButton disabled={loading} pendingText='Saving...'>
|
||||||
Save Changes
|
Save Changes
|
||||||
|
@@ -2,14 +2,9 @@
|
|||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useMutation } from 'convex/react';
|
import { useMutation, useQuery } from 'convex/react';
|
||||||
import { api } from '~/convex/_generated/api';
|
import { api } from '~/convex/_generated/api';
|
||||||
|
|
||||||
type Props = {
|
|
||||||
lunchTime: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const nextOccurrenceMs = (hhmm: string, from = new Date()): number => {
|
const nextOccurrenceMs = (hhmm: string, from = new Date()): number => {
|
||||||
const [hStr, mStr] = hhmm.split(':');
|
const [hStr, mStr] = hhmm.split(':');
|
||||||
const target = new Date(from);
|
const target = new Date(from);
|
||||||
@@ -18,13 +13,13 @@ const nextOccurrenceMs = (hhmm: string, from = new Date()): number => {
|
|||||||
return target.getTime() - from.getTime();
|
return target.getTime() - from.getTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LunchReminder = ({ lunchTime, enabled = true }: Props) => {
|
export const LunchReminder = () => {
|
||||||
const setStatus = useMutation(api.statuses.create);
|
const setStatus = useMutation(api.statuses.create);
|
||||||
const timeoutRef = useRef<number | null>(null);
|
const timeoutRef = useRef<number | null>(null);
|
||||||
console.log('LunchReminder is running!')
|
const user = useQuery(api.auth.getUser);
|
||||||
|
const lunchTime = user?.lunchTime ?? '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!enabled) return;
|
|
||||||
const schedule = () => {
|
const schedule = () => {
|
||||||
const ms = nextOccurrenceMs(lunchTime);
|
const ms = nextOccurrenceMs(lunchTime);
|
||||||
console.log('Ms = ', ms)
|
console.log('Ms = ', ms)
|
||||||
@@ -66,7 +61,7 @@ export const LunchReminder = ({ lunchTime, enabled = true }: Props) => {
|
|||||||
return () => {
|
return () => {
|
||||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
};
|
};
|
||||||
}, [enabled, lunchTime, setStatus]);
|
}, [lunchTime, setStatus]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@@ -33,6 +33,7 @@ export const getUser = query(async (ctx) => {
|
|||||||
email: user.email ?? null,
|
email: user.email ?? null,
|
||||||
name: user.name ?? null,
|
name: user.name ?? null,
|
||||||
image,
|
image,
|
||||||
|
lunchTime: user.lunchTime ?? null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ export const getAllUsers = query(async (ctx) => {
|
|||||||
email: u.email ?? null,
|
email: u.email ?? null,
|
||||||
name: u.name ?? null,
|
name: u.name ?? null,
|
||||||
image: u.image ?? 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 => {
|
export const validatePassword = (password: string): boolean => {
|
||||||
if (
|
if (
|
||||||
password.length < 8 ||
|
password.length < 8 ||
|
||||||
|
@@ -12,6 +12,7 @@ export default defineSchema({
|
|||||||
image: v.optional(v.string()),
|
image: v.optional(v.string()),
|
||||||
email: v.optional(v.string()),
|
email: v.optional(v.string()),
|
||||||
currentStatusId: v.optional(v.id('statuses')),
|
currentStatusId: v.optional(v.id('statuses')),
|
||||||
|
lunchTime: v.optional(v.string()),
|
||||||
emailVerificationTime: v.optional(v.number()),
|
emailVerificationTime: v.optional(v.number()),
|
||||||
phone: v.optional(v.string()),
|
phone: v.optional(v.string()),
|
||||||
phoneVerificationTime: v.optional(v.number()),
|
phoneVerificationTime: v.optional(v.number()),
|
||||||
|
Reference in New Issue
Block a user