From cc83207ff8c0e1035f4adff29605603384fbda18 Mon Sep 17 00:00:00 2001 From: Taek Been Nam Date: Tue, 7 May 2024 12:25:57 -0400 Subject: [PATCH 1/5] update sanity groq and schema to use dateTime --- .../appointmentDateTimePicker.tsx | 2 +- .../appointmentTimeslots.tsx | 4 ++-- .../appointmentForm/appointmentForm.tsx | 6 +----- .../appointments/upcomingAppointments.tsx | 2 +- lib/sanity/client.ts | 17 +++++++++-------- lib/sanity/sanity.types.ts | 7 ++++--- 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentDateTimePicker.tsx b/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentDateTimePicker.tsx index cd76854..c5641e6 100644 --- a/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentDateTimePicker.tsx +++ b/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentDateTimePicker.tsx @@ -7,7 +7,7 @@ import { mutate } from "swr"; import AppointmentTimeslots from "./appointmentTimeslots"; interface AppointmentDateTimePickerProps { - availableDates: string[]; + availableDates?: string[]; } function AppointmentDateTimePicker({ diff --git a/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx b/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx index 4e4edde..0f7489f 100644 --- a/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx +++ b/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx @@ -1,6 +1,7 @@ import { Badge } from "@/components/atoms/badge"; import { FormControl } from "@/components/molecules/form"; import { TimeslotSchema } from "@/lib/formSchema"; +import { format } from "date-fns"; import { useState } from "react"; import { useController } from "react-hook-form"; import useSWR from "swr"; @@ -27,7 +28,6 @@ function AppointmentTimeslots({ currentDate }: AppointmentTimeslotsProps) { e: React.SyntheticEvent, timeslot: z.infer, ) => { - // getValues('timeslot').id field.onChange(timeslot); if (selected !== timeslot.id) { return setSelected(timeslot.id); @@ -52,7 +52,7 @@ function AppointmentTimeslots({ currentDate }: AppointmentTimeslotsProps) { className="shrink flex-grow-0" key={timeslot.id} selected={selected === timeslot.id} - label={timeslot.time} + label={format(new Date(`${timeslot.time}`), "p")} onClick={(e) => handleSelect(e, timeslot)} /> )) diff --git a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx index ddbb317..59dc12c 100644 --- a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx +++ b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx @@ -71,8 +71,6 @@ interface AppointmentFormProps { onClose?: () => void; } -const AVAILABLE_DATES = ["2024-05-06", "2024-05-02", "2024-05-13"]; - /** * A client side form component that handles both creating and editing appointments. */ @@ -143,9 +141,7 @@ export const AppointmentForm = ({ Pick your appointment date - + diff --git a/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx b/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx index 84476e5..46eb47d 100644 --- a/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx +++ b/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx @@ -37,7 +37,7 @@ export default async function UpcomingAppointments() {

- {format(new Date(`${date} ${time}`), "p")} + {format(new Date(`${time}`), "p")}

{[address1, address2, city, state, zipCode] diff --git a/lib/sanity/client.ts b/lib/sanity/client.ts index 7f84d4c..ec35c15 100644 --- a/lib/sanity/client.ts +++ b/lib/sanity/client.ts @@ -49,13 +49,14 @@ export const getAvailableDate = async () => { export const getAvailableTimeSlot = async (date: string) => { const timeSlots = await client.fetch( `*[_type=='timeslot' - && date=='${date}' && !(_id in *[_type=='appointment'].timeslot._ref) - ]{ - "id": _id, - date, - "time": duration.start - }`, + && date == '${date}' + && dateTime(time) >= dateTime(now()) + ]{ + "id": _id, + date, + time, + }`, {}, { cache: "no-store" }, ); @@ -79,12 +80,12 @@ export const getAppointments = async (userId = "") => { const res = await client.fetch( `*[_type=='appointment' && customer->_id == '${userId}' - && timeslot->date >= now() + && dateTime(timeslot->time) >= dateTime(now()) ]{ "id":_id, "timeslotId":timeslot->_id, "date":timeslot->date, - "time":timeslot->duration.start, + "time":timeslot->time, address1, address2, city, diff --git a/lib/sanity/sanity.types.ts b/lib/sanity/sanity.types.ts index 766cce3..ec74b44 100644 --- a/lib/sanity/sanity.types.ts +++ b/lib/sanity/sanity.types.ts @@ -262,18 +262,19 @@ export type AVAILABLE_DATE_QUERYResult = Array<{ id: string; date: string; }>; +// Source: ./groq/groq.ts // Variable: AVAILABLE_TIMESLOT_QUERY -// Query: *[_type=='timeslot' && date=='' && !(_id in *[_type=='appointment'].timeslot._ref)]{ "id": _id, date, "startTime": duration.start} +// Query: *[_type=='timeslot' && !(_id in *[_type=='appointment'].timeslot._ref) && date == '' && dateTime(timeslot) >= dateTime(now())]{ "id": _id, date, timeslot,} export type AVAILABLE_TIMESLOT_QUERYResult = Array<{ id: string; date: string; - time: string; + timeslot: string; }>; // Variable: IS_TIMESLOT_RESERVED_QUERY // Query: count(*[_type=='appointment' && references('') ]) > 0 export type IS_TIMESLOT_RESERVED_QUERYResult = unknown; // Variable: APPOINTMENT_QUERY -// Query: *[_type=='appointment' && customer->_id == '']{ "id":_id, "timeslotId":timeslot->_id, "date":timeslot->date, "time":timeslot->duration.start, address1, address2, city, state, zipCode, comment, customer->{"id": _id, firstName, lastName}, stylist->{"id": _id, firstName, lastName}} +// Query: *[_type=='appointment' && customer->_id == '' && dateTime(timeslot->timeslot) >= dateTime(now())]{ "id":_id, "timeslotId":timeslot->_id, "date":timeslot->date, "time":timeslot->timeslot, address1, address2, city, state, zipCode, comment, customer->{"id": _id, firstName, lastName}, stylist->{"id": _id, firstName, lastName}} export type APPOINTMENT_QUERYResult = Array<{ id: string; timeslotId: string; From eb4a42a4a96c171b2114484b1365ed60effc54a1 Mon Sep 17 00:00:00 2001 From: Taek Been Nam Date: Tue, 7 May 2024 12:36:29 -0400 Subject: [PATCH 2/5] adjust sendEmail date time format --- .../components/appointmentForm/appointmentForm.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx index 59dc12c..291c71f 100644 --- a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx +++ b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx @@ -42,10 +42,10 @@ async function sendEmail(formValues: z.infer) { const res = await fetch("/api/email", { method: "POST", headers: { "Content-type": "application/json" }, - // This is a mock data. Replace with proper form values later. + body: JSON.stringify({ date: new Date(timeslot.date).toUTCString().slice(0, 16), - time: format(new Date(`${timeslot.date} ${timeslot.time}`), "p"), + time: format(new Date(`${timeslot.time}`), "p"), location, comment, }), @@ -104,6 +104,7 @@ export const AppointmentForm = ({ const onSubmit = async (values: z.infer) => { try { + console.log(values); await createAppointment({ ...values }); await sendEmail(values); From fbb507d89f21ffc8823ce8edc83a432826b0d06d Mon Sep 17 00:00:00 2001 From: Taek Been Nam Date: Tue, 7 May 2024 12:56:51 -0400 Subject: [PATCH 3/5] util functions to format date and time --- .../appointmentTimeslots.tsx | 3 ++- .../appointmentForm/appointmentForm.tsx | 6 +++--- .../appointments/upcomingAppointments.tsx | 5 +++-- lib/utils.ts | 18 +++++++++++++----- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx b/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx index 0f7489f..7d4e7dc 100644 --- a/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx +++ b/app/(protected)/my-appointments/components/appointmentDateTimePicker/appointmentTimeslots.tsx @@ -1,6 +1,7 @@ import { Badge } from "@/components/atoms/badge"; import { FormControl } from "@/components/molecules/form"; import { TimeslotSchema } from "@/lib/formSchema"; +import { formatToDisplayTime } from "@/lib/utils"; import { format } from "date-fns"; import { useState } from "react"; import { useController } from "react-hook-form"; @@ -52,7 +53,7 @@ function AppointmentTimeslots({ currentDate }: AppointmentTimeslotsProps) { className="shrink flex-grow-0" key={timeslot.id} selected={selected === timeslot.id} - label={format(new Date(`${timeslot.time}`), "p")} + label={formatToDisplayTime(timeslot.time)} onClick={(e) => handleSelect(e, timeslot)} /> )) diff --git a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx index 291c71f..bc919ae 100644 --- a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx +++ b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx @@ -17,7 +17,7 @@ import { } from "@/components/molecules/form"; import { useToast } from "@/components/molecules/toast"; import { FormSchema } from "@/lib/formSchema"; -import { cn } from "@/lib/utils"; +import { cn, formatToDisplayDate, formatToDisplayTime } from "@/lib/utils"; import AppointmentDateTimePicker from "../appointmentDateTimePicker/appointmentDateTimePicker"; /** @@ -44,8 +44,8 @@ async function sendEmail(formValues: z.infer) { headers: { "Content-type": "application/json" }, body: JSON.stringify({ - date: new Date(timeslot.date).toUTCString().slice(0, 16), - time: format(new Date(`${timeslot.time}`), "p"), + date: formatToDisplayDate(timeslot.date), + time: formatToDisplayTime(timeslot.time), location, comment, }), diff --git a/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx b/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx index 46eb47d..178ea21 100644 --- a/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx +++ b/app/(protected)/my-appointments/components/appointments/upcomingAppointments.tsx @@ -8,6 +8,7 @@ import CancelDialog from "../cancelDialog/cancelDialog"; import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server"; import { getAppointments } from "@/lib/sanity/client"; import { format } from "date-fns"; +import { formatToDisplayDate, formatToDisplayTime } from "@/lib/utils"; export default async function UpcomingAppointments() { const { getUser } = getKindeServerSession(); @@ -33,11 +34,11 @@ export default async function UpcomingAppointments() {

- {new Date(date).toUTCString().slice(0, 16)} + {formatToDisplayDate(date)}

- {format(new Date(`${time}`), "p")} + {formatToDisplayTime(time)}

{[address1, address2, city, state, zipCode] diff --git a/lib/utils.ts b/lib/utils.ts index 289d9e6..ce2f99c 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,16 +1,24 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; import { get, set, ref, child } from "firebase/database"; +import { format } from "date-fns"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function dateTimeToUnixTimeStamp(date: string, time: string) { - const combinedDateTimeString = date + "T" + time; - const dateTime = new Date(combinedDateTimeString); - const unixTimeStamp = dateTime.getTime() / 1000; - return unixTimeStamp; +/** Convert incoming date string to UTC format. This only returns date portion. */ +export function formatToDisplayDate(date: string): string { + const res = new Date(date).toUTCString().slice(0, 16); + + return res; +} + +/** Convert incoming date string to HH:mm AM/PM format*/ +export function formatToDisplayTime(time: string): string { + const res = format(new Date(`${time}`), "p"); + + return res; } export function unixToDateTimeStrings(unixTimeStamp: number) { From 15b412db4fed902f338ce86c336a231aab7506a7 Mon Sep 17 00:00:00 2001 From: Taek Been Nam Date: Tue, 7 May 2024 13:08:08 -0400 Subject: [PATCH 4/5] getAvailableTimeSlot -> getAvailableTimeSlots --- app/api/timeslots/[date]/route.ts | 4 ++-- lib/sanity/client.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/api/timeslots/[date]/route.ts b/app/api/timeslots/[date]/route.ts index a5ea925..21384a1 100644 --- a/app/api/timeslots/[date]/route.ts +++ b/app/api/timeslots/[date]/route.ts @@ -1,4 +1,4 @@ -import { getAvailableTimeSlot } from "@/lib/sanity/client"; +import { getAvailableTimeSlots } from "@/lib/sanity/client"; import { formatISO } from "date-fns"; import { NextRequest } from "next/server"; @@ -12,6 +12,6 @@ export async function GET( representation: "date", }); - const res = await getAvailableTimeSlot(formattedDate); + const res = await getAvailableTimeSlots(formattedDate); return Response.json(res, { status: 200 }); } diff --git a/lib/sanity/client.ts b/lib/sanity/client.ts index ec35c15..19829b5 100644 --- a/lib/sanity/client.ts +++ b/lib/sanity/client.ts @@ -46,7 +46,7 @@ export const getAvailableDate = async () => { return Object.keys(distinctDates); }; -export const getAvailableTimeSlot = async (date: string) => { +export const getAvailableTimeSlots = async (date: string) => { const timeSlots = await client.fetch( `*[_type=='timeslot' && !(_id in *[_type=='appointment'].timeslot._ref) From 7460924e61cdd901d4132cd27a8a2289d56043f5 Mon Sep 17 00:00:00 2001 From: Taek Been Nam Date: Tue, 7 May 2024 13:08:29 -0400 Subject: [PATCH 5/5] remove console log --- .../components/appointmentForm/appointmentForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx index bc919ae..4510dc7 100644 --- a/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx +++ b/app/(protected)/my-appointments/components/appointmentForm/appointmentForm.tsx @@ -104,7 +104,6 @@ export const AppointmentForm = ({ const onSubmit = async (values: z.infer) => { try { - console.log(values); await createAppointment({ ...values }); await sendEmail(values);