From 15a5e2575cc7dc67391b49a44a332d7a11e2c9a5 Mon Sep 17 00:00:00 2001 From: jiho Date: Thu, 19 Dec 2024 12:56:03 +0900 Subject: [PATCH 01/11] feat(fe): confirm modal when leaving settings page --- .../settings/_components/ConfirmModal.tsx | 62 +++++++++++++++++++ .../_components/ConfirmNavigation.tsx | 16 +++-- .../app/(client)/(main)/settings/page.tsx | 22 ++++++- apps/frontend/components/BaseModal.tsx | 50 +++++++++++++++ 4 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx create mode 100644 apps/frontend/components/BaseModal.tsx 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..4a137420c8 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx @@ -0,0 +1,62 @@ +import BaseModal from '@/components/BaseModal' +import { + AlertDialogAction, + AlertDialogCancel, + AlertDialogFooter +} from '@/components/shadcn/alert-dialog' +import type { ReactNode } from 'react' + +interface ModalProps { + open: boolean + handleOpen: () => void + handleClose: () => void + confirmAction: () => void + title?: string + description?: ReactNode +} + +/** + * + * 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 + * * because its design and functionality vary depending on context (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 5e7afa67c6..4cc473bb81 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 b32b8352f8..018c430ec0 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,23 @@ export default function Page() { onSubmitClick={onSubmitClick} /> + + + Your changes have not been saved. +
+ If you leave this page, all changes will be lost. +
+ Do you still want to proceed? + + } + open={isConfirmModalOpen} + handleOpen={() => 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..cfb26cf67e --- /dev/null +++ b/apps/frontend/components/BaseModal.tsx @@ -0,0 +1,50 @@ +import { Loader2 } from 'lucide-react' +import React, { type ReactNode } from 'react' +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogHeader, + AlertDialogTitle +} from './shadcn/alert-dialog' + +interface BaseModalProps { + open: boolean + handleClose: () => void + children?: React.ReactNode + loading?: boolean + loadingMessage?: string + title?: string + description?: ReactNode +} + +export default function BaseModal({ + open, + handleClose, + children, + loading = false, + loadingMessage = '', + title = '', + description = '' +}: BaseModalProps) { + return ( + + + + {title} + + {loading ? ( +
+ + {loadingMessage} +
+ ) : ( + <>{description} + )} +
+
+ {children} +
+
+ ) +} From 1af0f76a96e436f702a8a7be206bf88a5ff41600 Mon Sep 17 00:00:00 2001 From: jiho Date: Thu, 19 Dec 2024 13:43:02 +0900 Subject: [PATCH 02/11] feat(fe): add dark and bright mode --- apps/frontend/components/BaseModal.tsx | 2 ++ apps/frontend/components/shadcn/alert-dialog.tsx | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index cfb26cf67e..2c244188a5 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -5,6 +5,7 @@ import { AlertDialogContent, AlertDialogDescription, AlertDialogHeader, + AlertDialogOverlay, AlertDialogTitle } from './shadcn/alert-dialog' @@ -29,6 +30,7 @@ export default function BaseModal({ }: BaseModalProps) { return ( + {title} diff --git a/apps/frontend/components/shadcn/alert-dialog.tsx b/apps/frontend/components/shadcn/alert-dialog.tsx index baaf9e0e13..f0e057fed3 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) => ( Date: Thu, 19 Dec 2024 13:47:38 +0900 Subject: [PATCH 03/11] chore(fe): add comments for basemodal --- apps/frontend/components/BaseModal.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index 2c244188a5..76b849675f 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -19,6 +19,8 @@ interface BaseModalProps { description?: ReactNode } +// AlertDialogFooter section (Button section) is separated using ConfirmModal component +// because its design and functionality vary depending on context (for reusability). export default function BaseModal({ open, handleClose, From 06c9976fac48a0f4c3c674382f515d16b5af4a7d Mon Sep 17 00:00:00 2001 From: jiho Date: Thu, 19 Dec 2024 13:57:43 +0900 Subject: [PATCH 04/11] fix(fe): decide darkmode at ConfirmModal --- .../(client)/(main)/settings/_components/ConfirmModal.tsx | 1 + apps/frontend/components/BaseModal.tsx | 6 ++++-- apps/frontend/components/shadcn/alert-dialog.tsx | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx index 4a137420c8..136c2e1665 100644 --- a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx +++ b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx @@ -42,6 +42,7 @@ export default function ConfirmModal({ title={title} open={open} description={description} + modalBgDarkMode={false} > - + {title} diff --git a/apps/frontend/components/shadcn/alert-dialog.tsx b/apps/frontend/components/shadcn/alert-dialog.tsx index f0e057fed3..1a67ebd193 100644 --- a/apps/frontend/components/shadcn/alert-dialog.tsx +++ b/apps/frontend/components/shadcn/alert-dialog.tsx @@ -20,7 +20,9 @@ const AlertDialogOverlay = React.forwardRef< Date: Thu, 19 Dec 2024 14:39:07 +0900 Subject: [PATCH 05/11] chore(fe): add explanation for BaseModal component --- apps/frontend/components/BaseModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index b7d5c9fa32..863e2b8577 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -22,6 +22,7 @@ interface BaseModalProps { // AlertDialogFooter section (Button section) is separated using ConfirmModal component // because its design and functionality vary depending on context (for reusability). +// * Use BaseModal Component by creating a new component that extends BaseModal export default function BaseModal({ open, handleClose, From 4bf34671da3040fe627536c5dd093fe38e57fad3 Mon Sep 17 00:00:00 2001 From: jiho Date: Thu, 19 Dec 2024 14:40:56 +0900 Subject: [PATCH 06/11] chore(fe): add explanation for Basemodal component_2 --- apps/frontend/components/BaseModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index 863e2b8577..75f2bda6fd 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -22,7 +22,7 @@ interface BaseModalProps { // AlertDialogFooter section (Button section) is separated using ConfirmModal component // because its design and functionality vary depending on context (for reusability). -// * Use BaseModal Component by creating a new component that extends BaseModal +// * Use BaseModal Component by creating a new component(which includes 'AlertDialogFooter') that extends BaseModal export default function BaseModal({ open, handleClose, From 0792ba996226cc905db55dc93a8a44df8f28bf6d Mon Sep 17 00:00:00 2001 From: jiho Date: Thu, 19 Dec 2024 17:50:49 +0900 Subject: [PATCH 07/11] chore(fe): change comment for BaseModal --- apps/frontend/components/BaseModal.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index 75f2bda6fd..0c8940d1d7 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -20,9 +20,13 @@ interface BaseModalProps { modalBgDarkMode?: boolean } -// AlertDialogFooter section (Button section) is separated using ConfirmModal component -// because its design and functionality vary depending on context (for reusability). -// * Use BaseModal Component by creating a new component(which includes 'AlertDialogFooter') that extends BaseModal +/** + * + * @remarks + * * Use BaseModal Component by creating a new component(which includes 'AlertDialogFooter') that extends BaseModal + * * AlertDialogFooter section (Button section) is separated using ConfirmModal component + * because its design and functionality vary depending on context (for reusability). + */ export default function BaseModal({ open, handleClose, From 216eb81deb7fd0cf8ceaf8841b677d7d7b68678f Mon Sep 17 00:00:00 2001 From: jiho Date: Thu, 19 Dec 2024 18:50:26 +0900 Subject: [PATCH 08/11] fix(fe): change description props type to string from ReactNode --- .../(main)/settings/_components/ConfirmModal.tsx | 3 +-- .../app/(client)/(main)/settings/page.tsx | 10 +--------- apps/frontend/components/BaseModal.tsx | 16 ++++++++++++---- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx index 136c2e1665..7a20a43565 100644 --- a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx +++ b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx @@ -4,7 +4,6 @@ import { AlertDialogCancel, AlertDialogFooter } from '@/components/shadcn/alert-dialog' -import type { ReactNode } from 'react' interface ModalProps { open: boolean @@ -12,7 +11,7 @@ interface ModalProps { handleClose: () => void confirmAction: () => void title?: string - description?: ReactNode + description?: string } /** diff --git a/apps/frontend/app/(client)/(main)/settings/page.tsx b/apps/frontend/app/(client)/(main)/settings/page.tsx index 018c430ec0..f5af4c615a 100644 --- a/apps/frontend/app/(client)/(main)/settings/page.tsx +++ b/apps/frontend/app/(client)/(main)/settings/page.tsx @@ -328,15 +328,7 @@ export default function Page() { - Your changes have not been saved. -
- If you leave this page, all changes will be lost. -
- Do you still want to proceed? - - } + description={`Your changes have not been saved.\nIf you leave this page, all changes will be lost.\nDo you still want to proceed?`} open={isConfirmModalOpen} handleOpen={() => setIsConfirmModalOpen(true)} handleClose={() => setIsConfirmModalOpen(false)} diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index 0c8940d1d7..c2603ee9d0 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -1,5 +1,5 @@ import { Loader2 } from 'lucide-react' -import React, { type ReactNode } from 'react' +import React from 'react' import { AlertDialog, AlertDialogContent, @@ -16,7 +16,7 @@ interface BaseModalProps { loading?: boolean loadingMessage?: string title?: string - description?: ReactNode + description?: string modalBgDarkMode?: boolean } @@ -37,20 +37,28 @@ export default function BaseModal({ description = '', modalBgDarkMode = false }: BaseModalProps) { + const formattedDescription = + description.split('\n').map((line, index) => ( + + {line} +
+
+ )) ?? '' + return ( {title} - + {loading ? (
{loadingMessage}
) : ( - <>{description} + formattedDescription )}
From c773605361359d9aa6d76b2e448a4c051fe28713 Mon Sep 17 00:00:00 2001 From: jiho Date: Fri, 20 Dec 2024 18:56:38 +0900 Subject: [PATCH 09/11] chore(fe): import types --- apps/frontend/components/BaseModal.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index c2603ee9d0..b72f6ca0d9 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -1,5 +1,5 @@ import { Loader2 } from 'lucide-react' -import React from 'react' +import React, { Fragment, type ReactNode } from 'react' import { AlertDialog, AlertDialogContent, @@ -12,7 +12,7 @@ import { interface BaseModalProps { open: boolean handleClose: () => void - children?: React.ReactNode + children?: ReactNode loading?: boolean loadingMessage?: string title?: string @@ -39,10 +39,10 @@ export default function BaseModal({ }: BaseModalProps) { const formattedDescription = description.split('\n').map((line, index) => ( - + {line}
-
+ )) ?? '' return ( From 0943160d4362136dae688fefab907bb7cd43b2c1 Mon Sep 17 00:00:00 2001 From: jiho Date: Mon, 23 Dec 2024 10:30:02 +0900 Subject: [PATCH 10/11] chore(fe): change comment for basemodal --- apps/frontend/components/BaseModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/frontend/components/BaseModal.tsx b/apps/frontend/components/BaseModal.tsx index b72f6ca0d9..23810a4083 100644 --- a/apps/frontend/components/BaseModal.tsx +++ b/apps/frontend/components/BaseModal.tsx @@ -24,8 +24,7 @@ interface BaseModalProps { * * @remarks * * Use BaseModal Component by creating a new component(which includes 'AlertDialogFooter') that extends BaseModal - * * AlertDialogFooter section (Button section) is separated using ConfirmModal component - * because its design and functionality vary depending on context (for reusability). + * * AlertDialogFooter section (Button section) is separated using ConfirmModal component for reusability. */ export default function BaseModal({ open, From 65d962128e64666c1ac092069e5bb78fc107922b Mon Sep 17 00:00:00 2001 From: jiho Date: Mon, 23 Dec 2024 10:46:16 +0900 Subject: [PATCH 11/11] chore(fe): change comment for confirmmodal --- .../app/(client)/(main)/settings/_components/ConfirmModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx index 7a20a43565..942556fe65 100644 --- a/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx +++ b/apps/frontend/app/(client)/(main)/settings/_components/ConfirmModal.tsx @@ -25,8 +25,7 @@ interface ModalProps { * @param description - Description of the modal. * * @remarks - * * AlertDialogFooter section (Button section) is separated using ConfirmModal component - * * because its design and functionality vary depending on context (for reusability). + * * AlertDialogFooter section (Button section) is separated using ConfirmModal component for reusability. */ export default function ConfirmModal({ open,