- 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:
commit
2d2cf6d378
18 changed files with 508 additions and 175 deletions
1
.env
1
.env
|
@ -1,2 +1,3 @@
|
|||
VITE_APP_API_URL=http://localhost:2021
|
||||
VITE_IS_LANDING_DEFAULT=false
|
||||
VITE_OPENREPLAY_PROJECT_KEY=
|
||||
|
|
50
package-lock.json
generated
50
package-lock.json
generated
|
@ -1,18 +1,19 @@
|
|||
{
|
||||
"name": "fe-template",
|
||||
"version": "0.1.61",
|
||||
"name": "donetick",
|
||||
"version": "0.1.72",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "fe-template",
|
||||
"version": "0.1.61",
|
||||
"name": "donetick",
|
||||
"version": "0.1.72",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.15.2",
|
||||
"@mui/joy": "^5.0.0-beta.20",
|
||||
"@mui/material": "^5.15.2",
|
||||
"@openreplay/tracker": "^14.0.4",
|
||||
"@tanstack/react-query": "^5.17.0",
|
||||
"aos": "^2.3.4",
|
||||
"dotenv": "^16.4.5",
|
||||
|
@ -2481,6 +2482,12 @@
|
|||
"@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": {
|
||||
"version": "5.0.0-beta.29",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.29.tgz",
|
||||
|
@ -2839,6 +2846,20 @@
|
|||
"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": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
@ -4747,6 +4768,15 @@
|
|||
"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": {
|
||||
"version": "1.22.3",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
|
||||
|
@ -5327,6 +5357,12 @@
|
|||
"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": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
@ -8064,6 +8100,12 @@
|
|||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"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": {
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "donetick",
|
||||
"private": true,
|
||||
"version": "0.1.65",
|
||||
"version": "0.1.74",
|
||||
"type": "module",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
|
@ -25,6 +25,7 @@
|
|||
"@mui/icons-material": "^5.15.2",
|
||||
"@mui/joy": "^5.0.0-beta.20",
|
||||
"@mui/material": "^5.15.2",
|
||||
"@openreplay/tracker": "^14.0.4",
|
||||
"@tanstack/react-query": "^5.17.0",
|
||||
"aos": "^2.3.4",
|
||||
"dotenv": "^16.4.5",
|
||||
|
|
10
src/App.jsx
10
src/App.jsx
|
@ -1,5 +1,6 @@
|
|||
import NavBar from '@/views/components/NavBar'
|
||||
import { Button, Snackbar, Typography, useColorScheme } from '@mui/joy'
|
||||
import Tracker from '@openreplay/tracker'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import { useRegisterSW } from 'virtual:pwa-register/react'
|
||||
|
@ -19,6 +20,8 @@ const remove = className => {
|
|||
const intervalMS = 5 * 60 * 1000 // 5 minutes
|
||||
|
||||
function App() {
|
||||
startOpenReplay()
|
||||
|
||||
const { mode, systemMode } = useColorScheme()
|
||||
const [userProfile, setUserProfile] = useState(null)
|
||||
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
|
||||
|
|
|
@ -83,6 +83,15 @@ const SkipChore = id => {
|
|||
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 => {
|
||||
return Fetch(`${API_URL}/chores/`, {
|
||||
method: 'POST',
|
||||
|
@ -306,4 +315,5 @@ export {
|
|||
UpdateChoreHistory,
|
||||
UpdateThingState,
|
||||
UpdateUserDetails,
|
||||
UpdateChoreAssignee,
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
FormHelperText,
|
||||
Input,
|
||||
Sheet,
|
||||
Snackbar,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import React from 'react'
|
||||
|
@ -25,6 +26,8 @@ const SignupView = () => {
|
|||
const [emailError, setEmailError] = React.useState('')
|
||||
const [displayNameError, setDisplayNameError] = React.useState('')
|
||||
const [error, setError] = React.useState(null)
|
||||
const [snackbarOpen, setSnackbarOpen] = React.useState(false)
|
||||
const [snackbarMessage, setSnackbarMessage] = React.useState('')
|
||||
const handleLogin = (username, password) => {
|
||||
login(username, password).then(response => {
|
||||
if (response.status === 200) {
|
||||
|
@ -39,6 +42,7 @@ const SignupView = () => {
|
|||
})
|
||||
} else {
|
||||
console.log('Login failed', response)
|
||||
|
||||
// Navigate('/login')
|
||||
}
|
||||
})
|
||||
|
@ -101,7 +105,10 @@ const SignupView = () => {
|
|||
handleLogin(username, password)
|
||||
} else {
|
||||
console.log('Signup failed')
|
||||
setError('Signup failed')
|
||||
response.json().then(res => {
|
||||
setError(res.error)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -256,6 +263,14 @@ const SignupView = () => {
|
|||
</Button>
|
||||
</Sheet>
|
||||
</Box>
|
||||
<Snackbar
|
||||
open={error !== null}
|
||||
onClose={() => setError(null)}
|
||||
autoHideDuration={5000}
|
||||
message={error}
|
||||
>
|
||||
{error}
|
||||
</Snackbar>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
CancelScheduleSend,
|
||||
Check,
|
||||
Checklist,
|
||||
Edit,
|
||||
History,
|
||||
PeopleAlt,
|
||||
Person,
|
||||
|
@ -258,7 +259,7 @@ const ChoreView = () => {
|
|||
>
|
||||
<Grid container spacing={1}>
|
||||
{infoCards.map((detail, index) => (
|
||||
<Grid item xs={4} key={index}>
|
||||
<Grid item xs={6} key={index}>
|
||||
{/* divider between the list items: */}
|
||||
|
||||
<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
|
||||
sx={{
|
||||
display: 'flex',
|
||||
|
@ -433,6 +421,22 @@ const ChoreView = () => {
|
|||
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
|
||||
fullWidth
|
||||
size='lg'
|
||||
|
@ -454,9 +458,26 @@ const ChoreView = () => {
|
|||
})
|
||||
}}
|
||||
startDecorator={<SwitchAccessShortcut />}
|
||||
sx={
|
||||
{
|
||||
flex: 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Box>Skip</Box>
|
||||
</Button>
|
||||
</Box>
|
||||
<Divider sx={{ my: 0.5 }}>More</Divider>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 1,
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
startDecorator={<History />}
|
||||
size='lg'
|
||||
|
@ -469,8 +490,21 @@ const ChoreView = () => {
|
|||
>
|
||||
History
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
startDecorator={<Edit />}
|
||||
size='lg'
|
||||
color='primary'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
navigate(`/chores/${choreId}/edit`)
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
|
||||
</Box>
|
||||
<Snackbar
|
||||
open={isPendingCompletion}
|
||||
endDecorator={
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
Check,
|
||||
Delete,
|
||||
Edit,
|
||||
HowToReg,
|
||||
KeyboardDoubleArrowUp,
|
||||
LocalOffer,
|
||||
ManageSearch,
|
||||
|
@ -39,7 +38,11 @@ import moment from 'moment'
|
|||
import React, { useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { API_URL } from '../../Config'
|
||||
import { MarkChoreComplete, SkipChore } from '../../utils/Fetcher'
|
||||
import {
|
||||
MarkChoreComplete,
|
||||
SkipChore,
|
||||
UpdateChoreAssignee,
|
||||
} from '../../utils/Fetcher'
|
||||
import { Fetch } from '../../utils/TokenManager'
|
||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||
import DateModal from '../Modals/Inputs/DateModal'
|
||||
|
@ -219,7 +222,14 @@ const ChoreCard = ({
|
|||
})
|
||||
}
|
||||
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 => {
|
||||
Fetch(`${API_URL}/chores/${chore.id}/do`, {
|
||||
|
@ -305,8 +315,28 @@ const ChoreCard = ({
|
|||
return 'Yearly'
|
||||
} else if (chore.frequencyType === 'days_of_the_week') {
|
||||
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'))
|
||||
return days.join(', ')
|
||||
}
|
||||
} else if (chore.frequencyType === 'day_of_the_month') {
|
||||
let freqData = JSON.parse(chore.frequencyMetadata)
|
||||
const months = freqData.months.map(m => moment().month(m).format('MMM'))
|
||||
|
@ -547,10 +577,6 @@ const ChoreCard = ({
|
|||
<RecordVoiceOver />
|
||||
Delegate to someone else
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<HowToReg />
|
||||
Complete as someone else
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
|
@ -620,10 +646,13 @@ const ChoreCard = ({
|
|||
options={performers}
|
||||
displayKey='displayName'
|
||||
title={`Delegate to someone else`}
|
||||
placeholder={'Select a performer'}
|
||||
onClose={() => {
|
||||
setIsChangeAssigneeModalOpen(false)
|
||||
}}
|
||||
onSave={handleAssigneChange}
|
||||
onSave={selected => {
|
||||
handleAssigneChange(selected.id)
|
||||
}}
|
||||
/>
|
||||
<ConfirmationModal config={confirmModelConfig} />
|
||||
<TextModal
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import { Add, CancelRounded, EditCalendar } from '@mui/icons-material'
|
||||
import {
|
||||
Badge,
|
||||
Add,
|
||||
CancelRounded,
|
||||
EditCalendar,
|
||||
FilterAlt,
|
||||
FilterAltOff,
|
||||
} from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
Chip,
|
||||
Container,
|
||||
IconButton,
|
||||
Input,
|
||||
List,
|
||||
ListItem,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Snackbar,
|
||||
|
@ -192,95 +196,27 @@ const MyChores = () => {
|
|||
My Chores
|
||||
</Typography> */}
|
||||
{/* <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 */}
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder='Search'
|
||||
value={searchTerm}
|
||||
fullWidth
|
||||
sx={{
|
||||
mt: 1,
|
||||
mb: 1,
|
||||
borderRadius: 20,
|
||||
borderRadius: 24,
|
||||
// border: '1px solid',
|
||||
height: 24,
|
||||
borderColor: 'text.disabled',
|
||||
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>
|
||||
|
||||
{/* </Sheet> */}
|
||||
|
|
40
src/views/Landing/CookiePermissionSnackbar.jsx
Normal file
40
src/views/Landing/CookiePermissionSnackbar.jsx
Normal 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
|
|
@ -110,18 +110,6 @@ function FeaturesSection() {
|
|||
|
||||
return (
|
||||
<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}>
|
||||
Why Donetick?
|
||||
</Typography>
|
||||
|
|
|
@ -4,6 +4,7 @@ import Box from '@mui/joy/Box'
|
|||
import Link from '@mui/joy/Link'
|
||||
import Typography from '@mui/joy/Typography'
|
||||
import * as React from 'react'
|
||||
import { version } from '../../../package.json'
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
|
@ -47,17 +48,6 @@ function Footer() {
|
|||
tick✓
|
||||
</span>
|
||||
</Typography>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
position: 'relative',
|
||||
top: 12,
|
||||
right: 45,
|
||||
}}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
|
@ -65,11 +55,11 @@ function Footer() {
|
|||
Github
|
||||
</Typography>
|
||||
<Link
|
||||
href='https://github.com/donetick/core'
|
||||
href='https://github.com/donetick/donetick'
|
||||
level='body2'
|
||||
sx={{ display: 'block' }}
|
||||
>
|
||||
Core(Backend)
|
||||
Donetick
|
||||
</Link>
|
||||
<Link
|
||||
href='https://github.com/donetick/frontend'
|
||||
|
@ -86,11 +76,18 @@ function Footer() {
|
|||
Home Assistant Addon
|
||||
</Link>
|
||||
<Link
|
||||
href='https://github.com/orgs/Donetick/packages'
|
||||
href='https://github.com/orgs/donetick/packages'
|
||||
level='body2'
|
||||
sx={{ display: 'block' }}
|
||||
>
|
||||
Packages
|
||||
Docker Images
|
||||
</Link>
|
||||
<Link
|
||||
href='https://github.com/donetick/donetick/releases'
|
||||
level='body2'
|
||||
sx={{ display: 'block' }}
|
||||
>
|
||||
Releases
|
||||
</Link>
|
||||
</Box>
|
||||
<Box>
|
||||
|
@ -107,6 +104,9 @@ function Footer() {
|
|||
<Link disabled={true} level='body2' sx={{ display: 'block' }}>
|
||||
Changelog(soon)
|
||||
</Link>
|
||||
<Link disabled={true} level='body2' sx={{ display: 'block' }}>
|
||||
V{version}
|
||||
</Link>
|
||||
</Box>
|
||||
{/* <Box>
|
||||
<Typography level='body2' fontWeight='bold' mb={1}>
|
||||
|
|
171
src/views/Landing/GettingStarted.jsx
Normal file
171
src/views/Landing/GettingStarted.jsx
Normal 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
|
|
@ -61,17 +61,6 @@ const HomeHero = () => {
|
|||
>
|
||||
tick
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontWeight: 700,
|
||||
position: 'relative',
|
||||
top: 12,
|
||||
right: 45,
|
||||
}}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
|
@ -167,7 +156,7 @@ const HomeHero = () => {
|
|||
className='hover:scale-105'
|
||||
onClick={() => {
|
||||
// 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 />}
|
||||
>
|
||||
|
|
|
@ -3,12 +3,14 @@ import AOS from 'aos'
|
|||
import 'aos/dist/aos.css'
|
||||
import { useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import CookiePermissionSnackbar from './CookiePermissionSnackbar'
|
||||
import DemoAssignee from './DemoAssignee'
|
||||
import DemoHistory from './DemoHistory'
|
||||
import DemoMyChore from './DemoMyChore'
|
||||
import DemoScheduler from './DemoScheduler'
|
||||
import FeaturesSection from './FeaturesSection'
|
||||
import Footer from './Footer'
|
||||
import GettingStarted from './GettingStarted'
|
||||
import HomeHero from './HomeHero'
|
||||
const Landing = () => {
|
||||
const Navigate = useNavigate()
|
||||
|
@ -39,6 +41,8 @@ const Landing = () => {
|
|||
<DemoHistory />
|
||||
</Grid>
|
||||
<FeaturesSection />
|
||||
<GettingStarted />
|
||||
|
||||
{/* <PricingSection /> */}
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -49,7 +53,7 @@ const Landing = () => {
|
|||
mb: 5,
|
||||
}}
|
||||
></Box>
|
||||
|
||||
<CookiePermissionSnackbar />
|
||||
<Footer />
|
||||
</Container>
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@mui/joy'
|
||||
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 handleSave = () => {
|
||||
onSave(options.find(item => item.id === selected))
|
||||
|
@ -20,7 +20,7 @@ function SelectModal({ isOpen, onClose, onSave, options, title, displayKey }) {
|
|||
<Modal open={isOpen} onClose={onClose}>
|
||||
<ModalDialog>
|
||||
<Typography variant='h4'>{title}</Typography>
|
||||
<Select>
|
||||
<Select placeholder={placeholder}>
|
||||
{options.map((item, index) => (
|
||||
<Option
|
||||
value={item.id}
|
||||
|
|
|
@ -30,11 +30,11 @@ const links = [
|
|||
label: 'Home',
|
||||
icon: <HomeOutlined />,
|
||||
},
|
||||
{
|
||||
to: '/chores',
|
||||
label: 'Desktop View',
|
||||
icon: <ListAltRounded />,
|
||||
},
|
||||
// {
|
||||
// to: '/chores',
|
||||
// label: 'Desktop View',
|
||||
// icon: <ListAltRounded />,
|
||||
// },
|
||||
{
|
||||
to: '/things',
|
||||
label: 'Things',
|
||||
|
@ -114,17 +114,6 @@ const NavBar = () => {
|
|||
tick✓
|
||||
</span>
|
||||
</Typography>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
position: 'relative',
|
||||
top: 12,
|
||||
right: 45,
|
||||
}}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
</Box>
|
||||
<Drawer
|
||||
open={drawerOpen}
|
||||
|
|
|
@ -14,7 +14,10 @@ export default defineConfig({
|
|||
'safari-pinned-tab.svg',
|
||||
'mstile-150x150.png',
|
||||
],
|
||||
injectManifest: true,
|
||||
injectManifest: {
|
||||
globPatterns: ['**/*.{js,css,html,png,svg}'],
|
||||
globIgnores: ['index.html'],
|
||||
},
|
||||
manifest: {
|
||||
name: 'Donetick: Simplify Tasks & Chores, Together.',
|
||||
short_name: 'Donetick',
|
||||
|
@ -55,6 +58,10 @@ export default defineConfig({
|
|||
background_color: '#ffffff',
|
||||
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
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue