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:
Mo Tarbin
2025-09-04 01:49:19 -04:00
parent d704588624
commit 057c5f7ec8
10 changed files with 874 additions and 328 deletions

View File

@@ -13,6 +13,7 @@ import Option from '@mui/joy/Option'
import Select from '@mui/joy/Select'
import Typography from '@mui/joy/Typography'
import { useCallback, useEffect, useState } from 'react'
import { NOTIFICATION_TYPE, TASK_COLOR } from '../utils/Colors'
const timeUnits = [
{ label: 'Mins', value: 'm' },
@@ -307,8 +308,10 @@ const NotificationTemplate = ({
flexDirection: 'column',
position: 'relative',
height: 90,
bgcolor: 'background.level1',
bgcolor: 'background.surface',
borderRadius: 'md',
border: '1px solid',
borderColor: 'neutral.outlinedBorder',
p: 2,
transition: 'height 0.3s ease',
// '&:hover': {
@@ -368,11 +371,11 @@ const NotificationTemplate = ({
left: `${percent}%`,
transform: 'translateX(-50%)',
color:
Number(n.value) < 0
? 'primary.600'
: Number(n.value) === 0
? 'warning.600'
: 'success.600',
convertToMinutes(n.value, n.unit) < 0
? TASK_COLOR.SCHEDULED
: convertToMinutes(n.value, n.unit) === 0
? TASK_COLOR.TODAY
: TASK_COLOR.LATE,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
@@ -399,13 +402,6 @@ const NotificationTemplate = ({
}
size={'sm'}
variant={'solid'}
color={
Number(n.value) < 0
? 'success'
: Number(n.value) === 0
? 'warning'
: 'danger'
}
sx={{
'--Badge-paddingX': '4px',
'--Badge-minHeight': '16px',
@@ -413,6 +409,15 @@ const NotificationTemplate = ({
display: 'flex',
alignItems: '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
@@ -433,36 +438,7 @@ const NotificationTemplate = ({
}
return (
<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> */}
<Box>
{error && (
<Alert
variant='soft'
@@ -486,104 +462,200 @@ const NotificationTemplate = ({
const badgeNumber = notificationIndexMap[idx]
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 (
<Box
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
className='notification-icon'
sx={{
display: 'flex',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'start',
width: 18,
justifyContent: 'center',
width: 32,
height: 32,
borderRadius: 6,
background: `${colors.bgColor}15`,
color: colors.textColor,
position: 'relative',
flexShrink: 0,
'& svg': {
fontSize: 16,
},
}}
>
<Badge
badgeContent={badgeNumber}
size={'sm'}
sx={{
'--Badge-minHeight': '20px',
'--Badge-fontSize': '0.75rem',
'--Badge-minHeight': '16px',
'--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
? 'success'
: Number(n.value) === 0
? 'warning'
: 'danger'
}
>
{/* Empty box to attach badge to */}
</Badge>
/>
<NotificationsIcon />
</Box>
<Select
value={uiRep.timing}
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)
}
<Box
sx={{
width: 80,
mr: 1,
opacity: uiRep.timing === 'ondue' ? 0.6 : 1,
minWidth: 0,
flex: '1',
display: { xs: 'none', md: 'flex' }, // Show only on md and up
}}
size={'sm'}
placeholder='0'
/>
<Select
value={n.unit}
disabled={uiRep.timing === 'ondue'}
onChange={(_, value) => handleChange(idx, 'unit', value)}
>
<Typography
level='body-sm'
sx={{
fontWeight: 600,
color: 'text.primary',
fontSize: 14,
}}
>
{getRelativeLabel(n)}
</Typography>
</Box>
<Box
sx={{
mr: 1,
minWidth: 80,
opacity: uiRep.timing === 'ondue' ? 0.6 : 1,
display: 'flex',
alignItems: 'center',
gap: 1,
flexShrink: 0,
}}
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'}
sx={{ mr: 1 }}
variant={'soft'}
>
<DeleteIcon fontSize={'small'} />
</IconButton>
<Select
value={uiRep.timing}
onChange={(_, value) => handleChange(idx, 'timing', value)}
sx={{ minWidth: 80 }}
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={{
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 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
onClick={() => addSmartNotification('reminder')}
disabled={notifications.length >= maxNotifications}
startDecorator={<AddIcon />}
size={'sm'}
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
</Button>
@@ -596,7 +668,16 @@ const NotificationTemplate = ({
startDecorator={<AddIcon />}
size={'sm'}
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
</Button>
@@ -606,28 +687,45 @@ const NotificationTemplate = ({
startDecorator={<AddIcon />}
size={'sm'}
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
</Button>
</Box>
{showSaveDefault && (
<Button
variant='outlined'
size='sm'
color='neutral'
// sx={{ ml: 'auto', mt: 0.5 }}
startDecorator={<Save />}
onClick={() => {
localStorage.setItem(
'defaultNotificationTemplate',
JSON.stringify(notifications),
)
setShowSaveDefault(false)
}}
>
Save as Default for Future Tasks
</Button>
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button
variant='outlined'
size='sm'
color='neutral'
startDecorator={<Save />}
sx={{
borderRadius: 6,
fontWeight: 500,
'&:hover': {
background: 'neutral.softHoverBg',
},
}}
onClick={() => {
localStorage.setItem(
'defaultNotificationTemplate',
JSON.stringify(notifications),
)
setShowSaveDefault(false)
}}
>
Save as Default
</Button>
</Box>
)}
{showTimeline && renderTimeline()}

View File

@@ -53,6 +53,11 @@ export const COLORS = {
sand: '#d7ccc8',
white: '#FFFFFF',
}
export const NOTIFICATION_TYPE = {
PREDUE: '#4ec1a2',
DUE_DATE: '#f6ad55',
POSTDUE: '#F03A47',
}
export const TASK_COLOR = {
COMPLETED: '#4ec1a2',

View File

@@ -22,6 +22,7 @@ import {
Typography,
} from '@mui/joy'
import moment from 'moment'
import { TASK_COLOR } from '../../utils/Colors.jsx'
const getCompletedChip = historyEntry => {
if (historyEntry.status === 0) {
@@ -49,7 +50,7 @@ const getCompletedChip = historyEntry => {
<Chip
size='sm'
variant='solid'
color='success'
sx={{ backgroundColor: TASK_COLOR.COMPLETED, color: 'white' }}
startDecorator={<Check />}
>
On Time
@@ -57,7 +58,12 @@ const getCompletedChip = historyEntry => {
)
} else if (performedAt.isBefore(dueDate)) {
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
</Chip>
)
@@ -66,7 +72,7 @@ const getCompletedChip = historyEntry => {
<Chip
size='sm'
variant='solid'
color='warning'
sx={{ backgroundColor: TASK_COLOR.LATE, color: 'white' }}
startDecorator={<Timelapse />}
>
Late

View 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

View File

@@ -14,6 +14,7 @@ const DemoMyChore = () => {
nextDueDate: moment().add(1, 'days').hour(8).minute(0).toISOString(),
isRolling: false,
assignedTo: 1,
status: 0,
},
{
id: 9,
@@ -24,6 +25,7 @@ const DemoMyChore = () => {
nextDueDate: moment().subtract(7, 'day').toISOString(),
isRolling: false,
assignedTo: 1,
status: 0,
},
{
id: 6,

View 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

View File

@@ -10,6 +10,7 @@ import {
Psychology,
Schedule,
Security,
Settings,
Timer,
} from '@mui/icons-material'
import { Box, Card, Container, Grid, Typography, useTheme } from '@mui/joy'
@@ -19,10 +20,10 @@ const FeaturesSection = () => {
const features = [
{
icon: <Schedule />,
title: 'Smart Scheduling',
icon: <Psychology />,
title: 'Natural Language Input',
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 />,
@@ -32,9 +33,21 @@ const FeaturesSection = () => {
},
{
icon: <EmojiEvents />,
title: 'Motivating Gamification',
title: 'Gamification and Points',
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 />,
@@ -42,36 +55,6 @@ const FeaturesSection = () => {
description:
'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 />,
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.',
},
{
icon: <Psychology />,
title: 'Natural Language Input',
icon: <AutoAwesome />,
title: 'Advanced Organization',
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 />,
title: 'Visual Calendar View',
icon: <Settings />,
title: 'Advanced Task Settings',
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',
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)': {
'&:active': {
transform: 'scale(0.98)',
@@ -286,62 +266,7 @@ const FeaturesSection = () => {
mt={{ xs: 8, sm: 10, md: 12 }}
data-aos='fade-up'
data-aos-duration='800'
>
<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>
></Box>
</Container>
)
}

View File

@@ -21,11 +21,13 @@ const Footer = () => {
{ label: 'Features', href: '#features' },
{ label: 'Demo', href: '#demo' },
{ label: 'Getting Started', href: '#getting-started' },
{ label: 'Pricing', href: '/pricing' },
],
resources: [
{ 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',
href: 'https://github.com/donetick/donetick/discussions',

View File

@@ -1,66 +1,132 @@
import {
AddHome,
Android,
Apple,
AutoAwesome,
Cloud,
GitHub,
InstallMobile,
Storage,
} 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'
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'
data-aos='fade-up'
data-aos-delay={index * 100}
data-aos-duration='800'
variant='outlined'
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',
minHeight: '300px',
py: 4,
flexDirection: 'column',
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
sx={{
display: 'flex',
flexDirection: 'row',
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}
</Typography>
</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}
</Typography>
<ButtonContainer>{button}</ButtonContainer>
<Box sx={{ mt: 'auto' }}>{button}</Box>
</Card>
)
}
@@ -70,7 +136,7 @@ const GettingStarted = () => {
const information = [
{
title: 'Donetick Web',
icon: <Cloud style={{ fontSize: '48px' }} />,
icon: <Cloud />,
description:
'The easiest way! Just create account and start using Donetick',
button: (
@@ -88,7 +154,7 @@ const GettingStarted = () => {
},
{
title: 'Selfhosted',
icon: <Storage style={{ fontSize: '48px' }} />,
icon: <Storage />,
description: 'Download the binary and manage your own Donetick instance',
button: (
<Button
@@ -108,7 +174,7 @@ const GettingStarted = () => {
},
{
title: 'Hassio Addon',
icon: <AddHome style={{ fontSize: '48px' }} />,
icon: <AddHome />,
description:
'Have Home Assistant? Install Donetick as a Home Assistant Addon with single click',
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 }}>
<Typography level='body-md' color='neutral'>
You can start using Donetick in multiple ways, the easiest of which is
to use Donetick Web so you can get started 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
return (
<Container maxWidth='xl' sx={{ py: { xs: 6, sm: 8, md: 12 } }}>
{/* Section Header */}
<Box textAlign='center' mb={{ xs: 6, md: 8 }} data-aos='fade-up'>
<Typography
level='h2'
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>
<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>
{/* 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>
)
}

View File

@@ -7,11 +7,13 @@ import CookiePermissionSnackbar from './CookiePermissionSnackbar'
import DemoAssignee from './DemoAssignee'
import DemoHistory from './DemoHistory'
import DemoMyChore from './DemoMyChore'
import DemoNotificationTemplate from './DemoNotificationTemplate'
import DemoScheduler from './DemoScheduler'
import FeaturesSection from './FeaturesSection'
import Footer from './Footer'
import GettingStarted from './GettingStarted'
import HomeHero from './HomeHero'
import TabletInstallationSection from './TabletInstallationSection'
const Landing = () => {
const Navigate = useNavigate()
useEffect(() => {
@@ -37,13 +39,13 @@ const Landing = () => {
<DemoMyChore />
<DemoAssignee />
<DemoScheduler />
<DemoNotificationTemplate />
<DemoHistory />
</Grid>
<FeaturesSection />
<TabletInstallationSection />
<GettingStarted />
{/* <PricingSection /> */}
<Box
sx={{
display: 'flex',