aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-07-09 21:21:16 -0400
committerLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-07-09 21:21:16 -0400
commitc684bdb9ece99935c4c5f3a3eb81787cc2a0ef19 (patch)
treedb6b73e9722ed92b2471cd86dcb32be82322d85a /src
parent1255ea83568701f460f721e0c1a5d461fb0c5a25 (diff)
parente25a6d3be9b9ae443dd3e1cd57a8c5912cc088b2 (diff)
downloaddonetick-frontend-c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19.tar.gz
donetick-frontend-c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19.tar.bz2
donetick-frontend-c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19.zip
Merge branch 'dev'
Diffstat (limited to '')
-rw-r--r--src/contexts/RouterContext.jsx8
-rw-r--r--src/hooks/useWindowWidth.js14
-rw-r--r--src/views/Chores/ChoreCard.jsx10
-rw-r--r--src/views/History/ChoreHistory.jsx113
-rw-r--r--src/views/History/HistoryCard.jsx120
-rw-r--r--src/views/Landing/DemoAssignee.jsx192
-rw-r--r--src/views/Landing/DemoHistory.jsx95
-rw-r--r--src/views/Landing/DemoMyChore.jsx138
-rw-r--r--src/views/Landing/DemoScheduler.jsx68
-rw-r--r--src/views/Landing/HomeHero.jsx53
-rw-r--r--src/views/Landing/Landing.jsx31
-rw-r--r--src/views/components/NavBar.jsx19
12 files changed, 727 insertions, 134 deletions
diff --git a/src/contexts/RouterContext.jsx b/src/contexts/RouterContext.jsx
index 02df35f..4fea83d 100644
--- a/src/contexts/RouterContext.jsx
+++ b/src/contexts/RouterContext.jsx
@@ -20,6 +20,12 @@ import TermsView from '../views/Terms/TermsView'
import TestView from '../views/TestView/Test'
import ThingsHistory from '../views/Things/ThingsHistory'
import ThingsView from '../views/Things/ThingsView'
+const getMainRoute = () => {
+ if (import.meta.env.VITE_IS_LANDING_DEFAULT === 'true') {
+ return <Landing />
+ }
+ return <MyChores />
+}
const Router = createBrowserRouter([
{
path: '/',
@@ -28,7 +34,7 @@ const Router = createBrowserRouter([
children: [
{
path: '/',
- element: <Landing />,
+ element: getMainRoute(),
},
{
path: '/settings',
diff --git a/src/hooks/useWindowWidth.js b/src/hooks/useWindowWidth.js
new file mode 100644
index 0000000..fa79cc4
--- /dev/null
+++ b/src/hooks/useWindowWidth.js
@@ -0,0 +1,14 @@
+import { useEffect, useState } from 'react'
+function useWindowWidth() {
+ const [width, setWidth] = useState(window.innerWidth)
+
+ useEffect(() => {
+ const handleResize = () => setWidth(window.innerWidth)
+ window.addEventListener('resize', handleResize)
+ return () => window.removeEventListener('resize', handleResize)
+ }, [])
+
+ return width
+}
+
+export default useWindowWidth
diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx
index 5e54e6b..08a5406 100644
--- a/src/views/Chores/ChoreCard.jsx
+++ b/src/views/Chores/ChoreCard.jsx
@@ -45,7 +45,14 @@ import DateModal from '../Modals/Inputs/DateModal'
import SelectModal from '../Modals/Inputs/SelectModal'
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,
+ viewOnly,
+}) => {
const [activeUserId, setActiveUserId] = React.useState(0)
const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] =
React.useState(false)
@@ -367,6 +374,7 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
</Chip>
<Card
+ style={viewOnly ? { pointerEvents: 'none' } : {}}
variant='plain'
sx={{
...sx,
diff --git a/src/views/History/ChoreHistory.jsx b/src/views/History/ChoreHistory.jsx
index 22ea6a9..b85e456 100644
--- a/src/views/History/ChoreHistory.jsx
+++ b/src/views/History/ChoreHistory.jsx
@@ -3,12 +3,10 @@ import {
Avatar,
Box,
Button,
- Chip,
CircularProgress,
Container,
Grid,
List,
- ListDivider,
ListItem,
ListItemContent,
ListItemDecorator,
@@ -21,6 +19,7 @@ import { Link, useParams } from 'react-router-dom'
import { API_URL } from '../../Config'
import { GetAllCircleMembers } from '../../utils/Fetcher'
import { Fetch } from '../../utils/TokenManager'
+import HistoryCard from './HistoryCard'
const ChoreHistory = () => {
const [choreHistory, setChoresHistory] = useState([])
@@ -144,25 +143,6 @@ const ChoreHistory = () => {
setHistoryInfo(historyInfo)
}
- function formatTimeDifference(startDate, endDate) {
- const diffInMinutes = moment(startDate).diff(endDate, 'minutes')
- let timeValue = diffInMinutes
- let unit = 'minute'
-
- if (diffInMinutes >= 60) {
- const diffInHours = moment(startDate).diff(endDate, 'hours')
- timeValue = diffInHours
- unit = 'hour'
-
- if (diffInHours >= 24) {
- const diffInDays = moment(startDate).diff(endDate, 'days')
- timeValue = diffInDays
- unit = 'day'
- }
- }
-
- return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
- }
if (isLoading) {
return <CircularProgress /> // Show loading indicator
}
@@ -251,89 +231,14 @@ const ChoreHistory = () => {
{/* Chore History List (Updated Style) */}
<List sx={{ p: 0 }}>
- {choreHistory.map((chore, index) => (
- <>
- <ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
- {' '}
- {/* Adjusted spacing and alignment */}
- <ListItemDecorator>
- <Avatar sx={{ mr: 1 }}>
- {performers
- .find(p => p.userId === chore.completedBy)
- ?.displayName?.charAt(0) || '?'}
- </Avatar>
- </ListItemDecorator>
- <ListItemContent sx={{ my: 0 }}>
- {' '}
- {/* Removed vertical margin */}
- <Box
- sx={{
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- }}
- >
- <Typography level='body1' sx={{ fontWeight: 'md' }}>
- {moment(chore.completedAt).format('ddd MM/DD/yyyy HH:mm')}
- </Typography>
-
- <Chip>
- {chore.dueDate && chore.completedAt > chore.dueDate
- ? 'Late'
- : 'On Time'}
- </Chip>
- </Box>
- <Typography level='body2' color='text.tertiary'>
- <Chip>
- {
- performers.find(p => p.userId === chore.completedBy)
- ?.displayName
- }
- </Chip>{' '}
- completed
- {chore.completedBy !== chore.assignedTo && (
- <>
- {', '}
- assigned to{' '}
- <Chip>
- {
- performers.find(p => p.userId === chore.assignedTo)
- ?.displayName
- }
- </Chip>
- </>
- )}
- </Typography>
- {chore.dueDate && (
- <Typography level='body2' color='text.tertiary'>
- Due: {moment(chore.dueDate).format('ddd MM/DD/yyyy')}
- </Typography>
- )}
- {chore.notes && (
- <Typography level='body2' color='text.tertiary'>
- Note: {chore.notes}
- </Typography>
- )}
- </ListItemContent>
- </ListItem>
- {index < choreHistory.length - 1 && (
- <>
- <ListDivider component='li'>
- {/* time between two completion: */}
- {index < choreHistory.length - 1 &&
- choreHistory[index + 1].completedAt && (
- <Typography level='body3' color='text.tertiary'>
- {formatTimeDifference(
- chore.completedAt,
- choreHistory[index + 1].completedAt,
- )}{' '}
- before
- </Typography>
- )}
- </ListDivider>
- </>
- )}
- </>
+ {choreHistory.map((historyEntry, index) => (
+ <HistoryCard
+ historyEntry={historyEntry}
+ performers={performers}
+ allHistory={choreHistory}
+ key={index}
+ index={index}
+ />
))}
</List>
</Box>
diff --git a/src/views/History/HistoryCard.jsx b/src/views/History/HistoryCard.jsx
new file mode 100644
index 0000000..c606fbf
--- /dev/null
+++ b/src/views/History/HistoryCard.jsx
@@ -0,0 +1,120 @@
+import {
+ Avatar,
+ Box,
+ Chip,
+ ListDivider,
+ ListItem,
+ ListItemContent,
+ ListItemDecorator,
+ Typography,
+} from '@mui/joy'
+import moment from 'moment'
+
+const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
+ function formatTimeDifference(startDate, endDate) {
+ const diffInMinutes = moment(startDate).diff(endDate, 'minutes')
+ let timeValue = diffInMinutes
+ let unit = 'minute'
+
+ if (diffInMinutes >= 60) {
+ const diffInHours = moment(startDate).diff(endDate, 'hours')
+ timeValue = diffInHours
+ unit = 'hour'
+
+ if (diffInHours >= 24) {
+ const diffInDays = moment(startDate).diff(endDate, 'days')
+ timeValue = diffInDays
+ unit = 'day'
+ }
+ }
+
+ return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
+ }
+ return (
+ <>
+ <ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
+ {' '}
+ {/* Adjusted spacing and alignment */}
+ <ListItemDecorator>
+ <Avatar sx={{ mr: 1 }}>
+ {performers
+ .find(p => p.userId === historyEntry.completedBy)
+ ?.displayName?.charAt(0) || '?'}
+ </Avatar>
+ </ListItemDecorator>
+ <ListItemContent sx={{ my: 0 }}>
+ {' '}
+ {/* Removed vertical margin */}
+ <Box
+ sx={{
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ }}
+ >
+ <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>
+ </Box>
+ <Typography level='body2' color='text.tertiary'>
+ <Chip>
+ {
+ performers.find(p => p.userId === historyEntry.completedBy)
+ ?.displayName
+ }
+ </Chip>{' '}
+ completed
+ {historyEntry.completedBy !== historyEntry.assignedTo && (
+ <>
+ {', '}
+ assigned to{' '}
+ <Chip>
+ {
+ performers.find(p => p.userId === historyEntry.assignedTo)
+ ?.displayName
+ }
+ </Chip>
+ </>
+ )}
+ </Typography>
+ {historyEntry.dueDate && (
+ <Typography level='body2' color='text.tertiary'>
+ Due: {moment(historyEntry.dueDate).format('ddd MM/DD/yyyy')}
+ </Typography>
+ )}
+ {historyEntry.notes && (
+ <Typography level='body2' color='text.tertiary'>
+ Note: {historyEntry.notes}
+ </Typography>
+ )}
+ </ListItemContent>
+ </ListItem>
+ {index < allHistory.length - 1 && (
+ <>
+ <ListDivider component='li'>
+ {/* time between two completion: */}
+ {index < allHistory.length - 1 &&
+ allHistory[index + 1].completedAt && (
+ <Typography level='body3' color='text.tertiary'>
+ {formatTimeDifference(
+ historyEntry.completedAt,
+ allHistory[index + 1].completedAt,
+ )}{' '}
+ before
+ </Typography>
+ )}
+ </ListDivider>
+ </>
+ )}
+ </>
+ )
+}
+
+export default HistoryCard
diff --git a/src/views/Landing/DemoAssignee.jsx b/src/views/Landing/DemoAssignee.jsx
new file mode 100644
index 0000000..5064cf0
--- /dev/null
+++ b/src/views/Landing/DemoAssignee.jsx
@@ -0,0 +1,192 @@
+import {
+ Box,
+ Card,
+ Checkbox,
+ Grid,
+ List,
+ ListItem,
+ Option,
+ Select,
+ Typography,
+} from '@mui/joy'
+import { useState } from 'react'
+const ASSIGN_STRATEGIES = [
+ 'random',
+ 'least_assigned',
+ 'least_completed',
+ 'keep_last_assigned',
+]
+const DemoAssignee = () => {
+ const [assignStrategy, setAssignStrategy] = useState('random')
+ const [assignees, setAssignees] = useState([
+ {
+ userId: 3,
+ id: 3,
+ displayName: 'Ryan',
+ },
+ ])
+ const [assignedTo, setAssignedTo] = useState(3)
+ const performers = [
+ {
+ userId: 1,
+ id: 1,
+ displayName: 'Mo',
+ },
+ {
+ userId: 2,
+ id: 2,
+ displayName: 'Jiji',
+ },
+ {
+ userId: 3,
+ id: 3,
+ displayName: 'Ryan',
+ },
+ ]
+ return (
+ <>
+ <Grid item xs={12} sm={6} data-aos-create-chore-assignee>
+ <Box
+ mt={2}
+ data-aos-delay={200}
+ data-aos-anchor='[data-aos-create-chore-assignee]'
+ data-aos='fade-right'
+ >
+ <Typography level='h4'>Assignees :</Typography>
+ <Typography level='h5'>Who can do this chore?</Typography>
+ <Card>
+ <List
+ orientation='horizontal'
+ wrap
+ sx={{
+ '--List-gap': '8px',
+ '--ListItem-radius': '20px',
+ }}
+ >
+ {performers?.map(item => (
+ <ListItem key={item.id}>
+ <Checkbox
+ // disabled={index === 0}
+ checked={assignees.find(a => a.userId == item.id) != null}
+ onClick={() => {
+ if (assignees.find(a => a.userId == item.id)) {
+ setAssignees(
+ assignees.filter(i => i.userId !== item.id),
+ )
+ } else {
+ setAssignees([...assignees, { userId: item.id }])
+ }
+ }}
+ overlay
+ disableIcon
+ variant='soft'
+ label={item.displayName}
+ />
+ </ListItem>
+ ))}
+ </List>
+ </Card>
+ </Box>
+ <Box
+ mt={2}
+ data-aos-delay={300}
+ data-aos-anchor='[data-aos-create-chore-assignee]'
+ data-aos='fade-right'
+ >
+ <Typography level='h4'>Assigned :</Typography>
+ <Typography level='h5'>
+ Who is assigned the next due chore?
+ </Typography>
+
+ <Select
+ placeholder={
+ assignees.length === 0
+ ? 'No Assignees yet can perform this chore'
+ : 'Select an assignee for this chore'
+ }
+ disabled={assignees.length === 0}
+ value={assignedTo > -1 ? assignedTo : null}
+ >
+ {performers
+ ?.filter(p => assignees.find(a => a.userId == p.userId))
+ .map((item, index) => (
+ <Option
+ value={item.id}
+ key={item.displayName}
+ onClick={() => {}}
+ >
+ {item.displayName}
+ {/* <Chip size='sm' color='neutral' variant='soft'>
+ </Chip> */}
+ </Option>
+ ))}
+ </Select>
+ </Box>
+ <Box
+ mt={2}
+ data-aos-delay={400}
+ data-aos-anchor='[data-aos-create-chore-assignee]'
+ data-aos='fade-right'
+ >
+ <Typography level='h4'>Picking Mode :</Typography>
+ <Typography level='h5'>
+ How to pick the next assignee for the following chore?
+ </Typography>
+
+ <Card>
+ <List
+ orientation='horizontal'
+ wrap
+ sx={{
+ '--List-gap': '8px',
+ '--ListItem-radius': '20px',
+ }}
+ >
+ {ASSIGN_STRATEGIES.map((item, idx) => (
+ <ListItem key={item}>
+ <Checkbox
+ // disabled={index === 0}
+ checked={assignStrategy === item}
+ onClick={() => setAssignStrategy(item)}
+ overlay
+ disableIcon
+ variant='soft'
+ label={item
+ .split('_')
+ .map(x => x.charAt(0).toUpperCase() + x.slice(1))
+ .join(' ')}
+ />
+ </ListItem>
+ ))}
+ </List>
+ </Card>
+ </Box>
+ </Grid>
+ <Grid item xs={12} sm={6} data-aos-create-chore-section-assignee>
+ <Card
+ sx={{
+ p: 4,
+ py: 6,
+ }}
+ data-aos-delay={200}
+ data-aos-anchor='[data-aos-create-chore-section-assignee]'
+ data-aos='fade-left'
+ >
+ <Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
+ Flexible Task Assignment
+ </Typography>
+ <Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
+ Whether you’re a solo user managing personal tasks or coordinating
+ chores with others, Donetick provides robust assignment options.
+ Assign tasks to different people and choose specific rotation
+ strategies, such as assigning tasks based on who completed the most
+ or least, randomly rotating assignments, or sticking with the last
+ assigned person.
+ </Typography>
+ </Card>
+ </Grid>
+ </>
+ )
+}
+
+export default DemoAssignee
diff --git a/src/views/Landing/DemoHistory.jsx b/src/views/Landing/DemoHistory.jsx
new file mode 100644
index 0000000..2c713f1
--- /dev/null
+++ b/src/views/Landing/DemoHistory.jsx
@@ -0,0 +1,95 @@
+import { Box, Card, Grid, List, Typography } from '@mui/joy'
+import moment from 'moment'
+import HistoryCard from '../History/HistoryCard'
+
+const DemoHistory = () => {
+ const allHistory = [
+ {
+ id: 32,
+ choreId: 12,
+ completedAt: moment().format(),
+ completedBy: 1,
+ assignedTo: 1,
+ notes: null,
+ dueDate: moment().format(),
+ },
+ {
+ id: 31,
+ choreId: 12,
+ completedAt: moment().day(-1).format(),
+ completedBy: 1,
+ assignedTo: 1,
+ notes: 'Need to be replaced with a new one',
+ dueDate: moment().day(-2).format(),
+ },
+ {
+ id: 31,
+ choreId: 12,
+ completedAt: moment().day(-10).format(),
+ completedBy: 1,
+ assignedTo: 1,
+ notes: null,
+ dueDate: moment().day(-10).format(),
+ },
+ ]
+ const performers = [
+ {
+ userId: 1,
+ displayName: 'Ryan',
+ },
+ {
+ userId: 2,
+ displayName: 'Sarah',
+ },
+ ]
+
+ return (
+ <>
+ <Grid item xs={12} sm={6} data-aos-history-list>
+ <Box sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
+ <List sx={{ p: 0 }}>
+ {allHistory.map((historyEntry, index) => (
+ <div
+ data-aos-delay={100 * index + 200}
+ data-aos-anchor='[data-aos-history-list]'
+ data-aos='fade-right'
+ key={index}
+ >
+ <HistoryCard
+ allHistory={allHistory}
+ historyEntry={historyEntry}
+ key={index}
+ index={index}
+ performers={performers}
+ />
+ </div>
+ ))}
+ </List>
+ </Box>
+ </Grid>
+ <Grid item xs={12} sm={6} data-aos-history-demo-section>
+ <Card
+ sx={{
+ p: 4,
+ py: 6,
+ }}
+ data-aos-delay={200}
+ data-aos-anchor='[data-aos-history-demo-section]'
+ data-aos='fade-left'
+ >
+ <Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
+ History with a purpose
+ </Typography>
+ <Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
+ Keep track of all your chores and tasks with ease. Donetick records
+ due dates, completion dates, and who completed each task. Any notes
+ added to tasks are also tracked, providing a complete history for
+ your reference. Stay organized and informed with detailed task
+ tracking.
+ </Typography>
+ </Card>
+ </Grid>
+ </>
+ )
+}
+export default DemoHistory
diff --git a/src/views/Landing/DemoMyChore.jsx b/src/views/Landing/DemoMyChore.jsx
new file mode 100644
index 0000000..be56cae
--- /dev/null
+++ b/src/views/Landing/DemoMyChore.jsx
@@ -0,0 +1,138 @@
+import { Card, Grid, Typography } from '@mui/joy'
+import moment from 'moment'
+import ChoreCard from '../Chores/ChoreCard'
+
+const DemoMyChore = () => {
+ const cards = [
+ {
+ id: 12,
+ name: '♻️ Take out recycle ',
+ frequencyType: 'days_of_the_week',
+ frequency: 1,
+ frequencyMetadata:
+ '{"days":["thursday"],"time":"2024-07-07T22:00:00-04:00"}',
+ nextDueDate: moment().add(1, 'days').hour(8).minute(0).toISOString(),
+ isRolling: false,
+ assignedTo: 1,
+ },
+ {
+ id: 9,
+ name: '🐜 Spray Pesticide',
+ frequencyType: 'interval',
+ frequency: 3,
+ frequencyMetadata: '{"unit":"months"}',
+ nextDueDate: moment().subtract(7, 'day').toISOString(),
+ isRolling: false,
+ assignedTo: 1,
+ },
+ {
+ id: 6,
+ name: '🍂 Gutter Cleaning',
+ frequencyType: 'day_of_the_month',
+ frequency: 1,
+ frequencyMetadata: '{"months":["may"]}',
+ nextDueDate: moment()
+ .month('may')
+ .year(moment().year() + 1)
+ .date(1)
+ .hour(17)
+ .minute(0)
+ .toISOString(),
+ isRolling: false,
+ assignedTo: 1,
+ },
+ // {
+ // id: 10,
+ // name: '💨 Air dust Synology NAS and',
+ // frequencyType: 'interval',
+ // frequency: 12,
+ // frequencyMetadata: '{"unit":"weeks"}',
+ // nextDueDate: '2024-07-24T17:18:00Z',
+ // isRolling: false,
+ // assignedTo: 1,
+ // },
+ // {
+ // id: 8,
+ // name: '🛁 Deep Cleaning Bathroom',
+ // frequencyType: 'monthly',
+ // frequency: 1,
+ // frequencyMetadata: '{}',
+ // nextDueDate: '2024-08-04T17:15:00Z',
+ // isRolling: false,
+ // assignedTo: 1,
+ // },
+ // {
+ // id: 11,
+ // name: '☴ Replace AC Air filter',
+ // frequencyType: 'adaptive',
+ // frequency: 1,
+ // frequencyMetadata: '{"unit":"days"}',
+ // nextDueDate: moment().add(120, 'days').toISOString(),
+ // isRolling: false,
+ // assignedTo: 1,
+ // },
+ // {
+ // id: 6,
+ // name: '🍂 Gutter Cleaning ',
+ // frequencyType: 'day_of_the_month',
+ // frequency: 1,
+ // frequencyMetadata: '{"months":["may"]}',
+ // nextDueDate: '2025-05-01T17:00:00Z',
+ // isRolling: false,
+ // assignedTo: 1,
+ // },
+ // {
+ // id: 13,
+ // name: '🚰 Replace Water Filter',
+ // frequencyType: 'yearly',
+ // frequency: 1,
+ // frequencyMetadata: '{}',
+ // nextDueDate: '2025-07-08T01:00:00Z',
+ // isRolling: false,
+ // assignedTo: 1,
+ // },
+ ]
+
+ const users = [{ displayName: 'Me', id: 1 }]
+ return (
+ <>
+ <Grid item xs={12} sm={5} data-aos-first-tasks-list>
+ {cards.map((card, index) => (
+ <div
+ key={index}
+ data-aos-delay={100 * index + 200}
+ data-aos-anchor='[data-aos-first-tasks-list]'
+ data-aos='fade-up'
+ >
+ <ChoreCard chore={card} performers={users} viewOnly={true} />
+ </div>
+ ))}
+ </Grid>
+ <Grid item xs={12} sm={7} data-aos-my-chore-demo-section>
+ <Card
+ sx={{
+ p: 4,
+ py: 6,
+ }}
+ data-aos-delay={200}
+ data-aos-anchor='[data-aos-my-chore-demo-section]'
+ data-aos='fade-left'
+ >
+ <Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
+ Glance at your task and chores
+ </Typography>
+ <Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
+ Main view prioritize tasks due today, followed by overdue ones, and
+ finally, future tasks or those without due dates. With Donetick, you
+ can view all the tasks you've created (whether assigned to you or
+ not) as well as tasks assigned to you by others. Quickly mark them
+ as done with just one click, ensuring a smooth and efficient task
+ management experience.
+ </Typography>
+ </Card>
+ </Grid>
+ </>
+ )
+}
+
+export default DemoMyChore
diff --git a/src/views/Landing/DemoScheduler.jsx b/src/views/Landing/DemoScheduler.jsx
new file mode 100644
index 0000000..5e34adb
--- /dev/null
+++ b/src/views/Landing/DemoScheduler.jsx
@@ -0,0 +1,68 @@
+import { Box, Card, Grid, Typography } from '@mui/joy'
+import { useState } from 'react'
+import RepeatSection from '../ChoreEdit/RepeatSection'
+
+const DemoScheduler = () => {
+ const [assignees, setAssignees] = useState([])
+ const [frequency, setFrequency] = useState(2)
+ const [frequencyType, setFrequencyType] = useState('weekly')
+ const [frequencyMetadata, setFrequencyMetadata] = useState({
+ months: ['may', 'june', 'july'],
+ })
+
+ return (
+ <>
+ <Grid item xs={12} sm={5} data-aos-create-chore-scheduler>
+ <Box
+ data-aos-delay={300}
+ data-aos-anchor='[data-aos-create-chore-scheduler]'
+ data-aos='fade-right'
+ >
+ <RepeatSection
+ frequency={frequency}
+ onFrequencyUpdate={setFrequency}
+ frequencyType={frequencyType}
+ onFrequencyTypeUpdate={setFrequencyType}
+ frequencyMetadata={frequencyMetadata}
+ onFrequencyMetadataUpdate={setFrequencyMetadata}
+ onFrequencyTimeUpdate={t => {}}
+ frequencyError={null}
+ allUserThings={[]}
+ onTriggerUpdate={thingUpdate => {}}
+ OnTriggerValidate={() => {}}
+ isAttemptToSave={false}
+ selectedThing={null}
+ />
+ </Box>
+ </Grid>
+ <Grid item xs={12} sm={7} data-aos-create-chore-section-scheduler>
+ <Card
+ sx={{
+ p: 4,
+ py: 6,
+ }}
+ data-aos-delay={200}
+ data-aos-anchor='[data-aos-create-chore-section-scheduler]'
+ data-aos='fade-left'
+ >
+ <Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
+ Advanced Scheduling and Automation
+ </Typography>
+ <Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
+ Scheduling is a crucial aspect of managing tasks and chores.
+ Donetick offers basic scheduling options, such as recurring tasks
+ daily, weekly, or yearly, as well as more customizable schedules
+ like specific days of the week or month. For those unsure of exact
+ frequencies, the adaptive scheduling feature averages based on how
+ often you mark a task as completed. Additionally, Donetick supports
+ automation by linking tasks with triggers via API. When specific
+ conditions are met, Donetick’s Things feature will automatically
+ initiate the task, streamlining your workflow.
+ </Typography>
+ </Card>
+ </Grid>
+ </>
+ )
+}
+
+export default DemoScheduler
diff --git a/src/views/Landing/HomeHero.jsx b/src/views/Landing/HomeHero.jsx
index a96374a..644c9cc 100644
--- a/src/views/Landing/HomeHero.jsx
+++ b/src/views/Landing/HomeHero.jsx
@@ -4,15 +4,18 @@ import { Button } from '@mui/joy'
import Typography from '@mui/joy/Typography'
import Box from '@mui/material/Box'
import Grid from '@mui/material/Grid'
-import React from 'react'
+import React, { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import Logo from '@/assets/logo.svg'
import screenShotMyChore from '@/assets/screenshot-my-chore.png'
import { GitHub } from '@mui/icons-material'
+import useWindowWidth from '../../hooks/useWindowWidth'
const HomeHero = () => {
const navigate = useNavigate()
+ const windowWidth = useWindowWidth()
+ const windowThreshold = 600
const HERO_TEXT_THAT = [
// 'Donetick simplifies the entire process, from scheduling and reminders to automatic task assignment and progress tracking.',
// 'Donetick is the intuitive task and chore management app designed for groups. Take charge of shared responsibilities, automate your workflow, and achieve more together.',
@@ -21,7 +24,7 @@ const HomeHero = () => {
const [heroTextIndex, setHeroTextIndex] = React.useState(0)
- React.useEffect(() => {
+ useEffect(() => {
// const intervalId = setInterval(
// () => setHeroTextIndex(index => index + 1),
// 4000, // every 4 seconds
@@ -58,6 +61,17 @@ const HomeHero = () => {
>
tick
</span>
+ <span
+ style={{
+ fontSize: 20,
+ fontWeight: 700,
+ position: 'relative',
+ top: 12,
+ right: 45,
+ }}
+ >
+ Beta
+ </span>
</Typography>
</Box>
)
@@ -162,23 +176,24 @@ const HomeHero = () => {
</Box>
</div>
</Grid>
-
- <Grid item xs={12} md={5}>
- <div className='flex justify-center'>
- <img
- src={screenShotMyChore}
- width={'100%'}
- style={{
- maxWidth: 300,
- }}
- height={'auto'}
- alt='Hero img'
- data-aos-delay={100 * 2}
- data-aos-anchor='[data-aos-id-hero]'
- data-aos='fade-left'
- />
- </div>
- </Grid>
+ {windowWidth > windowThreshold && (
+ <Grid item xs={12} md={5}>
+ <div className='flex justify-center'>
+ <img
+ src={screenShotMyChore}
+ width={'100%'}
+ style={{
+ maxWidth: 300,
+ }}
+ height={'auto'}
+ alt='Hero img'
+ data-aos-delay={100 * 2}
+ data-aos-anchor='[data-aos-id-hero]'
+ data-aos='fade-left'
+ />
+ </div>
+ </Grid>
+ )}
</Grid>
)
}
diff --git a/src/views/Landing/Landing.jsx b/src/views/Landing/Landing.jsx
index 6d3a1ce..a8b650d 100644
--- a/src/views/Landing/Landing.jsx
+++ b/src/views/Landing/Landing.jsx
@@ -1,18 +1,16 @@
-import { Container } from '@mui/joy'
+import { Container, Grid } from '@mui/joy'
import AOS from 'aos'
import 'aos/dist/aos.css'
-import { useEffect, useState } from 'react'
+import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
+import DemoAssignee from './DemoAssignee'
+import DemoHistory from './DemoHistory'
+import DemoMyChore from './DemoMyChore'
+import DemoScheduler from './DemoScheduler'
import FeaturesSection from './FeaturesSection'
import HomeHero from './HomeHero'
const Landing = () => {
const Navigate = useNavigate()
- const getCurrentUser = () => {
- return JSON.parse(localStorage.getItem('user'))
- }
- const [users, setUsers] = useState([])
- const [currentUser, setCurrentUser] = useState(getCurrentUser())
-
useEffect(() => {
AOS.init({
once: false, // whether animation should happen only once - while scrolling down
@@ -22,6 +20,23 @@ const Landing = () => {
return (
<Container className='flex h-full items-center justify-center'>
<HomeHero />
+ <Grid
+ overflow={'hidden'}
+ container
+ spacing={4}
+ sx={{
+ mt: 5,
+ mb: 5,
+ // align item vertically:
+ alignItems: 'center',
+ }}
+ >
+ <DemoMyChore />
+ <DemoAssignee />
+ <DemoScheduler />
+
+ <DemoHistory />
+ </Grid>
<FeaturesSection />
{/* <PricingSection /> */}
</Container>
diff --git a/src/views/components/NavBar.jsx b/src/views/components/NavBar.jsx
index 939c9fd..2da09f5 100644
--- a/src/views/components/NavBar.jsx
+++ b/src/views/components/NavBar.jsx
@@ -71,12 +71,18 @@ const NavBar = () => {
const location = useLocation()
// if url has /landing then remove the navbar:
if (
- ['/', '/signup', '/login', '/landing', '/forgot-password'].includes(
+ ['/signup', '/login', '/landing', '/forgot-password'].includes(
location.pathname,
)
) {
return null
}
+ if (
+ location.pathname === '/' &&
+ import.meta.env.VITE_IS_LANDING_DEFAULT === 'true'
+ ) {
+ return null
+ }
return (
<nav className='flex gap-2 p-3'>
@@ -102,6 +108,17 @@ const NavBar = () => {
tick✓
</span>
</Typography>
+ <span
+ style={{
+ fontSize: 12,
+ fontWeight: 700,
+ position: 'relative',
+ top: 12,
+ right: 45,
+ }}
+ >
+ Beta
+ </span>
</Box>
<Drawer
open={drawerOpen}