feat: Add NFC tag writing functionality to ChoreCard component, Add Email to sign up
This commit is contained in:
parent
c34da50c8c
commit
9a07689dfe
6 changed files with 452 additions and 11 deletions
|
@ -8,6 +8,7 @@ import ForgotPasswordView from '../views/Authorization/ForgotPasswordView'
|
||||||
import LoginView from '../views/Authorization/LoginView'
|
import LoginView from '../views/Authorization/LoginView'
|
||||||
import SignupView from '../views/Authorization/Signup'
|
import SignupView from '../views/Authorization/Signup'
|
||||||
import UpdatePasswordView from '../views/Authorization/UpdatePasswordView'
|
import UpdatePasswordView from '../views/Authorization/UpdatePasswordView'
|
||||||
|
import ChoreView from '../views/ChoreEdit/ChoreView'
|
||||||
import MyChores from '../views/Chores/MyChores'
|
import MyChores from '../views/Chores/MyChores'
|
||||||
import JoinCircleView from '../views/Circles/JoinCircle'
|
import JoinCircleView from '../views/Circles/JoinCircle'
|
||||||
import ChoreHistory from '../views/History/ChoreHistory'
|
import ChoreHistory from '../views/History/ChoreHistory'
|
||||||
|
@ -41,6 +42,10 @@ const Router = createBrowserRouter([
|
||||||
path: '/chores/:choreId/edit',
|
path: '/chores/:choreId/edit',
|
||||||
element: <ChoreEdit />,
|
element: <ChoreEdit />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/chores/:choreId',
|
||||||
|
element: <ChoreView />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/chores/create',
|
path: '/chores/create',
|
||||||
element: <ChoreEdit />,
|
element: <ChoreEdit />,
|
||||||
|
|
|
@ -51,6 +51,19 @@ const GetChoreByID = id => {
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const GetChoreDetailById = id => {
|
||||||
|
return Fetch(`${API_URL}/chores/${id}/details`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: HEADERS(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarkChoreComplete = id => {
|
||||||
|
return Fetch(`${API_URL}/chores/${id}/do`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: HEADERS(),
|
||||||
|
})
|
||||||
|
}
|
||||||
const CreateChore = chore => {
|
const CreateChore = chore => {
|
||||||
return Fetch(`${API_URL}/chores/`, {
|
return Fetch(`${API_URL}/chores/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -238,6 +251,7 @@ export {
|
||||||
GetAllCircleMembers,
|
GetAllCircleMembers,
|
||||||
GetAllUsers,
|
GetAllUsers,
|
||||||
GetChoreByID,
|
GetChoreByID,
|
||||||
|
GetChoreDetailById,
|
||||||
GetChoreHistory,
|
GetChoreHistory,
|
||||||
GetChores,
|
GetChores,
|
||||||
GetCircleMemberRequests,
|
GetCircleMemberRequests,
|
||||||
|
@ -250,6 +264,7 @@ export {
|
||||||
JoinCircle,
|
JoinCircle,
|
||||||
LeaveCircle,
|
LeaveCircle,
|
||||||
login,
|
login,
|
||||||
|
MarkChoreComplete,
|
||||||
SaveChore,
|
SaveChore,
|
||||||
SaveThing,
|
SaveThing,
|
||||||
signUp,
|
signUp,
|
||||||
|
|
|
@ -62,7 +62,6 @@ const LoginView = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const loggedWithProvider = function (provider, data) {
|
const loggedWithProvider = function (provider, data) {
|
||||||
console.log(provider, data)
|
|
||||||
return fetch(API_URL + `/auth/${provider}/callback`, {
|
return fetch(API_URL + `/auth/${provider}/callback`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -80,8 +79,14 @@ const LoginView = () => {
|
||||||
return response.json().then(data => {
|
return response.json().then(data => {
|
||||||
localStorage.setItem('ca_token', data.token)
|
localStorage.setItem('ca_token', data.token)
|
||||||
localStorage.setItem('ca_expiration', data.expire)
|
localStorage.setItem('ca_expiration', data.expire)
|
||||||
// setIsLoggedIn(true);
|
|
||||||
getUserProfileAndNavigateToHome()
|
const redirectUrl = Cookies.get('ca_redirect')
|
||||||
|
if (redirectUrl) {
|
||||||
|
Cookies.remove('ca_redirect')
|
||||||
|
Navigate(redirectUrl)
|
||||||
|
} else {
|
||||||
|
getUserProfileAndNavigateToHome()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return response.json().then(error => {
|
return response.json().then(error => {
|
||||||
|
|
292
src/views/ChoreEdit/ChoreView.jsx
Normal file
292
src/views/ChoreEdit/ChoreView.jsx
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
import {
|
||||||
|
CalendarMonth,
|
||||||
|
CancelScheduleSend,
|
||||||
|
Check,
|
||||||
|
Checklist,
|
||||||
|
PeopleAlt,
|
||||||
|
Person,
|
||||||
|
} from '@mui/icons-material'
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Grid,
|
||||||
|
ListItem,
|
||||||
|
ListItemContent,
|
||||||
|
ListItemDecorator,
|
||||||
|
Sheet,
|
||||||
|
Snackbar,
|
||||||
|
styled,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/joy'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
GetAllUsers,
|
||||||
|
GetChoreDetailById,
|
||||||
|
MarkChoreComplete,
|
||||||
|
} from '../../utils/Fetcher'
|
||||||
|
const IconCard = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#f0f0f0', // Adjust the background color as needed
|
||||||
|
borderRadius: '50%',
|
||||||
|
minWidth: '50px',
|
||||||
|
height: '50px',
|
||||||
|
marginRight: '16px',
|
||||||
|
})
|
||||||
|
const ChoreView = () => {
|
||||||
|
const [chore, setChore] = useState({})
|
||||||
|
|
||||||
|
const [performers, setPerformers] = useState([])
|
||||||
|
const [infoCards, setInfoCards] = useState([])
|
||||||
|
const { choreId } = useParams()
|
||||||
|
|
||||||
|
// query param `complete=true`
|
||||||
|
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
|
const [isPendingCompletion, setIsPendingCompletion] = useState(false)
|
||||||
|
const [timeoutId, setTimeoutId] = useState(null)
|
||||||
|
const [secondsLeftToCancel, setSecondsLeftToCancel] = useState(null)
|
||||||
|
useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
GetChoreDetailById(choreId).then(resp => {
|
||||||
|
if (resp.ok) {
|
||||||
|
return resp.json().then(data => {
|
||||||
|
setChore(data.res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
GetAllUsers()
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setPerformers(data.res)
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
const auto_complete = searchParams.get('auto_complete')
|
||||||
|
if (auto_complete === 'true') {
|
||||||
|
handleTaskCompletion()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
useEffect(() => {
|
||||||
|
if (chore && performers.length > 0) {
|
||||||
|
generateInfoCards(chore)
|
||||||
|
}
|
||||||
|
}, [chore, performers])
|
||||||
|
|
||||||
|
const generateInfoCards = chore => {
|
||||||
|
const cards = [
|
||||||
|
{
|
||||||
|
icon: <CalendarMonth />,
|
||||||
|
text: 'Due Date',
|
||||||
|
subtext: moment(chore.dueDate).format('MM/DD/YYYY hh:mm A'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <PeopleAlt />,
|
||||||
|
text: 'Assigned To',
|
||||||
|
subtext: performers.find(p => p.id === chore.assignedTo)?.displayName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Person />,
|
||||||
|
text: 'Created By',
|
||||||
|
subtext: performers.find(p => p.id === chore.createdBy)?.displayName,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// icon: <TextFields />,
|
||||||
|
// text: 'Frequency',
|
||||||
|
// subtext:
|
||||||
|
// chore.frequencyType.charAt(0).toUpperCase() +
|
||||||
|
// chore.frequencyType.slice(1),
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
icon: <Checklist />,
|
||||||
|
text: 'Total Completed',
|
||||||
|
subtext: `${chore.totalCompletedCount}`,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// icon: <Timelapse />,
|
||||||
|
// text: 'Last Completed',
|
||||||
|
// subtext:
|
||||||
|
// chore.lastCompletedDate &&
|
||||||
|
// moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'),
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
icon: <Person />,
|
||||||
|
text: 'Last Completed',
|
||||||
|
subtext: chore.lastCompletedDate
|
||||||
|
? `${
|
||||||
|
chore.lastCompletedDate &&
|
||||||
|
moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A')
|
||||||
|
}(${
|
||||||
|
performers.find(p => p.id === chore.lastCompletedBy)?.displayName
|
||||||
|
})`
|
||||||
|
: 'Never',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
setInfoCards(cards)
|
||||||
|
}
|
||||||
|
const handleTaskCompletion = () => {
|
||||||
|
setIsPendingCompletion(true)
|
||||||
|
let seconds = 3 // Starting countdown from 3 seconds
|
||||||
|
setSecondsLeftToCancel(seconds)
|
||||||
|
|
||||||
|
const countdownInterval = setInterval(() => {
|
||||||
|
seconds -= 1
|
||||||
|
setSecondsLeftToCancel(seconds)
|
||||||
|
|
||||||
|
if (seconds <= 0) {
|
||||||
|
clearInterval(countdownInterval) // Stop the countdown when it reaches 0
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
const id = setTimeout(() => {
|
||||||
|
MarkChoreComplete(choreId)
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.ok) {
|
||||||
|
return resp.json().then(data => {
|
||||||
|
setChore(data.res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
setIsPendingCompletion(false)
|
||||||
|
clearTimeout(id)
|
||||||
|
clearInterval(countdownInterval) // Ensure to clear this interval as well
|
||||||
|
setTimeoutId(null)
|
||||||
|
setSecondsLeftToCancel(null)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// refetch the chore details
|
||||||
|
GetChoreDetailById(choreId).then(resp => {
|
||||||
|
if (resp.ok) {
|
||||||
|
return resp.json().then(data => {
|
||||||
|
setChore(data.res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, 3000)
|
||||||
|
|
||||||
|
setTimeoutId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth='sm'>
|
||||||
|
<Sheet
|
||||||
|
variant='plain'
|
||||||
|
sx={{
|
||||||
|
borderRadius: 'sm',
|
||||||
|
p: 2,
|
||||||
|
boxShadow: 'md',
|
||||||
|
minHeight: '90vh',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
level='h4'
|
||||||
|
textAlign={'center'}
|
||||||
|
sx={{
|
||||||
|
mt: 2,
|
||||||
|
mb: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chore.name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
{infoCards.map((info, index) => (
|
||||||
|
<Grid key={index} item xs={12} sm={6}>
|
||||||
|
<Sheet
|
||||||
|
sx={{ mb: 1, borderRadius: 'md', p: 1, boxShadow: 'sm' }}
|
||||||
|
>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemDecorator>
|
||||||
|
<IconCard>{info.icon}</IconCard>
|
||||||
|
</ListItemDecorator>
|
||||||
|
<ListItemContent>
|
||||||
|
<Typography level='body1' sx={{ fontWeight: 'md' }}>
|
||||||
|
{info.text}
|
||||||
|
</Typography>
|
||||||
|
<Typography level='body1' color='text.tertiary'>
|
||||||
|
{info.subtext ? info.subtext : '--'}
|
||||||
|
</Typography>
|
||||||
|
</ListItemContent>
|
||||||
|
</ListItem>
|
||||||
|
</Sheet>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
size='lg'
|
||||||
|
sx={{
|
||||||
|
height: 50,
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
onClick={handleTaskCompletion}
|
||||||
|
disabled={isPendingCompletion}
|
||||||
|
color={isPendingCompletion ? 'danger' : 'success'}
|
||||||
|
startDecorator={<Check />}
|
||||||
|
>
|
||||||
|
<Box>Mark as done</Box>
|
||||||
|
</Button>
|
||||||
|
{/* <Button
|
||||||
|
sx={{
|
||||||
|
borderRadius: '32px',
|
||||||
|
mt: 1,
|
||||||
|
height: 50,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
Navigate('/my/chores')
|
||||||
|
}}
|
||||||
|
color={isPendingCompletion ? 'danger' : 'success'}
|
||||||
|
startDecorator={isPendingCompletion ? <Close /> : <Check />}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<Box>Mark as {isPendingCompletion ? 'completed' : 'done'}</Box>
|
||||||
|
</Button> */}
|
||||||
|
</Box>
|
||||||
|
</Sheet>
|
||||||
|
<Snackbar
|
||||||
|
open={isPendingCompletion}
|
||||||
|
endDecorator={
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
setIsPendingCompletion(false)
|
||||||
|
setTimeoutId(null)
|
||||||
|
setSecondsLeftToCancel(null) // Reset or adjust as needed
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size='md'
|
||||||
|
variant='outlined'
|
||||||
|
color='primary'
|
||||||
|
startDecorator={<CancelScheduleSend />}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography level='body2' textAlign={'center'}>
|
||||||
|
Task will be marked as completed in {secondsLeftToCancel} seconds
|
||||||
|
</Typography>
|
||||||
|
</Snackbar>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChoreView
|
|
@ -35,12 +35,13 @@ import moment from 'moment'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { API_URL } from '../../Config'
|
import { API_URL } from '../../Config'
|
||||||
import writeToNFC from '../../service/NFCWriter'
|
import { MarkChoreComplete } from '../../utils/Fetcher'
|
||||||
import { Fetch } from '../../utils/TokenManager'
|
import { Fetch } from '../../utils/TokenManager'
|
||||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||||
import DateModal from '../Modals/Inputs/DateModal'
|
import DateModal from '../Modals/Inputs/DateModal'
|
||||||
import SelectModal from '../Modals/Inputs/SelectModal'
|
import SelectModal from '../Modals/Inputs/SelectModal'
|
||||||
import TextModal from '../Modals/Inputs/TextModal'
|
import TextModal from '../Modals/Inputs/TextModal'
|
||||||
|
import WriteNFCModal from '../Modals/Inputs/WriteNFCModal'
|
||||||
const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
const [activeUserId, setActiveUserId] = React.useState(0)
|
const [activeUserId, setActiveUserId] = React.useState(0)
|
||||||
const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] =
|
const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] =
|
||||||
|
@ -52,6 +53,7 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
const [isCompleteWithNoteModalOpen, setIsCompleteWithNoteModalOpen] =
|
const [isCompleteWithNoteModalOpen, setIsCompleteWithNoteModalOpen] =
|
||||||
React.useState(false)
|
React.useState(false)
|
||||||
const [confirmModelConfig, setConfirmModelConfig] = React.useState({})
|
const [confirmModelConfig, setConfirmModelConfig] = React.useState({})
|
||||||
|
const [isNFCModalOpen, setIsNFCModalOpen] = React.useState(false)
|
||||||
const [anchorEl, setAnchorEl] = React.useState(null)
|
const [anchorEl, setAnchorEl] = React.useState(null)
|
||||||
const menuRef = React.useRef(null)
|
const menuRef = React.useRef(null)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
@ -116,9 +118,7 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCompleteChore = () => {
|
const handleCompleteChore = () => {
|
||||||
Fetch(`${API_URL}/chores/${chore.id}/do`, {
|
MarkChoreComplete(chore.id).then(response => {
|
||||||
method: 'POST',
|
|
||||||
}).then(response => {
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
response.json().then(data => {
|
response.json().then(data => {
|
||||||
const newChore = data.res
|
const newChore = data.res
|
||||||
|
@ -323,7 +323,6 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getFrequencyIcon(chore)}
|
{getFrequencyIcon(chore)}
|
||||||
|
|
||||||
{getRecurrentChipText(chore)}
|
{getRecurrentChipText(chore)}
|
||||||
</div>
|
</div>
|
||||||
</Chip>
|
</Chip>
|
||||||
|
@ -344,7 +343,13 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={9}>
|
<Grid
|
||||||
|
item
|
||||||
|
xs={9}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/chores/${chore.id}`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
{/* Box in top right with Chip showing next due date */}
|
{/* Box in top right with Chip showing next due date */}
|
||||||
<Box display='flex' justifyContent='start' alignItems='center'>
|
<Box display='flex' justifyContent='start' alignItems='center'>
|
||||||
<Avatar sx={{ mr: 1, fontSize: 22 }}>
|
<Avatar sx={{ mr: 1, fontSize: 22 }}>
|
||||||
|
@ -408,7 +413,7 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
width: 50,
|
minWidth: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
|
@ -523,7 +528,8 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// write current chore URL to NFC
|
// write current chore URL to NFC
|
||||||
writeToNFC(`${window.location.origin}/chores/${chore.id}`)
|
// writeToNFC(`${window.location.origin}/chores/${chore.id}`)
|
||||||
|
setIsNFCModalOpen(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Nfc />
|
<Nfc />
|
||||||
|
@ -581,6 +587,15 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
okText={'Complete'}
|
okText={'Complete'}
|
||||||
onSave={handleCompleteWithNote}
|
onSave={handleCompleteWithNote}
|
||||||
/>
|
/>
|
||||||
|
<WriteNFCModal
|
||||||
|
config={{
|
||||||
|
isOpen: isNFCModalOpen,
|
||||||
|
url: `${window.location.origin}/chores/${chore.id}`,
|
||||||
|
onClose: () => {
|
||||||
|
setIsNFCModalOpen(false)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
109
src/views/Modals/Inputs/WriteNFCModal.jsx
Normal file
109
src/views/Modals/Inputs/WriteNFCModal.jsx
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
ListItem,
|
||||||
|
Modal,
|
||||||
|
ModalDialog,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/joy'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
function WriteNFCModal({ config }) {
|
||||||
|
const [nfcStatus, setNfcStatus] = useState('idle') // 'idle', 'writing', 'success', 'error'
|
||||||
|
const [errorMessage, setErrorMessage] = useState('')
|
||||||
|
const [isAutoCompleteWhenScan, setIsAutoCompleteWhenScan] = useState(false)
|
||||||
|
|
||||||
|
const requestNFCAccess = async () => {
|
||||||
|
if ('NDEFReader' in window) {
|
||||||
|
// Assuming permission request is implicit in 'write' or 'scan' methods
|
||||||
|
setNfcStatus('idle')
|
||||||
|
} else {
|
||||||
|
alert('NFC is not supported by this browser.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeToNFC = async url => {
|
||||||
|
if ('NDEFReader' in window) {
|
||||||
|
try {
|
||||||
|
const ndef = new window.NDEFReader()
|
||||||
|
await ndef.write({
|
||||||
|
records: [{ recordType: 'url', data: url }],
|
||||||
|
})
|
||||||
|
setNfcStatus('success')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error writing to NFC tag:', error)
|
||||||
|
setNfcStatus('error')
|
||||||
|
setErrorMessage('Error writing to NFC tag. Please try again.')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setNfcStatus('error')
|
||||||
|
setErrorMessage('NFC is not supported by this browser.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
config.onClose()
|
||||||
|
setNfcStatus('idle')
|
||||||
|
setErrorMessage('')
|
||||||
|
}
|
||||||
|
const getURL = () => {
|
||||||
|
let url = config.url
|
||||||
|
if (isAutoCompleteWhenScan) {
|
||||||
|
url = url + '?auto_complete=true'
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Modal open={config?.isOpen} onClose={handleClose}>
|
||||||
|
<ModalDialog>
|
||||||
|
<Typography level='h4' mb={1}>
|
||||||
|
{nfcStatus === 'success' ? 'Success!' : 'Write to NFC'}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{nfcStatus === 'success' ? (
|
||||||
|
<Typography level='body-md' gutterBottom>
|
||||||
|
URL written to NFC tag successfully!
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography level='body-md' gutterBottom>
|
||||||
|
{nfcStatus === 'error'
|
||||||
|
? errorMessage
|
||||||
|
: 'Press the button below to write to NFC.'}
|
||||||
|
</Typography>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box display={'flex'} justifyContent={'center'} mt={2}>
|
||||||
|
<Button onClick={handleClose} variant='outlined'>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</ModalDialog>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WriteNFCModal
|
Loading…
Add table
Reference in a new issue