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) => (