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,
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')

View File

@@ -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 (
<Modal open={open} onClose={handleClose}>
<ModalDialog size='sm' sx={{ maxWidth: 400 }}>
<ModalClose />
<FadeModal open={open} onClose={handleClose} size='sm'>
<ModalClose />
<Box className='mb-4 text-center'>
<Security sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} />
<Typography level='h4' sx={{ mb: 1 }}>
Two-Factor Authentication
</Typography>
<Typography level='body-md' sx={{ color: 'text.secondary' }}>
Enter the verification code from your authenticator app
<Box className='mb-4 text-center'>
<Security sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} />
<Typography level='h4' sx={{ mb: 1 }}>
Two-Factor Authentication
</Typography>
<Typography level='body-md' sx={{ color: 'text.secondary' }}>
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>
<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>
<Stack spacing={3}>
<Box>
<Typography level='body-sm' sx={{ mb: 1 }}>
{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>
{error && (
<Alert color='danger' size='sm'>
{error}
</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 {
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 (
<Modal open={config?.isOpen} onClose={config?.onClose}>
<ModalDialog>
<Typography level='h4' mb={1}>
Edit History
</Typography>
<FormLabel>Due Date</FormLabel>
<Input
type='datetime-local'
value={dueDate}
onChange={e => {
setDueDate(e.target.value)
}}
/>
<FormLabel>Completed Date</FormLabel>
<Input
type='datetime-local'
value={completedDate}
onChange={e => {
setCompletedDate(e.target.value)
}}
/>
<FormLabel>Note</FormLabel>
<Input
fullWidth
multiline
label='Additional Notes'
placeholder='Additional Notes'
value={notes}
onChange={e => {
if (e.target.value.trim() === '') {
setNotes(null)
return
}
setNotes(e.target.value)
}}
size='md'
sx={{
mb: 1,
}}
/>
<FadeModal open={config?.isOpen} onClose={config?.onClose}>
<Typography level='h4' mb={1}>
Edit History
</Typography>
<FormLabel>Due Date</FormLabel>
<Input
type='datetime-local'
value={dueDate}
onChange={e => {
setDueDate(e.target.value)
}}
/>
<FormLabel>Completed Date</FormLabel>
<Input
type='datetime-local'
value={completedDate}
onChange={e => {
setCompletedDate(e.target.value)
}}
/>
<FormLabel>Note</FormLabel>
<Input
fullWidth
multiline
label='Additional Notes'
placeholder='Additional Notes'
value={notes}
onChange={e => {
if (e.target.value.trim() === '') {
setNotes(null)
return
}
setNotes(e.target.value)
}}
size='md'
sx={{
mb: 1,
}}
/>
{/* 3 button save , cancel and delete */}
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
onClick={() =>
config.onSave({
id: historyRecord.id,
performedAt: moment(completedDate).toISOString(),
dueDate: moment(dueDate).toISOString(),
notes,
})
}
fullWidth
sx={{ mr: 1 }}
>
Save
</Button>
<Button onClick={config.onClose} variant='outlined'>
Cancel
</Button>
<Button
onClick={() => {
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',
{/* 3 button save , cancel and delete */}
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
onClick={() =>
config.onSave({
id: historyRecord.id,
performedAt: moment(completedDate).toISOString(),
dueDate: moment(dueDate).toISOString(),
notes,
})
}
fullWidth
sx={{ mr: 1 }}
>
Save
</Button>
<Button onClick={config.onClose} variant='outlined'>
Cancel
</Button>
<Button
onClick={() => {
setIsDeleteModalOpen(true)
}}
/>
</ModalDialog>
</Modal>
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',
}}
/>
</FadeModal>
)
}
export default EditHistoryModal

View File

@@ -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 (
<Modal open={config?.isOpen} onClose={config?.onClose}>
<ModalDialog>
<Typography level='h4' mb={1}>
{config?.title}
</Typography>
<FadeModal
open={config?.isOpen}
onClose={config?.onClose}
size='sm'
unmountDelay={250}
>
<Typography level='h4' mb={1}>
{config?.title}
</Typography>
<Typography level='body-md' gutterBottom>
{config?.message}
</Typography>
<Typography level='body-md' gutterBottom>
{config?.message}
</Typography>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
onClick={() => {
handleAction(true)
}}
fullWidth
sx={{ mr: 1 }}
color={config.color ? config.color : 'primary'}
>
{config?.confirmText}
</Button>
<Button
onClick={() => {
handleAction(false)
}}
variant='outlined'
>
{config?.cancelText}
</Button>
</Box>
</ModalDialog>
</Modal>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
onClick={() => {
handleAction(true)
}}
fullWidth
sx={{ mr: 1 }}
color={config.color ? config.color : 'primary'}
>
{config?.confirmText}
</Button>
<Button
onClick={() => {
handleAction(false)
}}
variant='outlined'
>
{config?.cancelText}
</Button>
</Box>
</FadeModal>
)
}
export default ConfirmationModal

View File

@@ -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 (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
{/* <ModalClose /> */}
<Typography level='h4'>
{currentThing?.id ? 'Edit' : 'Create'} Thing
</Typography>
<FadeModal open={isOpen} onClose={onClose}>
<Typography level='h4'>
{currentThing?.id ? 'Edit' : 'Create'} Thing
</Typography>
<FormControl>
<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>
<Typography>Name</Typography>
<Textarea
placeholder='Thing name'
value={name}
onChange={e => setName(e.target.value)}
<Typography>Value</Typography>
<Input
placeholder='Thing value'
value={state || ''}
onChange={e => setState(e.target.value)}
sx={{ minWidth: 300 }}
/>
<FormHelperText color='danger'>{errors.name}</FormHelperText>
<FormHelperText color='danger'>{errors.state}</FormHelperText>
</FormControl>
)}
{type === 'number' && (
<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)}
<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>
<FormHelperText color='danger'>{errors.type}</FormHelperText>
</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}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{currentThing?.id ? 'Update' : 'Create'}
</Button>
<Button onClick={onClose} variant='outlined'>
{currentThing?.id ? 'Cancel' : 'Close'}
</Button>
</Box>
</ModalDialog>
</Modal>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{currentThing?.id ? 'Update' : 'Create'}
</Button>
<Button onClick={onClose} variant='outlined'>
{currentThing?.id ? 'Cancel' : 'Close'}
</Button>
</Box>
</FadeModal>
)
}
export default CreateThingModal

