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

@@ -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