diff options
author | Mo Tarbin <mhed.t91@gmail.com> | 2024-07-09 21:21:16 -0400 |
---|---|---|
committer | Mo Tarbin <mhed.t91@gmail.com> | 2024-07-09 21:21:16 -0400 |
commit | c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19 (patch) | |
tree | db6b73e9722ed92b2471cd86dcb32be82322d85a | |
parent | 1255ea83568701f460f721e0c1a5d461fb0c5a25 (diff) | |
parent | e25a6d3be9b9ae443dd3e1cd57a8c5912cc088b2 (diff) | |
download | donetick-frontend-c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19.tar.gz donetick-frontend-c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19.tar.bz2 donetick-frontend-c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19.zip |
Merge branch 'dev'
Diffstat (limited to '')
-rw-r--r-- | .env | 3 | ||||
-rw-r--r-- | src/contexts/RouterContext.jsx | 8 | ||||
-rw-r--r-- | src/hooks/useWindowWidth.js | 14 | ||||
-rw-r--r-- | src/views/Chores/ChoreCard.jsx | 10 | ||||
-rw-r--r-- | src/views/History/ChoreHistory.jsx | 113 | ||||
-rw-r--r-- | src/views/History/HistoryCard.jsx | 120 | ||||
-rw-r--r-- | src/views/Landing/DemoAssignee.jsx | 192 | ||||
-rw-r--r-- | src/views/Landing/DemoHistory.jsx | 95 | ||||
-rw-r--r-- | src/views/Landing/DemoMyChore.jsx | 138 | ||||
-rw-r--r-- | src/views/Landing/DemoScheduler.jsx | 68 | ||||
-rw-r--r-- | src/views/Landing/HomeHero.jsx | 53 | ||||
-rw-r--r-- | src/views/Landing/Landing.jsx | 31 | ||||
-rw-r--r-- | src/views/components/NavBar.jsx | 19 |
13 files changed, 729 insertions, 135 deletions
@@ -1 +1,2 @@ -VITE_APP_API_URL=http://localhost:2021
\ No newline at end of file +VITE_APP_API_URL=http://localhost:2021 +VITE_IS_LANDING_DEFAULT=false
\ No newline at end of file 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} |