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 0000000000..9a33f03ccf --- /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/_libs/utils.ts similarity index 75% rename from apps/frontend/app/(client)/(main)/settings/_components/ConfirmNavigation.tsx rename to apps/frontend/app/(client)/(main)/settings/_libs/utils.ts index 5e7afa67c6..60bc764a1d 100644 --- a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmNavigation.tsx +++ b/apps/frontend/app/(client)/(main)/settings/_libs/utils.ts @@ -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) @@ -51,5 +53,7 @@ export const useConfirmNavigation = ( return () => { router.push = originalPush } - }, [router, bypassConfirmation.current]) + }, [router, bypassConfirmation]) + + 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 b32b8352f8..7488911b59 100644 --- a/apps/frontend/app/(client)/(main)/settings/page.tsx +++ b/apps/frontend/app/(client)/(main)/settings/page.tsx @@ -9,7 +9,7 @@ import { useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { z } from 'zod' -import { useConfirmNavigation } from './_components/ConfirmNavigation' +import ConfirmModal from './_components/ConfirmModal' import CurrentPwSection from './_components/CurrentPwSection' import IdSection from './_components/IdSection' import LogoSection from './_components/LogoSection' @@ -20,6 +20,7 @@ import ReEnterNewPwSection from './_components/ReEnterNewPwSection' import SaveButton from './_components/SaveButton' import StudentIdSection from './_components/StudentIdSection' import TopicSection from './_components/TopicSection' +import { useConfirmNavigation } from './_libs/utils' interface getProfile { username: string // ID @@ -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 0000000000..6a655ebf16 --- /dev/null +++ b/apps/frontend/components/BaseModal.tsx @@ -0,0 +1,64 @@ +import { Loader2 } from 'lucide-react' +import React, { 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 + darkMode?: 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 = '', + darkMode = 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 baaf9e0e13..c3cecdc510 100644 --- a/apps/frontend/components/shadcn/alert-dialog.tsx +++ b/apps/frontend/components/shadcn/alert-dialog.tsx @@ -13,11 +13,14 @@ const AlertDialogPortal = AlertDialogPrimitive.Portal const AlertDialogOverlay = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + darkMode?: boolean + } +>(({ className, darkMode = false, ...props }, ref) => (