Merge branch 'dev'

This commit is contained in:
Mo Tarbin 2024-07-16 19:37:59 -04:00
commit 8e42e59a80
13 changed files with 610 additions and 260 deletions

View file

@ -73,6 +73,16 @@ const MarkChoreComplete = (id, note, completedDate) => {
body: JSON.stringify(body),
})
}
const SkipChore = id => {
return Fetch(`${API_URL}/chores/${id}/skip`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
})
}
const CreateChore = chore => {
return Fetch(`${API_URL}/chores/`, {
method: 'POST',
@ -277,6 +287,7 @@ export {
SaveChore,
SaveThing,
signUp,
SkipChore,
UpdateThingState,
UpdateUserDetails,
}

View file

@ -3,48 +3,53 @@ import {
CancelScheduleSend,
Check,
Checklist,
Note,
History,
PeopleAlt,
Person,
SwitchAccessShortcut,
Timelapse,
} from '@mui/icons-material'
import {
Box,
Button,
Card,
Checkbox,
Chip,
Container,
FormControl,
Grid,
Input,
ListItem,
ListItemContent,
ListItemDecorator,
Sheet,
Snackbar,
styled,
Typography,
} from '@mui/joy'
import { Divider } from '@mui/material'
import moment from 'moment'
import { useEffect, useState } from 'react'
import { useParams, useSearchParams } from 'react-router-dom'
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
import {
GetAllUsers,
GetChoreDetailById,
MarkChoreComplete,
SkipChore,
} from '../../utils/Fetcher'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
const IconCard = styled('div')({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f0f0f0', // Adjust the background color as needed
borderRadius: '50%',
minWidth: '50px',
height: '50px',
minWidth: '40px',
height: '40px',
marginRight: '16px',
})
const ChoreView = () => {
const [chore, setChore] = useState({})
const navigate = useNavigate()
const [performers, setPerformers] = useState([])
const [infoCards, setInfoCards] = useState([])
const { choreId } = useParams()
@ -56,6 +61,8 @@ const ChoreView = () => {
const [timeoutId, setTimeoutId] = useState(null)
const [secondsLeftToCancel, setSecondsLeftToCancel] = useState(null)
const [completedDate, setCompletedDate] = useState(null)
const [confirmModelConfig, setConfirmModelConfig] = useState({})
useEffect(() => {
Promise.all([
GetChoreDetailById(choreId).then(resp => {
@ -85,20 +92,20 @@ const ChoreView = () => {
const generateInfoCards = chore => {
const cards = [
{
icon: <CalendarMonth />,
text: 'Due Date',
subtext: moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A'),
},
{
size: 6,
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,
size: 6,
icon: <CalendarMonth />,
text: 'Due Date',
subtext: chore.nextDueDate
? moment(chore.nextDueDate).fromNow()
: 'N/A',
},
// {
// icon: <TextFields />,
// text: 'Frequency',
@ -107,35 +114,42 @@ const ChoreView = () => {
// chore.frequencyType.slice(1),
// },
{
size: 6,
icon: <Checklist />,
text: 'Total Completed',
subtext: `${chore.totalCompletedCount}`,
subtext: `${chore.totalCompletedCount} times`,
},
// {
// icon: <Timelapse />,
// text: 'Last Completed',
// subtext:
// chore.lastCompletedDate &&
// moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'),
// },
{
icon: <Person />,
size: 6,
icon: <Timelapse />,
text: 'Last Completed',
subtext:
// chore.lastCompletedDate &&
// moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'),
chore.lastCompletedDate && moment(chore.lastCompletedDate).fromNow(),
},
{
size: 6,
icon: <Person />,
text: 'Last Performer',
subtext: chore.lastCompletedDate
? `${
chore.lastCompletedDate &&
moment(chore.lastCompletedDate).fromNow()
// moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'))
}(${
performers.find(p => p.id === chore.lastCompletedBy)?.displayName
})`
: 'Never',
}`
: '--',
},
{
icon: <Note />,
text: 'Recent Note',
subtext: chore.notes || '--',
size: 6,
icon: <Person />,
text: 'Created By',
subtext: performers.find(p => p.id === chore.createdBy)?.displayName,
},
// {
// size: 12,
// icon: <Note />,
// text: 'Recent Note',
// subtext: chore.notes || '--',
// },
]
setInfoCards(cards)
}
@ -184,7 +198,16 @@ const ChoreView = () => {
setTimeoutId(id)
}
const handleSkippingTask = () => {
SkipChore(choreId).then(response => {
if (response.ok) {
response.json().then(data => {
const newChore = data.res
setChore(newChore)
})
}
})
}
return (
<Container
maxWidth='sm'
@ -195,46 +218,88 @@ const ChoreView = () => {
justifyContent: 'space-between',
}}
>
<Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
}}
>
<Typography
level='h3'
textAlign={'center'}
// textAlign={'center'}
sx={{
mt: 2,
mb: 4,
mt: 1,
mb: 0.5,
}}
>
{chore.name}
</Typography>
<Chip startDecorator={<CalendarMonth />} size='lg' sx={{ mb: 4 }}>
{chore.nextDueDate
? `Due at ${moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A')}`
: 'N/A'}
</Chip>
</Box>
<Box>
<Typography level='title-md' sx={{ mb: 0.5 }}>
Details
</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>
<Sheet
sx={{
mb: 1,
borderRadius: 'lg',
p: 2,
}}
variant='outlined'
>
<Grid container spacing={1}>
{infoCards.map((detail, index) => (
<Grid item xs={4} key={index}>
{/* divider between the list items: */}
<ListItem key={index}>
<ListItemContent>
<Typography level='body1' sx={{ fontWeight: 'md' }}>
{info.text}
</Typography>
<Typography level='body1' color='text.tertiary'>
{info.subtext ? info.subtext : '--'}
<Typography level='body-xs' sx={{ fontWeight: 'md' }}>
{detail.text}
</Typography>
<Chip
color='primary'
size='md'
startDecorator={detail.icon}
>
{detail.subtext ? detail.subtext : '--'}
</Chip>
</ListItemContent>
</ListItem>
</Sheet>
</Grid>
))}
</Grid>
</Grid>
))}
</Grid>
</Sheet>
{chore.notes && (
<>
<Typography level='title-md' sx={{ mb: 1 }}>
Previous note:
</Typography>
<Sheet variant='outlined' sx={{ p: 2, borderRadius: 'lg' }}>
<Typography level='body-md' sx={{ mb: 1 }}>
{chore.notes || '--'}
</Typography>
</Sheet>
</>
)}
</Box>
{/* <Divider
sx={{
my: 2,
}}
/> */}
<Typography level='title-md' sx={{ mt: 1 }}>
Actions
</Typography>
<Card
sx={{
p: 2,
@ -243,27 +308,65 @@ const ChoreView = () => {
mt: 2,
}}
>
<Typography level='title-md'>Additional Notes</Typography>
<Input
fullWidth
multiline
label='Additional Notes'
placeholder='note or information about the task'
value={note || ''}
onChange={e => {
if (e.target.value.trim() === '') {
setNote(null)
return
}
setNote(e.target.value)
}}
size='md'
sx={{
my: 1,
}}
/>
<Typography level='body-md' sx={{ mb: 1 }}>
Complete the task
</Typography>
<FormControl size='sm' sx={{ width: 400 }}>
<FormControl size='sm'>
<Checkbox
defaultChecked={note !== null}
checked={note !== null}
value={note !== null}
size='lg'
onChange={e => {
if (e.target.checked) {
setNote('')
} else {
setNote(null)
}
}}
overlay
sx={
{
// my: 1,
}
}
label={
<Typography
level='body-sm'
sx={{
// center vertically
display: 'flex',
alignItems: 'center',
}}
>
Add Additional Notes
</Typography>
}
/>
</FormControl>
{note !== null && (
<Input
fullWidth
multiline
label='Additional Notes'
placeholder='note or information about the task'
value={note || ''}
onChange={e => {
if (e.target.value.trim() === '') {
setNote(null)
return
}
setNote(e.target.value)
}}
size='md'
sx={{
mb: 1,
}}
/>
)}
<FormControl size='sm'>
<Checkbox
defaultChecked={completedDate !== null}
checked={completedDate !== null}
@ -279,10 +382,23 @@ const ChoreView = () => {
}
}}
overlay
sx={{
my: 1,
}}
label={<Typography level='body2'>Set completion date</Typography>}
sx={
{
// my: 1,
}
}
label={
<Typography
level='body-sm'
sx={{
// center vertically
display: 'flex',
alignItems: 'center',
}}
>
Specify completion date
</Typography>
}
/>
</FormControl>
{completedDate !== null && (
@ -295,22 +411,10 @@ const ChoreView = () => {
}}
/>
)}
{completedDate === null && (
// placeholder for the completion date with margin:
<Box
sx={{
height: 56,
}}
/>
)}
<Button
fullWidth
size='lg'
sx={{
height: 50,
mb: 2,
}}
onClick={handleTaskCompletion}
disabled={isPendingCompletion}
color={isPendingCompletion ? 'danger' : 'success'}
@ -318,49 +422,82 @@ const ChoreView = () => {
>
<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> */}
</Card>
<Divider sx={{ my: 0.5 }}>or</Divider>
<Snackbar
open={isPendingCompletion}
endDecorator={
<Box
sx={{
display: 'flex',
flexDirection: 'row',
gap: 1,
alignContent: 'center',
justifyContent: 'center',
}}
>
<Button
onClick={() => {
if (timeoutId) {
clearTimeout(timeoutId)
setIsPendingCompletion(false)
setTimeoutId(null)
setSecondsLeftToCancel(null) // Reset or adjust as needed
}
}}
fullWidth
size='lg'
variant='outlined'
color='danger'
startDecorator={<CancelScheduleSend />}
onClick={() => {
setConfirmModelConfig({
isOpen: true,
title: 'Skip Task',
message: 'Are you sure you want to skip this task?',
confirmText: 'Skip',
cancelText: 'Cancel',
onClose: confirmed => {
if (confirmed) {
handleSkippingTask()
}
setConfirmModelConfig({})
},
})
}}
startDecorator={<SwitchAccessShortcut />}
>
Cancel
<Box>Skip</Box>
</Button>
}
>
<Typography level='body-md' textAlign={'center'}>
Task will be marked as completed in {secondsLeftToCancel} seconds
</Typography>
</Snackbar>
<Button
startDecorator={<History />}
size='lg'
color='primary'
variant='outlined'
fullWidth
onClick={() => {
navigate(`/chores/${choreId}/history`)
}}
>
History
</Button>
</Box>
<Snackbar
open={isPendingCompletion}
endDecorator={
<Button
onClick={() => {
if (timeoutId) {
clearTimeout(timeoutId)
setIsPendingCompletion(false)
setTimeoutId(null)
setSecondsLeftToCancel(null) // Reset or adjust as needed
}
}}
size='lg'
variant='outlined'
color='danger'
startDecorator={<CancelScheduleSend />}
>
Cancel
</Button>
}
>
<Typography level='body-md' textAlign={'center'}>
Task will be marked as completed in {secondsLeftToCancel} seconds
</Typography>
</Snackbar>
<ConfirmationModal config={confirmModelConfig} />
</Card>
</Container>
)
}

View file

@ -509,7 +509,7 @@ const RepeatSection = ({
}}
>
Is this something that should be done when a thing state changes?{' '}
{!isPlusAccount(userProfile) && (
{userProfile && !isPlusAccount(userProfile) && (
<Chip variant='soft' color='warning'>
Not available in Basic Plan
</Chip>

View file

@ -17,6 +17,7 @@ import {
SwitchAccessShortcut,
TimesOneMobiledata,
Update,
ViewCarousel,
Webhook,
} from '@mui/icons-material'
import {
@ -38,7 +39,7 @@ import moment from 'moment'
import React, { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { API_URL } from '../../Config'
import { MarkChoreComplete } from '../../utils/Fetcher'
import { MarkChoreComplete, SkipChore } from '../../utils/Fetcher'
import { Fetch } from '../../utils/TokenManager'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import DateModal from '../Modals/Inputs/DateModal'
@ -107,6 +108,9 @@ const ChoreCard = ({
const handleEdit = () => {
navigate(`/chores/${chore.id}/edit`)
}
const handleView = () => {
navigate(`/chores/${chore.id}`)
}
const handleDelete = () => {
setConfirmModelConfig({
isOpen: true,
@ -521,13 +525,7 @@ const ChoreCard = ({
</MenuItem>
<MenuItem
onClick={() => {
Fetch(`${API_URL}/chores/${chore.id}/skip`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
}).then(response => {
SkipChore(chore.id).then(response => {
if (response.ok) {
response.json().then(data => {
const newChore = data.res
@ -585,6 +583,10 @@ const ChoreCard = ({
<Edit />
Edit
</MenuItem>
<MenuItem onClick={handleView}>
<ViewCarousel />
View
</MenuItem>
<MenuItem onClick={handleDelete} color='danger'>
<Delete />
Delete

View file

@ -3,7 +3,6 @@ import {
Badge,
Box,
Checkbox,
CircularProgress,
Container,
IconButton,
Input,
@ -18,8 +17,8 @@ import Fuse from 'fuse.js'
import { useContext, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { UserContext } from '../../contexts/UserContext'
import Logo from '../../Logo'
import { GetAllUsers, GetChores, GetUserProfile } from '../../utils/Fetcher'
import LoadingComponent from '../components/Loading'
import ChoreCard from './ChoreCard'
const MyChores = () => {
@ -184,18 +183,7 @@ const MyChores = () => {
}
if (userProfile === null) {
return (
<Container className='flex h-full items-center justify-center'>
<Box className='flex flex-col items-center justify-center'>
<CircularProgress
color='success'
sx={{ '--CircularProgress-size': '200px' }}
>
<Logo />
</CircularProgress>
</Box>
</Container>
)
return <LoadingComponent />
}
return (

View file

@ -1,15 +1,13 @@
import { Checklist, EventBusy, Timelapse } from '@mui/icons-material'
import { Checklist, EventBusy, Group, Timelapse } from '@mui/icons-material'
import {
Avatar,
Box,
Button,
CircularProgress,
Chip,
Container,
Grid,
List,
ListItem,
ListItemContent,
ListItemDecorator,
Sheet,
Typography,
} from '@mui/joy'
@ -19,6 +17,7 @@ import { Link, useParams } from 'react-router-dom'
import { API_URL } from '../../Config'
import { GetAllCircleMembers } from '../../utils/Fetcher'
import { Fetch } from '../../utils/TokenManager'
import LoadingComponent from '../components/Loading'
import HistoryCard from './HistoryCard'
const ChoreHistory = () => {
@ -92,59 +91,49 @@ const ChoreHistory = () => {
const historyInfo = [
{
icon: (
<Avatar>
<Checklist />
</Avatar>
),
text: `${histories.length} completed`,
subtext: `${Object.keys(userHistories).length} users contributed`,
icon: <Checklist />,
text: 'Total Completed',
subtext: `${histories.length} times`,
},
{
icon: (
<Avatar>
<Timelapse />
</Avatar>
),
text: `Completed within ${moment
.duration(averageDelayMoment)
.humanize()}`,
subtext: `Maximum delay was ${moment
.duration(maxDelayMoment)
.humanize()}`,
icon: <Timelapse />,
text: 'Usually Within',
subtext: moment.duration(averageDelayMoment).humanize(),
},
{
icon: <Avatar></Avatar>,
text: `${
icon: <Timelapse />,
text: 'Maximum Delay',
subtext: moment.duration(maxDelayMoment).humanize(),
},
{
icon: <Avatar />,
text: ' Completed Most',
subtext: `${
performers.find(p => p.userId === Number(userCompletedByMost))
?.displayName
} completed most`,
subtext: `${userHistories[userCompletedByMost]} time/s`,
} `,
},
// contributes:
{
icon: <Group />,
text: 'Total Performers',
subtext: `${Object.keys(userHistories).length} users`,
},
{
icon: <Avatar />,
text: 'Last Completed',
subtext: `${
performers.find(p => p.userId === Number(histories[0].completedBy))
?.displayName
}`,
},
]
if (userCompletedByLeast !== userCompletedByMost) {
historyInfo.push({
icon: (
<Avatar>
{
performers.find(p => p.userId === userCompletedByLeast)
?.displayName
}
</Avatar>
),
text: `${
performers.find(p => p.userId === Number(userCompletedByLeast))
.displayName
} completed least`,
subtext: `${userHistories[userCompletedByLeast]} time/s`,
})
}
setHistoryInfo(historyInfo)
}
if (isLoading) {
return <CircularProgress /> // Show loading indicator
return <LoadingComponent />
}
if (!choreHistory.length) {
return (
@ -184,50 +173,43 @@ const ChoreHistory = () => {
return (
<Container maxWidth='md'>
<Typography level='h3' mb={1.5}>
<Typography level='title-md' mb={1.5}>
Summary:
</Typography>
{/* <Sheet sx={{ mb: 1, borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
<ListItem sx={{ gap: 1.5 }}>
<ListItemDecorator>
<Avatar>
<AccountCircle />
</Avatar>
</ListItemDecorator>
<ListItemContent>
<Typography level='body1' sx={{ fontWeight: 'md' }}>
{choreHistory.length} completed
</Typography>
<Typography level='body2' color='text.tertiary'>
{Object.keys(userHistory).length} users contributed
</Typography>
</ListItemContent>
</ListItem>
</Sheet> */}
<Grid container>
{historyInfo.map((info, index) => (
<Grid key={index} item xs={12} sm={6}>
<Sheet sx={{ mb: 1, borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
<ListItem sx={{ gap: 1.5 }}>
<ListItemDecorator>{info.icon}</ListItemDecorator>
<Sheet
// sx={{
// mb: 1,
// borderRadius: 'lg',
// p: 2,
// }}
sx={{ borderRadius: 'sm', p: 2 }}
variant='outlined'
>
<Grid container spacing={1}>
{historyInfo.map((info, index) => (
<Grid item xs={4} key={index}>
{/* divider between the list items: */}
<ListItem key={index}>
<ListItemContent>
<Typography level='body1' sx={{ fontWeight: 'md' }}>
<Typography level='body-xs' sx={{ fontWeight: 'md' }}>
{info.text}
</Typography>
<Typography level='body1' color='text.tertiary'>
{info.subtext}
</Typography>
<Chip color='primary' size='md' startDecorator={info.icon}>
{info.subtext ? info.subtext : '--'}
</Chip>
</ListItemContent>
</ListItem>
</Sheet>
</Grid>
))}
</Grid>
</Grid>
))}
</Grid>
</Sheet>
{/* User History Cards */}
<Typography level='h3' my={1.5}>
<Typography level='title-md' my={1.5}>
History:
</Typography>
<Box sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
<Sheet sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
{/* Chore History List (Updated Style) */}
<List sx={{ p: 0 }}>
@ -241,7 +223,7 @@ const ChoreHistory = () => {
/>
))}
</List>
</Box>
</Sheet>
</Container>
)
}

View file

@ -1,3 +1,4 @@
import { CalendarViewDay, Check, Timelapse } from '@mui/icons-material'
import {
Avatar,
Box,
@ -30,6 +31,50 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
}
const getCompletedChip = historyEntry => {
var text = 'No Due Date'
var color = 'info'
var icon = <CalendarViewDay />
// if completed few hours +-6 hours
if (
historyEntry.dueDate &&
historyEntry.completedAt > historyEntry.dueDate - 1000 * 60 * 60 * 6 &&
historyEntry.completedAt < historyEntry.dueDate + 1000 * 60 * 60 * 6
) {
text = 'On Time'
color = 'success'
icon = <Check />
} else if (
historyEntry.dueDate &&
historyEntry.completedAt < historyEntry.dueDate
) {
text = 'On Time'
color = 'success'
icon = <Check />
}
// if completed after due date then it's late
else if (
historyEntry.dueDate &&
historyEntry.completedAt > historyEntry.dueDate
) {
text = 'Late'
color = 'warning'
icon = <Timelapse />
} else {
text = 'No Due Date'
color = 'info'
icon = <CalendarViewDay />
}
return (
<Chip startDecorator={icon} color={color}>
{text}
</Chip>
)
}
return (
<>
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
@ -55,13 +100,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
<Typography level='body1' sx={{ fontWeight: 'md' }}>
{moment(historyEntry.completedAt).format('ddd MM/DD/yyyy HH:mm')}
</Typography>
<Chip>
{historyEntry.dueDate &&
historyEntry.completedAt > historyEntry.dueDate
? 'Late'
: 'On Time'}
</Chip>
{getCompletedChip(historyEntry)}
</Box>
<Typography level='body2' color='text.tertiary'>
<Chip>

View file

@ -7,7 +7,7 @@ const DemoHistory = () => {
{
id: 32,
choreId: 12,
completedAt: moment().format(),
completedAt: moment().hour(4).format(),
completedBy: 1,
assignedTo: 1,
notes: null,
@ -25,8 +25,8 @@ const DemoHistory = () => {
{
id: 31,
choreId: 12,
completedAt: moment().day(-10).format(),
completedBy: 1,
completedAt: moment().day(-10).hour(1).format(),
completedBy: 2,
assignedTo: 1,
notes: null,
dueDate: moment().day(-10).format(),

View file

@ -25,44 +25,39 @@ const FeatureIcon = styled('div')({
const CardData = [
{
title: 'Open Source & Transparent',
headline: 'Built for the Community',
description:
'Donetick is a community-driven, open-source project. Contribute, customize, and make task management truly yours.',
'Donetick is open source software. You can view, modify, and contribute to the code on GitHub.',
icon: CodeRounded,
},
{
title: 'Circles: Your Task Hub',
headline: 'Share & Conquer Together',
description:
'Create circles for your family, friends, or team. Easily share tasks and track progress within each group.',
'build with sharing in mind. invite other to the circle and you can assign tasks to each other. and only see the tasks the should be shared',
icon: GroupRounded,
},
{
title: 'Track Your Progress',
headline: "See Who's Done What",
description:
'View a history of task completion for each member of your circles. Celebrate successes and stay on top of your goals.',
'View a history of completed tasks. or use things to track simply things!',
icon: HistoryRounded,
},
{
title: 'Automated Chore Scheduling',
headline: 'Fully Customizable Recurring Tasks',
title: 'Automated Task Scheduling',
description:
'Set up chores to repeat daily, weekly, or monthly. Donetick will automatically assign and track each task for you.',
'Set up Tasks to repeat daily, weekly, or monthly, or maybe specifc day in specifc months? Donetick have a flexible scheduling system',
icon: AutoAwesomeMosaicOutlined,
},
{
title: 'Automated Task Assignment',
headline: 'Share Responsibilities Equally',
description:
'can automatically assigns tasks to each member of your circle. Randomly or based on past completion.',
'For shared tasks, Donetick can randomly rotate assignments or choose based on last completion or least assigned.',
icon: AutoAwesomeRounded,
},
{
title: 'Integrations & Webhooks',
headline: 'API & 3rd Party Integrations',
description:
'Connect Donetick with your favorite apps and services. Trigger tasks based on events from other platforms.',
'Donetick can update things programmatically with API call. you can update things from other services like IFTTT, Homeassistant or even your own service',
icon: Webhook,
},
]
@ -80,7 +75,7 @@ function Feature2({ icon: Icon, title, headline, description, index }) {
<FeatureIcon>
<Icon
color='primary'
style={{ Width: '30px', height: '30px' }}
style={{ Width: '30px', height: '30px', fontSize: '30px' }}
stroke={1.5}
/>
</FeatureIcon>
@ -106,7 +101,7 @@ function FeaturesSection() {
<Feature2
icon={feature.icon}
title={feature.title}
headline={feature.headline}
// headline={feature.headline}
description={feature.description}
index={index}
key={index}
@ -128,7 +123,7 @@ function FeaturesSection() {
</Container>
<Typography level='h4' mt={2} mb={4}>
Features Overview
Why Donetick?
</Typography>
<Container maxWidth={'lg'} sx={{ mb: 8 }}></Container>

View file

@ -0,0 +1,127 @@
import LogoSVG from '@/assets/logo.svg'
import { Card, Grid } from '@mui/joy'
import Box from '@mui/joy/Box'
import Link from '@mui/joy/Link'
import Typography from '@mui/joy/Typography'
import * as React from 'react'
function Footer() {
return (
<Card
data-aos-landing-footer
data-aos-delay={200}
data-aos-anchor='[data-aos-landing-footer]'
data-aos='zoom-in-up'
>
<Grid
container
component='footer'
sx={{
display: 'flex',
justifyContent: 'space-around',
p: 4,
// borderTop: '1px solid',
bottom: 0,
width: '100%',
}}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<div className='logo'>
<img src={LogoSVG} alt='logo' width='64px' height='64px' />
</div>
<Box className='flex items-center gap-2'>
<Typography
level='title-lg'
sx={{
fontWeight: 700,
fontSize: 24,
}}
>
Done
<span
style={{
color: '#06b6d4',
fontWeight: 600,
}}
>
tick
</span>
</Typography>
<span
style={{
fontSize: 12,
fontWeight: 700,
position: 'relative',
top: 12,
right: 45,
}}
>
Beta
</span>
</Box>
</Box>
<Box>
<Typography level='body2' fontWeight='bold' mb={1}>
Github
</Typography>
<Link
href='https://github.com/donetick/core'
level='body2'
sx={{ display: 'block' }}
>
Core(Backend)
</Link>
<Link
href='https://github.com/donetick/frontend'
level='body2'
sx={{ display: 'block' }}
>
Frontend
</Link>
<Link
href='https://github.com/donetick/hassio-addons'
level='body2'
sx={{ display: 'block' }}
>
Home Assistant Addon
</Link>
<Link
href='https://github.com/orgs/Donetick/packages'
level='body2'
sx={{ display: 'block' }}
>
Packages
</Link>
</Box>
<Box>
<Typography level='body2' fontWeight='bold' mb={1}>
Links
</Typography>
<Link disabled={true} level='body2' sx={{ display: 'block' }}>
Roadmap(soon)
</Link>
<Link disabled={true} level='body2' sx={{ display: 'block' }}>
Documentation(soon)
</Link>
<Link disabled={true} level='body2' sx={{ display: 'block' }}>
Changelog(soon)
</Link>
</Box>
{/* <Box>
<Typography level='body2' fontWeight='bold' mb={1}>
Others
</Typography>
<Link href='#' level='body2' sx={{ display: 'block' }}>
Telegram Integration
</Link>
<Link href='#' level='body2' sx={{ display: 'block' }}>
Slash Commands
</Link>
</Box> */}
</Grid>
</Card>
)
}
export default Footer

View file

@ -1,4 +1,4 @@
import { Container, Grid } from '@mui/joy'
import { Box, Container, Grid } from '@mui/joy'
import AOS from 'aos'
import 'aos/dist/aos.css'
import { useEffect } from 'react'
@ -8,6 +8,7 @@ import DemoHistory from './DemoHistory'
import DemoMyChore from './DemoMyChore'
import DemoScheduler from './DemoScheduler'
import FeaturesSection from './FeaturesSection'
import Footer from './Footer'
import HomeHero from './HomeHero'
const Landing = () => {
const Navigate = useNavigate()
@ -39,6 +40,17 @@ const Landing = () => {
</Grid>
<FeaturesSection />
{/* <PricingSection /> */}
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mt: 5,
mb: 5,
}}
></Box>
<Footer />
</Container>
)
}

View file

@ -0,0 +1,51 @@
import { Box, CircularProgress, Container } from '@mui/joy'
import { Typography } from '@mui/material'
import { useEffect, useState } from 'react'
import Logo from '../../Logo'
const LoadingComponent = () => {
const [message, setMessage] = useState('Loading...')
const [subMessage, setSubMessage] = useState('')
useEffect(() => {
// if loading took more than 5 seconds update submessage to mention there might be an error:
const timeout = setTimeout(() => {
setSubMessage(
'This is taking longer than usual. There might be an issue.',
)
}, 5000)
return () => clearTimeout(timeout)
}, [])
return (
<Container className='flex h-full items-center justify-center'>
<Box
className='flex flex-col items-center justify-center'
sx={{
minHeight: '80vh',
}}
>
<CircularProgress
color='success'
sx={{ '--CircularProgress-size': '200px' }}
>
<Logo />
</CircularProgress>
<Box
className='flex items-center gap-2'
sx={{
fontWeight: 700,
fontSize: 24,
mt: 2,
}}
>
{message}
</Box>
<Typography level='h2' fontWeight={500} textAlign={'center'}>
{subMessage}
</Typography>
</Box>
</Container>
)
}
export default LoadingComponent

View file

@ -21,7 +21,7 @@ import {
Typography,
} from '@mui/joy'
import { useState } from 'react'
import { useLocation } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router-dom'
import { version } from '../../../package.json'
import NavBarLink from './NavBarLink'
const links = [
@ -63,6 +63,7 @@ const links = [
]
const NavBar = () => {
const navigate = useNavigate()
const [drawerOpen, setDrawerOpen] = useState(false)
const [openDrawer, closeDrawer] = [
() => setDrawerOpen(true),
@ -89,7 +90,12 @@ const NavBar = () => {
<IconButton size='sm' variant='plain' onClick={() => setDrawerOpen(true)}>
<MenuRounded />
</IconButton>
<Box className='flex items-center gap-2'>
<Box
className='flex items-center gap-2'
onClick={() => {
navigate('/my/chores')
}}
>
<img component='img' src={Logo} width='34' />
<Typography
level='title-lg'