From 2620e5a5996eba0a20d167980ec4d4ce9c0a1f66 Mon Sep 17 00:00:00 2001 From: Md Kaif Ansari Date: Wed, 24 Sep 2025 15:53:38 +0530 Subject: [PATCH 1/2] feat: implement hint on the schedule page, to create schedules --- bun.lock | 13 ++ package.json | 1 + src/app/page.tsx | 17 +-- src/app/schedules/page.tsx | 130 +++++++----------- .../schedules/forms/CreateScheduleForm.tsx | 106 +++----------- src/providers/index.tsx | 58 +++++++- 6 files changed, 143 insertions(+), 182 deletions(-) diff --git a/bun.lock b/bun.lock index d1848ca..59d2fbf 100644 --- a/bun.lock +++ b/bun.lock @@ -39,6 +39,7 @@ "@heroui/user": "^2.2.21", "@hookform/resolvers": "^5.2.2", "@internationalized/date": "^3.9.0", + "@reactour/tour": "^3.8.0", "@tanstack/react-query": "^5.89.0", "@tanstack/react-query-devtools": "^5.89.0", "axios": "^1.12.2", @@ -599,6 +600,16 @@ "@react-types/tooltip": ["@react-types/tooltip@3.4.20", "", { "dependencies": { "@react-types/overlays": "^3.9.1", "@react-types/shared": "^3.32.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-tF1yThwvgSgW8Gu/CLL0p92AUldHR6szlwhwW+ewT318sQlfabMGO4xlCNFdxJYtqTpEXk2rlaVrBuaC//du0w=="], + "@reactour/mask": ["@reactour/mask@1.2.0", "", { "dependencies": { "@reactour/utils": "*" }, "peerDependencies": { "react": "16.x || 17.x || 18.x || 19.x" } }, "sha512-XLgBLWfKJybtZjNTSO5lt/SIvRlCZBadB6JfE/hO1ErqURRjYhnv+edC0Ki1haUCqMGFppWk3lwcPCjmK0xNog=="], + + "@reactour/popover": ["@reactour/popover@1.3.0", "", { "dependencies": { "@reactour/utils": "*" }, "peerDependencies": { "react": "16.x || 17.x || 18.x || 19.x" } }, "sha512-YdyjSmHPvEeQEcJM4gcGFa5pI/Yf4nZGqwG4JnT+rK1SyUJBIPnm4Gkl/h7/+1g0KCFMkwNwagS3ZiXvZB7ThA=="], + + "@reactour/tour": ["@reactour/tour@3.8.0", "", { "dependencies": { "@reactour/mask": "*", "@reactour/popover": "*", "@reactour/utils": "*" }, "peerDependencies": { "react": "16.x || 17.x || 18.x || 19.x" } }, "sha512-KZTFi1pAvoTVKKRdBN5+XCYxXBp4k4Ql/acZcXyPvec8VU24fkMSEeV+v8krfYQpoVcewxIu3gM6xWZZLjxi7w=="], + + "@reactour/utils": ["@reactour/utils@0.6.0", "", { "dependencies": { "@rooks/use-mutation-observer": "^4.11.2", "resize-observer-polyfill": "^1.5.1" }, "peerDependencies": { "react": "16.x || 17.x || 18.x || 19.x" } }, "sha512-GqaLjQi7MJsgtAKjdiw2Eak1toFkADoLRnm1+HZpaD+yl+DkaHpC1N7JAl+kVOO5I17bWInPA+OFbXjO9Co8Qg=="], + + "@rooks/use-mutation-observer": ["@rooks/use-mutation-observer@4.11.2", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-vpsdrZdr6TkB1zZJcHx+fR1YC/pHs2BaqcuYiEGjBVbwY5xcC49+h0hAUtQKHth3oJqXfIX/Ng8S7s5HFHdM/A=="], + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.12.0", "", {}, "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw=="], @@ -1397,6 +1408,8 @@ "requestidlecallback": ["requestidlecallback@0.3.0", "", {}, "sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ=="], + "resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="], + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], diff --git a/package.json b/package.json index 0f66fc0..970d1d4 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@heroui/user": "^2.2.21", "@hookform/resolvers": "^5.2.2", "@internationalized/date": "^3.9.0", + "@reactour/tour": "^3.8.0", "@tanstack/react-query": "^5.89.0", "@tanstack/react-query-devtools": "^5.89.0", "axios": "^1.12.2", diff --git a/src/app/page.tsx b/src/app/page.tsx index 9dc55c2..63f49da 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,6 +2,7 @@ import { useProviders } from "@/hooks/data/use-providers"; import { Button } from "@heroui/button"; +import { useTour } from "@reactour/tour"; import Link from "next/link"; export default function Home() { @@ -21,9 +22,7 @@ export default function Home() { } if (data && data.length > 0) { - const hasLinkedIn = data.some( - (provider) => provider.provider.toLowerCase() === "linkedin" - ); + const hasLinkedIn = data.some((provider) => provider.provider.toLowerCase() === "linkedin"); if (!hasLinkedIn) { return true; } @@ -31,25 +30,19 @@ export default function Home() { return false; } - return ( -
+
{actionNeeded() ? ( <>

Welcome to Post0

-

- Please connect your LinkedIn account to get started. -

+

Please connect your LinkedIn account to get started.

) : ( <>

Welcome Back to Post0

-

- Your LinkedIn account is connected. -

+

Your LinkedIn account is connected.

)} -
{actionNeeded() ? ( diff --git a/src/app/schedules/page.tsx b/src/app/schedules/page.tsx index 52fcabf..1cd9d9d 100644 --- a/src/app/schedules/page.tsx +++ b/src/app/schedules/page.tsx @@ -1,7 +1,7 @@ "use client"; import { Button } from "@heroui/button"; import { useDisclosure } from "@heroui/modal"; -import { PlusIcon, CalendarIcon, AlertCircle, Clock } from "lucide-react"; +import { PlusIcon, CalendarIcon, AlertCircle, Clock, HelpCircle } from "lucide-react"; import React, { useState } from "react"; import { ScheduleFormModal } from "@/components/schedules/forms/CreateScheduleForm"; @@ -14,12 +14,13 @@ import { Divider } from "@heroui/divider"; import { Tabs, Tab } from "@heroui/tabs"; import { useRouter } from "next/navigation"; import { getPlatform } from "@/lib/icons"; +import { useTour } from "@reactour/tour"; export default function Schedules() { const { isOpen, onOpen, onOpenChange } = useDisclosure(); const { data: schedules, isLoading, isError } = useSchedules(); const [selectedTab, setSelectedTab] = useState("all"); - + const { setIsOpen: setTourOpen, setCurrentStep } = useTour(); // Filter schedules based on search query and selected tab const filteredSchedules = React.useMemo(() => { if (!schedules?.schedules_list) return []; @@ -28,44 +29,27 @@ export default function Schedules() { // Filter by tab/status if (selectedTab !== "all") { - filtered = filtered.filter( - (schedule) => - schedule.status.toLowerCase() === selectedTab.toLowerCase() - ); + filtered = filtered.filter((schedule) => schedule.status.toLowerCase() === selectedTab.toLowerCase()); } return filtered; }, [schedules?.schedules_list, selectedTab]); return ( -
+

Schedules

-

- Manage your posting schedules across platforms -

+

Manage your posting schedules across platforms

-
-
- setSelectedTab(key as string)} - color="primary" - variant="underlined" - className="max-w-full" - > +
+ setSelectedTab(key as string)} color="primary" variant="underlined" className="max-w-full"> @@ -73,27 +57,31 @@ export default function Schedules() {
- +
+ +
); } -function ScheduleList({ - isError, - isLoading, - schedules, -}: { - schedules: Schedule[]; - isLoading: boolean; - isError: boolean; -}) { +function ScheduleList({ isError, isLoading, schedules }: { schedules: Schedule[]; isLoading: boolean; isError: boolean }) { const router = useRouter(); // Function to get status badge color @@ -130,7 +118,7 @@ function ScheduleList({ }; return ( -
+
{isLoading && } {isError && ( @@ -141,28 +129,20 @@ function ScheduleList({ )} {!isLoading && !isError && schedules.length === 0 && ( - + -
+

No schedules found

-

- Create a new schedule to start posting content -

+

Create a new schedule to start posting content

)} {!isLoading && !isError && schedules.length > 0 && ( -
+
{schedules.map((schedule) => ( - handleCardClick(schedule.id)} - shadow="sm" - > + handleCardClick(schedule.id)} shadow="sm">
@@ -174,30 +154,22 @@ function ScheduleList({
-
- - {schedule.status} - +
+ {schedule.status}
-

- {formatDateRange(schedule.startDate, schedule.endDate)} -

+

{formatDateRange(schedule.startDate, schedule.endDate)}

-

- {schedule.timesPerDay} posts per day -

+

{schedule.timesPerDay} posts per day

- -

- Created {new Date(schedule.createdAt).toLocaleDateString()} -

-

View Details

+ +

Created {new Date(schedule.createdAt).toLocaleDateString()}

+

View Details

))} @@ -209,16 +181,16 @@ function ScheduleList({ function LocalSkeletion() { return ( -
+
{[1, 2, 3].map((item) => ( - +
- +
- - + +
@@ -226,18 +198,18 @@ function LocalSkeletion() {
- - + +
- - + +
- - + +
))} diff --git a/src/components/schedules/forms/CreateScheduleForm.tsx b/src/components/schedules/forms/CreateScheduleForm.tsx index c81c5ed..6cfca18 100644 --- a/src/components/schedules/forms/CreateScheduleForm.tsx +++ b/src/components/schedules/forms/CreateScheduleForm.tsx @@ -1,11 +1,5 @@ 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"; @@ -44,9 +38,7 @@ const scheduleSchema = z timezone: z.string(), startDate: z.date(), endDate: z.date(), - postTimes: z - .array(z.instanceof(Time)) - .nonempty("At least one time is required"), + postTimes: z.array(z.instanceof(Time)).nonempty("At least one time is required"), assetSource: z.nativeEnum(AssetSource), }) .refine((data) => data.endDate >= data.startDate, { @@ -56,20 +48,13 @@ const scheduleSchema = z type ScheduleFormValues = z.infer; -export function ScheduleFormModal({ - isOpen, - onOpenChange, -}: ScheduleFormModalProps) { +export function ScheduleFormModal({ isOpen, onOpenChange }: ScheduleFormModalProps) { const { data: providersData } = useProviders(); const { data: user } = authClient.useSession(); 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)); const availablePlatforms = connectedProviders.map(([key, provider]) => ({ value: key.toUpperCase(), @@ -126,12 +111,7 @@ export function ScheduleFormModal({ }; return ( - +
{(onClose) => ( @@ -142,22 +122,7 @@ export function ScheduleFormModal({
- ( - - )} - /> + } />
@@ -181,10 +146,7 @@ export function ScheduleFormModal({ variant="bordered" > {availablePlatforms.map((platform) => ( - } - > + }> {platform.label} ))} @@ -208,34 +170,18 @@ export function ScheduleFormModal({ label="Duration" variant="faded" value={{ - start: parseDate( - field.value.toISOString().split("T")[0] - ), - end: parseDate( - endField.value.toISOString().split("T")[0] - ), + start: parseDate(field.value.toISOString().split("T")[0]), + end: parseDate(endField.value.toISOString().split("T")[0]), }} onChange={(range) => { if (range?.start && range?.end) { - field.onChange( - range.start.toDate( - form.getValues("timezone") - ) - ); - endField.onChange( - range.end.toDate( - form.getValues("timezone") - ) - ); + field.onChange(range.start.toDate(form.getValues("timezone"))); + endField.onChange(range.end.toDate(form.getValues("timezone"))); } }} className="w-full" /> - {errors.endDate && ( -

- {errors.endDate.message} -

- )} + {errors.endDate &&

{errors.endDate.message}

}
)} /> @@ -264,21 +210,15 @@ export function ScheduleFormModal({ isRequired variant="bordered" > - - Gallery Assets - - - Unsplash - - - No image - + Gallery Assets + Unsplash + No image )} />
-
+
- ( - - )} - /> + } />
- + +
+ + + +
+
+ +

Your Profile

+
+ +
+
+ +
+ +
+ {isEditingProfile ? ( +
+ setProfileForm({ ...profileForm, name: e.target.value })} variant="bordered" isDisabled={updateProfileMutation.isPending} /> +