From 43481eaa1ac075957dfde7eed2a92bf22acc1544 Mon Sep 17 00:00:00 2001 From: Priyanshu Verma Date: Tue, 30 Sep 2025 13:45:22 +0000 Subject: [PATCH 1/3] feat: added more options while generating schedule --- instrumentation-client.ts | 16 +- src/app/schedules/page.tsx | 3 +- .../schedules/forms/CreateScheduleForm.tsx | 782 ++++++++++++++---- .../schedules/forms/TimeSlotInput.tsx | 14 +- 4 files changed, 647 insertions(+), 168 deletions(-) diff --git a/instrumentation-client.ts b/instrumentation-client.ts index b9d0899..b320c1b 100644 --- a/instrumentation-client.ts +++ b/instrumentation-client.ts @@ -1,9 +1,9 @@ -import posthog from "posthog-js"; +// import posthog from "posthog-js"; -posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { - api_host: "/ingest", - ui_host: "https://us.posthog.com", - defaults: "2025-05-24", - capture_exceptions: true, // This enables capturing exceptions using Error Tracking, set to false if you don't want this - debug: process.env.NODE_ENV === "development", -}); +// posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { +// api_host: "/ingest", +// ui_host: "https://us.posthog.com", +// defaults: "2025-05-24", +// capture_exceptions: true, // This enables capturing exceptions using Error Tracking, set to false if you don't want this +// debug: process.env.NODE_ENV === "development", +// }); diff --git a/src/app/schedules/page.tsx b/src/app/schedules/page.tsx index dc7a836..91686b4 100644 --- a/src/app/schedules/page.tsx +++ b/src/app/schedules/page.tsx @@ -199,7 +199,8 @@ function ScheduleList({
- + {schedule.status}
diff --git a/src/components/schedules/forms/CreateScheduleForm.tsx b/src/components/schedules/forms/CreateScheduleForm.tsx index 6cfca18..1eb7bbf 100644 --- a/src/components/schedules/forms/CreateScheduleForm.tsx +++ b/src/components/schedules/forms/CreateScheduleForm.tsx @@ -1,8 +1,16 @@ +"use client"; + import React from "react"; -import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/modal"; +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, +} from "@heroui/modal"; import { Select, SelectItem } from "@heroui/select"; -import { Input } from "@heroui/input"; +import { Input, Textarea } from "@heroui/input"; import { DateRangePicker } from "@heroui/date-picker"; import { parseDate } from "@internationalized/date"; @@ -21,6 +29,8 @@ import { addToast } from "@heroui/toast"; import { AssetSource } from "@/hooks/data/use-schedules"; import { timezones } from "@/lib/timezones"; import { authClient } from "@/lib/auth-client"; +import { Chip } from "@heroui/chip"; +import { ArrowLeftIcon, ArrowRightIcon, PlusIcon, X } from "lucide-react"; export interface ScheduleFormModalProps { isOpen: boolean; @@ -36,10 +46,22 @@ const scheduleSchema = z title: z.string().min(1, "Schedule name is required"), platform: z.string().min(1, "Platform is required"), timezone: z.string(), + startDate: z.date(), endDate: z.date(), - postTimes: z.array(z.instanceof(Time)).nonempty("At least one time is required"), - assetSource: z.nativeEnum(AssetSource), + + postTimes: z + .array(z.instanceof(Time)) + .nonempty("At least one time is required"), + + assetSource: z.enum(AssetSource), + unsplashTags: z.array(z.string().optional()), + + postType: z.enum(["IMAGE", "TEXT"], { + error: "Post type is required", + }), + + prompt: z.string().optional(), }) .refine((data) => data.endDate >= data.startDate, { message: "End date must be after start date", @@ -48,13 +70,33 @@ const scheduleSchema = z type ScheduleFormValues = z.infer; -export function ScheduleFormModal({ isOpen, onOpenChange }: ScheduleFormModalProps) { +const stepFormConfig = { + 1: ["title", "platform"] as const, + 2: ["postType", "assetSource", "unsplashTags", "prompt"] as const, + 3: ["startDate", "endDate", "timezone"] as const, + 4: ["postTimes"] as const, +}; + +export function ScheduleFormModal({ + isOpen, + onOpenChange, +}: ScheduleFormModalProps) { const { data: providersData } = useProviders(); const { data: user } = authClient.useSession(); + const [steps, setSteps] = React.useState(1); + const totalSteps = Object.keys(stepFormConfig).length; + const [isLoading, setIsLoading] = React.useState(false); const router = useRouter(); - const connectedProviders = Object.entries(REQUIRED_PROVIDER_CONNECTION).filter(([key]) => providersData?.some((p) => p.provider.toLowerCase() === key)); + const connectedProviders = Object.entries( + REQUIRED_PROVIDER_CONNECTION + ).filter(([key]) => + providersData?.some( + (p) => + p.provider.toLowerCase() === key && p.provider.toLowerCase() != "google" + ) + ); const availablePlatforms = connectedProviders.map(([key, provider]) => ({ value: key.toUpperCase(), @@ -67,12 +109,13 @@ export function ScheduleFormModal({ isOpen, onOpenChange }: ScheduleFormModalPro resolver: zodResolver(scheduleSchema), defaultValues: { title: "", - platform: "", - timezone: (user?.user as any)?.settings?.timezone || "UTC", startDate: new Date(), + timezone: (user?.user as any)?.settings?.timezone || "UTC", endDate: new Date(new Date().setDate(new Date().getDate() + 7)), postTimes: [new Time(9, 0, 0)], - assetSource: AssetSource.UNSPLASH, + assetSource: AssetSource.NONE, + unsplashTags: ["technology"], + prompt: "", }, mode: "onChange", }); @@ -83,8 +126,47 @@ export function ScheduleFormModal({ isOpen, onOpenChange }: ScheduleFormModalPro formState: { errors }, } = form; + const isNextStepDisabled = () => { + const live = form.watch(); + + const currentStepFields = stepFormConfig[steps]; + const optionalFields = ["unsplashTags", "prompt"]; + + // check if current includes optional fields + if (currentStepFields.some((field) => optionalFields.includes(field))) { + // If any of the optional fields are present, only require the non-optional fields to be filled + return !currentStepFields.every( + (field) => + optionalFields.includes(field) || + (!errors[field] && live[field] && live[field] !== "") + ); + } + + // If all fields in the current step are optional, enable Next button + console.log("Current Step Fields:", currentStepFields); + console.log("Live Values:", live); + + // parse and check of details are filled and optional + return !currentStepFields.every( + (field) => !errors[field] && live[field] && live[field] !== "" + ); + }; + const onSubmit = async (data: ScheduleFormValues) => { try { + if (steps < totalSteps) { + setSteps((steps + 1) as keyof typeof stepFormConfig); + return; + } + + if (connectedProviders.length === 0) { + addToast({ + title: "Please connect at least one platform", + color: "danger", + }); + return; + } + setIsLoading(true); const scheduleId = await fetcher("POST", "/schedules", { @@ -110,163 +192,555 @@ export function ScheduleFormModal({ isOpen, onOpenChange }: ScheduleFormModalPro } }; - return ( - -
- - {(onClose) => ( - <> - -

Create New Schedule

-
- -
-
- } /> -
- + const stepComponents: { [key: number]: React.ReactNode } = { + 1: ( +
+ ( + + )} + /> + ( + + )} + /> +
+

+ You have connected {connectedProviders.length} platform + {connectedProviders.length > 1 ? "s" : ""}. +

+ {connectedProviders.length === 0 && ( +

+ Please connect at least one platform to create a schedule. +

+ )} +
+
+

Hints & Tips

+
    +
  • + Choose a descriptive schedule name to easily identify it later. +
  • +
  • Ensure your selected platform is connected and authorized.
  • +
  • You can create multiple schedules for different platforms.
  • +
+
+
+ ), + 2: ( +
+ ( + + )} + /> + {form.watch("postType") == "IMAGE" && ( + ( + + )} + /> + )} + {form.watch("assetSource") == "UNSPLASH" && ( + { + const [inputValue, setInputValue] = React.useState(""); + const MAX_TAGS = 5; + if (field.value && field.value.length >= MAX_TAGS) { + return (
- ( - - )} - /> + {tag} + + ))} +
+

+ You have reached the maximum number of tags. +

+ ); + } + return ( +
+ setInputValue(e.target.value)} + isRequired + variant="bordered" + endContent={ + + } + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + const value = inputValue.trim(); + if (value && !field.value.includes(value)) { + field.onChange([...field.value, value]); + setInputValue(""); + } + } + }} + /> -
- ( - ( -
- { - if (range?.start && range?.end) { - field.onChange(range.start.toDate(form.getValues("timezone"))); - endField.onChange(range.end.toDate(form.getValues("timezone"))); - } - }} - className="w-full" - /> - {errors.endDate &&

{errors.endDate.message}

} -
- )} - /> - )} - /> +
+ {field.value.map((tag, index) => ( + { + const newTags = [...field.value]; + newTags.splice(index, 1); + field.onChange(newTags); + }} + > + + + } + > + {tag} + + ))}
- +
+ ); + }} + /> + )} + {form.watch("postType") === "TEXT" && ( + ( +
+