diff --git a/src/Application.jsx b/src/Application.jsx new file mode 100644 index 0000000..5bd01fb --- /dev/null +++ b/src/Application.jsx @@ -0,0 +1,10 @@ +import App from './App.jsx' +import Contexts from './contexts/Contexts.jsx' + +const Application = () => ( + + + +) + +export default Application diff --git a/src/MarketingApp.jsx b/src/MarketingApp.jsx new file mode 100644 index 0000000..4f862fb --- /dev/null +++ b/src/MarketingApp.jsx @@ -0,0 +1,66 @@ +import { useColorScheme } from '@mui/joy' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { useEffect } from 'react' +import { createBrowserRouter, RouterProvider } from 'react-router-dom' + +import { ImpersonateUserProvider } from './contexts/ImpersonateUserContext' +import { LocalizationProvider } from './contexts/LocalizationContext' +import ThemeContext from './contexts/ThemeContext' +import Landing from './views/Landing/Landing' +import PrivacyPolicyView from './views/PrivacyPolicy/PrivacyPolicyView' +import TermsView from './views/Terms/TermsView' + +const AppRedirect = () => { + useEffect(() => { + const { hash, pathname, search } = window.location + window.location.replace( + `https://app.donetick.com${pathname}${search}${hash}`, + ) + }, []) + + return null +} + +const router = createBrowserRouter([ + { path: '/', element: }, + { path: '/privacy', element: }, + { path: '/terms', element: }, + { path: '*', element: }, +]) + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { enabled: false, refetchOnWindowFocus: false, retry: false }, + }, +}) + +const ThemeClass = () => { + const { mode, systemMode } = useColorScheme() + + useEffect(() => { + const storedMode = JSON.parse(localStorage.getItem('themeMode') || 'null') + const selectedMode = storedMode || mode + const isDark = + selectedMode === 'dark' || + (selectedMode === 'system' && systemMode === 'dark') + + document.getElementById('root').classList.toggle('dark', isDark) + }, [mode, systemMode]) + + return null +} + +const MarketingApp = () => ( + + + + + + + + + + +) + +export default MarketingApp diff --git a/src/contexts/RouterContext.jsx b/src/contexts/RouterContext.jsx index 3c17b90..bb5c8dd 100644 --- a/src/contexts/RouterContext.jsx +++ b/src/contexts/RouterContext.jsx @@ -1,3 +1,5 @@ +import { createBrowserRouter, RouterProvider } from 'react-router-dom' + import App from '@/App' import ChoreEdit from '@/views/ChoreEdit/ChoreEdit' import Error from '@/views/Error' @@ -10,7 +12,7 @@ import Settings from '@/views/Settings/Settings' import SettingsOverview from '@/views/Settings/SettingsOverview' import SettingsRoutes from '@/views/Settings/SettingsRoutes' import ThemeSettings from '@/views/Settings/ThemeSettings' -import { RouterProvider, createBrowserRouter } from 'react-router-dom' + import AuthenticationLoading from '../views/Authorization/Authenticating' import ForgotPasswordView from '../views/Authorization/ForgotPasswordView' import LoginSettings from '../views/Authorization/LoginSettings' @@ -49,16 +51,6 @@ import ThingsView from '../views/Things/ThingsView' import TimerDetails from '../views/Timer/TimerDetails' import UserActivities from '../views/User/UserActivities' import UserPoints from '../views/User/UserPoints' -const getMainRoute = () => { - if ( - // if domain is www.donetick.com or donetick.com then show landing page: - window.location.hostname === 'www.donetick.com' || - window.location.hostname === 'donetick.com' - ) { - return - } - return -} const Router = createBrowserRouter([ { path: '/', @@ -67,7 +59,7 @@ const Router = createBrowserRouter([ children: [ { path: '/', - element: getMainRoute(), + element: , }, { path: '/settings', diff --git a/src/main.jsx b/src/main.jsx index b339e91..0aefb3d 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,14 +1,23 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.jsx' -import Contexts from './contexts/Contexts.jsx' import './i18n/config' import './index.css' +import React from 'react' +import ReactDOM from 'react-dom/client' + +const marketingHosts = new Set(['donetick.com', 'www.donetick.com']) +const isMarketingSite = + marketingHosts.has(window.location.hostname) || + (import.meta.env.DEV && + new URLSearchParams(window.location.search).get('site') === 'marketing') + +export const Site = React.lazy(() => + isMarketingSite ? import('./MarketingApp.jsx') : import('./Application.jsx'), +) + ReactDOM.createRoot(document.getElementById('root')).render( - - - + + + , ) diff --git a/src/queries/UserQueries.jsx b/src/queries/UserQueries.jsx index 588f1a2..8200769 100644 --- a/src/queries/UserQueries.jsx +++ b/src/queries/UserQueries.jsx @@ -1,4 +1,5 @@ import { useQuery, useQueryClient } from '@tanstack/react-query' + import { GetAllCircleMembers, GetAllUsers, @@ -54,7 +55,7 @@ export const useCircleMembers = () => { return { data, error, isLoading, handleRefetch } } -export const useUserProfile = () => { +export const useUserProfile = ({ enabled = true } = {}) => { const queryClient = useQueryClient() const token = localStorage.getItem('token') @@ -80,7 +81,7 @@ export const useUserProfile = () => { }, staleTime: 30 * 60 * 1000, gcTime: 30 * 60 * 1000, - enabled: !!token, + enabled: enabled && !!token, }) return { data, diff --git a/src/views/ChoreEdit/RepeatSection.jsx b/src/views/ChoreEdit/RepeatSection.jsx index 4cb4fa9..ed030fd 100644 --- a/src/views/ChoreEdit/RepeatSection.jsx +++ b/src/views/ChoreEdit/RepeatSection.jsx @@ -84,9 +84,7 @@ const generateSchedulePreview = (metadata, formatTimeFn) => { .map(day => day.charAt(0).toUpperCase() + day.slice(1, 3)) .join(', ') - const timeStr = metadata.time - ? formatTimeFn(metadata.time) - : '6:00 PM' + const timeStr = metadata.time ? formatTimeFn(metadata.time) : '6:00 PM' if (metadata.weekPattern === 'every_week' || !metadata.weekPattern) { return `Every ${dayNames} at ${timeStr}` @@ -109,11 +107,11 @@ const generateSchedulePreview = (metadata, formatTimeFn) => { } export const RepeatOnSections = ({ - frequencyType, frequency, - onFrequencyUpdate, frequencyMetadata, + frequencyType, onFrequencyMetadataUpdate, + onFrequencyUpdate, }) => { const { fmt } = useLocalization() // if time on frequencyMetadata is not set, try to set it to the nextDueDate if available, @@ -527,20 +525,21 @@ export const RepeatOnSections = ({ } const RepeatSection = ({ - frequencyType, - frequency, - onFrequencyUpdate, - onFrequencyTypeUpdate, - frequencyMetadata, - onFrequencyMetadataUpdate, - frequencyError, - allUserThings, - onTriggerUpdate, OnTriggerValidate, + allUserThings, + frequency, + frequencyError, + frequencyMetadata, + frequencyType, isAttemptToSave, + onFrequencyMetadataUpdate, + onFrequencyTypeUpdate, + onFrequencyUpdate, + onTriggerUpdate, selectedThing, + viewOnly = false, }) => { - const { data: userProfile } = useUserProfile() + const { data: userProfile } = useUserProfile({ enabled: !viewOnly }) return ( diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx index cf04f64..69e06f1 100644 --- a/src/views/Chores/ChoreCard.jsx +++ b/src/views/Chores/ChoreCard.jsx @@ -49,7 +49,7 @@ const ChoreCard = ({ sx, viewOnly, }) => { - const { data: userProfile } = useUserProfile() + const { data: userProfile } = useUserProfile({ enabled: !viewOnly }) const { timeFormat } = useLocalization() const { data: pendingCmds } = usePendingCommands(chore.id) diff --git a/src/views/Landing/DemoHistory.jsx b/src/views/Landing/DemoHistory.jsx index 57ae8c8..227a8b7 100644 --- a/src/views/Landing/DemoHistory.jsx +++ b/src/views/Landing/DemoHistory.jsx @@ -1,5 +1,6 @@ import { Box, Card, Grid, List, Typography } from '@mui/joy' import moment from 'moment' + import HistoryCard from '../History/HistoryCard' const DemoHistory = () => { @@ -7,29 +8,32 @@ const DemoHistory = () => { { id: 32, choreId: 12, - completedAt: moment().hour(4).format(), + performedAt: moment().hour(4).format(), completedBy: 1, assignedTo: 1, notes: null, dueDate: moment().format(), + status: 1, }, { id: 31, choreId: 12, - completedAt: moment().day(-1).format(), + performedAt: moment().day(-1).format(), completedBy: 1, assignedTo: 1, notes: 'Need to be replaced with a new one', dueDate: moment().day(-2).format(), + status: 1, }, { id: 31, choreId: 12, - completedAt: moment().day(-10).hour(1).format(), + performedAt: moment().day(-10).hour(1).format(), completedBy: 2, assignedTo: 1, notes: null, dueDate: moment().day(-10).format(), + status: 1, }, ] const performers = [ @@ -61,6 +65,7 @@ const DemoHistory = () => { key={index} index={index} performers={performers} + pendingCommands={[]} /> ))} diff --git a/src/views/Landing/DemoMyChore.jsx b/src/views/Landing/DemoMyChore.jsx index a4a8dd7..201815b 100644 --- a/src/views/Landing/DemoMyChore.jsx +++ b/src/views/Landing/DemoMyChore.jsx @@ -1,6 +1,7 @@ import { Card, Grid, Typography } from '@mui/joy' import moment from 'moment' import { useState } from 'react' + import ChoreCard from '../Chores/ChoreCard' const DemoMyChore = () => { @@ -121,7 +122,12 @@ const DemoMyChore = () => { data-aos-anchor='[data-aos-first-tasks-list]' data-aos='fade-up' > - + ))} @@ -141,10 +147,10 @@ const DemoMyChore = () => { Main view prioritize tasks due today, followed by overdue ones, and finally, future tasks or those without due dates. With Donetick, you - can view all the tasks you've created (whether assigned to you or - not) as well as tasks assigned to you by others. Quickly mark them - as done with just one click, ensuring a smooth and efficient task - management experience. + can view all the tasks you've created (whether assigned to you + or not) as well as tasks assigned to you by others. Quickly mark + them as done with just one click, ensuring a smooth and efficient + task management experience. diff --git a/src/views/Landing/DemoScheduler.jsx b/src/views/Landing/DemoScheduler.jsx index 5e34adb..08ee63e 100644 --- a/src/views/Landing/DemoScheduler.jsx +++ b/src/views/Landing/DemoScheduler.jsx @@ -1,5 +1,6 @@ import { Box, Card, Grid, Typography } from '@mui/joy' import { useState } from 'react' + import RepeatSection from '../ChoreEdit/RepeatSection' const DemoScheduler = () => { @@ -32,6 +33,7 @@ const DemoScheduler = () => { OnTriggerValidate={() => {}} isAttemptToSave={false} selectedThing={null} + viewOnly />