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:
82
src/components/common/FadeModal.jsx
Normal file
82
src/components/common/FadeModal.jsx
Normal 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
|
||||||
@@ -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')
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'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'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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user