implement Application and MarketingApp components, enhance routing and user context management (#194)
This commit is contained in:
10
src/Application.jsx
Normal file
10
src/Application.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import App from './App.jsx'
|
||||||
|
import Contexts from './contexts/Contexts.jsx'
|
||||||
|
|
||||||
|
const Application = () => (
|
||||||
|
<Contexts>
|
||||||
|
<App />
|
||||||
|
</Contexts>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Application
|
||||||
66
src/MarketingApp.jsx
Normal file
66
src/MarketingApp.jsx
Normal file
@@ -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: <Landing /> },
|
||||||
|
{ path: '/privacy', element: <PrivacyPolicyView /> },
|
||||||
|
{ path: '/terms', element: <TermsView /> },
|
||||||
|
{ path: '*', element: <AppRedirect /> },
|
||||||
|
])
|
||||||
|
|
||||||
|
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 = () => (
|
||||||
|
<ThemeContext>
|
||||||
|
<ThemeClass />
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<LocalizationProvider>
|
||||||
|
<ImpersonateUserProvider>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</ImpersonateUserProvider>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</ThemeContext>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default MarketingApp
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
|
||||||
|
|
||||||
import App from '@/App'
|
import App from '@/App'
|
||||||
import ChoreEdit from '@/views/ChoreEdit/ChoreEdit'
|
import ChoreEdit from '@/views/ChoreEdit/ChoreEdit'
|
||||||
import Error from '@/views/Error'
|
import Error from '@/views/Error'
|
||||||
@@ -10,7 +12,7 @@ import Settings from '@/views/Settings/Settings'
|
|||||||
import SettingsOverview from '@/views/Settings/SettingsOverview'
|
import SettingsOverview from '@/views/Settings/SettingsOverview'
|
||||||
import SettingsRoutes from '@/views/Settings/SettingsRoutes'
|
import SettingsRoutes from '@/views/Settings/SettingsRoutes'
|
||||||
import ThemeSettings from '@/views/Settings/ThemeSettings'
|
import ThemeSettings from '@/views/Settings/ThemeSettings'
|
||||||
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
|
|
||||||
import AuthenticationLoading from '../views/Authorization/Authenticating'
|
import AuthenticationLoading from '../views/Authorization/Authenticating'
|
||||||
import ForgotPasswordView from '../views/Authorization/ForgotPasswordView'
|
import ForgotPasswordView from '../views/Authorization/ForgotPasswordView'
|
||||||
import LoginSettings from '../views/Authorization/LoginSettings'
|
import LoginSettings from '../views/Authorization/LoginSettings'
|
||||||
@@ -49,16 +51,6 @@ import ThingsView from '../views/Things/ThingsView'
|
|||||||
import TimerDetails from '../views/Timer/TimerDetails'
|
import TimerDetails from '../views/Timer/TimerDetails'
|
||||||
import UserActivities from '../views/User/UserActivities'
|
import UserActivities from '../views/User/UserActivities'
|
||||||
import UserPoints from '../views/User/UserPoints'
|
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 <Landing />
|
|
||||||
}
|
|
||||||
return <MyChores />
|
|
||||||
}
|
|
||||||
const Router = createBrowserRouter([
|
const Router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -67,7 +59,7 @@ const Router = createBrowserRouter([
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: getMainRoute(),
|
element: <MyChores />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
|
|||||||
23
src/main.jsx
23
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 './i18n/config'
|
||||||
import './index.css'
|
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(
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Contexts>
|
<React.Suspense fallback={null}>
|
||||||
<App />
|
<Site />
|
||||||
</Contexts>
|
</React.Suspense>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GetAllCircleMembers,
|
GetAllCircleMembers,
|
||||||
GetAllUsers,
|
GetAllUsers,
|
||||||
@@ -54,7 +55,7 @@ export const useCircleMembers = () => {
|
|||||||
return { data, error, isLoading, handleRefetch }
|
return { data, error, isLoading, handleRefetch }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUserProfile = () => {
|
export const useUserProfile = ({ enabled = true } = {}) => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ export const useUserProfile = () => {
|
|||||||
},
|
},
|
||||||
staleTime: 30 * 60 * 1000,
|
staleTime: 30 * 60 * 1000,
|
||||||
gcTime: 30 * 60 * 1000,
|
gcTime: 30 * 60 * 1000,
|
||||||
enabled: !!token,
|
enabled: enabled && !!token,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -84,9 +84,7 @@ const generateSchedulePreview = (metadata, formatTimeFn) => {
|
|||||||
.map(day => day.charAt(0).toUpperCase() + day.slice(1, 3))
|
.map(day => day.charAt(0).toUpperCase() + day.slice(1, 3))
|
||||||
.join(', ')
|
.join(', ')
|
||||||
|
|
||||||
const timeStr = metadata.time
|
const timeStr = metadata.time ? formatTimeFn(metadata.time) : '6:00 PM'
|
||||||
? formatTimeFn(metadata.time)
|
|
||||||
: '6:00 PM'
|
|
||||||
|
|
||||||
if (metadata.weekPattern === 'every_week' || !metadata.weekPattern) {
|
if (metadata.weekPattern === 'every_week' || !metadata.weekPattern) {
|
||||||
return `Every ${dayNames} at ${timeStr}`
|
return `Every ${dayNames} at ${timeStr}`
|
||||||
@@ -109,11 +107,11 @@ const generateSchedulePreview = (metadata, formatTimeFn) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const RepeatOnSections = ({
|
export const RepeatOnSections = ({
|
||||||
frequencyType,
|
|
||||||
frequency,
|
frequency,
|
||||||
onFrequencyUpdate,
|
|
||||||
frequencyMetadata,
|
frequencyMetadata,
|
||||||
|
frequencyType,
|
||||||
onFrequencyMetadataUpdate,
|
onFrequencyMetadataUpdate,
|
||||||
|
onFrequencyUpdate,
|
||||||
}) => {
|
}) => {
|
||||||
const { fmt } = useLocalization()
|
const { fmt } = useLocalization()
|
||||||
// if time on frequencyMetadata is not set, try to set it to the nextDueDate if available,
|
// 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 = ({
|
const RepeatSection = ({
|
||||||
frequencyType,
|
|
||||||
frequency,
|
|
||||||
onFrequencyUpdate,
|
|
||||||
onFrequencyTypeUpdate,
|
|
||||||
frequencyMetadata,
|
|
||||||
onFrequencyMetadataUpdate,
|
|
||||||
frequencyError,
|
|
||||||
allUserThings,
|
|
||||||
onTriggerUpdate,
|
|
||||||
OnTriggerValidate,
|
OnTriggerValidate,
|
||||||
|
allUserThings,
|
||||||
|
frequency,
|
||||||
|
frequencyError,
|
||||||
|
frequencyMetadata,
|
||||||
|
frequencyType,
|
||||||
isAttemptToSave,
|
isAttemptToSave,
|
||||||
|
onFrequencyMetadataUpdate,
|
||||||
|
onFrequencyTypeUpdate,
|
||||||
|
onFrequencyUpdate,
|
||||||
|
onTriggerUpdate,
|
||||||
selectedThing,
|
selectedThing,
|
||||||
|
viewOnly = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { data: userProfile } = useUserProfile()
|
const { data: userProfile } = useUserProfile({ enabled: !viewOnly })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box mt={2}>
|
<Box mt={2}>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const ChoreCard = ({
|
|||||||
sx,
|
sx,
|
||||||
viewOnly,
|
viewOnly,
|
||||||
}) => {
|
}) => {
|
||||||
const { data: userProfile } = useUserProfile()
|
const { data: userProfile } = useUserProfile({ enabled: !viewOnly })
|
||||||
const { timeFormat } = useLocalization()
|
const { timeFormat } = useLocalization()
|
||||||
const { data: pendingCmds } = usePendingCommands(chore.id)
|
const { data: pendingCmds } = usePendingCommands(chore.id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Box, Card, Grid, List, Typography } from '@mui/joy'
|
import { Box, Card, Grid, List, Typography } from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
import HistoryCard from '../History/HistoryCard'
|
import HistoryCard from '../History/HistoryCard'
|
||||||
|
|
||||||
const DemoHistory = () => {
|
const DemoHistory = () => {
|
||||||
@@ -7,29 +8,32 @@ const DemoHistory = () => {
|
|||||||
{
|
{
|
||||||
id: 32,
|
id: 32,
|
||||||
choreId: 12,
|
choreId: 12,
|
||||||
completedAt: moment().hour(4).format(),
|
performedAt: moment().hour(4).format(),
|
||||||
completedBy: 1,
|
completedBy: 1,
|
||||||
assignedTo: 1,
|
assignedTo: 1,
|
||||||
notes: null,
|
notes: null,
|
||||||
dueDate: moment().format(),
|
dueDate: moment().format(),
|
||||||
|
status: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 31,
|
id: 31,
|
||||||
choreId: 12,
|
choreId: 12,
|
||||||
completedAt: moment().day(-1).format(),
|
performedAt: moment().day(-1).format(),
|
||||||
completedBy: 1,
|
completedBy: 1,
|
||||||
assignedTo: 1,
|
assignedTo: 1,
|
||||||
notes: 'Need to be replaced with a new one',
|
notes: 'Need to be replaced with a new one',
|
||||||
dueDate: moment().day(-2).format(),
|
dueDate: moment().day(-2).format(),
|
||||||
|
status: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 31,
|
id: 31,
|
||||||
choreId: 12,
|
choreId: 12,
|
||||||
completedAt: moment().day(-10).hour(1).format(),
|
performedAt: moment().day(-10).hour(1).format(),
|
||||||
completedBy: 2,
|
completedBy: 2,
|
||||||
assignedTo: 1,
|
assignedTo: 1,
|
||||||
notes: null,
|
notes: null,
|
||||||
dueDate: moment().day(-10).format(),
|
dueDate: moment().day(-10).format(),
|
||||||
|
status: 1,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const performers = [
|
const performers = [
|
||||||
@@ -61,6 +65,7 @@ const DemoHistory = () => {
|
|||||||
key={index}
|
key={index}
|
||||||
index={index}
|
index={index}
|
||||||
performers={performers}
|
performers={performers}
|
||||||
|
pendingCommands={[]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Card, Grid, Typography } from '@mui/joy'
|
import { Card, Grid, Typography } from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import ChoreCard from '../Chores/ChoreCard'
|
import ChoreCard from '../Chores/ChoreCard'
|
||||||
|
|
||||||
const DemoMyChore = () => {
|
const DemoMyChore = () => {
|
||||||
@@ -121,7 +122,12 @@ const DemoMyChore = () => {
|
|||||||
data-aos-anchor='[data-aos-first-tasks-list]'
|
data-aos-anchor='[data-aos-first-tasks-list]'
|
||||||
data-aos='fade-up'
|
data-aos='fade-up'
|
||||||
>
|
>
|
||||||
<ChoreCard chore={card} performers={users} viewOnly={true} />
|
<ChoreCard
|
||||||
|
chore={card}
|
||||||
|
performers={users}
|
||||||
|
showActions={false}
|
||||||
|
viewOnly
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -141,10 +147,10 @@ const DemoMyChore = () => {
|
|||||||
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
||||||
Main view prioritize tasks due today, followed by overdue ones, and
|
Main view prioritize tasks due today, followed by overdue ones, and
|
||||||
finally, future tasks or those without due dates. With Donetick, you
|
finally, future tasks or those without due dates. With Donetick, you
|
||||||
can view all the tasks you've created (whether assigned to you or
|
can view all the tasks you've created (whether assigned to you
|
||||||
not) as well as tasks assigned to you by others. Quickly mark them
|
or not) as well as tasks assigned to you by others. Quickly mark
|
||||||
as done with just one click, ensuring a smooth and efficient task
|
them as done with just one click, ensuring a smooth and efficient
|
||||||
management experience.
|
task management experience.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Box, Card, Grid, Typography } from '@mui/joy'
|
import { Box, Card, Grid, Typography } from '@mui/joy'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import RepeatSection from '../ChoreEdit/RepeatSection'
|
import RepeatSection from '../ChoreEdit/RepeatSection'
|
||||||
|
|
||||||
const DemoScheduler = () => {
|
const DemoScheduler = () => {
|
||||||
@@ -32,6 +33,7 @@ const DemoScheduler = () => {
|
|||||||
OnTriggerValidate={() => {}}
|
OnTriggerValidate={() => {}}
|
||||||
isAttemptToSave={false}
|
isAttemptToSave={false}
|
||||||
selectedThing={null}
|
selectedThing={null}
|
||||||
|
viewOnly
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
Reference in New Issue
Block a user