From 8b8345d0e6ed9684c172925166aaa83e88bc0e6f Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Fri, 27 Jun 2025 01:42:38 -0400 Subject: [PATCH] Refactor modals to use FadeModal component for consistent styling and improved user experience - fix https://github.com/donetick/donetick/issues/222 - Replaced Modal and ModalDialog with FadeModal in LabelModal, PasswordChangeModal, SelectModal, TextModal, UserModal, WriteNFCModal, RedeemPointsModal, and AddTaskModal. - Updated modal structure and layout to maintain functionality while enhancing visual consistency. - Removed unused imports and commented-out code for cleaner codebase. --- src/components/common/FadeModal.jsx | 82 +++ src/views/Authorization/LoginView.jsx | 43 +- .../Authorization/MFAVerificationModal.jsx | 159 +++--- src/views/Modals/EditHistoryModal.jsx | 179 +++--- src/views/Modals/Inputs/ConfirmationModal.jsx | 67 +-- src/views/Modals/Inputs/CreateThingModal.jsx | 138 +++-- src/views/Modals/Inputs/LabelModal.jsx | 159 +++--- .../Modals/Inputs/PasswordChangeModal.jsx | 143 +++-- src/views/Modals/Inputs/SelectModal.jsx | 71 ++- src/views/Modals/Inputs/TextModal.jsx | 44 +- src/views/Modals/Inputs/UserModal.jsx | 89 ++- src/views/Modals/Inputs/WriteNFCModal.jsx | 118 ++-- src/views/Modals/RedeemPointsModal.jsx | 138 +++-- src/views/components/AddTaskModal.jsx | 512 +++++++++--------- 14 files changed, 969 insertions(+), 973 deletions(-) create mode 100644 src/components/common/FadeModal.jsx diff --git a/src/components/common/FadeModal.jsx b/src/components/common/FadeModal.jsx new file mode 100644 index 0000000..745cada --- /dev/null +++ b/src/components/common/FadeModal.jsx @@ -0,0 +1,82 @@ +import { Modal, ModalDialog, ModalOverflow } from '@mui/joy' + +/** + * FadeModal component with consistent fade-in/out animations + * Can be used as a drop-in replacement for Joy UI's Modal component + */ +const FadeModal = ({ + open, + onClose, + children, + size = 'md', + fullWidth = false, + backdropBlur = true, + ...props +}) => { + return ( + + + *': { + opacity: 0, + animation: open + ? 'contentFadeIn 0.35s forwards' + : 'contentFadeOut 0.2s forwards', + }, + // Stagger child animations + '& > *:nth-of-type(1)': { animationDelay: '0.05s' }, + '& > *:nth-of-type(2)': { animationDelay: '0.1s' }, + '& > *:nth-of-type(3)': { animationDelay: '0.15s' }, + '& > *:nth-of-type(4)': { animationDelay: '0.2s' }, + '& > *:nth-of-type(5)': { animationDelay: '0.25s' }, + '@keyframes contentFadeIn': { + to: { opacity: 1 }, + }, + '@keyframes contentFadeOut': { + to: { opacity: 0 }, + }, + }} + > + {children} + + + + ) +} + +export default FadeModal diff --git a/src/views/Authorization/LoginView.jsx b/src/views/Authorization/LoginView.jsx index 2ffcdaf..36e1655 100644 --- a/src/views/Authorization/LoginView.jsx +++ b/src/views/Authorization/LoginView.jsx @@ -14,6 +14,7 @@ import { Sheet, Typography, } from '@mui/joy' +import { useQueryClient } from '@tanstack/react-query' import Cookies from 'js-cookie' import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' @@ -27,8 +28,8 @@ import { apiManager, isTokenValid } from '../../utils/TokenManager' import MFAVerificationModal from './MFAVerificationModal' const LoginView = () => { - // Only fetch user profile if token is valid to prevent unnecessary queries - // const { data: userProfileData } = useUserProfile() + // Use React Query client directly to invalidate the user profile query + const queryClient = useQueryClient() const [userProfile, setUserProfile] = useState(null) const [username, setUsername] = useState('') const [password, setPassword] = useState('') @@ -78,11 +79,19 @@ const LoginView = () => { // Normal login without MFA localStorage.setItem('ca_token', data.token) localStorage.setItem('ca_expiration', data.expire) + + // Refetch user profile after successful login + queryClient.refetchQueries(['userProfile']) + const redirectUrl = Cookies.get('ca_redirect') - if (redirectUrl) { + + if (redirectUrl && redirectUrl !== '/') { + console.log('Redirecting to', redirectUrl) + Cookies.remove('ca_redirect') Navigate(redirectUrl) } else { + Cookies.remove('ca_redirect') Navigate('/my/chores') } }) @@ -143,6 +152,9 @@ const LoginView = () => { localStorage.setItem('ca_token', data.token) localStorage.setItem('ca_expiration', data.expire) + // Refetch user profile after successful OAuth login + queryClient.invalidateQueries(['userProfile']) + const redirectUrl = Cookies.get('ca_redirect') if (redirectUrl) { Cookies.remove('ca_redirect') @@ -161,17 +173,17 @@ const LoginView = () => { }) } const getUserProfileAndNavigateToHome = () => { - // Refetch user profile after login - // refetchUserProfile().then(() => { - // // check if redirect url is set in cookie: - const redirectUrl = Cookies.get('ca_redirect') - if (redirectUrl) { - Cookies.remove('ca_redirect') - Navigate(redirectUrl) - } else { - Navigate('/my/chores') - } - // }) + // Refetch user profile after login using React Query + queryClient.invalidateQueries(['userProfile']).then(() => { + // check if redirect url is set in cookie: + const redirectUrl = Cookies.get('ca_redirect') + if (redirectUrl) { + Cookies.remove('ca_redirect') + Navigate(redirectUrl) + } else { + Navigate('/my/chores') + } + }) } const handleMFASuccess = data => { @@ -180,6 +192,9 @@ const LoginView = () => { setMfaModalOpen(false) setMfaSessionToken('') + // Refetch user profile after MFA success + queryClient.invalidateQueries(['userProfile']) + const redirectUrl = Cookies.get('ca_redirect') if (redirectUrl) { Cookies.remove('ca_redirect') diff --git a/src/views/Authorization/MFAVerificationModal.jsx b/src/views/Authorization/MFAVerificationModal.jsx index 5f76e98..991df11 100644 --- a/src/views/Authorization/MFAVerificationModal.jsx +++ b/src/views/Authorization/MFAVerificationModal.jsx @@ -5,13 +5,12 @@ import { Button, Input, Link, - Modal, ModalClose, - ModalDialog, Stack, Typography, } from '@mui/joy' import { useState } from 'react' +import FadeModal from '../../components/common/FadeModal' import { VerifyMFA } from '../../utils/Fetcher' const MFAVerificationModal = ({ @@ -70,90 +69,88 @@ const MFAVerificationModal = ({ } return ( - - - + + - - - - Two-Factor Authentication - - - Enter the verification code from your authenticator app + + + + Two-Factor Authentication + + + Enter the verification code from your authenticator app + + + + + + + {isBackupCode ? 'Backup Code' : 'Verification Code'} + setVerificationCode(e.target.value)} + onKeyPress={handleKeyPress} + sx={{ + textAlign: 'center', + fontSize: '1.1em', + letterSpacing: isBackupCode ? 'normal' : '0.1em', + }} + slotProps={{ + input: { + maxLength: isBackupCode ? 50 : 6, + pattern: isBackupCode ? undefined : '[0-9]*', + }, + }} + startDecorator={} + autoFocus + /> - - - - {isBackupCode ? 'Backup Code' : 'Verification Code'} - - setVerificationCode(e.target.value)} - onKeyPress={handleKeyPress} - sx={{ - textAlign: 'center', - fontSize: '1.1em', - letterSpacing: isBackupCode ? 'normal' : '0.1em', - }} - slotProps={{ - input: { - maxLength: isBackupCode ? 50 : 6, - pattern: isBackupCode ? undefined : '[0-9]*', - }, - }} - startDecorator={} - autoFocus - /> - - - {error && ( - - {error} - - )} - - - - - { - setIsBackupCode(!isBackupCode) - setVerificationCode('') - setError('') - }} - sx={{ fontSize: 'sm' }} - > - {isBackupCode - ? 'Use authenticator app instead' - : "Can't access your authenticator? Use a backup code"} - - - - - - Having trouble? Make sure your authenticator app is synced and try - again. Each backup code can only be used once. - + {error && ( + + {error} - - - + )} + + + + + { + setIsBackupCode(!isBackupCode) + setVerificationCode('') + setError('') + }} + sx={{ fontSize: 'sm' }} + > + {isBackupCode + ? 'Use authenticator app instead' + : "Can't access your authenticator? Use a backup code"} + + + + + + Having trouble? Make sure your authenticator app is synced and try + again. Each backup code can only be used once. + + + + ) } diff --git a/src/views/Modals/EditHistoryModal.jsx b/src/views/Modals/EditHistoryModal.jsx index 2450759..0795c99 100644 --- a/src/views/Modals/EditHistoryModal.jsx +++ b/src/views/Modals/EditHistoryModal.jsx @@ -1,14 +1,7 @@ -import { - Box, - Button, - FormLabel, - Input, - Modal, - ModalDialog, - Typography, -} from '@mui/joy' +import { Box, Button, FormLabel, Input, Typography } from '@mui/joy' import moment from 'moment' import { useEffect, useState } from 'react' +import FadeModal from '../../components/common/FadeModal' import ConfirmationModal from './Inputs/ConfirmationModal' function EditHistoryModal({ config, historyRecord }) { @@ -29,93 +22,91 @@ function EditHistoryModal({ config, historyRecord }) { const [notes, setNotes] = useState(historyRecord.notes) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) return ( - - - - Edit History - - Due Date - { - setDueDate(e.target.value) - }} - /> - Completed Date - { - setCompletedDate(e.target.value) - }} - /> - Note - { - if (e.target.value.trim() === '') { - setNotes(null) - return - } - setNotes(e.target.value) - }} - size='md' - sx={{ - mb: 1, - }} - /> + + + Edit History + + Due Date + { + setDueDate(e.target.value) + }} + /> + Completed Date + { + setCompletedDate(e.target.value) + }} + /> + Note + { + if (e.target.value.trim() === '') { + setNotes(null) + return + } + setNotes(e.target.value) + }} + size='md' + sx={{ + mb: 1, + }} + /> - {/* 3 button save , cancel and delete */} - - - - - - { - if (isConfirm) { - config.onDelete(historyRecord.id) - } - setIsDeleteModalOpen(false) - }, - title: 'Delete History', - message: 'Are you sure you want to delete this history?', - confirmText: 'Delete', - cancelText: 'Cancel', + {/* 3 button save , cancel and delete */} + + + + + + { + if (isConfirm) { + config.onDelete(historyRecord.id) + } + setIsDeleteModalOpen(false) + }, + title: 'Delete History', + message: 'Are you sure you want to delete this history?', + confirmText: 'Delete', + cancelText: 'Cancel', + }} + /> + ) } export default EditHistoryModal diff --git a/src/views/Modals/Inputs/ConfirmationModal.jsx b/src/views/Modals/Inputs/ConfirmationModal.jsx index 882522e..f81a303 100644 --- a/src/views/Modals/Inputs/ConfirmationModal.jsx +++ b/src/views/Modals/Inputs/ConfirmationModal.jsx @@ -1,5 +1,5 @@ -import { Box, Button, Modal, ModalDialog, Typography } from '@mui/joy' -import React from 'react' +import { Box, Button, Typography } from '@mui/joy' +import FadeModal from '../../../components/common/FadeModal' function ConfirmationModal({ config }) { const handleAction = isConfirmed => { @@ -7,38 +7,41 @@ function ConfirmationModal({ config }) { } return ( - - - - {config?.title} - + + + {config?.title} + - - {config?.message} - + + {config?.message} + - - - - - - + + + + + ) } export default ConfirmationModal diff --git a/src/views/Modals/Inputs/CreateThingModal.jsx b/src/views/Modals/Inputs/CreateThingModal.jsx index 96b7954..a4863f7 100644 --- a/src/views/Modals/Inputs/CreateThingModal.jsx +++ b/src/views/Modals/Inputs/CreateThingModal.jsx @@ -4,14 +4,13 @@ import { FormControl, FormHelperText, Input, - Modal, - ModalDialog, Option, Select, Textarea, Typography, } from '@mui/joy' import { useEffect, useState } from 'react' +import FadeModal from '../../../components/common/FadeModal' function CreateThingModal({ isOpen, onClose, onSave, currentThing }) { const [name, setName] = useState(currentThing?.name || '') @@ -59,87 +58,80 @@ function CreateThingModal({ isOpen, onClose, onSave, currentThing }) { } return ( - - - {/* */} - - {currentThing?.id ? 'Edit' : 'Create'} Thing - + + + {currentThing?.id ? 'Edit' : 'Create'} Thing + + + Name +