feat: Add notification types and enhance color management
- Introduced NOTIFICATION_TYPE constants for pre-due, due date, and post-due notifications in Colors.jsx. - Updated HistoryCard.jsx to utilize TASK_COLOR for chip colors based on task status. - Created DemoCalendar.jsx to showcase a visual task calendar with sample chore data. - Enhanced DemoMyChore.jsx to include status for demo tasks. - Added DemoNotificationTemplate.jsx for demonstrating smart notification scheduling. - Revamped FeaturesSection.jsx to reflect updated feature descriptions and icons. - Updated Footer.jsx to link to the correct API reference documentation. - Improved GettingStarted.jsx with new mobile app download options and enhanced layout. - Integrated TabletInstallationSection into Landing.jsx for better user guidance.
This commit is contained in:
@@ -13,6 +13,7 @@ import Option from '@mui/joy/Option'
|
|||||||
import Select from '@mui/joy/Select'
|
import Select from '@mui/joy/Select'
|
||||||
import Typography from '@mui/joy/Typography'
|
import Typography from '@mui/joy/Typography'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { NOTIFICATION_TYPE, TASK_COLOR } from '../utils/Colors'
|
||||||
|
|
||||||
const timeUnits = [
|
const timeUnits = [
|
||||||
{ label: 'Mins', value: 'm' },
|
{ label: 'Mins', value: 'm' },
|
||||||
@@ -307,8 +308,10 @@ const NotificationTemplate = ({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
height: 90,
|
height: 90,
|
||||||
bgcolor: 'background.level1',
|
bgcolor: 'background.surface',
|
||||||
borderRadius: 'md',
|
borderRadius: 'md',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'neutral.outlinedBorder',
|
||||||
p: 2,
|
p: 2,
|
||||||
transition: 'height 0.3s ease',
|
transition: 'height 0.3s ease',
|
||||||
// '&:hover': {
|
// '&:hover': {
|
||||||
@@ -368,11 +371,11 @@ const NotificationTemplate = ({
|
|||||||
left: `${percent}%`,
|
left: `${percent}%`,
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
color:
|
color:
|
||||||
Number(n.value) < 0
|
convertToMinutes(n.value, n.unit) < 0
|
||||||
? 'primary.600'
|
? TASK_COLOR.SCHEDULED
|
||||||
: Number(n.value) === 0
|
: convertToMinutes(n.value, n.unit) === 0
|
||||||
? 'warning.600'
|
? TASK_COLOR.TODAY
|
||||||
: 'success.600',
|
: TASK_COLOR.LATE,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -399,13 +402,6 @@ const NotificationTemplate = ({
|
|||||||
}
|
}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant={'solid'}
|
variant={'solid'}
|
||||||
color={
|
|
||||||
Number(n.value) < 0
|
|
||||||
? 'success'
|
|
||||||
: Number(n.value) === 0
|
|
||||||
? 'warning'
|
|
||||||
: 'danger'
|
|
||||||
}
|
|
||||||
sx={{
|
sx={{
|
||||||
'--Badge-paddingX': '4px',
|
'--Badge-paddingX': '4px',
|
||||||
'--Badge-minHeight': '16px',
|
'--Badge-minHeight': '16px',
|
||||||
@@ -413,6 +409,15 @@ const NotificationTemplate = ({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
'& .MuiBadge-badge': {
|
||||||
|
background:
|
||||||
|
convertToMinutes(n.value, n.unit) < 0
|
||||||
|
? NOTIFICATION_TYPE.PREDUE
|
||||||
|
: convertToMinutes(n.value, n.unit) === 0
|
||||||
|
? NOTIFICATION_TYPE.DUE_DATE
|
||||||
|
: NOTIFICATION_TYPE.POSTDUE,
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NotificationsIcon
|
<NotificationsIcon
|
||||||
@@ -433,36 +438,7 @@ const NotificationTemplate = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box>
|
||||||
sx={
|
|
||||||
{
|
|
||||||
// border: '1px solid',
|
|
||||||
// borderColor: 'neutral.outlinedBorder',
|
|
||||||
// borderRadius: 2,
|
|
||||||
// p: 3,
|
|
||||||
// maxWidth: 500,
|
|
||||||
// bgcolor: 'background.body',
|
|
||||||
// boxShadow: 'sm',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{/* <Typography level={'h4'} sx={{ mb: 2 }}>
|
|
||||||
Schedule Name
|
|
||||||
</Typography> */}
|
|
||||||
|
|
||||||
{/* Template Name Field */}
|
|
||||||
{/* <Box sx={{ mb: 3 }}>
|
|
||||||
<Typography level={'body2'} sx={{ mb: 1, fontWeight: 'md' }}>
|
|
||||||
Template Name
|
|
||||||
</Typography>
|
|
||||||
<Input
|
|
||||||
value={templateName}
|
|
||||||
onChange={handleNameChange}
|
|
||||||
placeholder='Enter template name'
|
|
||||||
sx={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
</Box> */}
|
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert
|
<Alert
|
||||||
variant='soft'
|
variant='soft'
|
||||||
@@ -486,104 +462,200 @@ const NotificationTemplate = ({
|
|||||||
const badgeNumber = notificationIndexMap[idx]
|
const badgeNumber = notificationIndexMap[idx]
|
||||||
const uiRep = getUIRepresentation(n)
|
const uiRep = getUIRepresentation(n)
|
||||||
|
|
||||||
|
const getNotificationColors = value => {
|
||||||
|
if (Number(value) < 0) {
|
||||||
|
return {
|
||||||
|
bgColor: NOTIFICATION_TYPE.PREDUE,
|
||||||
|
lightBg: `${NOTIFICATION_TYPE.PREDUE}20`,
|
||||||
|
borderColor: `${NOTIFICATION_TYPE.PREDUE}40`,
|
||||||
|
textColor: NOTIFICATION_TYPE.PREDUE,
|
||||||
|
}
|
||||||
|
} else if (Number(value) === 0) {
|
||||||
|
return {
|
||||||
|
bgColor: NOTIFICATION_TYPE.DUE_DATE,
|
||||||
|
lightBg: `${NOTIFICATION_TYPE.DUE_DATE}20`,
|
||||||
|
borderColor: `${NOTIFICATION_TYPE.DUE_DATE}40`,
|
||||||
|
textColor: NOTIFICATION_TYPE.DUE_DATE,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
bgColor: NOTIFICATION_TYPE.POSTDUE,
|
||||||
|
lightBg: `${NOTIFICATION_TYPE.POSTDUE}20`,
|
||||||
|
borderColor: `${NOTIFICATION_TYPE.POSTDUE}40`,
|
||||||
|
textColor: NOTIFICATION_TYPE.POSTDUE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = getNotificationColors(n.value)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
key={idx}
|
key={idx}
|
||||||
sx={{ display: 'flex', alignItems: 'center', mb: 1 }}
|
sx={{
|
||||||
|
mb: 1.5,
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'neutral.outlinedBorder',
|
||||||
|
background: 'background.surface',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1.5,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
className='notification-icon'
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'inline-flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'start',
|
justifyContent: 'center',
|
||||||
width: 18,
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 6,
|
||||||
|
background: `${colors.bgColor}15`,
|
||||||
|
color: colors.textColor,
|
||||||
|
position: 'relative',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
'& svg': {
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
badgeContent={badgeNumber}
|
badgeContent={badgeNumber}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
sx={{
|
sx={{
|
||||||
'--Badge-minHeight': '20px',
|
'--Badge-minHeight': '16px',
|
||||||
'--Badge-fontSize': '0.75rem',
|
'--Badge-fontSize': '0.7rem',
|
||||||
|
'--Badge-paddingX': '5px',
|
||||||
|
position: 'absolute',
|
||||||
|
top: -6,
|
||||||
|
right: -6,
|
||||||
|
'& .MuiBadge-badge': {
|
||||||
|
background: colors.bgColor,
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
color={
|
/>
|
||||||
Number(n.value) < 0
|
<NotificationsIcon />
|
||||||
? 'success'
|
|
||||||
: Number(n.value) === 0
|
|
||||||
? 'warning'
|
|
||||||
: 'danger'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{/* Empty box to attach badge to */}
|
|
||||||
</Badge>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Select
|
|
||||||
value={uiRep.timing}
|
<Box
|
||||||
onChange={(_, value) => handleChange(idx, 'timing', value)}
|
|
||||||
sx={{ mr: 1, minWidth: 100 }}
|
|
||||||
size={'sm'}
|
|
||||||
>
|
|
||||||
{timingOptions.map(opt => (
|
|
||||||
<Option key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
<Input
|
|
||||||
type={'number'}
|
|
||||||
min={0}
|
|
||||||
value={uiRep.displayValue}
|
|
||||||
disabled={uiRep.timing === 'ondue'}
|
|
||||||
onChange={e =>
|
|
||||||
handleChange(idx, 'displayValue', e.target.value)
|
|
||||||
}
|
|
||||||
sx={{
|
sx={{
|
||||||
width: 80,
|
minWidth: 0,
|
||||||
mr: 1,
|
flex: '1',
|
||||||
opacity: uiRep.timing === 'ondue' ? 0.6 : 1,
|
display: { xs: 'none', md: 'flex' }, // Show only on md and up
|
||||||
}}
|
}}
|
||||||
size={'sm'}
|
>
|
||||||
placeholder='0'
|
<Typography
|
||||||
/>
|
level='body-sm'
|
||||||
<Select
|
sx={{
|
||||||
value={n.unit}
|
fontWeight: 600,
|
||||||
disabled={uiRep.timing === 'ondue'}
|
color: 'text.primary',
|
||||||
onChange={(_, value) => handleChange(idx, 'unit', value)}
|
fontSize: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getRelativeLabel(n)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mr: 1,
|
display: 'flex',
|
||||||
minWidth: 80,
|
alignItems: 'center',
|
||||||
opacity: uiRep.timing === 'ondue' ? 0.6 : 1,
|
gap: 1,
|
||||||
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
size={'sm'}
|
|
||||||
>
|
>
|
||||||
{timeUnits.map(opt => (
|
<Select
|
||||||
<Option key={opt.value} value={opt.value}>
|
value={uiRep.timing}
|
||||||
{opt.label}
|
onChange={(_, value) => handleChange(idx, 'timing', value)}
|
||||||
</Option>
|
sx={{ minWidth: 80 }}
|
||||||
))}
|
size={'sm'}
|
||||||
</Select>
|
>
|
||||||
<IconButton
|
{timingOptions.map(opt => (
|
||||||
onClick={() => removeNotification(idx)}
|
<Option key={opt.value} value={opt.value}>
|
||||||
disabled={notifications.length === 1}
|
{opt.label}
|
||||||
color={'danger'}
|
</Option>
|
||||||
size={'sm'}
|
))}
|
||||||
sx={{ mr: 1 }}
|
</Select>
|
||||||
variant={'soft'}
|
<Input
|
||||||
>
|
type={'number'}
|
||||||
<DeleteIcon fontSize={'small'} />
|
min={0}
|
||||||
</IconButton>
|
value={uiRep.displayValue}
|
||||||
|
disabled={uiRep.timing === 'ondue'}
|
||||||
|
onChange={e =>
|
||||||
|
handleChange(idx, 'displayValue', e.target.value)
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
width: 60,
|
||||||
|
opacity: uiRep.timing === 'ondue' ? 0.6 : 1,
|
||||||
|
}}
|
||||||
|
size={'sm'}
|
||||||
|
placeholder='0'
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
value={n.unit}
|
||||||
|
disabled={uiRep.timing === 'ondue'}
|
||||||
|
onChange={(_, value) => handleChange(idx, 'unit', value)}
|
||||||
|
sx={{
|
||||||
|
minWidth: 70,
|
||||||
|
opacity: uiRep.timing === 'ondue' ? 0.6 : 1,
|
||||||
|
}}
|
||||||
|
size={'sm'}
|
||||||
|
>
|
||||||
|
{timeUnits.map(opt => (
|
||||||
|
<Option key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => removeNotification(idx)}
|
||||||
|
disabled={notifications.length === 1}
|
||||||
|
color={'danger'}
|
||||||
|
size={'sm'}
|
||||||
|
variant={'soft'}
|
||||||
|
sx={{
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'scale(1.1)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon fontSize={'small'} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<Box sx={{ display: 'flex', gap: 1, mt: 1, mb: 2, flexWrap: 'wrap' }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 1.5,
|
||||||
|
mt: 1,
|
||||||
|
mb: 2,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => addSmartNotification('reminder')}
|
onClick={() => addSmartNotification('reminder')}
|
||||||
disabled={notifications.length >= maxNotifications}
|
disabled={notifications.length >= maxNotifications}
|
||||||
startDecorator={<AddIcon />}
|
startDecorator={<AddIcon />}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant={'outlined'}
|
variant={'outlined'}
|
||||||
color={'primary'}
|
sx={{
|
||||||
|
borderRadius: 6,
|
||||||
|
fontWeight: 500,
|
||||||
|
borderColor: `${TASK_COLOR.SCHEDULED}60`,
|
||||||
|
color: TASK_COLOR.SCHEDULED,
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: TASK_COLOR.SCHEDULED,
|
||||||
|
background: `${TASK_COLOR.SCHEDULED}10`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Reminder
|
Reminder
|
||||||
</Button>
|
</Button>
|
||||||
@@ -596,7 +668,16 @@ const NotificationTemplate = ({
|
|||||||
startDecorator={<AddIcon />}
|
startDecorator={<AddIcon />}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant={'outlined'}
|
variant={'outlined'}
|
||||||
color={'warning'}
|
sx={{
|
||||||
|
borderRadius: 6,
|
||||||
|
fontWeight: 500,
|
||||||
|
borderColor: `${NOTIFICATION_TYPE.DUE_DATE}60`,
|
||||||
|
color: NOTIFICATION_TYPE.DUE_DATE,
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: NOTIFICATION_TYPE.DUE_DATE,
|
||||||
|
background: `${NOTIFICATION_TYPE.DUE_DATE}10`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Due Alert
|
Due Alert
|
||||||
</Button>
|
</Button>
|
||||||
@@ -606,28 +687,45 @@ const NotificationTemplate = ({
|
|||||||
startDecorator={<AddIcon />}
|
startDecorator={<AddIcon />}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant={'outlined'}
|
variant={'outlined'}
|
||||||
color={'danger'}
|
sx={{
|
||||||
|
borderRadius: 6,
|
||||||
|
fontWeight: 500,
|
||||||
|
borderColor: `${NOTIFICATION_TYPE.POSTDUE}60`,
|
||||||
|
color: NOTIFICATION_TYPE.POSTDUE,
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: NOTIFICATION_TYPE.POSTDUE,
|
||||||
|
background: `${NOTIFICATION_TYPE.POSTDUE}10`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Follow-up
|
Follow-up
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{showSaveDefault && (
|
{showSaveDefault && (
|
||||||
<Button
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
variant='outlined'
|
<Button
|
||||||
size='sm'
|
variant='outlined'
|
||||||
color='neutral'
|
size='sm'
|
||||||
// sx={{ ml: 'auto', mt: 0.5 }}
|
color='neutral'
|
||||||
startDecorator={<Save />}
|
startDecorator={<Save />}
|
||||||
onClick={() => {
|
sx={{
|
||||||
localStorage.setItem(
|
borderRadius: 6,
|
||||||
'defaultNotificationTemplate',
|
fontWeight: 500,
|
||||||
JSON.stringify(notifications),
|
'&:hover': {
|
||||||
)
|
background: 'neutral.softHoverBg',
|
||||||
setShowSaveDefault(false)
|
},
|
||||||
}}
|
}}
|
||||||
>
|
onClick={() => {
|
||||||
Save as Default for Future Tasks
|
localStorage.setItem(
|
||||||
</Button>
|
'defaultNotificationTemplate',
|
||||||
|
JSON.stringify(notifications),
|
||||||
|
)
|
||||||
|
setShowSaveDefault(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save as Default
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showTimeline && renderTimeline()}
|
{showTimeline && renderTimeline()}
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ export const COLORS = {
|
|||||||
sand: '#d7ccc8',
|
sand: '#d7ccc8',
|
||||||
white: '#FFFFFF',
|
white: '#FFFFFF',
|
||||||
}
|
}
|
||||||
|
export const NOTIFICATION_TYPE = {
|
||||||
|
PREDUE: '#4ec1a2',
|
||||||
|
DUE_DATE: '#f6ad55',
|
||||||
|
POSTDUE: '#F03A47',
|
||||||
|
}
|
||||||
|
|
||||||
export const TASK_COLOR = {
|
export const TASK_COLOR = {
|
||||||
COMPLETED: '#4ec1a2',
|
COMPLETED: '#4ec1a2',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import { TASK_COLOR } from '../../utils/Colors.jsx'
|
||||||
|
|
||||||
const getCompletedChip = historyEntry => {
|
const getCompletedChip = historyEntry => {
|
||||||
if (historyEntry.status === 0) {
|
if (historyEntry.status === 0) {
|
||||||
@@ -49,7 +50,7 @@ const getCompletedChip = historyEntry => {
|
|||||||
<Chip
|
<Chip
|
||||||
size='sm'
|
size='sm'
|
||||||
variant='solid'
|
variant='solid'
|
||||||
color='success'
|
sx={{ backgroundColor: TASK_COLOR.COMPLETED, color: 'white' }}
|
||||||
startDecorator={<Check />}
|
startDecorator={<Check />}
|
||||||
>
|
>
|
||||||
On Time
|
On Time
|
||||||
@@ -57,7 +58,12 @@ const getCompletedChip = historyEntry => {
|
|||||||
)
|
)
|
||||||
} else if (performedAt.isBefore(dueDate)) {
|
} else if (performedAt.isBefore(dueDate)) {
|
||||||
return (
|
return (
|
||||||
<Chip size='sm' variant='soft' color='primary' startDecorator={<Check />}>
|
<Chip
|
||||||
|
size='sm'
|
||||||
|
variant='soft'
|
||||||
|
sx={{ backgroundColor: TASK_COLOR.SCHEDULED, color: 'white' }}
|
||||||
|
startDecorator={<Check />}
|
||||||
|
>
|
||||||
Early
|
Early
|
||||||
</Chip>
|
</Chip>
|
||||||
)
|
)
|
||||||
@@ -66,7 +72,7 @@ const getCompletedChip = historyEntry => {
|
|||||||
<Chip
|
<Chip
|
||||||
size='sm'
|
size='sm'
|
||||||
variant='solid'
|
variant='solid'
|
||||||
color='warning'
|
sx={{ backgroundColor: TASK_COLOR.LATE, color: 'white' }}
|
||||||
startDecorator={<Timelapse />}
|
startDecorator={<Timelapse />}
|
||||||
>
|
>
|
||||||
Late
|
Late
|
||||||
|
|||||||
155
src/views/Landing/DemoCalendar.jsx
Normal file
155
src/views/Landing/DemoCalendar.jsx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import { Card, Grid, Typography } from '@mui/joy'
|
||||||
|
import moment from 'moment'
|
||||||
|
import CalendarView from '../components/CalendarView'
|
||||||
|
|
||||||
|
const DemoCalendar = () => {
|
||||||
|
// Generate sample chore data across different dates
|
||||||
|
const generateSampleChores = () => {
|
||||||
|
const today = moment()
|
||||||
|
const chores = []
|
||||||
|
|
||||||
|
// High priority tasks
|
||||||
|
chores.push({
|
||||||
|
id: 1,
|
||||||
|
name: '🧹 Deep Clean Living Room',
|
||||||
|
priority: 1,
|
||||||
|
nextDueDate: today.clone().add(2, 'days').hour(10).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
chores.push({
|
||||||
|
id: 2,
|
||||||
|
name: '🚗 Car Maintenance Check',
|
||||||
|
priority: 1,
|
||||||
|
nextDueDate: today.clone().add(5, 'days').hour(14).minute(30).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Medium priority tasks
|
||||||
|
chores.push({
|
||||||
|
id: 3,
|
||||||
|
name: '🌱 Water Indoor Plants',
|
||||||
|
priority: 2,
|
||||||
|
nextDueDate: today.clone().add(1, 'days').hour(8).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
chores.push({
|
||||||
|
id: 4,
|
||||||
|
name: '🛒 Weekly Grocery Shopping',
|
||||||
|
priority: 2,
|
||||||
|
nextDueDate: today.clone().add(3, 'days').hour(16).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
chores.push({
|
||||||
|
id: 5,
|
||||||
|
name: '📧 Organize Email Inbox',
|
||||||
|
priority: 2,
|
||||||
|
nextDueDate: today.clone().add(7, 'days').hour(11).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Low priority tasks
|
||||||
|
chores.push({
|
||||||
|
id: 6,
|
||||||
|
name: '📚 Organize Bookshelf',
|
||||||
|
priority: 3,
|
||||||
|
nextDueDate: today.clone().add(4, 'days').hour(15).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
chores.push({
|
||||||
|
id: 7,
|
||||||
|
name: '🎨 Paint Bedroom Wall',
|
||||||
|
priority: 3,
|
||||||
|
nextDueDate: today.clone().add(10, 'days').hour(9).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tasks for today
|
||||||
|
chores.push({
|
||||||
|
id: 8,
|
||||||
|
name: '🍽️ Do Dishes',
|
||||||
|
priority: 2,
|
||||||
|
nextDueDate: today.clone().hour(19).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
chores.push({
|
||||||
|
id: 9,
|
||||||
|
name: '🗑️ Take Out Trash',
|
||||||
|
priority: 1,
|
||||||
|
nextDueDate: today.clone().hour(7).minute(30).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tasks with no priority
|
||||||
|
chores.push({
|
||||||
|
id: 10,
|
||||||
|
name: '🎵 Practice Guitar',
|
||||||
|
priority: null,
|
||||||
|
nextDueDate: today.clone().add(6, 'days').hour(18).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Multiple tasks on same day
|
||||||
|
chores.push({
|
||||||
|
id: 11,
|
||||||
|
name: '🧺 Do Laundry',
|
||||||
|
priority: 2,
|
||||||
|
nextDueDate: today.clone().add(2, 'days').hour(12).minute(0).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
chores.push({
|
||||||
|
id: 12,
|
||||||
|
name: '🏃 Morning Jog',
|
||||||
|
priority: 3,
|
||||||
|
nextDueDate: today.clone().add(2, 'days').hour(6).minute(30).toISOString(),
|
||||||
|
assignedTo: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
return chores
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleChores = generateSampleChores()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid item xs={12} sm={7} data-aos-calendar-demo-section>
|
||||||
|
<div
|
||||||
|
data-aos-delay={100}
|
||||||
|
data-aos-anchor='[data-aos-calendar-demo-section]'
|
||||||
|
data-aos='fade-up'
|
||||||
|
>
|
||||||
|
<CalendarView chores={sampleChores} />
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={5} data-aos-calendar-description>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
p: 4,
|
||||||
|
py: 6,
|
||||||
|
height: 'fit-content',
|
||||||
|
}}
|
||||||
|
data-aos-delay={200}
|
||||||
|
data-aos-anchor='[data-aos-calendar-description]'
|
||||||
|
data-aos='fade-left'
|
||||||
|
>
|
||||||
|
<Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
|
||||||
|
Visual Task Calendar
|
||||||
|
</Typography>
|
||||||
|
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
||||||
|
Get a bird's-eye view of all your tasks with the interactive calendar.
|
||||||
|
See priority-coded dots for each day, click to view detailed task lists,
|
||||||
|
and easily track your upcoming responsibilities. The color-coded priority
|
||||||
|
system helps you focus on what matters most.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DemoCalendar
|
||||||
@@ -14,6 +14,7 @@ const DemoMyChore = () => {
|
|||||||
nextDueDate: moment().add(1, 'days').hour(8).minute(0).toISOString(),
|
nextDueDate: moment().add(1, 'days').hour(8).minute(0).toISOString(),
|
||||||
isRolling: false,
|
isRolling: false,
|
||||||
assignedTo: 1,
|
assignedTo: 1,
|
||||||
|
status: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9,
|
id: 9,
|
||||||
@@ -24,6 +25,7 @@ const DemoMyChore = () => {
|
|||||||
nextDueDate: moment().subtract(7, 'day').toISOString(),
|
nextDueDate: moment().subtract(7, 'day').toISOString(),
|
||||||
isRolling: false,
|
isRolling: false,
|
||||||
assignedTo: 1,
|
assignedTo: 1,
|
||||||
|
status: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
|
|||||||
57
src/views/Landing/DemoNotificationTemplate.jsx
Normal file
57
src/views/Landing/DemoNotificationTemplate.jsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Card, Grid, Typography } from '@mui/joy'
|
||||||
|
import NotificationTemplate from '../../components/NotificationTemplate'
|
||||||
|
|
||||||
|
const DemoNotificationTemplate = () => {
|
||||||
|
const demoNotifications = [
|
||||||
|
{ value: -3, unit: 'd' }, // 3 days before
|
||||||
|
{ value: 0, unit: 'm' }, // On due
|
||||||
|
{ value: 1, unit: 'd' }, // 1 day after
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleNotificationChange = data => {
|
||||||
|
// Demo handler - doesn't need to do anything
|
||||||
|
console.log('Demo notification change:', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid item xs={12} sm={7} data-aos-notification-template-list>
|
||||||
|
<div
|
||||||
|
data-aos-delay={100}
|
||||||
|
data-aos-anchor='[data-aos-notification-template-list]'
|
||||||
|
data-aos='fade-up'
|
||||||
|
>
|
||||||
|
<NotificationTemplate
|
||||||
|
value={{ templates: demoNotifications }}
|
||||||
|
onChange={handleNotificationChange}
|
||||||
|
maxNotifications={5}
|
||||||
|
showTimeline={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={5} data-aos-notification-demo-section>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
p: 4,
|
||||||
|
py: 6,
|
||||||
|
height: 'fit-content',
|
||||||
|
}}
|
||||||
|
data-aos-delay={200}
|
||||||
|
data-aos-anchor='[data-aos-notification-demo-section]'
|
||||||
|
data-aos='fade-left'
|
||||||
|
>
|
||||||
|
<Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
|
||||||
|
Smart Notification Scheduling
|
||||||
|
</Typography>
|
||||||
|
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
||||||
|
Set up intelligent reminders for your tasks with flexible timing
|
||||||
|
options. Get notified before, on, or after due dates with
|
||||||
|
customizable intervals.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DemoNotificationTemplate
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Psychology,
|
Psychology,
|
||||||
Schedule,
|
Schedule,
|
||||||
Security,
|
Security,
|
||||||
|
Settings,
|
||||||
Timer,
|
Timer,
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import { Box, Card, Container, Grid, Typography, useTheme } from '@mui/joy'
|
import { Box, Card, Container, Grid, Typography, useTheme } from '@mui/joy'
|
||||||
@@ -19,10 +20,10 @@ const FeaturesSection = () => {
|
|||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
icon: <Schedule />,
|
icon: <Psychology />,
|
||||||
title: 'Smart Scheduling',
|
title: 'Natural Language Input',
|
||||||
description:
|
description:
|
||||||
'Flexible recurring tasks: daily, weekly, monthly, or custom intervals. Choose rolling or fixed schedules and set completion windows to fit any routine.',
|
'Add tasks just by typing. Donetick understands dates, priorities, labels. you can have quickest way to add tasks.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Groups />,
|
icon: <Groups />,
|
||||||
@@ -32,9 +33,21 @@ const FeaturesSection = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <EmojiEvents />,
|
icon: <EmojiEvents />,
|
||||||
title: 'Motivating Gamification',
|
title: 'Gamification and Points',
|
||||||
description:
|
description:
|
||||||
'Earn points, climb leaderboards, and unlock achievements for completing tasks. Make productivity fun and rewarding.',
|
'Earn points, climb leaderboards for completing tasks. You can even require admin approval for points!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <CalendarMonth />,
|
||||||
|
title: 'Visual Calendar View',
|
||||||
|
description:
|
||||||
|
'See all your tasks in a color-coded calendar. Filter by assignee, view daily agendas, and plan with ease.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Schedule />,
|
||||||
|
title: 'Smart Scheduling',
|
||||||
|
description:
|
||||||
|
'Flexible recurring tasks: daily, weekly, monthly, or custom intervals. Choose rolling or fixed schedules and set completion windows to fit any routine.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Assignment />,
|
icon: <Assignment />,
|
||||||
@@ -42,36 +55,6 @@ const FeaturesSection = () => {
|
|||||||
description:
|
description:
|
||||||
'Distribute tasks fairly using smart algorithms: round-robin, least busy, random, or custom rules. No more manual juggling.',
|
'Distribute tasks fairly using smart algorithms: round-robin, least busy, random, or custom rules. No more manual juggling.',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: <Analytics />,
|
|
||||||
title: 'Insightful Analytics',
|
|
||||||
description:
|
|
||||||
'Track progress, view completion history, and spot trends with clear reports and visualizations. Understand your productivity at a glance.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <AutoAwesome />,
|
|
||||||
title: 'Advanced Organization',
|
|
||||||
description:
|
|
||||||
'Break down tasks with subtasks, set priorities, add labels and tags, and use templates for quick setup. Stay organized your way.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Notifications />,
|
|
||||||
title: 'Smart Notifications',
|
|
||||||
description:
|
|
||||||
'Get timely reminders via email, Telegram, push, or webhooks. Customize alerts so you never miss what matters.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Api />,
|
|
||||||
title: 'Seamless Integrations',
|
|
||||||
description:
|
|
||||||
'Connect with REST API, webhooks, and automation tools. Import, export, and trigger actions to fit your workflow.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Security />,
|
|
||||||
title: 'Privacy & Security',
|
|
||||||
description:
|
|
||||||
'Open-source and secure by design. Choose cloud or self-hosting for full control over your data.',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: <Timer />,
|
icon: <Timer />,
|
||||||
title: 'Built-in Time Tracking',
|
title: 'Built-in Time Tracking',
|
||||||
@@ -79,16 +62,40 @@ const FeaturesSection = () => {
|
|||||||
'Track work sessions with a timer, review detailed logs, and analyze productivity patterns for every task.',
|
'Track work sessions with a timer, review detailed logs, and analyze productivity patterns for every task.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Psychology />,
|
icon: <AutoAwesome />,
|
||||||
title: 'Natural Language Input',
|
title: 'Advanced Organization',
|
||||||
description:
|
description:
|
||||||
'Add tasks just by typing. Our AI understands dates, priorities, labels, and more—no forms needed.',
|
'Break down tasks with subtasks, set priorities, add labels and tags, and use templates for quick setup.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <CalendarMonth />,
|
icon: <Settings />,
|
||||||
title: 'Visual Calendar View',
|
title: 'Advanced Task Settings',
|
||||||
description:
|
description:
|
||||||
'See all your tasks in a color-coded calendar. Filter by assignee, view daily agendas, and plan with ease.',
|
'Configure task-specific settings like completion windows, custom point values, and require admin approval for tasks.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Analytics />,
|
||||||
|
title: 'Insightful Analytics',
|
||||||
|
description:
|
||||||
|
'Track progress, view completion history, and spot trends with clear reports and visualizations.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Notifications />,
|
||||||
|
title: 'Smart Notifications',
|
||||||
|
description:
|
||||||
|
'Get timely reminders via Donetick app or other way like Telegram, push, or webhooks.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Api />,
|
||||||
|
title: 'Seamless Integrations',
|
||||||
|
description:
|
||||||
|
'Connect with REST API, webhooks, and automation tools. Import, export, and trigger actions to fit your workflows.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Security />,
|
||||||
|
title: 'Privacy & Security',
|
||||||
|
description:
|
||||||
|
'Open-source and transparent. For security, you can secure your account with 2FA.',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -179,33 +186,6 @@ const FeaturesSection = () => {
|
|||||||
transition: 'opacity 0.4s ease',
|
transition: 'opacity 0.4s ease',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
},
|
},
|
||||||
'&:hover': {
|
|
||||||
transform: { xs: 'translateY(-4px)', sm: 'translateY(-8px)' },
|
|
||||||
boxShadow: {
|
|
||||||
xs: '0 15px 30px rgba(0, 0, 0, 0.08)',
|
|
||||||
sm: '0 25px 50px rgba(0, 0, 0, 0.1)',
|
|
||||||
},
|
|
||||||
borderColor: 'primary.200',
|
|
||||||
'&::before': {
|
|
||||||
transform: 'scaleX(1)',
|
|
||||||
},
|
|
||||||
'&::after': {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
'& .feature-icon': {
|
|
||||||
transform: {
|
|
||||||
xs: 'scale(1.05)',
|
|
||||||
sm: 'scale(1.1) rotate(5deg)',
|
|
||||||
},
|
|
||||||
background:
|
|
||||||
'linear-gradient(135deg, var(--joy-palette-primary-500) 0%, var(--joy-palette-primary-600) 100%)',
|
|
||||||
color: 'primary.50',
|
|
||||||
boxShadow: '0 8px 20px rgba(6, 182, 212, 0.3)',
|
|
||||||
},
|
|
||||||
'& .feature-title': {
|
|
||||||
color: 'primary.600',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'@media (max-width: 600px)': {
|
'@media (max-width: 600px)': {
|
||||||
'&:active': {
|
'&:active': {
|
||||||
transform: 'scale(0.98)',
|
transform: 'scale(0.98)',
|
||||||
@@ -286,62 +266,7 @@ const FeaturesSection = () => {
|
|||||||
mt={{ xs: 8, sm: 10, md: 12 }}
|
mt={{ xs: 8, sm: 10, md: 12 }}
|
||||||
data-aos='fade-up'
|
data-aos='fade-up'
|
||||||
data-aos-duration='800'
|
data-aos-duration='800'
|
||||||
>
|
></Box>
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
p: { xs: 4, sm: 5, md: 6 },
|
|
||||||
borderRadius: { xs: 20, md: 24 },
|
|
||||||
background:
|
|
||||||
'linear-gradient(135deg, rgba(6, 182, 212, 0.05) 0%, rgba(8, 145, 178, 0.08) 100%)',
|
|
||||||
border: '1px solid rgba(6, 182, 212, 0.15)',
|
|
||||||
maxWidth: { xs: '100%', sm: 550, md: 600 },
|
|
||||||
mx: 'auto',
|
|
||||||
position: 'relative',
|
|
||||||
overflow: 'hidden',
|
|
||||||
'&::before': {
|
|
||||||
content: '""',
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
background:
|
|
||||||
'radial-gradient(circle at 50% 50%, rgba(6, 182, 212, 0.1) 0%, transparent 70%)',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
level='h3'
|
|
||||||
sx={{
|
|
||||||
fontSize: { xs: 22, sm: 26, md: 28 },
|
|
||||||
fontWeight: 700,
|
|
||||||
mb: { xs: 1.5, sm: 2 },
|
|
||||||
color: 'text.primary',
|
|
||||||
lineHeight: 1.2,
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Ready to Transform Your Task Management?
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
level='body-lg'
|
|
||||||
sx={{
|
|
||||||
color: 'text.secondary',
|
|
||||||
mb: { xs: 3, sm: 4 },
|
|
||||||
lineHeight: 1.6,
|
|
||||||
fontSize: { xs: 15, sm: 16 },
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 1,
|
|
||||||
px: { xs: 0, sm: 2 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Join thousands who have streamlined their workflows with Donetick's
|
|
||||||
powerful features
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ const Footer = () => {
|
|||||||
{ label: 'Features', href: '#features' },
|
{ label: 'Features', href: '#features' },
|
||||||
{ label: 'Demo', href: '#demo' },
|
{ label: 'Demo', href: '#demo' },
|
||||||
{ label: 'Getting Started', href: '#getting-started' },
|
{ label: 'Getting Started', href: '#getting-started' },
|
||||||
{ label: 'Pricing', href: '/pricing' },
|
|
||||||
],
|
],
|
||||||
resources: [
|
resources: [
|
||||||
{ label: 'Documentation', href: 'https://docs.donetick.com/' },
|
{ label: 'Documentation', href: 'https://docs.donetick.com/' },
|
||||||
{ label: 'API Reference', href: 'https://docs.donetick.com/api' },
|
{
|
||||||
|
label: 'API Reference',
|
||||||
|
href: 'https://docs.donetick.com/advance-settings/api',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Discussions',
|
label: 'Discussions',
|
||||||
href: 'https://github.com/donetick/donetick/discussions',
|
href: 'https://github.com/donetick/donetick/discussions',
|
||||||
|
|||||||
@@ -1,66 +1,132 @@
|
|||||||
import {
|
import {
|
||||||
AddHome,
|
AddHome,
|
||||||
|
Android,
|
||||||
|
Apple,
|
||||||
AutoAwesome,
|
AutoAwesome,
|
||||||
Cloud,
|
Cloud,
|
||||||
GitHub,
|
GitHub,
|
||||||
InstallMobile,
|
InstallMobile,
|
||||||
Storage,
|
Storage,
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import { Box, Button, Card, Grid, styled, Typography } from '@mui/joy'
|
import { Box, Button, Card, Container, Grid, Typography } from '@mui/joy'
|
||||||
import { useNavigate } from 'react-router-dom'
|
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 }) {
|
function StartOptionCard({ icon: Icon, title, description, button, index }) {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
variant='plain'
|
data-aos='fade-up'
|
||||||
|
data-aos-delay={index * 100}
|
||||||
|
data-aos-duration='800'
|
||||||
|
variant='outlined'
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: { xs: 3, sm: 4, md: 4 },
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: { xs: 16, md: 20 },
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
background: 'background.surface',
|
||||||
|
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
cursor: 'pointer',
|
||||||
|
minHeight: { xs: 300, sm: 320 },
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
minHeight: '300px',
|
|
||||||
py: 4,
|
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
'&::before': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: '4px',
|
||||||
|
background:
|
||||||
|
'linear-gradient(90deg, var(--joy-palette-primary-400) 0%, var(--joy-palette-primary-600) 100%)',
|
||||||
|
transform: 'scaleX(0)',
|
||||||
|
transformOrigin: 'left',
|
||||||
|
transition: 'transform 0.4s ease',
|
||||||
|
},
|
||||||
|
'&::after': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background:
|
||||||
|
'linear-gradient(135deg, rgba(6, 182, 212, 0.02) 0%, rgba(8, 145, 178, 0.04) 100%)',
|
||||||
|
opacity: 0,
|
||||||
|
transition: 'opacity 0.4s ease',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
'@media (max-width: 600px)': {
|
||||||
|
'&:active': {
|
||||||
|
transform: 'scale(0.98)',
|
||||||
|
},
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
data-aos-delay={100 * index}
|
|
||||||
data-aos-anchor='[data-aos-id-getting-started-container]'
|
|
||||||
data-aos='fade-up'
|
|
||||||
>
|
>
|
||||||
{/* Changes are within this div */}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
flexDirection: 'column',
|
||||||
|
textAlign: 'center',
|
||||||
|
mb: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconContainer>{Icon}</IconContainer>
|
<Box
|
||||||
|
className='option-icon'
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: { xs: 60, sm: 80 },
|
||||||
|
height: { xs: 60, sm: 80 },
|
||||||
|
borderRadius: { xs: 16, sm: 20 },
|
||||||
|
background:
|
||||||
|
'linear-gradient(135deg, var(--joy-palette-primary-50) 0%, var(--joy-palette-primary-100) 100%)',
|
||||||
|
color: 'primary.500',
|
||||||
|
mb: 3,
|
||||||
|
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
'& svg': {
|
||||||
|
fontSize: { xs: 32, sm: 40 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Icon}
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Typography level='h4' textAlign={'center'}>
|
<Typography
|
||||||
|
className='option-title'
|
||||||
|
level='title-lg'
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: 20, sm: 24 },
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'text.primary',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
transition: 'color 0.3s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography level='body-md' color='neutral' lineHeight={1.6}>
|
<Typography
|
||||||
|
level='body-md'
|
||||||
|
sx={{
|
||||||
|
color: 'text.secondary',
|
||||||
|
lineHeight: 1.6,
|
||||||
|
fontSize: { xs: 14, sm: 15 },
|
||||||
|
textAlign: 'center',
|
||||||
|
mb: 3,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{description}
|
{description}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<ButtonContainer>{button}</ButtonContainer>
|
<Box sx={{ mt: 'auto' }}>{button}</Box>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -70,7 +136,7 @@ const GettingStarted = () => {
|
|||||||
const information = [
|
const information = [
|
||||||
{
|
{
|
||||||
title: 'Donetick Web',
|
title: 'Donetick Web',
|
||||||
icon: <Cloud style={{ fontSize: '48px' }} />,
|
icon: <Cloud />,
|
||||||
description:
|
description:
|
||||||
'The easiest way! Just create account and start using Donetick',
|
'The easiest way! Just create account and start using Donetick',
|
||||||
button: (
|
button: (
|
||||||
@@ -88,7 +154,7 @@ const GettingStarted = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Selfhosted',
|
title: 'Selfhosted',
|
||||||
icon: <Storage style={{ fontSize: '48px' }} />,
|
icon: <Storage />,
|
||||||
description: 'Download the binary and manage your own Donetick instance',
|
description: 'Download the binary and manage your own Donetick instance',
|
||||||
button: (
|
button: (
|
||||||
<Button
|
<Button
|
||||||
@@ -108,7 +174,7 @@ const GettingStarted = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Hassio Addon',
|
title: 'Hassio Addon',
|
||||||
icon: <AddHome style={{ fontSize: '48px' }} />,
|
icon: <AddHome />,
|
||||||
description:
|
description:
|
||||||
'Have Home Assistant? Install Donetick as a Home Assistant Addon with single click',
|
'Have Home Assistant? Install Donetick as a Home Assistant Addon with single click',
|
||||||
button: (
|
button: (
|
||||||
@@ -127,44 +193,272 @@ const GettingStarted = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
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 }}>
|
return (
|
||||||
<Typography level='body-md' color='neutral'>
|
<Container maxWidth='xl' sx={{ py: { xs: 6, sm: 8, md: 12 } }}>
|
||||||
You can start using Donetick in multiple ways, the easiest of which is
|
{/* Section Header */}
|
||||||
to use Donetick Web so you can get started in seconds, or if you are
|
<Box textAlign='center' mb={{ xs: 6, md: 8 }} data-aos='fade-up'>
|
||||||
into selfhosting you can download the binary and run it on your own
|
<Typography
|
||||||
server, or if you are using Home Assistant you can install Donetick as
|
level='h2'
|
||||||
a Home Assistant Addon
|
sx={{
|
||||||
|
fontSize: { xs: 28, sm: 36, md: 42, lg: 48 },
|
||||||
|
fontWeight: 700,
|
||||||
|
mb: { xs: 2, md: 3 },
|
||||||
|
color: 'text.primary',
|
||||||
|
lineHeight: { xs: 1.2, md: 1.1 },
|
||||||
|
background:
|
||||||
|
'linear-gradient(135deg, var(--joy-palette-primary-500) 0%, var(--joy-palette-primary-600) 100%)',
|
||||||
|
backgroundClip: 'text',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Get Started Today
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
level='body-lg'
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: 16, sm: 17, md: 18, lg: 19 },
|
||||||
|
color: 'text.secondary',
|
||||||
|
maxWidth: { xs: '100%', sm: 600, md: 700 },
|
||||||
|
mx: 'auto',
|
||||||
|
lineHeight: 1.6,
|
||||||
|
px: { xs: 1, sm: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Ready to transform your household management? Start with our mobile
|
||||||
|
app for the best experience, or choose from our other convenient
|
||||||
|
options.
|
||||||
</Typography>
|
</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>
|
||||||
</Box>
|
|
||||||
|
{/* Mobile App CTA Section - Primary */}
|
||||||
|
<Box
|
||||||
|
textAlign='center'
|
||||||
|
mb={{ xs: 8, sm: 10, md: 12 }}
|
||||||
|
data-aos='fade-up'
|
||||||
|
data-aos-duration='800'
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: { xs: 5, sm: 6, md: 7 },
|
||||||
|
borderRadius: { xs: 20, md: 24 },
|
||||||
|
background:
|
||||||
|
'linear-gradient(135deg, rgba(6, 182, 212, 0.08) 0%, rgba(8, 145, 178, 0.12) 100%)',
|
||||||
|
border: '2px solid rgba(6, 182, 212, 0.2)',
|
||||||
|
maxWidth: { xs: '100%', sm: 650, md: 700 },
|
||||||
|
mx: 'auto',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.1)',
|
||||||
|
'&::before': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
level='body-lg'
|
||||||
|
sx={{
|
||||||
|
color: 'text.secondary',
|
||||||
|
mb: { xs: 4, sm: 5 },
|
||||||
|
lineHeight: 1.6,
|
||||||
|
fontSize: { xs: 16, sm: 17 },
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
px: { xs: 0, sm: 2 },
|
||||||
|
maxWidth: 500,
|
||||||
|
mx: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Get the full Donetick experience with notifications, realtime
|
||||||
|
updates, and seamless task management on the go
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
|
gap: { xs: 3, sm: 4 },
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* App Store Badge */}
|
||||||
|
<Box
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
'https://apps.apple.com/app/apple-store/id6742807441?pt=127258663&ct=website&mt=8',
|
||||||
|
'_blank',
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
background: '#000000',
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '12px 24px',
|
||||||
|
minWidth: { xs: 200, sm: 180 },
|
||||||
|
height: 60,
|
||||||
|
border: '1px solid #333',
|
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Apple sx={{ color: 'white', fontSize: 40, mr: 2 }} />
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
level='body-xs'
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 1,
|
||||||
|
mb: 0.5,
|
||||||
|
fontWeight: 400,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download on the
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
level='title-md'
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 18,
|
||||||
|
lineHeight: 1,
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
App Store
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Google Play Badge */}
|
||||||
|
<Box
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
'https://github.com/donetick/donetick/releases',
|
||||||
|
'_blank',
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
background:
|
||||||
|
'linear-gradient(135deg, #000000 0%, #1a1a1a 100%)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '12px 24px',
|
||||||
|
minWidth: { xs: 200, sm: 180 },
|
||||||
|
height: 60,
|
||||||
|
border: '1px solid #333',
|
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mr: 2,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Android sx={{ color: 'white', fontSize: 40 }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
level='body-xs'
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 1,
|
||||||
|
mb: 0.5,
|
||||||
|
fontWeight: 400,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download for
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
level='title-md'
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 18,
|
||||||
|
lineHeight: 1,
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Android
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Alternative Options Section */}
|
||||||
|
<Box textAlign='center' mb={{ xs: 4, md: 6 }} data-aos='fade-up'>
|
||||||
|
<Typography
|
||||||
|
level='h4'
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: 20, sm: 22, md: 24 },
|
||||||
|
fontWeight: 600,
|
||||||
|
mb: { xs: 1, md: 1.5 },
|
||||||
|
color: 'text.primary',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Other Ways to Get Started
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
level='body-md'
|
||||||
|
sx={{
|
||||||
|
color: 'text.secondary',
|
||||||
|
maxWidth: 500,
|
||||||
|
mx: 'auto',
|
||||||
|
fontSize: { xs: 14, sm: 15 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Prefer a different setup? Choose from these alternatives
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Options Grid */}
|
||||||
|
<Grid container spacing={{ xs: 3, sm: 4, md: 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}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ 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 DemoNotificationTemplate from './DemoNotificationTemplate'
|
||||||
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 GettingStarted from './GettingStarted'
|
||||||
import HomeHero from './HomeHero'
|
import HomeHero from './HomeHero'
|
||||||
|
import TabletInstallationSection from './TabletInstallationSection'
|
||||||
const Landing = () => {
|
const Landing = () => {
|
||||||
const Navigate = useNavigate()
|
const Navigate = useNavigate()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -37,13 +39,13 @@ const Landing = () => {
|
|||||||
<DemoMyChore />
|
<DemoMyChore />
|
||||||
<DemoAssignee />
|
<DemoAssignee />
|
||||||
<DemoScheduler />
|
<DemoScheduler />
|
||||||
|
<DemoNotificationTemplate />
|
||||||
|
|
||||||
<DemoHistory />
|
<DemoHistory />
|
||||||
</Grid>
|
</Grid>
|
||||||
<FeaturesSection />
|
<FeaturesSection />
|
||||||
|
<TabletInstallationSection />
|
||||||
<GettingStarted />
|
<GettingStarted />
|
||||||
|
|
||||||
{/* <PricingSection /> */}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
Reference in New Issue
Block a user