diff --git a/package.json b/package.json index 0d65369..c6f03e8 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", @@ -25,7 +26,8 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^2.5.5", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zustand": "^5.0.2" }, "devDependencies": { "typescript": "^5", diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 3497e5a..aa09dac 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -14,6 +14,7 @@ import { import { SessionProvider } from "next-auth/react"; import { AppSidebar } from "@/components/app-sidebar"; import { Separator } from "@/components/ui/separator"; +import ConfirmationDialog from "@/dialogs/ConfirmationDialog"; export default function DashboardLayout({ children, @@ -44,6 +45,7 @@ export default function DashboardLayout({ {children} + ); diff --git a/src/components/nav-user.tsx b/src/components/nav-user.tsx index 3be56fa..4df53ac 100644 --- a/src/components/nav-user.tsx +++ b/src/components/nav-user.tsx @@ -4,10 +4,9 @@ import { Bell, LogIn, LogOut, - Sparkles, - BadgeCheck, - CreditCard, + Settings, ChevronsUpDown, + CircleUserRound, } from "lucide-react"; import { useMemo } from "react"; import { @@ -25,7 +24,8 @@ import { DropdownMenuTrigger, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; -import { useSession } from "next-auth/react"; +import useConfirmationStore from "@/store/confirmationStore"; +import { signIn, signOut, useSession } from "next-auth/react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; const UserAvatar = ({ src, alt }: { src: string; alt: string }) => ( @@ -46,6 +46,7 @@ export const NavUser = ({ }) => { const { isMobile } = useSidebar(); const { data: session } = useSession(); + const { openConfirmation } = useConfirmationStore(); const currentUser = useMemo(() => { if (session?.user) { @@ -104,28 +105,37 @@ export const NavUser = ({ - - Upgrade to Pro + + Profile - - - Account - - - - Billing - Notifications + + + Settings + - + { + openConfirmation({ + title: "Leaving Already?", + description: "Are you sure you want to say goodbye?", + cancelLabel: "Stay", + actionLabel: "Leave", + onCancel: () => {}, + onAction: () => { + signOut(); + }, + }); + }} + > Log out @@ -133,7 +143,7 @@ export const NavUser = ({ ) : ( - + signIn()}> Log in diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..57760f2 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/dialogs/ConfirmationDialog.tsx b/src/dialogs/ConfirmationDialog.tsx new file mode 100644 index 0000000..816009b --- /dev/null +++ b/src/dialogs/ConfirmationDialog.tsx @@ -0,0 +1,48 @@ +"use client"; + +import React from "react"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import useConfirmationStore from "@/store/confirmationStore"; + +const ConfirmationDialog = () => { + const { + open, + title, + description, + cancelLabel, + actionLabel, + onCancel, + onAction, + closeConfirmation, + } = useConfirmationStore(); + + return ( + + + + {title} + {description} + + + + {cancelLabel} + + + {actionLabel} + + + + + ); +}; + +export default ConfirmationDialog; diff --git a/src/store/confirmationStore.ts b/src/store/confirmationStore.ts new file mode 100644 index 0000000..2a75b47 --- /dev/null +++ b/src/store/confirmationStore.ts @@ -0,0 +1,64 @@ +import { create } from "zustand"; + +interface ConfirmationState { + open: boolean; + title: string | null; + description: string | null; + cancelLabel: string | null; + actionLabel: string | null; + onCancel: () => void; + onAction: () => void; +} + +interface ConfirmationActions { + openConfirmation: (data: { + title: string; + description: string; + cancelLabel: string; + actionLabel: string; + onCancel: () => void; + onAction: () => void; + }) => void; + closeConfirmation: () => void; +} + +const useConfirmationStore = create( + (set) => ({ + open: false, + title: null, + description: null, + cancelLabel: null, + actionLabel: null, + onCancel: () => {}, + onAction: () => {}, + openConfirmation: (data) => + set(() => ({ + open: true, + title: data.title, + description: data.description, + cancelLabel: data.cancelLabel, + actionLabel: data.actionLabel, + onCancel: data.onCancel, + onAction: data.onAction, + })), + closeConfirmation: () => { + set((state) => ({ + ...state, + open: false, + })); + setTimeout(() => { + set((state) => ({ + ...state, + title: null, + description: null, + cancelLabel: null, + actionLabel: null, + onCancel: () => {}, + onAction: () => {}, + })); + }, 100); + }, + }) +); + +export default useConfirmationStore;