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.
This commit is contained in:
Mo Tarbin
2025-06-27 01:42:38 -04:00
parent 79362162d2
commit 8b8345d0e6
14 changed files with 969 additions and 973 deletions

View File

@@ -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 (
<Modal
open={open}
onClose={onClose}
sx={{
'& .MuiModal-backdrop': {
backdropFilter: backdropBlur ? 'blur(3px)' : 'none',
},
}}
keepMounted
// These transition properties create a smooth fade + slide effect
transition={{
mount: { opacity: 1, transform: 'translateY(0px)' },
unmount: { opacity: 0, transform: 'translateY(20px)' },
duration: 250, // Animation duration in ms
easing: {
enter: 'cubic-bezier(0.34, 1.56, 0.64, 1)', // Slight overshoot for natural feel
exit: 'cubic-bezier(0.4, 0, 0.2, 1)', // Standard ease out
},
}}
{...props}
>
<ModalOverflow>
<ModalDialog
size={size}
sx={{
minWidth: fullWidth ? '100%' : 'auto',
animation: open
? 'modalFadeIn 0.35s forwards'
: 'modalFadeOut 0.25s forwards',
'@keyframes modalFadeIn': {
from: { opacity: 0, transform: 'translateY(8px)' },
to: { opacity: 1, transform: 'translateY(0)' },
},
'@keyframes modalFadeOut': {
from: { opacity: 1, transform: 'translateY(0)' },
to: { opacity: 0, transform: 'translateY(8px)' },
},
// Add staggered animation for child elements
'& > *': {
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}
</ModalDialog>
</ModalOverflow>
</Modal>
)
}
export default FadeModal

View File

@@ -14,6 +14,7 @@ import {
Sheet, Sheet,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import { useQueryClient } from '@tanstack/react-query'
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
@@ -27,8 +28,8 @@ import { apiManager, isTokenValid } from '../../utils/TokenManager'
import MFAVerificationModal from './MFAVerificationModal' import MFAVerificationModal from './MFAVerificationModal'
const LoginView = () => { const LoginView = () => {
// Only fetch user profile if token is valid to prevent unnecessary queries // Use React Query client directly to invalidate the user profile query
// const { data: userProfileData } = useUserProfile() const queryClient = useQueryClient()
const [userProfile, setUserProfile] = useState(null) const [userProfile, setUserProfile] = useState(null)
const [username, setUsername] = useState('') const [username, setUsername] = useState('')
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
@@ -78,11 +79,19 @@ const LoginView = () => {
// Normal login without MFA // Normal login without MFA
localStorage.setItem('ca_token', data.token) localStorage.setItem('ca_token', data.token)
localStorage.setItem('ca_expiration', data.expire) localStorage.setItem('ca_expiration', data.expire)
// Refetch user profile after successful login
queryClient.refetchQueries(['userProfile'])
const redirectUrl = Cookies.get('ca_redirect') const redirectUrl = Cookies.get('ca_redirect')
if (redirectUrl) {
if (redirectUrl && redirectUrl !== '/') {
console.log('Redirecting to', redirectUrl)
Cookies.remove('ca_redirect') Cookies.remove('ca_redirect')
Navigate(redirectUrl) Navigate(redirectUrl)
} else { } else {
Cookies.remove('ca_redirect')
Navigate('/my/chores') Navigate('/my/chores')
} }
}) })
@@ -143,6 +152,9 @@ const LoginView = () => {
localStorage.setItem('ca_token', data.token) localStorage.setItem('ca_token', data.token)
localStorage.setItem('ca_expiration', data.expire) localStorage.setItem('ca_expiration', data.expire)
// Refetch user profile after successful OAuth login
queryClient.invalidateQueries(['userProfile'])
const redirectUrl = Cookies.get('ca_redirect') const redirectUrl = Cookies.get('ca_redirect')
if (redirectUrl) { if (redirectUrl) {
Cookies.remove('ca_redirect') Cookies.remove('ca_redirect')
@@ -161,17 +173,17 @@ const LoginView = () => {
}) })
} }
const getUserProfileAndNavigateToHome = () => { const getUserProfileAndNavigateToHome = () => {
// Refetch user profile after login // Refetch user profile after login using React Query
// refetchUserProfile().then(() => { queryClient.invalidateQueries(['userProfile']).then(() => {
// // check if redirect url is set in cookie: // check if redirect url is set in cookie:
const redirectUrl = Cookies.get('ca_redirect') const redirectUrl = Cookies.get('ca_redirect')
if (redirectUrl) { if (redirectUrl) {
Cookies.remove('ca_redirect') Cookies.remove('ca_redirect')
Navigate(redirectUrl) Navigate(redirectUrl)
} else { } else {
Navigate('/my/chores') Navigate('/my/chores')
} }
// }) })
} }
const handleMFASuccess = data => { const handleMFASuccess = data => {
@@ -180,6 +192,9 @@ const LoginView = () => {
setMfaModalOpen(false) setMfaModalOpen(false)
setMfaSessionToken('') setMfaSessionToken('')
// Refetch user profile after MFA success
queryClient.invalidateQueries(['userProfile'])
const redirectUrl = Cookies.get('ca_redirect') const redirectUrl = Cookies.get('ca_redirect')
if (redirectUrl) { if (redirectUrl) {
Cookies.remove('ca_redirect') Cookies.remove('ca_redirect')

View File

@@ -5,13 +5,12 @@ import {
Button, Button,
Input, Input,
Link, Link,
Modal,
ModalClose, ModalClose,
ModalDialog,
Stack, Stack,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import { useState } from 'react' import { useState } from 'react'
import FadeModal from '../../components/common/FadeModal'
import { VerifyMFA } from '../../utils/Fetcher' import { VerifyMFA } from '../../utils/Fetcher'
const MFAVerificationModal = ({ const MFAVerificationModal = ({
@@ -70,90 +69,88 @@ const MFAVerificationModal = ({
} }
return ( return (
<Modal open={open} onClose={handleClose}> <FadeModal open={open} onClose={handleClose} size='sm'>
<ModalDialog size='sm' sx={{ maxWidth: 400 }}> <ModalClose />
<ModalClose />
<Box className='mb-4 text-center'> <Box className='mb-4 text-center'>
<Security sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} /> <Security sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} />
<Typography level='h4' sx={{ mb: 1 }}> <Typography level='h4' sx={{ mb: 1 }}>
Two-Factor Authentication Two-Factor Authentication
</Typography> </Typography>
<Typography level='body-md' sx={{ color: 'text.secondary' }}> <Typography level='body-md' sx={{ color: 'text.secondary' }}>
Enter the verification code from your authenticator app Enter the verification code from your authenticator app
</Typography>
</Box>
<Stack spacing={3}>
<Box>
<Typography level='body-sm' sx={{ mb: 1 }}>
{isBackupCode ? 'Backup Code' : 'Verification Code'}
</Typography> </Typography>
<Input
placeholder={
isBackupCode ? 'Enter backup code' : 'Enter 6-digit code'
}
value={verificationCode}
onChange={e => 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={<Smartphone />}
autoFocus
/>
</Box> </Box>
<Stack spacing={3}> {error && (
<Box> <Alert color='danger' size='sm'>
<Typography level='body-sm' sx={{ mb: 1 }}> {error}
{isBackupCode ? 'Backup Code' : 'Verification Code'}
</Typography>
<Input
placeholder={
isBackupCode ? 'Enter backup code' : 'Enter 6-digit code'
}
value={verificationCode}
onChange={e => 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={<Smartphone />}
autoFocus
/>
</Box>
{error && (
<Alert color='danger' size='sm'>
{error}
</Alert>
)}
<Button
color='primary'
loading={loading}
onClick={handleVerify}
disabled={!verificationCode.trim()}
size='lg'
>
Verify & Sign In
</Button>
<Box className='text-center'>
<Link
component='button'
type='button'
onClick={() => {
setIsBackupCode(!isBackupCode)
setVerificationCode('')
setError('')
}}
sx={{ fontSize: 'sm' }}
>
{isBackupCode
? 'Use authenticator app instead'
: "Can't access your authenticator? Use a backup code"}
</Link>
</Box>
<Alert color='neutral' size='sm'>
<Typography level='body-xs'>
Having trouble? Make sure your authenticator app is synced and try
again. Each backup code can only be used once.
</Typography>
</Alert> </Alert>
</Stack> )}
</ModalDialog>
</Modal> <Button
color='primary'
loading={loading}
onClick={handleVerify}
disabled={!verificationCode.trim()}
size='lg'
>
Verify & Sign In
</Button>
<Box className='text-center'>
<Link
component='button'
type='button'
onClick={() => {
setIsBackupCode(!isBackupCode)
setVerificationCode('')
setError('')
}}
sx={{ fontSize: 'sm' }}
>
{isBackupCode
? 'Use authenticator app instead'
: "Can't access your authenticator? Use a backup code"}
</Link>
</Box>
<Alert color='neutral' size='sm'>
<Typography level='body-xs'>
Having trouble? Make sure your authenticator app is synced and try
again. Each backup code can only be used once.
</Typography>
</Alert>
</Stack>
</FadeModal>
) )
} }

View File

@@ -1,14 +1,7 @@
import { import { Box, Button, FormLabel, Input, Typography } from '@mui/joy'
Box,
Button,
FormLabel,
Input,
Modal,
ModalDialog,
Typography,
} from '@mui/joy'
import moment from 'moment' import moment from 'moment'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import FadeModal from '../../components/common/FadeModal'
import ConfirmationModal from './Inputs/ConfirmationModal' import ConfirmationModal from './Inputs/ConfirmationModal'
function EditHistoryModal({ config, historyRecord }) { function EditHistoryModal({ config, historyRecord }) {
@@ -29,93 +22,91 @@ function EditHistoryModal({ config, historyRecord }) {
const [notes, setNotes] = useState(historyRecord.notes) const [notes, setNotes] = useState(historyRecord.notes)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
return ( return (
<Modal open={config?.isOpen} onClose={config?.onClose}> <FadeModal open={config?.isOpen} onClose={config?.onClose}>
<ModalDialog> <Typography level='h4' mb={1}>
<Typography level='h4' mb={1}> Edit History
Edit History </Typography>
</Typography> <FormLabel>Due Date</FormLabel>
<FormLabel>Due Date</FormLabel> <Input
<Input type='datetime-local'
type='datetime-local' value={dueDate}
value={dueDate} onChange={e => {
onChange={e => { setDueDate(e.target.value)
setDueDate(e.target.value) }}
}} />
/> <FormLabel>Completed Date</FormLabel>
<FormLabel>Completed Date</FormLabel> <Input
<Input type='datetime-local'
type='datetime-local' value={completedDate}
value={completedDate} onChange={e => {
onChange={e => { setCompletedDate(e.target.value)
setCompletedDate(e.target.value) }}
}} />
/> <FormLabel>Note</FormLabel>
<FormLabel>Note</FormLabel> <Input
<Input fullWidth
fullWidth multiline
multiline label='Additional Notes'
label='Additional Notes' placeholder='Additional Notes'
placeholder='Additional Notes' value={notes}
value={notes} onChange={e => {
onChange={e => { if (e.target.value.trim() === '') {
if (e.target.value.trim() === '') { setNotes(null)
setNotes(null) return
return }
} setNotes(e.target.value)
setNotes(e.target.value) }}
}} size='md'
size='md' sx={{
sx={{ mb: 1,
mb: 1, }}
}} />
/>
{/* 3 button save , cancel and delete */} {/* 3 button save , cancel and delete */}
<Box display={'flex'} justifyContent={'space-around'} mt={1}> <Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button <Button
onClick={() => onClick={() =>
config.onSave({ config.onSave({
id: historyRecord.id, id: historyRecord.id,
performedAt: moment(completedDate).toISOString(), performedAt: moment(completedDate).toISOString(),
dueDate: moment(dueDate).toISOString(), dueDate: moment(dueDate).toISOString(),
notes, notes,
}) })
} }
fullWidth fullWidth
sx={{ mr: 1 }} sx={{ mr: 1 }}
> >
Save Save
</Button> </Button>
<Button onClick={config.onClose} variant='outlined'> <Button onClick={config.onClose} variant='outlined'>
Cancel Cancel
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
setIsDeleteModalOpen(true) setIsDeleteModalOpen(true)
}}
variant='outlined'
color='danger'
>
Delete
</Button>
</Box>
<ConfirmationModal
config={{
isOpen: isDeleteModalOpen,
onClose: isConfirm => {
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',
}} }}
/> variant='outlined'
</ModalDialog> color='danger'
</Modal> >
Delete
</Button>
</Box>
<ConfirmationModal
config={{
isOpen: isDeleteModalOpen,
onClose: isConfirm => {
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',
}}
/>
</FadeModal>
) )
} }
export default EditHistoryModal export default EditHistoryModal

View File

@@ -1,5 +1,5 @@
import { Box, Button, Modal, ModalDialog, Typography } from '@mui/joy' import { Box, Button, Typography } from '@mui/joy'
import React from 'react' import FadeModal from '../../../components/common/FadeModal'
function ConfirmationModal({ config }) { function ConfirmationModal({ config }) {
const handleAction = isConfirmed => { const handleAction = isConfirmed => {
@@ -7,38 +7,41 @@ function ConfirmationModal({ config }) {
} }
return ( return (
<Modal open={config?.isOpen} onClose={config?.onClose}> <FadeModal
<ModalDialog> open={config?.isOpen}
<Typography level='h4' mb={1}> onClose={config?.onClose}
{config?.title} size='sm'
</Typography> unmountDelay={250}
>
<Typography level='h4' mb={1}>
{config?.title}
</Typography>
<Typography level='body-md' gutterBottom> <Typography level='body-md' gutterBottom>
{config?.message} {config?.message}
</Typography> </Typography>
<Box display={'flex'} justifyContent={'space-around'} mt={1}> <Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button <Button
onClick={() => { onClick={() => {
handleAction(true) handleAction(true)
}} }}
fullWidth fullWidth
sx={{ mr: 1 }} sx={{ mr: 1 }}
color={config.color ? config.color : 'primary'} color={config.color ? config.color : 'primary'}
> >
{config?.confirmText} {config?.confirmText}
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
handleAction(false) handleAction(false)
}} }}
variant='outlined' variant='outlined'
> >
{config?.cancelText} {config?.cancelText}
</Button> </Button>
</Box> </Box>
</ModalDialog> </FadeModal>
</Modal>
) )
} }
export default ConfirmationModal export default ConfirmationModal

View File

@@ -4,14 +4,13 @@ import {
FormControl, FormControl,
FormHelperText, FormHelperText,
Input, Input,
Modal,
ModalDialog,
Option, Option,
Select, Select,
Textarea, Textarea,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import FadeModal from '../../../components/common/FadeModal'
function CreateThingModal({ isOpen, onClose, onSave, currentThing }) { function CreateThingModal({ isOpen, onClose, onSave, currentThing }) {
const [name, setName] = useState(currentThing?.name || '') const [name, setName] = useState(currentThing?.name || '')
@@ -59,87 +58,80 @@ function CreateThingModal({ isOpen, onClose, onSave, currentThing }) {
} }
return ( return (
<Modal open={isOpen} onClose={onClose}> <FadeModal open={isOpen} onClose={onClose}>
<ModalDialog> <Typography level='h4'>
{/* <ModalClose /> */} {currentThing?.id ? 'Edit' : 'Create'} Thing
<Typography level='h4'> </Typography>
{currentThing?.id ? 'Edit' : 'Create'} Thing <FormControl>
</Typography> <Typography>Name</Typography>
<Textarea
placeholder='Thing name'
value={name}
onChange={e => setName(e.target.value)}
sx={{ minWidth: 300 }}
/>
<FormHelperText color='danger'>{errors.name}</FormHelperText>
</FormControl>
<FormControl>
<Typography>Type</Typography>
<Select value={type} sx={{ minWidth: 300 }}>
{['text', 'number', 'boolean'].map(type => (
<Option value={type} key={type} onClick={() => setType(type)}>
{type.charAt(0).toUpperCase() + type.slice(1)}
</Option>
))}
</Select>
<FormHelperText color='danger'>{errors.type}</FormHelperText>
</FormControl>
{type === 'text' && (
<FormControl> <FormControl>
<Typography>Name</Typography> <Typography>Value</Typography>
<Textarea <Input
placeholder='Thing name' placeholder='Thing value'
value={name} value={state || ''}
onChange={e => setName(e.target.value)} onChange={e => setState(e.target.value)}
sx={{ minWidth: 300 }} sx={{ minWidth: 300 }}
/> />
<FormHelperText color='danger'>{errors.name}</FormHelperText> <FormHelperText color='danger'>{errors.state}</FormHelperText>
</FormControl> </FormControl>
)}
{type === 'number' && (
<FormControl> <FormControl>
<Typography>Type</Typography> <Typography>Value</Typography>
<Select value={type} sx={{ minWidth: 300 }}> <Input
{['text', 'number', 'boolean'].map(type => ( placeholder='Thing value'
<Option value={type} key={type} onClick={() => setType(type)}> type='number'
{type.charAt(0).toUpperCase() + type.slice(1)} value={state || ''}
onChange={e => {
setState(e.target.value)
}}
sx={{ minWidth: 300 }}
/>
</FormControl>
)}
{type === 'boolean' && (
<FormControl>
<Typography>Value</Typography>
<Select sx={{ minWidth: 300 }} value={state}>
{['true', 'false'].map(value => (
<Option value={value} key={value} onClick={() => setState(value)}>
{value.charAt(0).toUpperCase() + value.slice(1)}
</Option> </Option>
))} ))}
</Select> </Select>
<FormHelperText color='danger'>{errors.type}</FormHelperText>
</FormControl> </FormControl>
{type === 'text' && ( )}
<FormControl>
<Typography>Value</Typography>
<Input
placeholder='Thing value'
value={state || ''}
onChange={e => setState(e.target.value)}
sx={{ minWidth: 300 }}
/>
<FormHelperText color='danger'>{errors.state}</FormHelperText>
</FormControl>
)}
{type === 'number' && (
<FormControl>
<Typography>Value</Typography>
<Input
placeholder='Thing value'
type='number'
value={state || ''}
onChange={e => {
setState(e.target.value)
}}
sx={{ minWidth: 300 }}
/>
</FormControl>
)}
{type === 'boolean' && (
<FormControl>
<Typography>Value</Typography>
<Select sx={{ minWidth: 300 }} value={state}>
{['true', 'false'].map(value => (
<Option
value={value}
key={value}
onClick={() => setState(value)}
>
{value.charAt(0).toUpperCase() + value.slice(1)}
</Option>
))}
</Select>
</FormControl>
)}
<Box display={'flex'} justifyContent={'space-around'} mt={1}> <Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}> <Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{currentThing?.id ? 'Update' : 'Create'} {currentThing?.id ? 'Update' : 'Create'}
</Button> </Button>
<Button onClick={onClose} variant='outlined'> <Button onClick={onClose} variant='outlined'>
{currentThing?.id ? 'Cancel' : 'Close'} {currentThing?.id ? 'Cancel' : 'Close'}
</Button> </Button>
</Box> </Box>
</ModalDialog> </FadeModal>
</Modal>
) )
} }
export default CreateThingModal export default CreateThingModal

View File

@@ -3,13 +3,12 @@ import {
Button, Button,
FormControl, FormControl,
Input, Input,
Modal,
ModalDialog,
Option, Option,
Select, Select,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import FadeModal from '../../../components/common/FadeModal'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { useNotification } from '../../../service/NotificationProvider.jsx' import { useNotification } from '../../../service/NotificationProvider.jsx'
@@ -58,29 +57,9 @@ function LabelModal({ isOpen, onClose, label }) {
return true return true
} }
// Mutation for saving labels
// const saveLabelMutation = useMutation(
// newLabel =>
// label
// ? UpdateLabel({ id: label.id, ...newLabel })
// : CreateLabel(newLabel),
// {
// onSuccess: () => {
// queryClient.invalidateQueries('labels')
// onClose()
// },
// onError: () => {
// setError('Failed to save label. Please try again.')
// },
// },
// )
const handleSave = () => { const handleSave = () => {
if (!validateLabel()) return if (!validateLabel()) return
const saveLabel = label?.id && label.id !== -1 ? UpdateLabel : CreateLabel const saveLabel = label?.id && label.id !== -1 ? UpdateLabel : CreateLabel
// ? { id: label.id, name: labelName, color }
// : { name: labelName, color }
// saveLabelMutation.mutate({ name: labelName, color })
saveLabel({ saveLabel({
id: label?.id, id: label?.id,
name: labelName, name: labelName,
@@ -110,79 +89,77 @@ function LabelModal({ isOpen, onClose, label }) {
} }
return ( return (
<Modal open={isOpen} onClose={onClose}> <FadeModal open={isOpen} onClose={onClose}>
<ModalDialog> <Typography level='title-md' mb={1}>
<Typography level='title-md' mb={1}> {label ? 'Edit Label' : 'Add Label'}
{label ? 'Edit Label' : 'Add Label'} </Typography>
<FormControl>
<Typography gutterBottom level='body-sm' alignSelf='start'>
Name
</Typography> </Typography>
<Input
fullWidth
id='labelName'
value={labelName}
onChange={e => setLabelName(e.target.value)}
/>
</FormControl>
<FormControl> <FormControl>
<Typography gutterBottom level='body-sm' alignSelf='start'> <Typography gutterBottom level='body-sm' alignSelf='start'>
Name Color
</Typography> </Typography>
<Input <Select
fullWidth value={color}
id='labelName' onChange={(e, value) => value && setColor(value)}
value={labelName} renderValue={selected => (
onChange={e => setLabelName(e.target.value)} <Typography
/> startDecorator={
</FormControl> <Box
className='size-4'
borderRadius={10}
sx={{ background: selected.value }}
/>
}
>
{selected.label}
</Typography>
)}
>
{LABEL_COLORS.map(val => (
<Option key={val.value} value={val.value}>
<Box className='flex items-center justify-between'>
<Box
width={20}
height={20}
borderRadius={10}
sx={{ background: val.value }}
/>
<Typography sx={{ ml: 1 }} variant='caption'>
{val.name}
</Typography>
</Box>
</Option>
))}
</Select>
</FormControl>
<FormControl> {error && (
<Typography gutterBottom level='body-sm' alignSelf='start'> <Typography color='warning' level='body-sm'>
Color {error}
</Typography> </Typography>
<Select )}
value={color}
onChange={(e, value) => value && setColor(value)}
renderValue={selected => (
<Typography
startDecorator={
<Box
className='size-4'
borderRadius={10}
sx={{ background: selected.value }}
/>
}
>
{selected.label}
</Typography>
)}
>
{LABEL_COLORS.map(val => (
<Option key={val.value} value={val.value}>
<Box className='flex items-center justify-between'>
<Box
width={20}
height={20}
borderRadius={10}
sx={{ background: val.value }}
/>
<Typography sx={{ ml: 1 }} variant='caption'>
{val.name}
</Typography>
</Box>
</Option>
))}
</Select>
</FormControl>
{error && ( <Box display='flex' justifyContent='space-around' mt={1}>
<Typography color='warning' level='body-sm'> <Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{error} {label ? 'Save Changes' : 'Add Label'}
</Typography> </Button>
)} <Button onClick={onClose} variant='outlined'>
Cancel
<Box display='flex' justifyContent='space-around' mt={1}> </Button>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}> </Box>
{label ? 'Save Changes' : 'Add Label'} </FadeModal>
</Button>
<Button onClick={onClose} variant='outlined'>
Cancel
</Button>
</Box>
</ModalDialog>
</Modal>
) )
} }

View File

@@ -4,11 +4,10 @@ import {
FormControl, FormControl,
FormHelperText, FormHelperText,
Input, Input,
Modal,
ModalDialog,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import FadeModal from '../../../components/common/FadeModal'
function PassowrdChangeModal({ isOpen, onClose }) { function PassowrdChangeModal({ isOpen, onClose }) {
const [password, setPassword] = React.useState('') const [password, setPassword] = React.useState('')
@@ -40,78 +39,76 @@ function PassowrdChangeModal({ isOpen, onClose }) {
} }
return ( return (
<Modal open={isOpen} onClose={onClose}> <FadeModal open={isOpen} onClose={onClose}>
<ModalDialog> <Typography level='h4' mb={1}>
<Typography level='h4' mb={1}> Change Password
</Typography>
<Typography level='body-md' gutterBottom>
Please enter your new password.
</Typography>
<FormControl>
<Typography level='body2' alignSelf={'start'}>
New Password
</Typography>
<Input
margin='normal'
required
fullWidth
name='password'
label='Password'
type='password'
id='password'
value={password}
onChange={e => {
setPasswordTouched(true)
setPassword(e.target.value)
}}
/>
</FormControl>
<FormControl>
<Typography level='body2' alignSelf={'start'}>
Confirm Password
</Typography>
<Input
margin='normal'
required
fullWidth
name='confirmPassword'
label='confirmPassword'
type='password'
id='confirmPassword'
value={confirmPassword}
onChange={e => {
setConfirmPasswordTouched(true)
setConfirmPassword(e.target.value)
}}
/>
<FormHelperText>{passwordError}</FormHelperText>
</FormControl>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
disabled={passwordError != null}
onClick={() => {
handleAction(true)
}}
fullWidth
sx={{ mr: 1 }}
>
Change Password Change Password
</Typography> </Button>
<Button
<Typography level='body-md' gutterBottom> onClick={() => {
Please enter your new password. handleAction(false)
</Typography> }}
<FormControl> variant='outlined'
<Typography level='body2' alignSelf={'start'}> >
New Password Cancel
</Typography> </Button>
<Input </Box>
margin='normal' </FadeModal>
required
fullWidth
name='password'
label='Password'
type='password'
id='password'
value={password}
onChange={e => {
setPasswordTouched(true)
setPassword(e.target.value)
}}
/>
</FormControl>
<FormControl>
<Typography level='body2' alignSelf={'start'}>
Confirm Password
</Typography>
<Input
margin='normal'
required
fullWidth
name='confirmPassword'
label='confirmPassword'
type='password'
id='confirmPassword'
value={confirmPassword}
onChange={e => {
setConfirmPasswordTouched(true)
setConfirmPassword(e.target.value)
}}
/>
<FormHelperText>{passwordError}</FormHelperText>
</FormControl>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
disabled={passwordError != null}
onClick={() => {
handleAction(true)
}}
fullWidth
sx={{ mr: 1 }}
>
Change Password
</Button>
<Button
onClick={() => {
handleAction(false)
}}
variant='outlined'
>
Cancel
</Button>
</Box>
</ModalDialog>
</Modal>
) )
} }
export default PassowrdChangeModal export default PassowrdChangeModal

View File

@@ -1,15 +1,16 @@
import { import { Box, Button, Option, Select, Typography } from '@mui/joy'
Box,
Button,
Modal,
ModalDialog,
Option,
Select,
Typography,
} from '@mui/joy'
import React from 'react' import React from 'react'
import FadeModal from '../../../components/common/FadeModal'
function SelectModal({ isOpen, onClose, onSave, options, title, displayKey,placeholder }) { function SelectModal({
isOpen,
onClose,
onSave,
options,
title,
displayKey,
placeholder,
}) {
const [selected, setSelected] = React.useState(null) const [selected, setSelected] = React.useState(null)
const handleSave = () => { const handleSave = () => {
onSave(options.find(item => item.id === selected)) onSave(options.find(item => item.id === selected))
@@ -17,33 +18,31 @@ function SelectModal({ isOpen, onClose, onSave, options, title, displayKey,place
} }
return ( return (
<Modal open={isOpen} onClose={onClose}> <FadeModal open={isOpen} onClose={onClose}>
<ModalDialog> <Typography variant='h4'>{title}</Typography>
<Typography variant='h4'>{title}</Typography> <Select placeholder={placeholder}>
<Select placeholder={placeholder}> {options.map((item, index) => (
{options.map((item, index) => ( <Option
<Option value={item.id}
value={item.id} key={item[displayKey]}
key={item[displayKey]} onClick={() => {
onClick={() => { setSelected(item.id)
setSelected(item.id) }}
}} >
> {item[displayKey]}
{item[displayKey]} </Option>
</Option> ))}
))} </Select>
</Select>
<Box display={'flex'} justifyContent={'space-around'} mt={1}> <Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}> <Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
Save Save
</Button> </Button>
<Button onClick={onClose} variant='outlined'> <Button onClick={onClose} variant='outlined'>
Cancel Cancel
</Button> </Button>
</Box> </Box>
</ModalDialog> </FadeModal>
</Modal>
) )
} }
export default SelectModal export default SelectModal

View File

@@ -1,5 +1,6 @@
import { Box, Button, Modal, ModalDialog, Textarea, Typography } from '@mui/joy' import { Box, Button, Textarea, Typography } from '@mui/joy'
import { useState } from 'react' import { useState } from 'react'
import FadeModal from '../../../components/common/FadeModal'
function TextModal({ function TextModal({
isOpen, isOpen,
@@ -18,29 +19,26 @@ function TextModal({
} }
return ( return (
<Modal open={isOpen} onClose={onClose}> <FadeModal open={isOpen} onClose={onClose}>
<ModalDialog> <Typography variant='h4'>{title}</Typography>
{/* <ModalClose /> */} <Textarea
<Typography variant='h4'>{title}</Typography> placeholder='Type in here…'
<Textarea value={text}
placeholder='Type in here…' onChange={e => setText(e.target.value)}
value={text} minRows={2}
onChange={e => setText(e.target.value)} maxRows={4}
minRows={2} sx={{ minWidth: 300 }}
maxRows={4} />
sx={{ minWidth: 300 }}
/>
<Box display={'flex'} justifyContent={'space-around'} mt={1}> <Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}> <Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{okText ? okText : 'Save'} {okText ? okText : 'Save'}
</Button> </Button>
<Button onClick={onClose} variant='outlined'> <Button onClick={onClose} variant='outlined'>
{cancelText ? cancelText : 'Cancel'} {cancelText ? cancelText : 'Cancel'}
</Button> </Button>
</Box> </Box>
</ModalDialog> </FadeModal>
</Modal>
) )
} }
export default TextModal export default TextModal

View File

@@ -1,57 +1,44 @@
import { import { Avatar, Box, Button, List, ListItem, Typography } from '@mui/joy'
Avatar, import FadeModal from '../../../components/common/FadeModal'
Box,
Button,
List,
ListItem,
Modal,
ModalDialog,
ModalOverflow,
Typography,
} from '@mui/joy'
const UserModal = ({ isOpen, performers = [], onSelect, onClose }) => { const UserModal = ({ isOpen, performers = [], onSelect, onClose }) => {
return ( return (
<Modal open={isOpen} onClose={onClose}> <FadeModal open={isOpen} onClose={onClose} size='md' fullWidth>
<ModalOverflow> <Typography level='h4' sx={{ mb: 2 }}>
<ModalDialog size='md' sx={{ minWidth: 360 }}> Select User
<Typography level='h4' sx={{ mb: 2 }}> </Typography>
Select User <List sx={{ mb: 2 }}>
</Typography> {performers.map(user => (
<List sx={{ mb: 2 }}> <ListItem
{performers.map(user => ( key={user.id}
<ListItem sx={{
key={user.id} cursor: 'pointer',
sx={{ '&:hover': {
cursor: 'pointer', backgroundColor: 'rgba(0, 0, 0, 0.04)',
'&:hover': { },
backgroundColor: 'rgba(0, 0, 0, 0.04)', }}
}, onClick={() => {
}} onSelect(user)
onClick={() => { onClose()
onSelect(user) }}
onClose() >
}} <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
> <Avatar
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> size='lg'
<Avatar src={user.image || user.avatar}
size='lg' alt={user.displayName || user.name}
src={user.image || user.avatar} />
alt={user.displayName || user.name} <Typography>{user.displayName || user.name}</Typography>
/> </Box>
<Typography>{user.displayName || user.name}</Typography> </ListItem>
</Box> ))}
</ListItem> </List>
))} <Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
</List> <Button variant='outlined' color='neutral' onClick={onClose}>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}> Cancel
<Button variant='outlined' color='neutral' onClick={onClose}> </Button>
Cancel </Box>
</Button> </FadeModal>
</Box>
</ModalDialog>
</ModalOverflow>
</Modal>
) )
} }

View File

@@ -1,15 +1,7 @@
import { CopyAll } from '@mui/icons-material' import { CopyAll } from '@mui/icons-material'
import { import { Box, Button, Checkbox, Input, ListItem, Typography } from '@mui/joy'
Box, import { useState } from 'react'
Button, import FadeModal from '../../../components/common/FadeModal'
Checkbox,
Input,
ListItem,
Modal,
ModalDialog,
Typography,
} from '@mui/joy'
import React, { useState } from 'react'
function WriteNFCModal({ config }) { function WriteNFCModal({ config }) {
const [nfcStatus, setNfcStatus] = useState('idle') // 'idle', 'writing', 'success', 'error' const [nfcStatus, setNfcStatus] = useState('idle') // 'idle', 'writing', 'success', 'error'
@@ -60,63 +52,61 @@ function WriteNFCModal({ config }) {
return url return url
} }
return ( return (
<Modal open={config?.isOpen} onClose={handleClose}> <FadeModal open={config?.isOpen} onClose={handleClose}>
<ModalDialog> <Typography level='h4' mb={1}>
<Typography level='h4' mb={1}> {nfcStatus === 'success' ? 'Success!' : 'Write to NFC'}
{nfcStatus === 'success' ? 'Success!' : 'Write to NFC'} </Typography>
</Typography>
{nfcStatus === 'success' ? ( {nfcStatus === 'success' ? (
<Typography level='body-md' gutterBottom>
URL written to NFC tag successfully!
</Typography>
) : (
<>
<Typography level='body-md' gutterBottom> <Typography level='body-md' gutterBottom>
URL written to NFC tag successfully! {nfcStatus === 'error'
? errorMessage
: 'Press the button below to write to NFC.'}
</Typography> </Typography>
) : ( <Input
<> value={getURL()}
<Typography level='body-md' gutterBottom> fullWidth
{nfcStatus === 'error' readOnly
? errorMessage label='URL'
: 'Press the button below to write to NFC.'} sx={{ mt: 1 }}
</Typography> endDecorator={
<Input <CopyAll
value={getURL()} sx={{ cursor: 'pointer' }}
fullWidth onClick={() => {
readOnly navigator.clipboard.writeText(getURL())
label='URL' alert('URL copied to clipboard!')
sx={{ mt: 1 }} }}
endDecorator={
<CopyAll
sx={{ cursor: 'pointer' }}
onClick={() => {
navigator.clipboard.writeText(getURL())
alert('URL copied to clipboard!')
}}
/>
}
/>
<ListItem>
<Checkbox
checked={isAutoCompleteWhenScan}
onChange={e => setIsAutoCompleteWhenScan(e.target.checked)}
label='Auto-complete when scanned'
/> />
</ListItem> }
<Box display={'flex'} justifyContent={'space-around'} mt={1}> />
<Button <ListItem>
onClick={() => writeToNFC(getURL())} <Checkbox
fullWidth checked={isAutoCompleteWhenScan}
sx={{ mr: 1 }} onChange={e => setIsAutoCompleteWhenScan(e.target.checked)}
disabled={nfcStatus === 'writing'} label='Auto-complete when scanned'
> />
Write NFC </ListItem>
</Button> <Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={requestNFCAccess} variant='outlined'> <Button
Request Access onClick={() => writeToNFC(getURL())}
</Button> fullWidth
</Box> sx={{ mr: 1 }}
</> disabled={nfcStatus === 'writing'}
)} >
</ModalDialog> Write NFC
</Modal> </Button>
<Button onClick={requestNFCAccess} variant='outlined'>
Request Access
</Button>
</Box>
</>
)}
</FadeModal>
) )
} }

View File

@@ -1,14 +1,6 @@
import { import { Box, Button, FormLabel, IconButton, Input, Typography } from '@mui/joy'
Box,
Button,
FormLabel,
IconButton,
Input,
Modal,
ModalDialog,
Typography,
} from '@mui/joy'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import FadeModal from '../../components/common/FadeModal'
function RedeemPointsModal({ config }) { function RedeemPointsModal({ config }) {
useEffect(() => { useEffect(() => {
@@ -20,71 +12,69 @@ function RedeemPointsModal({ config }) {
const predefinedPoints = [1, 5, 10, 25] const predefinedPoints = [1, 5, 10, 25]
return ( return (
<Modal open={config?.isOpen} onClose={config?.onClose}> <FadeModal open={config?.isOpen} onClose={config?.onClose}>
<ModalDialog> <Typography level='h4' mb={1}>
<Typography level='h4' mb={1}> Redeem Points
Redeem Points </Typography>
</Typography> <FormLabel>
<FormLabel> Points to Redeem ({config.available ? config.available : 0} points
Points to Redeem ({config.available ? config.available : 0} points available)
available) </FormLabel>
</FormLabel> <Input
<Input type='number'
type='number' value={points}
value={points} slotProps={{
slotProps={{ input: { min: 0, max: config.available ? config.available : 0 },
input: { min: 0, max: config.available ? config.available : 0 }, }}
}} onChange={e => {
onChange={e => { if (e.target.value > config.available) {
if (e.target.value > config.available) { setPoints(config.available)
setPoints(config.available) return
return }
} setPoints(e.target.value)
setPoints(e.target.value) }}
}} />
/> <FormLabel>Or select from predefined points:</FormLabel>
<FormLabel>Or select from predefined points:</FormLabel> <Box display='flex' justifyContent='space-evenly' mb={1}>
<Box display='flex' justifyContent='space-evenly' mb={1}> {predefinedPoints.map(point => (
{predefinedPoints.map(point => ( <IconButton
<IconButton variant='outlined'
variant='outlined' disabled={points + point > config.available}
disabled={points + point > config.available} sx={{ borderRadius: '50%' }}
sx={{ borderRadius: '50%' }} key={point}
key={point} onClick={() => {
onClick={() => { const newPoints = points + point
const newPoints = points + point if (newPoints > config.available) {
if (newPoints > config.available) { setPoints(config.available)
setPoints(config.available) return
return }
} setPoints(newPoints)
setPoints(newPoints) }}
}}
>
{point}
</IconButton>
))}
</Box>
{/* 3 button save , cancel and delete */}
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
onClick={() =>
config.onSave({
points: Number(points),
userId: config.user.userId,
})
}
fullWidth
sx={{ mr: 1 }}
> >
Redeem {point}
</Button> </IconButton>
<Button onClick={config.onClose} variant='outlined'> ))}
Cancel </Box>
</Button>
</Box> {/* 3 button save , cancel and delete */}
</ModalDialog> <Box display={'flex'} justifyContent={'space-around'} mt={1}>
</Modal> <Button
onClick={() =>
config.onSave({
points: Number(points),
userId: config.user.userId,
})
}
fullWidth
sx={{ mr: 1 }}
>
Redeem
</Button>
<Button onClick={config.onClose} variant='outlined'>
Cancel
</Button>
</Box>
</FadeModal>
) )
} }
export default RedeemPointsModal export default RedeemPointsModal

View File

@@ -1,20 +1,10 @@
import { Add, EditNotifications } from '@mui/icons-material' import { Add, EditNotifications } from '@mui/icons-material'
import { import { Box, Button, Chip, Input, Option, Select, Typography } from '@mui/joy'
Box,
Button,
Chip,
Input,
Modal,
ModalDialog,
ModalOverflow,
Option,
Select,
Typography,
} from '@mui/joy'
import { FormControl } from '@mui/material' import { FormControl } from '@mui/material'
import * as chrono from 'chrono-node' import * as chrono from 'chrono-node'
import moment from 'moment' import moment from 'moment'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import FadeModal from '../../components/common/FadeModal'
import { useCreateChore } from '../../queries/ChoreQueries' import { useCreateChore } from '../../queries/ChoreQueries'
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
import { isPlusAccount } from '../../utils/Helpers' import { isPlusAccount } from '../../utils/Helpers'
@@ -399,101 +389,100 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
} }
return ( return (
<Modal open={isModalOpen} onClose={handleCloseModal}> <FadeModal
<ModalOverflow> open={isModalOpen}
<ModalDialog size='lg' sx={{ minWidth: '100%' }}> onClose={handleCloseModal}
<Typography level='h4'>Create new task</Typography> size='lg'
<Chip startDecorator='🚧' variant='soft' color='warning' size='sm'> fullWidth={true}
Experimental Feature >
</Chip> <Typography level='h4'>Create new task</Typography>
<Box> <Chip startDecorator='🚧' variant='soft' color='warning' size='sm'>
<Box Experimental Feature
sx={{ </Chip>
display: 'flex', <Box>
flexDirection: 'row', <Box
alignItems: 'center', sx={{
}} display: 'flex',
> flexDirection: 'row',
<Typography level='body-sm'>Task in a sentence:</Typography> alignItems: 'center',
<LearnMoreButton }}
content={ >
<> <Typography level='body-sm'>Task in a sentence:</Typography>
<Typography level='body-sm' sx={{ mb: 1 }}> <LearnMoreButton
This feature lets you create a task simply by typing a content={
sentence. It attempt parses the sentence to identify the <>
task&apos;s due date, priority, and frequency. <Typography level='body-sm' sx={{ mb: 1 }}>
</Typography> This feature lets you create a task simply by typing a
sentence. It attempt parses the sentence to identify the
task&apos;s due date, priority, and frequency.
</Typography>
<Typography <Typography level='body-sm' sx={{ fontWeight: 'bold', mt: 2 }}>
level='body-sm' Examples:
sx={{ fontWeight: 'bold', mt: 2 }} </Typography>
>
Examples:
</Typography>
<Typography <Typography
level='body-sm' level='body-sm'
component='ul' component='ul'
sx={{ pl: 2, mt: 1, listStyle: 'disc' }} sx={{ pl: 2, mt: 1, listStyle: 'disc' }}
> >
<li> <li>
<strong>Priority:</strong>For highest priority any of <strong>Priority:</strong>For highest priority any of the
the following keyword <em>P1</em>, <em>Urgent</em>,{' '} following keyword <em>P1</em>, <em>Urgent</em>,{' '}
<em>Important</em>, or <em>ASAP</em>. For lower <em>Important</em>, or <em>ASAP</em>. For lower priorities,
priorities, use <em>P2</em>, <em>P3</em>, or <em>P4</em> use <em>P2</em>, <em>P3</em>, or <em>P4</em>.
. </li>
</li> <li>
<li> <strong>Due date:</strong> Specify dates with phrases like{' '}
<strong>Due date:</strong> Specify dates with phrases <em>tomorrow</em>, <em>next week</em>, <em>Monday</em>, or{' '}
like <em>tomorrow</em>, <em>next week</em>,{' '} <em>August 1st at 12pm</em>.
<em>Monday</em>, or <em>August 1st at 12pm</em>. </li>
</li> <li>
<li> <strong>Frequency:</strong> Set recurring tasks with terms
<strong>Frequency:</strong> Set recurring tasks with like <em>daily</em>, <em>weekly</em>, <em>monthly</em>,{' '}
terms like <em>daily</em>, <em>weekly</em>,{' '} <em>yearly</em>, or patterns such as{' '}
<em>monthly</em>, <em>yearly</em>, or patterns such as{' '} <em>every Tuesday and Thursday</em>.
<em>every Tuesday and Thursday</em>. </li>
</li> </Typography>
</Typography> </>
</> }
} />
/> </Box>
</Box>
<SmartTaskTitleInput <SmartTaskTitleInput
autoFocus autoFocus
value={taskText} value={taskText}
placeholder='Type your full text here...' placeholder='Type your full text here...'
onChange={text => { onChange={text => {
setTaskText(text) setTaskText(text)
}} }}
customRenderer={renderedParts} customRenderer={renderedParts}
onEnterPressed={handleEnterPressed} onEnterPressed={handleEnterPressed}
suggestions={{ suggestions={{
'#': { '#': {
value: 'id', value: 'id',
display: 'name', display: 'name',
options: userLabels ? userLabels : [], options: userLabels ? userLabels : [],
}, },
'!': { '!': {
value: 'id', value: 'id',
display: 'name', display: 'name',
options: [ options: [
{ id: '1', name: 'P1' }, { id: '1', name: 'P1' },
{ id: '2', name: 'P2' }, { id: '2', name: 'P2' },
{ id: '3', name: 'P3' }, { id: '3', name: 'P3' },
{ id: '4', name: 'P4' }, { id: '4', name: 'P4' },
], ],
}, },
'@': { '@': {
value: 'userId', value: 'userId',
display: 'displayName', display: 'displayName',
options: circleMembers?.res || [], options: circleMembers?.res || [],
}, },
}} }}
/> />
</Box> </Box>
{/* <Box> {/* <Box>
<Typography level='body-sm'>Title:</Typography> <Typography level='body-sm'>Title:</Typography>
<Input <Input
value={taskTitle} value={taskTitle}
@@ -501,126 +490,122 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
sx={{ width: '100%', fontSize: '16px' }} sx={{ width: '100%', fontSize: '16px' }}
/> />
</Box> */} </Box> */}
<Box> <Box>
{!hasDescription && ( {!hasDescription && (
<Button <Button
startDecorator={<Add />} startDecorator={<Add />}
variant='plain' variant='plain'
size='sm' size='sm'
onClick={() => setHasDescription(true)} onClick={() => setHasDescription(true)}
> >
Description Description
</Button> </Button>
)} )}
{!hasSubTasks && ( {!hasSubTasks && (
<Button <Button
startDecorator={<Add />} startDecorator={<Add />}
variant='plain' variant='plain'
size='sm' size='sm'
onClick={() => setHasSubTasks(true)} onClick={() => setHasSubTasks(true)}
> >
Subtasks Subtasks
</Button> </Button>
)} )}
{!dueDate && ( {!dueDate && (
<Button <Button
startDecorator={<Add />} startDecorator={<Add />}
variant='plain' variant='plain'
size='sm' size='sm'
onClick={() => { onClick={() => {
setDueDate( setDueDate(moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'))
moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'),
)
}}
>
Due Date
</Button>
)}
{!hasNotifications && dueDate && (
<Button
startDecorator={<EditNotifications />}
variant='plain'
size='sm'
onClick={() => {
setHasNotifications(true)
setFrequencyHumanReadable('Once')
setFrequency(null)
setDueDate(
moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'),
)
}}
>
Edit Notifications
</Button>
)}
</Box>
{hasDescription && (
<Box>
<Typography level='body-sm'>Description:</Typography>
<div>
<RichTextEditor
onChange={setDescription}
entityType={'chore_description'}
/>
</div>
</Box>
)}
{hasSubTasks && (
<Box>
<Typography level='body-sm'>Subtasks:</Typography>
<SubTasks
editMode={true}
tasks={subTasks ? subTasks : []}
setTasks={setSubTasks}
/>
</Box>
)}
<Box
sx={{
marginTop: 2,
display: 'flex',
flexDirection: 'row',
gap: 2,
}} }}
> >
<FormControl> Due Date
<Typography level='body-sm'>Priority</Typography> </Button>
<Select )}
defaultValue={0} {!hasNotifications && dueDate && (
value={priority} <Button
onChange={(e, value) => setPriority(value)} startDecorator={<EditNotifications />}
> variant='plain'
<Option value='0'>No Priority</Option> size='sm'
<Option value='1'>P1</Option> onClick={() => {
<Option value='2'>P2</Option> setHasNotifications(true)
<Option value='3'>P3</Option> setFrequencyHumanReadable('Once')
<Option value='4'>P4</Option> setFrequency(null)
</Select> setDueDate(moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'))
</FormControl>
{dueDate && (
<FormControl>
<Typography level='body-sm'>Due Date</Typography>
<Input
type='datetime-local'
value={dueDate}
onChange={e => setDueDate(e.target.value)}
sx={{ width: '100%', fontSize: '16px' }}
/>
</FormControl>
)}
</Box>
<Box
sx={{
marginTop: 2,
display: 'flex',
flexDirection: 'row',
justifyContent: 'start',
gap: 2,
}} }}
> >
{/* <FormControl> Edit Notifications
</Button>
)}
</Box>
{hasDescription && (
<Box>
<Typography level='body-sm'>Description:</Typography>
<div>
<RichTextEditor
onChange={setDescription}
entityType={'chore_description'}
/>
</div>
</Box>
)}
{hasSubTasks && (
<Box>
<Typography level='body-sm'>Subtasks:</Typography>
<SubTasks
editMode={true}
tasks={subTasks ? subTasks : []}
setTasks={setSubTasks}
/>
</Box>
)}
<Box
sx={{
marginTop: 2,
display: 'flex',
flexDirection: 'row',
gap: 2,
}}
>
<FormControl>
<Typography level='body-sm'>Priority</Typography>
<Select
defaultValue={0}
value={priority}
onChange={(e, value) => setPriority(value)}
>
<Option value='0'>No Priority</Option>
<Option value='1'>P1</Option>
<Option value='2'>P2</Option>
<Option value='3'>P3</Option>
<Option value='4'>P4</Option>
</Select>
</FormControl>
{dueDate && (
<FormControl>
<Typography level='body-sm'>Due Date</Typography>
<Input
type='datetime-local'
value={dueDate}
onChange={e => setDueDate(e.target.value)}
sx={{ width: '100%', fontSize: '16px' }}
/>
</FormControl>
)}
</Box>
<Box
sx={{
marginTop: 2,
display: 'flex',
flexDirection: 'row',
justifyContent: 'start',
gap: 2,
}}
>
{/* <FormControl>
<Typography level='body-sm'>Assignees</Typography> <Typography level='body-sm'>Assignees</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{assignees.length > 0 ? ( {assignees.length > 0 ? (
@@ -641,58 +626,51 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
)} )}
</Box> </Box>
</FormControl> */} </FormControl> */}
{hasNotifications && dueDate && ( {hasNotifications && dueDate && (
<Box
sx={{
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography level='body-sm'>Notification Schedule</Typography>
<Box sx={{ p: 0.5 }}>
<NotificationTemplate
onChange={metadata => {
if (
metadata.notifications !==
notificationMetadata.templates
) {
const newNotificaitonMetadata = {
...notificationMetadata,
templates: metadata.notifications,
}
setNotificationMetadata(newNotificaitonMetadata)
}
}}
value={notificationMetadata}
showTimeline={false}
/>
</Box>
</Box>
)}
</Box>
<Box <Box
sx={{ sx={{
marginTop: 2, flexDirection: 'column',
display: 'flex', alignItems: 'center',
flexDirection: 'row',
justifyContent: 'end',
gap: 1,
}} }}
> >
<Button <Typography level='body-sm'>Notification Schedule</Typography>
variant='outlined' <Box sx={{ p: 0.5 }}>
color='neutral' <NotificationTemplate
onClick={handleCloseModal} onChange={metadata => {
> if (
Cancel metadata.notifications !== notificationMetadata.templates
</Button> ) {
<Button variant='solid' color='primary' onClick={handleSubmit}> const newNotificaitonMetadata = {
Create ...notificationMetadata,
</Button> templates: metadata.notifications,
}
setNotificationMetadata(newNotificaitonMetadata)
}
}}
value={notificationMetadata}
showTimeline={false}
/>
</Box>
</Box> </Box>
</ModalDialog> )}
</ModalOverflow> </Box>
</Modal> <Box
sx={{
marginTop: 2,
display: 'flex',
flexDirection: 'row',
justifyContent: 'end',
gap: 1,
}}
>
<Button variant='outlined' color='neutral' onClick={handleCloseModal}>
Cancel
</Button>
<Button variant='solid' color='primary' onClick={handleSubmit}>
Create
</Button>
</Box>
</FadeModal>
) )
} }