View File

@@ -3,13 +3,12 @@ import {
Button,
FormControl,
Input,
Modal,
ModalDialog,
Option,
Select,
Typography,
} from '@mui/joy'
import { useEffect, useState } from 'react'
import FadeModal from '../../../components/common/FadeModal'
import { useQueryClient } from '@tanstack/react-query'
import { useNotification } from '../../../service/NotificationProvider.jsx'
@@ -58,29 +57,9 @@ function LabelModal({ isOpen, onClose, label }) {
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 = () => {
if (!validateLabel()) return
const saveLabel = label?.id && label.id !== -1 ? UpdateLabel : CreateLabel
// ? { id: label.id, name: labelName, color }
// : { name: labelName, color }
// saveLabelMutation.mutate({ name: labelName, color })
saveLabel({
id: label?.id,
name: labelName,
@@ -110,79 +89,77 @@ function LabelModal({ isOpen, onClose, label }) {
}
return (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
<Typography level='title-md' mb={1}>
{label ? 'Edit Label' : 'Add Label'}
<FadeModal open={isOpen} onClose={onClose}>
<Typography level='title-md' mb={1}>
{label ? 'Edit Label' : 'Add Label'}
</Typography>
<FormControl>
<Typography gutterBottom level='body-sm' alignSelf='start'>
Name
</Typography>
<Input
fullWidth
id='labelName'
value={labelName}
onChange={e => setLabelName(e.target.value)}
/>
</FormControl>
<FormControl>
<Typography gutterBottom level='body-sm' alignSelf='start'>
Name
</Typography>
<Input
fullWidth
id='labelName'
value={labelName}
onChange={e => setLabelName(e.target.value)}
/>
</FormControl>
<FormControl>
<Typography gutterBottom level='body-sm' alignSelf='start'>
Color
</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>
<FormControl>
<Typography gutterBottom level='body-sm' alignSelf='start'>
Color
</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 && (
<Typography color='warning' level='body-sm'>
{error}
</Typography>
)}
{error && (
<Typography color='warning' level='body-sm'>
{error}
</Typography>
)}
<Box display='flex' justifyContent='space-around' mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{label ? 'Save Changes' : 'Add Label'}
</Button>
<Button onClick={onClose} variant='outlined'>
Cancel
</Button>
</Box>
</ModalDialog>
</Modal>
<Box display='flex' justifyContent='space-around' mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{label ? 'Save Changes' : 'Add Label'}
</Button>
<Button onClick={onClose} variant='outlined'>
Cancel
</Button>
</Box>
</FadeModal>
)
}

View File

@@ -4,11 +4,10 @@ import {
FormControl,
FormHelperText,
Input,
Modal,
ModalDialog,
Typography,
} from '@mui/joy'
import React, { useEffect } from 'react'
import FadeModal from '../../../components/common/FadeModal'
function PassowrdChangeModal({ isOpen, onClose }) {
const [password, setPassword] = React.useState('')
@@ -40,78 +39,76 @@ function PassowrdChangeModal({ isOpen, onClose }) {
}
return (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
<Typography level='h4' mb={1}>
<FadeModal open={isOpen} onClose={onClose}>
<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
</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
</Button>
<Button
onClick={() => {
handleAction(false)
}}
variant='outlined'
>
Cancel
</Button>
</Box>
</ModalDialog>
</Modal>
</Button>
<Button
onClick={() => {
handleAction(false)
}}
variant='outlined'
>
Cancel
</Button>
</Box>
</FadeModal>
)
}
export default PassowrdChangeModal

View File

@@ -1,15 +1,16 @@
import {
Box,
Button,
Modal,
ModalDialog,
Option,
Select,
Typography,
} from '@mui/joy'
import { Box, Button, Option, Select, Typography } from '@mui/joy'
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 handleSave = () => {
onSave(options.find(item => item.id === selected))
@@ -17,33 +18,31 @@ function SelectModal({ isOpen, onClose, onSave, options, title, displayKey,place
}
return (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
<Typography variant='h4'>{title}</Typography>
<Select placeholder={placeholder}>
{options.map((item, index) => (
<Option
value={item.id}
key={item[displayKey]}
onClick={() => {
setSelected(item.id)
}}
>
{item[displayKey]}
</Option>
))}
</Select>
<FadeModal open={isOpen} onClose={onClose}>
<Typography variant='h4'>{title}</Typography>
<Select placeholder={placeholder}>
{options.map((item, index) => (
<Option
value={item.id}
key={item[displayKey]}
onClick={() => {
setSelected(item.id)
}}
>
{item[displayKey]}
</Option>
))}
</Select>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
Save
</Button>
<Button onClick={onClose} variant='outlined'>
Cancel
</Button>
</Box>
</ModalDialog>
</Modal>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
Save
</Button>
<Button onClick={onClose} variant='outlined'>
Cancel
</Button>
</Box>
</FadeModal>
)
}
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 FadeModal from '../../../components/common/FadeModal'
function TextModal({
isOpen,
@@ -18,29 +19,26 @@ function TextModal({
}
return (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
{/* <ModalClose /> */}
<Typography variant='h4'>{title}</Typography>
<Textarea
placeholder='Type in here…'
value={text}
onChange={e => setText(e.target.value)}
minRows={2}
maxRows={4}
sx={{ minWidth: 300 }}
/>
<FadeModal open={isOpen} onClose={onClose}>
<Typography variant='h4'>{title}</Typography>
<Textarea
placeholder='Type in here…'
value={text}
onChange={e => setText(e.target.value)}
minRows={2}
maxRows={4}
sx={{ minWidth: 300 }}
/>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{okText ? okText : 'Save'}
</Button>
<Button onClick={onClose} variant='outlined'>
{cancelText ? cancelText : 'Cancel'}
</Button>
</Box>
</ModalDialog>
</Modal>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{okText ? okText : 'Save'}
</Button>
<Button onClick={onClose} variant='outlined'>
{cancelText ? cancelText : 'Cancel'}
</Button>
</Box>
</FadeModal>
)
}
export default TextModal

View File

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

View File

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

View File

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

View File

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