Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fe): confirm modal when leaving settings page #2261

Merged
merged 18 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 (
<BaseModal
handleClose={handleClose}
title={title}
open={open}
description={description}
modalBgDarkMode={false}
>
<AlertDialogFooter>
<AlertDialogAction
className="border-none bg-slate-100 text-[#3333334D] hover:bg-slate-200"
onClick={confirmAction}
>
Leave
</AlertDialogAction>
<AlertDialogCancel
className="bg-primary hover:bg-primary-strong border-none text-white"
onClick={handleClose}
>
Stay
</AlertDialogCancel>
</AlertDialogFooter>
</BaseModal>
)
}
jihorobert marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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) => {
Expand All @@ -22,6 +22,9 @@
updateNow: boolean
) => {
const router = useRouter()
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
const [confirmAction, setConfirmAction] = useState<() => void>(() => () => {})

useEffect(() => {
const originalPush = router.push
const newPush = (
Expand All @@ -37,12 +40,11 @@
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)
Expand All @@ -51,5 +53,7 @@
return () => {
router.push = originalPush
}
}, [router, bypassConfirmation.current])

Check warning on line 56 in apps/frontend/app/(client)/(main)/settings/_components/ConfirmNavigation.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has missing dependencies: 'bypassConfirmation' and 'updateNow'. Either include them or remove the dependency array. Mutable values like 'bypassConfirmation.current' aren't valid dependencies because mutating them doesn't re-render the component

return { isConfirmModalOpen, setIsConfirmModalOpen, confirmAction }
}
14 changes: 12 additions & 2 deletions apps/frontend/app/(client)/(main)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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'
Expand Down Expand Up @@ -91,8 +92,6 @@
fetchDefaultProfile()
}, [])

useConfirmNavigation(bypassConfirmation, !!updateNow)

const {
register,
handleSubmit,
Expand All @@ -112,6 +111,8 @@
}
})

const { isConfirmModalOpen, setIsConfirmModalOpen, confirmAction } =
useConfirmNavigation(bypassConfirmation, !!updateNow)
const [isCheckButtonClicked, setIsCheckButtonClicked] =
useState<boolean>(false)
const [isPasswordCorrect, setIsPasswordCorrect] = useState<boolean>(false)
Expand Down Expand Up @@ -152,7 +153,7 @@
setValue('newPassword', newPassword)
setValue('confirmPassword', confirmPassword)
}
}, [isPasswordsMatch, newPassword, confirmPassword])

Check warning on line 156 in apps/frontend/app/(client)/(main)/settings/page.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has a missing dependency: 'setValue'. Either include it or remove the dependency array

const onSubmit = async (data: SettingsFormat) => {
try {
Expand Down Expand Up @@ -324,6 +325,15 @@
onSubmitClick={onSubmitClick}
/>
</form>

<ConfirmModal
title="Are you sure you want to leave?"
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)}
confirmAction={confirmAction}
/>
</div>
)
}
68 changes: 68 additions & 0 deletions apps/frontend/components/BaseModal.tsx
Original file line number Diff line number Diff line change
@@ -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
jihorobert marked this conversation as resolved.
Show resolved Hide resolved
}: BaseModalProps) {
const formattedDescription =
description.split('\n').map((line, index) => (
<Fragment key={index}>
{line}
<br />
</Fragment>
)) ?? ''
jihorobert marked this conversation as resolved.
Show resolved Hide resolved

return (
<AlertDialog open={open} onOpenChange={handleClose}>
<AlertDialogOverlay darkMode={modalBgDarkMode} />
<AlertDialogContent className="max-w-[428px]">
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription className="min-w-72">
{loading ? (
<div className="flex flex-col items-center justify-center">
<Loader2 size={32} className="animate-spin" />
<span className="mt-2 text-sm">{loadingMessage}</span>
</div>
) : (
formattedDescription
)}
</AlertDialogDescription>
</AlertDialogHeader>
{children}
</AlertDialogContent>
</AlertDialog>
)
}
11 changes: 8 additions & 3 deletions apps/frontend/components/shadcn/alert-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ const AlertDialogPortal = AlertDialogPrimitive.Portal

const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> & {
darkMode?: boolean
}
>(({ className, darkMode = false, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50',
darkMode
? 'bg-black/80 backdrop-blur-sm'
jihorobert marked this conversation as resolved.
Show resolved Hide resolved
: 'bg-gray-300/20 backdrop-blur-sm',
className
)}
{...props}
Expand Down
Loading