diff --git a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx new file mode 100644 index 000000000..942556fe6 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx @@ -0,0 +1,61 @@ +import BaseModal from '@/components/BaseModal' +import { + AlertDialogAction, + AlertDialogCancel, + AlertDialogFooter +} from '@/components/shadcn/alert-dialog' + +interface ModalProps { + open: boolean + handleOpen: () => void + handleClose: () => void + confirmAction: () => void + title?: string + description?: string +} + +/** + * + * ConfirmModal component renders a modal dialog with confirm and cancel actions. + * + * @param open - Determines if the modal is open. + * @param handleClose - Function to close the modal. + * @param confirmAction - Function to execute when the user confirms. + * @param title - Title of the modal. + * @param description - Description of the modal. + * + * @remarks + * * AlertDialogFooter section (Button section) is separated using ConfirmModal component for reusability. + */ +export default function ConfirmModal({ + open, + handleClose, + confirmAction, + title = '', + description = '' +}: ModalProps) { + return ( + + + + Leave + + + Stay + + + + ) +} diff --git a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmNavigation.tsx b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmNavigation.tsx index 5e7afa67c..4cc473bb8 100644 --- a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmNavigation.tsx +++ b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmNavigation.tsx @@ -2,7 +2,7 @@ import type { Route } from 'next' import type { NavigateOptions } from 'next/dist/shared/lib/app-router-context.shared-runtime' import { useRouter } from 'next/navigation' import type { MutableRefObject } from 'react' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { toast } from 'sonner' // const beforeUnloadHandler = (event: BeforeUnloadEvent) => { @@ -22,6 +22,9 @@ export const useConfirmNavigation = ( updateNow: boolean ) => { const router = useRouter() + const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false) + const [confirmAction, setConfirmAction] = useState<() => void>(() => () => {}) + useEffect(() => { const originalPush = router.push const newPush = ( @@ -37,12 +40,11 @@ export const useConfirmNavigation = ( return } if (!bypassConfirmation.current) { - const isConfirmed = window.confirm( - 'Are you sure you want to leave?\nYour changes have not been saved.\nIf you leave this page, all changes will be lost.\nDo you still want to proceed?' - ) - if (isConfirmed) { + setIsConfirmModalOpen(true) + setConfirmAction(() => () => { + setIsConfirmModalOpen(false) originalPush(href as Route, options) - } + }) return } originalPush(href as Route, options) @@ -52,4 +54,6 @@ export const useConfirmNavigation = ( router.push = originalPush } }, [router, bypassConfirmation.current]) + + return { isConfirmModalOpen, setIsConfirmModalOpen, confirmAction } } diff --git a/apps/frontend/app/(client)/(main)/settings/page.tsx b/apps/frontend/app/(client)/(main)/settings/page.tsx index b32b8352f..f5af4c615 100644 --- a/apps/frontend/app/(client)/(main)/settings/page.tsx +++ b/apps/frontend/app/(client)/(main)/settings/page.tsx @@ -9,6 +9,7 @@ import { useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { z } from 'zod' +import ConfirmModal from './_components/ConfirmModal' import { useConfirmNavigation } from './_components/ConfirmNavigation' import CurrentPwSection from './_components/CurrentPwSection' import IdSection from './_components/IdSection' @@ -91,8 +92,6 @@ export default function Page() { fetchDefaultProfile() }, []) - useConfirmNavigation(bypassConfirmation, !!updateNow) - const { register, handleSubmit, @@ -112,6 +111,8 @@ export default function Page() { } }) + const { isConfirmModalOpen, setIsConfirmModalOpen, confirmAction } = + useConfirmNavigation(bypassConfirmation, !!updateNow) const [isCheckButtonClicked, setIsCheckButtonClicked] = useState(false) const [isPasswordCorrect, setIsPasswordCorrect] = useState(false) @@ -324,6 +325,15 @@ export default function Page() { onSubmitClick={onSubmitClick} /> + + setIsConfirmModalOpen(true)} + handleClose={() => setIsConfirmModalOpen(false)} + confirmAction={confirmAction} + /> ) } diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx new file mode 100644 index 000000000..23810a408 --- /dev/null +++ b/apps/frontend/components/BaseModal.tsx @@ -0,0 +1,68 @@ +import { Loader2 } from 'lucide-react' +import React, { Fragment, type ReactNode } from 'react' +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogHeader, + AlertDialogOverlay, + AlertDialogTitle +} from './shadcn/alert-dialog' + +interface BaseModalProps { + open: boolean + handleClose: () => void + children?: ReactNode + loading?: boolean + loadingMessage?: string + title?: string + description?: string + modalBgDarkMode?: boolean +} + +/** + * + * @remarks + * * Use BaseModal Component by creating a new component(which includes 'AlertDialogFooter') that extends BaseModal + * * AlertDialogFooter section (Button section) is separated using ConfirmModal component for reusability. + */ +export default function BaseModal({ + open, + handleClose, + children, + loading = false, + loadingMessage = '', + title = '', + description = '', + modalBgDarkMode = false +}: BaseModalProps) { + const formattedDescription = + description.split('\n').map((line, index) => ( + + {line} +
+
+ )) ?? '' + + return ( + + + + + {title} + + {loading ? ( +
+ + {loadingMessage} +
+ ) : ( + formattedDescription + )} +
+
+ {children} +
+
+ ) +} diff --git a/apps/frontend/components/shadcn/alert-dialog.tsx b/apps/frontend/components/shadcn/alert-dialog.tsx index baaf9e0e1..1a67ebd19 100644 --- a/apps/frontend/components/shadcn/alert-dialog.tsx +++ b/apps/frontend/components/shadcn/alert-dialog.tsx @@ -13,11 +13,16 @@ const AlertDialogPortal = AlertDialogPrimitive.Portal const AlertDialogOverlay = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + darkMode?: boolean + } +>(({ className, darkMode = false, ...props }, ref) => (