- Add Cookie Permission component

- Add Filter button instead of chips in mychores view
- show except days instead of showing evenything when days.length>4
This commit is contained in:
Mo Tarbin 2024-09-06 01:22:55 -04:00
commit 2d2cf6d378
18 changed files with 508 additions and 175 deletions

1
.env
View file

@ -1,2 +1,3 @@
VITE_APP_API_URL=http://localhost:2021 VITE_APP_API_URL=http://localhost:2021
VITE_IS_LANDING_DEFAULT=false VITE_IS_LANDING_DEFAULT=false
VITE_OPENREPLAY_PROJECT_KEY=

50
package-lock.json generated
View file

@ -1,18 +1,19 @@
{ {
"name": "fe-template", "name": "donetick",
"version": "0.1.61", "version": "0.1.72",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "fe-template", "name": "donetick",
"version": "0.1.61", "version": "0.1.72",
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.3", "@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.2", "@mui/icons-material": "^5.15.2",
"@mui/joy": "^5.0.0-beta.20", "@mui/joy": "^5.0.0-beta.20",
"@mui/material": "^5.15.2", "@mui/material": "^5.15.2",
"@openreplay/tracker": "^14.0.4",
"@tanstack/react-query": "^5.17.0", "@tanstack/react-query": "^5.17.0",
"aos": "^2.3.4", "aos": "^2.3.4",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
@ -2481,6 +2482,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@medv/finder": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@medv/finder/-/finder-3.2.0.tgz",
"integrity": "sha512-JmU7JIBwyL8RAzefvzALT4sP2M0biGk8i2invAgpQmma/QgfsaqoHIvJ7S0YC8n9hUVG8X3Leul2nGa06PvhbQ==",
"license": "MIT"
},
"node_modules/@mui/base": { "node_modules/@mui/base": {
"version": "5.0.0-beta.29", "version": "5.0.0-beta.29",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.29.tgz", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.29.tgz",
@ -2839,6 +2846,20 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@openreplay/tracker": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@openreplay/tracker/-/tracker-14.0.4.tgz",
"integrity": "sha512-Mz+MPw9EYbH6tpZ3d1u2yLJsY+MaoBmJX0gQt4HtvGlwJnd7xJvF37xHY8/e3N1Tc7zENsrf7xcpiZXabNKoVQ==",
"license": "MIT",
"dependencies": {
"@medv/finder": "^3.2.0",
"error-stack-parser": "^2.0.6",
"fflate": "^0.8.2"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -4747,6 +4768,15 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"node_modules/error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
"license": "MIT",
"dependencies": {
"stackframe": "^1.3.4"
}
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.22.3", "version": "1.22.3",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
@ -5327,6 +5357,12 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -8064,6 +8100,12 @@
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead" "deprecated": "Please use @jridgewell/sourcemap-codec instead"
}, },
"node_modules/stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
"license": "MIT"
},
"node_modules/streamx": { "node_modules/streamx": {
"version": "2.18.0", "version": "2.18.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz",

View file

@ -1,7 +1,7 @@
{ {
"name": "donetick", "name": "donetick",
"private": true, "private": true,
"version": "0.1.65", "version": "0.1.74",
"type": "module", "type": "module",
"lint-staged": { "lint-staged": {
"*.{js,jsx,ts,tsx}": [ "*.{js,jsx,ts,tsx}": [
@ -25,6 +25,7 @@
"@mui/icons-material": "^5.15.2", "@mui/icons-material": "^5.15.2",
"@mui/joy": "^5.0.0-beta.20", "@mui/joy": "^5.0.0-beta.20",
"@mui/material": "^5.15.2", "@mui/material": "^5.15.2",
"@openreplay/tracker": "^14.0.4",
"@tanstack/react-query": "^5.17.0", "@tanstack/react-query": "^5.17.0",
"aos": "^2.3.4", "aos": "^2.3.4",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",

View file

@ -1,5 +1,6 @@
import NavBar from '@/views/components/NavBar' import NavBar from '@/views/components/NavBar'
import { Button, Snackbar, Typography, useColorScheme } from '@mui/joy' import { Button, Snackbar, Typography, useColorScheme } from '@mui/joy'
import Tracker from '@openreplay/tracker'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Outlet } from 'react-router-dom' import { Outlet } from 'react-router-dom'
import { useRegisterSW } from 'virtual:pwa-register/react' import { useRegisterSW } from 'virtual:pwa-register/react'
@ -19,6 +20,8 @@ const remove = className => {
const intervalMS = 5 * 60 * 1000 // 5 minutes const intervalMS = 5 * 60 * 1000 // 5 minutes
function App() { function App() {
startOpenReplay()
const { mode, systemMode } = useColorScheme() const { mode, systemMode } = useColorScheme()
const [userProfile, setUserProfile] = useState(null) const [userProfile, setUserProfile] = useState(null)
const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(true) const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(true)
@ -115,4 +118,11 @@ function App() {
) )
} }
const startOpenReplay = () => {
if (!import.meta.env.VITE_OPENREPLAY_PROJECT_KEY) return
const tracker = new Tracker({
projectKey: import.meta.env.VITE_OPENREPLAY_PROJECT_KEY,
})
tracker.start()
}
export default App export default App

View file

@ -83,6 +83,15 @@ const SkipChore = id => {
body: JSON.stringify({}), body: JSON.stringify({}),
}) })
} }
const UpdateChoreAssignee = (id, assignee) => {
return Fetch(`${API_URL}/chores/${id}/assignee`, {
method: 'PUT',
headers: HEADERS(),
body: JSON.stringify({ assignee:Number(assignee) }),
})
}
const CreateChore = chore => { const CreateChore = chore => {
return Fetch(`${API_URL}/chores/`, { return Fetch(`${API_URL}/chores/`, {
method: 'POST', method: 'POST',
@ -306,4 +315,5 @@ export {
UpdateChoreHistory, UpdateChoreHistory,
UpdateThingState, UpdateThingState,
UpdateUserDetails, UpdateUserDetails,
UpdateChoreAssignee,
} }

View file

@ -7,6 +7,7 @@ import {
FormHelperText, FormHelperText,
Input, Input,
Sheet, Sheet,
Snackbar,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import React from 'react' import React from 'react'
@ -25,6 +26,8 @@ const SignupView = () => {
const [emailError, setEmailError] = React.useState('') const [emailError, setEmailError] = React.useState('')
const [displayNameError, setDisplayNameError] = React.useState('') const [displayNameError, setDisplayNameError] = React.useState('')
const [error, setError] = React.useState(null) const [error, setError] = React.useState(null)
const [snackbarOpen, setSnackbarOpen] = React.useState(false)
const [snackbarMessage, setSnackbarMessage] = React.useState('')
const handleLogin = (username, password) => { const handleLogin = (username, password) => {
login(username, password).then(response => { login(username, password).then(response => {
if (response.status === 200) { if (response.status === 200) {
@ -39,6 +42,7 @@ const SignupView = () => {
}) })
} else { } else {
console.log('Login failed', response) console.log('Login failed', response)
// Navigate('/login') // Navigate('/login')
} }
}) })
@ -101,7 +105,10 @@ const SignupView = () => {
handleLogin(username, password) handleLogin(username, password)
} else { } else {
console.log('Signup failed') console.log('Signup failed')
setError('Signup failed') response.json().then(res => {
setError(res.error)
}
)
} }
}) })
} }
@ -256,6 +263,14 @@ const SignupView = () => {
</Button> </Button>
</Sheet> </Sheet>
</Box> </Box>
<Snackbar
open={error !== null}
onClose={() => setError(null)}
autoHideDuration={5000}
message={error}
>
{error}
</Snackbar>
</Container> </Container>
) )
} }

