diff --git a/bun.lock b/bun.lock index 89d7211..805c89b 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", @@ -600,6 +601,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=="], @@ -1404,6 +1415,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 1c904e1..f89b7e7 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/personalize/page.tsx b/src/app/personalize/page.tsx new file mode 100644 index 0000000..d6fa9a1 --- /dev/null +++ b/src/app/personalize/page.tsx @@ -0,0 +1,381 @@ +"use client"; + +import React, { useState } from "react"; +import { Button } from "@heroui/button"; +import { Card, CardBody, CardHeader } from "@heroui/card"; +import { Input } from "@heroui/input"; +import { Textarea } from "@heroui/input"; +import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/modal"; +import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from "@heroui/dropdown"; +import { Avatar } from "@heroui/avatar"; +import { Badge } from "@heroui/badge"; +import { Chip } from "@heroui/chip"; +import { Divider } from "@heroui/divider"; +import { Plus, Search, Edit, Trash2, Copy, MoreVertical, User, Sparkles, Calendar, Settings } from "lucide-react"; +import { useUser } from "@/hooks/data/use-user"; +import { useGetPresets, useCreatePreset, useUpdatePreset, useDeletePreset, useUpdateUserProfile } from "@/hooks/data/use-presets"; +import LoaderSection from "@/components/shared/loader-section"; +import { addToast } from "@heroui/toast"; + +export default function PresetsPage() { + const [searchQuery, setSearchQuery] = useState(""); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [selectedPreset, setSelectedPreset] = useState(null); + const [isEditingProfile, setIsEditingProfile] = useState(false); + + const { data: userProfile, isLoading: userLoading, isError, error } = useUser(); + const createPresetMutation = useCreatePreset(); + const updatePresetMutation = useUpdatePreset(); + const deletePresetMutation = useDeletePreset(); + const updateProfileMutation = useUpdateUserProfile(); + + console.log("user", userProfile); + + const [presetForm, setPresetForm] = useState({ + title: "", + description: "", + }); + + const [profileForm, setProfileForm] = useState({ + name: userProfile?.name || "", + bio: userProfile?.bio || "", + }); + + React.useEffect(() => { + if (userProfile) { + setProfileForm({ + name: userProfile.name, + bio: userProfile.bio || "", + }); + } + }, [userProfile]); + + const handleCreatePreset = async () => { + if (presetForm.title.trim() && presetForm.description.trim()) { + try { + await createPresetMutation.mutateAsync({ + title: presetForm.title, + description: presetForm.description, + }); + + addToast({ + title: "Preset created successfully!", + color: "success", + }); + + setPresetForm({ title: "", description: "" }); + setIsCreateModalOpen(false); + } catch (error) { + addToast({ + title: "Failed to create preset. Please try again.", + color: "danger", + }); + } + } + }; + + const handleEditPreset = (preset: UserPreset) => { + setSelectedPreset(preset); + setPresetForm({ + title: preset.title, + description: preset.description, + }); + setIsEditModalOpen(true); + }; + + const handleUpdatePreset = async () => { + if (selectedPreset && presetForm.title.trim() && presetForm.description.trim()) { + try { + await updatePresetMutation.mutateAsync({ + title: presetForm.title, + description: presetForm.description, + id: selectedPreset.id, + }); + + addToast({ + title: "Preset updated successfully!", + color: "success", + }); + + setIsEditModalOpen(false); + setSelectedPreset(null); + setPresetForm({ title: "", description: "" }); + } catch (error) { + addToast({ + title: "Failed to update preset. Please try again.", + color: "danger", + }); + } + } + }; + + const handleDeletePreset = async (id: string) => { + try { + await deletePresetMutation.mutateAsync(id); + + addToast({ + title: "Preset deleted successfully!", + color: "success", + }); + } catch (error) { + addToast({ + title: "Failed to delete preset. Please try again.", + color: "danger", + }); + } + }; + + const handleDuplicatePreset = (preset: UserPreset) => { + setPresetForm({ + title: `${preset.title} (Copy)`, + description: preset.description, + }); + setIsCreateModalOpen(true); + }; + + const handleUpdateProfile = async () => { + try { + await updateProfileMutation.mutateAsync({ + name: profileForm.name, + bio: profileForm.bio, + }); + + addToast({ + title: "Profile updated successfully!", + color: "success", + }); + + setIsEditingProfile(false); + } catch (error) { + addToast({ + title: "Failed to update profile. Please try again.", + color: "danger", + }); + } + }; + + interface FormatDateOptions { + month: "short" | "numeric" | "2-digit" | "long" | "narrow"; + day: "numeric" | "2-digit"; + year: "numeric" | "2-digit"; + } + + const formatDate = (date: Date): string | undefined => { + const options: FormatDateOptions = { + month: "short", + day: "numeric", + year: "numeric", + }; + if (!date) return; + return new Intl.DateTimeFormat("en-US", options).format(date); + }; + + const isLoading = userLoading; + + if (isLoading) return ; + + if (!userProfile) { + return ( +
+

Failed to load user profile. Please try again.

+
+ ); + } + return ( +
+
+
+

Presets

+

Manage your AI personalities for customized content generation

+
+ +
+ + + +
+
+ +

Your Profile

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