View file

@ -3,6 +3,7 @@ import {
CancelScheduleSend, CancelScheduleSend,
Check, Check,
Checklist, Checklist,
Edit,
History, History,
PeopleAlt, PeopleAlt,
Person, Person,
@ -258,7 +259,7 @@ const ChoreView = () => {
> >
<Grid container spacing={1}> <Grid container spacing={1}>
{infoCards.map((detail, index) => ( {infoCards.map((detail, index) => (
<Grid item xs={4} key={index}> <Grid item xs={6} key={index}>
{/* divider between the list items: */} {/* divider between the list items: */}
<ListItem key={index}> <ListItem key={index}>
@ -411,19 +412,6 @@ const ChoreView = () => {
}} }}
/> />
)} )}
<Button
fullWidth
size='lg'
onClick={handleTaskCompletion}
disabled={isPendingCompletion}
color={isPendingCompletion ? 'danger' : 'success'}
startDecorator={<Check />}
>
<Box>Mark as done</Box>
</Button>
<Divider sx={{ my: 0.5 }}>or</Divider>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -433,6 +421,22 @@ const ChoreView = () => {
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
<Button
fullWidth
size='lg'
onClick={handleTaskCompletion}
disabled={isPendingCompletion}
color={isPendingCompletion ? 'danger' : 'success'}
startDecorator={<Check />}
sx={
{
flex: 4,
}
}
>
<Box>Mark as done</Box>
</Button>
<Button <Button
fullWidth fullWidth
size='lg' size='lg'
@ -454,9 +458,26 @@ const ChoreView = () => {
}) })
}} }}
startDecorator={<SwitchAccessShortcut />} startDecorator={<SwitchAccessShortcut />}
sx={
{
flex: 1,
}
}
> >
<Box>Skip</Box> <Box>Skip</Box>
</Button> </Button>
</Box>
<Divider sx={{ my: 0.5 }}>More</Divider>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
gap: 1,
alignContent: 'center',
justifyContent: 'center',
}}
>
<Button <Button
startDecorator={<History />} startDecorator={<History />}
size='lg' size='lg'
@ -469,8 +490,21 @@ const ChoreView = () => {
> >
History History
</Button> </Button>
</Box>
<Button
startDecorator={<Edit />}
size='lg'
color='primary'
variant='outlined'
fullWidth
onClick={() => {
navigate(`/chores/${choreId}/edit`)
}}
>
Edit
</Button>
</Box>
<Snackbar <Snackbar
open={isPendingCompletion} open={isPendingCompletion}
endDecorator={ endDecorator={

View file

@ -3,7 +3,6 @@ import {
Check, Check,
Delete, Delete,
Edit, Edit,
HowToReg,
KeyboardDoubleArrowUp, KeyboardDoubleArrowUp,
LocalOffer, LocalOffer,
ManageSearch, ManageSearch,
@ -39,7 +38,11 @@ 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 { MarkChoreComplete, SkipChore } from '../../utils/Fetcher' import {
MarkChoreComplete,
SkipChore,
UpdateChoreAssignee,
} 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'
@ -219,7 +222,14 @@ const ChoreCard = ({
}) })
} }
const handleAssigneChange = assigneeId => { const handleAssigneChange = assigneeId => {
// TODO: Implement assignee change UpdateChoreAssignee(chore.id, assigneeId).then(response => {
if (response.ok) {
response.json().then(data => {
const newChore = data.res
onChoreUpdate(newChore, 'assigned')
})
}
})
} }
const handleCompleteWithNote = note => { const handleCompleteWithNote = note => {
Fetch(`${API_URL}/chores/${chore.id}/do`, { Fetch(`${API_URL}/chores/${chore.id}/do`, {
@ -305,8 +315,28 @@ const ChoreCard = ({
return 'Yearly' return 'Yearly'
} else if (chore.frequencyType === 'days_of_the_week') { } else if (chore.frequencyType === 'days_of_the_week') {
let days = JSON.parse(chore.frequencyMetadata).days let days = JSON.parse(chore.frequencyMetadata).days
if (days.length > 4) {
const allDays = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
]
const selectedDays = days.map(d => moment().day(d).format('dddd'))
const notSelectedDay = allDays.filter(
day => !selectedDays.includes(day),
)
const notSelectedShortdays = notSelectedDay.map(d =>
moment().day(d).format('ddd'),
)
return `Daily except ${notSelectedShortdays.join(', ')}`
} else {
days = days.map(d => moment().day(d).format('ddd')) days = days.map(d => moment().day(d).format('ddd'))
return days.join(', ') return days.join(', ')
}
} else if (chore.frequencyType === 'day_of_the_month') { } else if (chore.frequencyType === 'day_of_the_month') {
let freqData = JSON.parse(chore.frequencyMetadata) let freqData = JSON.parse(chore.frequencyMetadata)
const months = freqData.months.map(m => moment().month(m).format('MMM')) const months = freqData.months.map(m => moment().month(m).format('MMM'))
@ -547,10 +577,6 @@ const ChoreCard = ({
<RecordVoiceOver /> <RecordVoiceOver />
Delegate to someone else Delegate to someone else
</MenuItem> </MenuItem>
<MenuItem>
<HowToReg />
Complete as someone else
</MenuItem>
<Divider /> <Divider />
<MenuItem <MenuItem
onClick={() => { onClick={() => {
@ -620,10 +646,13 @@ const ChoreCard = ({
options={performers} options={performers}
displayKey='displayName' displayKey='displayName'
title={`Delegate to someone else`} title={`Delegate to someone else`}
placeholder={'Select a performer'}
onClose={() => { onClose={() => {
setIsChangeAssigneeModalOpen(false) setIsChangeAssigneeModalOpen(false)
}} }}
onSave={handleAssigneChange} onSave={selected => {
handleAssigneChange(selected.id)
}}
/> />
<ConfirmationModal config={confirmModelConfig} /> <ConfirmationModal config={confirmModelConfig} />
<TextModal <TextModal

View file

@ -1,13 +1,17 @@
import { Add, CancelRounded, EditCalendar } from '@mui/icons-material'
import { import {
Badge, Add,
CancelRounded,
EditCalendar,
FilterAlt,
FilterAltOff,
} from '@mui/icons-material'
import {
Box, Box,
Checkbox, Chip,
Container, Container,
IconButton, IconButton,
Input, Input,
List, List,
ListItem,
Menu, Menu,
MenuItem, MenuItem,
Snackbar, Snackbar,
@ -192,95 +196,27 @@ const MyChores = () => {
My Chores My Chores
</Typography> */} </Typography> */}
{/* <Sheet> */} {/* <Sheet> */}
<List
orientation='horizontal'
wrap
sx={{
'--List-gap': '8px',
'--ListItem-radius': '20px',
'--ListItem-minHeight': '32px',
'--ListItem-gap': '4px',
mt: 0.2,
}}
>
{['All', 'Overdue', 'Due today', 'Due in week'].map(filter => (
<Badge
key={filter}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
variant='outlined'
color={selectedFilter === filter ? 'primary' : 'neutral'}
badgeContent={FILTERS[filter](chores).length}
badgeInset={'5px'}
>
<ListItem key={filter}>
<Checkbox
key={'checkbox' + filter}
label={filter}
onClick={() => handleSelectedFilter(filter)}
checked={filter === selectedFilter}
disableIcon
overlay
size='sm'
/>
</ListItem>
</Badge>
))}
<ListItem onClick={handleFilterMenuOpen}>
<Checkbox key='checkboxAll' label='⋮' disableIcon overlay size='lg' />
</ListItem>
<Menu
ref={menuRef}
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleFilterMenuClose}
>
<MenuItem
onClick={() => {
setFilteredChores(
FILTERS['Assigned To Me'](chores, userProfile.id),
)
setSelectedFilter('Assigned To Me')
handleFilterMenuClose()
}}
>
Assigned to me
</MenuItem>
<MenuItem
onClick={() => {
setFilteredChores(
FILTERS['Created By Me'](chores, userProfile.id),
)
setSelectedFilter('Created By Me')
handleFilterMenuClose()
}}
>
Created by me
</MenuItem>
<MenuItem
onClick={() => {
setFilteredChores(FILTERS['No Due Date'](chores, userProfile.id))
setSelectedFilter('No Due Date')
handleFilterMenuClose()
}}
>
No Due Date
</MenuItem>
</Menu>
</List>
{/* Search box to filter */} {/* Search box to filter */}
<Box> <Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignContent: 'center',
alignItems: 'center',
gap: 1,
}}
>
<Input <Input
placeholder='Search' placeholder='Search'
value={searchTerm} value={searchTerm}
fullWidth
sx={{ sx={{
mt: 1, mt: 1,
mb: 1, mb: 1,
borderRadius: 20, borderRadius: 24,
// border: '1px solid', // border: '1px solid',
height: 24,
borderColor: 'text.disabled', borderColor: 'text.disabled',
padding: 1, padding: 1,
}} }}
@ -296,6 +232,73 @@ const MyChores = () => {
) )
} }
/> />
<IconButton
onClick={handleFilterMenuOpen}
variant='outlined'
color={
selectedFilter && selectedFilter != 'All' ? 'primary' : 'neutral'
}
size='sm'
sx={{
height: 24,
borderRadius: 24,
}}
>
{selectedFilter && selectedFilter != 'All' ? (
<FilterAltOff />
) : (
<FilterAlt />
)}
</IconButton>
<List
orientation='horizontal'
wrap
sx={{
// '--List-gap': '8px',
// '--ListItem-radius': '20px',
// '--ListItem-minHeight': '32px',
// '--ListItem-gap': '4px',
mt: 0.2,
}}
>
{/* <Checkbox
key='checkboxAll'
label=''
disableIcon
overlay
size='sm'
/> */}
<Menu
ref={menuRef}
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleFilterMenuClose}
>
{Object.keys(FILTERS).map(filter => (
<MenuItem
key={filter}
onClick={() => {
const filterFunction = FILTERS[filter]
const filteredChores =
filterFunction.length === 2
? filterFunction(chores, userProfile.id)
: filterFunction(chores)
setFilteredChores(filteredChores)
setSelectedFilter(filter)
handleFilterMenuClose()
}}
>
{filter}
<Chip color={selectedFilter === filter ? 'primary' : 'neutral'}>
{FILTERS[filter].length === 2
? FILTERS[filter](chores, userProfile.id).length
: FILTERS[filter](chores).length}
</Chip>
</MenuItem>
))}
</Menu>
</List>
</Box> </Box>
{/* </Sheet> */} {/* </Sheet> */}

View file

@ -0,0 +1,40 @@
import { Button, Snackbar } from '@mui/joy'
import Cookies from 'js-cookie'
import { useEffect, useState } from 'react'
const CookiePermissionSnackbar = () => {
useEffect(() => {
const cookiePermission = Cookies.get('cookies_permission')
if (cookiePermission !== 'true') {
setOpen(true)
}
}, [])
const [open, setOpen] = useState(false)
const handleClose = () => {
Cookies.set('cookies_permission', 'true')
setOpen(false)
}
return (
<Snackbar
open={open}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={(event, reason) => {
if (reason === 'clickaway') {
return
}
// Cookies.set('cookies_permission', 'true')
handleClose()
}}
>
We use cookies to ensure you get the best experience on our website.
<Button variant='soft' onClick={handleClose}>
Accept
</Button>
</Snackbar>
)
}
export default CookiePermissionSnackbar

View file

@ -110,18 +110,6 @@ function FeaturesSection() {
return ( return (
<Container sx={{ textAlign: 'center' }}> <Container sx={{ textAlign: 'center' }}>
<Typography level='h4' mt={2} mb={4}>
Donetick is under development
</Typography>
<Container maxWidth={'lg'} sx={{ mb: 8 }}>
<Typography level='body-md' color='neutral'>
Donetick is beta software. and is still under development. thing may
change, break, or disappear at any time. Please use it at your own
risk and discretion. I will do my best to keep the service running
</Typography>
</Container>
<Typography level='h4' mt={2} mb={4}> <Typography level='h4' mt={2} mb={4}>
Why Donetick? Why Donetick?
</Typography> </Typography>

View file

@ -4,6 +4,7 @@ import Box from '@mui/joy/Box'
import Link from '@mui/joy/Link' import Link from '@mui/joy/Link'
import Typography from '@mui/joy/Typography' import Typography from '@mui/joy/Typography'
import * as React from 'react' import * as React from 'react'
import { version } from '../../../package.json'
function Footer() { function Footer() {
return ( return (
@ -47,17 +48,6 @@ function Footer() {
tick tick
</span> </span>
</Typography> </Typography>
<span
style={{
fontSize: 12,
fontWeight: 700,
position: 'relative',
top: 12,
right: 45,
}}
>
Beta
</span>
</Box> </Box>
</Box> </Box>
<Box> <Box>
@ -65,11 +55,11 @@ function Footer() {
Github Github
</Typography> </Typography>
<Link <Link
href='https://github.com/donetick/core' href='https://github.com/donetick/donetick'
level='body2' level='body2'
sx={{ display: 'block' }} sx={{ display: 'block' }}
> >
Core(Backend) Donetick
</Link> </Link>
<Link <Link
href='https://github.com/donetick/frontend' href='https://github.com/donetick/frontend'
@ -86,11 +76,18 @@ function Footer() {
Home Assistant Addon Home Assistant Addon
</Link> </Link>
<Link <Link
href='https://github.com/orgs/Donetick/packages' href='https://github.com/orgs/donetick/packages'
level='body2' level='body2'
sx={{ display: 'block' }} sx={{ display: 'block' }}
> >
Packages Docker Images
</Link>
<Link
href='https://github.com/donetick/donetick/releases'
level='body2'
sx={{ display: 'block' }}
>
Releases
</Link> </Link>
</Box> </Box>
<Box> <Box>
@ -107,6 +104,9 @@ function Footer() {
<Link disabled={true} level='body2' sx={{ display: 'block' }}> <Link disabled={true} level='body2' sx={{ display: 'block' }}>
Changelog(soon) Changelog(soon)
</Link> </Link>
<Link disabled={true} level='body2' sx={{ display: 'block' }}>
V{version}
</Link>
</Box> </Box>
{/* <Box> {/* <Box>
<Typography level='body2' fontWeight='bold' mb={1}> <Typography level='body2' fontWeight='bold' mb={1}>

View file

@ -0,0 +1,171 @@
import {
AddHome,
AutoAwesome,
Cloud,
GitHub,
InstallMobile,
Storage,
} from '@mui/icons-material'
import { Box, Button, Card, Grid, styled, Typography } from '@mui/joy'
import { useNavigate } from 'react-router-dom'
const IconContainer = styled('div')({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
minWidth: '60px',
height: '60px',
marginRight: '16px',
})
const ButtonContainer = styled('div')({
display: 'flex',
justifyContent: 'center',
marginTop: 'auto',
})
function StartOptionCard({ icon: Icon, title, description, button, index }) {
return (
<Card
variant='plain'
sx={{
p: 2,
display: 'flex',
minHeight: '300px',
py: 4,
flexDirection: 'column',
justifyContent: 'space-between',
}}
data-aos-delay={100 * index}
data-aos-anchor='[data-aos-id-getting-started-container]'
data-aos='fade-up'
>
{/* Changes are within this div */}
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
}}
>
<IconContainer>{Icon}</IconContainer>
<Typography level='h4' textAlign={'center'}>
{title}
</Typography>
</Box>
<Typography level='body-md' color='neutral' lineHeight={1.6}>
{description}
</Typography>
<ButtonContainer>{button}</ButtonContainer>
</Card>
)
}
const GettingStarted = () => {
const navigate = useNavigate()
const information = [
{
title: 'Donetick Web',
icon: <Cloud style={{ fontSize: '48px' }} />,
description:
'The easiest way! just create account and start using DoneTick',
button: (
<Button
size='lg'
fullWidth
startDecorator={<AutoAwesome />}
onClick={() => {
navigate('/my/chores')
}}
>
Start Now!
</Button>
),
},
{
title: 'Selfhosted',
icon: <Storage style={{ fontSize: '48px' }} />,
description: 'Download the binary and manage your own DoneTick instance',
button: (
<Button
size='lg'
fullWidth
startDecorator={<GitHub />}
onClick={() => {
window.open(
'https://github.com/donetick/donetick/releases',
'_blank',
)
}}
>
Github Releases
</Button>
),
},
{
title: 'Hassio Addon',
icon: <AddHome style={{ fontSize: '48px' }} />,
description:
'have Home Assistant? install DoneTick as a Home Assistant Addon with single click',
button: (
<Button
size='lg'
fullWidth
startDecorator={<InstallMobile />}
onClick={() => {
window.open(
'https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fdonetick%2Fhassio-addons',
)
}}
>
Add Addon
</Button>
),
},
]
return (
<Box
sx={{
alignContent: 'center',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
mt: 2,
}}
>
<Typography level='h4' mt={2} mb={4}>
Getting Started
</Typography>
<Box maxWidth={'lg'} sx={{ mb: 8 }}>
<Typography level='body-md' color='neutral'>
you can start using DoneTick in multiple ways, easiest way is to use
Donetick Web and you can start in seconds, or if you are into
selfhosting you can download the binary and run it on your own server,
or if you are using Home Assistant you can install DoneTick as a Home
Assistant Addon
</Typography>
<div data-aos-id-getting-started-container>
<Grid container spacing={4} mt={4}>
{information.map((info, index) => (
<Grid item xs={12} md={4} key={index}>
<StartOptionCard
icon={info.icon}
title={info.title}
description={info.description}
button={info.button}
/>
</Grid>
))}
</Grid>
</div>
</Box>
</Box>
)
}
export default GettingStarted

View file

@ -61,17 +61,6 @@ const HomeHero = () => {
> >
tick tick
</span> </span>
<span
style={{
fontSize: 20,
fontWeight: 700,
position: 'relative',
top: 12,
right: 45,
}}
>
Beta
</span>
</Typography> </Typography>
</Box> </Box>
) )
@ -167,7 +156,7 @@ const HomeHero = () => {
className='hover:scale-105' className='hover:scale-105'
onClick={() => { onClick={() => {
// new window open to https://github.com/Donetick: // new window open to https://github.com/Donetick:
window.open('https://github.com/donetick', '_blank') window.open('https://github.com/donetick/donetick', '_blank')
}} }}
startDecorator={<GitHub />} startDecorator={<GitHub />}
> >

View file

@ -3,12 +3,14 @@ import AOS from 'aos'
import 'aos/dist/aos.css' import 'aos/dist/aos.css'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import CookiePermissionSnackbar from './CookiePermissionSnackbar'
import DemoAssignee from './DemoAssignee' import DemoAssignee from './DemoAssignee'
import DemoHistory from './DemoHistory' import DemoHistory from './DemoHistory'
import DemoMyChore from './DemoMyChore' import DemoMyChore from './DemoMyChore'
import DemoScheduler from './DemoScheduler' import DemoScheduler from './DemoScheduler'
import FeaturesSection from './FeaturesSection' import FeaturesSection from './FeaturesSection'
import Footer from './Footer' import Footer from './Footer'
import GettingStarted from './GettingStarted'
import HomeHero from './HomeHero' import HomeHero from './HomeHero'
const Landing = () => { const Landing = () => {
const Navigate = useNavigate() const Navigate = useNavigate()
@ -39,6 +41,8 @@ const Landing = () => {
<DemoHistory /> <DemoHistory />
</Grid> </Grid>
<FeaturesSection /> <FeaturesSection />
<GettingStarted />
{/* <PricingSection /> */} {/* <PricingSection /> */}
<Box <Box
sx={{ sx={{
@ -49,7 +53,7 @@ const Landing = () => {
mb: 5, mb: 5,
}} }}
></Box> ></Box>
<CookiePermissionSnackbar />
<Footer /> <Footer />
</Container> </Container>
) )

View file

@ -9,7 +9,7 @@ import {
} from '@mui/joy' } from '@mui/joy'
import React from 'react' import React from 'react'
function SelectModal({ isOpen, onClose, onSave, options, title, displayKey }) { function SelectModal({ isOpen, onClose, onSave, options, title, displayKey,placeholder }) {
const [selected, setSelected] = React.useState(null) const [selected, setSelected] = React.useState(null)
const handleSave = () => { const handleSave = () => {
onSave(options.find(item => item.id === selected)) onSave(options.find(item => item.id === selected))
@ -20,7 +20,7 @@ function SelectModal({ isOpen, onClose, onSave, options, title, displayKey }) {
<Modal open={isOpen} onClose={onClose}> <Modal open={isOpen} onClose={onClose}>
<ModalDialog> <ModalDialog>
<Typography variant='h4'>{title}</Typography> <Typography variant='h4'>{title}</Typography>
<Select> <Select placeholder={placeholder}>
{options.map((item, index) => ( {options.map((item, index) => (
<Option <Option
value={item.id} value={item.id}

View file

@ -30,11 +30,11 @@ const links = [
label: 'Home', label: 'Home',
icon: <HomeOutlined />, icon: <HomeOutlined />,
}, },
{ // {
to: '/chores', // to: '/chores',
label: 'Desktop View', // label: 'Desktop View',
icon: <ListAltRounded />, // icon: <ListAltRounded />,
}, // },
{ {
to: '/things', to: '/things',
label: 'Things', label: 'Things',
@ -114,17 +114,6 @@ const NavBar = () => {
tick tick
</span> </span>
</Typography> </Typography>
<span
style={{
fontSize: 12,
fontWeight: 700,
position: 'relative',
top: 12,
right: 45,
}}
>
Beta
</span>
</Box> </Box>
<Drawer <Drawer
open={drawerOpen} open={drawerOpen}

View file

@ -14,7 +14,10 @@ export default defineConfig({
'safari-pinned-tab.svg', 'safari-pinned-tab.svg',
'mstile-150x150.png', 'mstile-150x150.png',
], ],
injectManifest: true, injectManifest: {
globPatterns: ['**/*.{js,css,html,png,svg}'],
globIgnores: ['index.html'],
},
manifest: { manifest: {
name: 'Donetick: Simplify Tasks & Chores, Together.', name: 'Donetick: Simplify Tasks & Chores, Together.',
short_name: 'Donetick', short_name: 'Donetick',
@ -55,6 +58,10 @@ export default defineConfig({
background_color: '#ffffff', background_color: '#ffffff',
display: 'standalone', display: 'standalone',
}, },
workbox: {
skipWaiting: true, // Force the waiting service worker to become the active service worker
clientsClaim: true, // Take control of uncontrolled clients as soon as the service worker becomes active
},
}), }),
], ],