diff --git a/src/components/common/EmptyState.jsx b/src/components/common/EmptyState.jsx
new file mode 100644
index 0000000..95250fd
--- /dev/null
+++ b/src/components/common/EmptyState.jsx
@@ -0,0 +1,231 @@
+import { Box, Button, Typography } from '@mui/joy'
+import PropTypes from 'prop-types'
+import { Link } from 'react-router-dom'
+
+/**
+ * The single empty/error surface for the app.
+ *
+ * variant drives the tone, the icon tile color and the a11y role:
+ * - 'empty' nothing exists yet. Teach the feature, offer the way in.
+ * - 'no-results' something exists, the current search/filter hides it.
+ * - 'error' we failed to load. Say what happened, offer a retry.
+ *
+ * Actions are objects instead of nodes so every call site gets the same
+ * button vocabulary (solid primary lead, plain neutral follow).
+ */
+
+const TONES = {
+ empty: {
+ tileBg: 'primary.softHoverBg',
+ halo: 'primary.softBg',
+ iconColor: 'primary.softColor',
+ role: 'status',
+ },
+ 'no-results': {
+ tileBg: 'neutral.softHoverBg',
+ halo: 'neutral.softBg',
+ iconColor: 'neutral.softColor',
+ role: 'status',
+ },
+ error: {
+ tileBg: 'danger.softHoverBg',
+ halo: 'danger.softBg',
+ iconColor: 'danger.softColor',
+ role: 'alert',
+ },
+}
+
+const SIZES = {
+ sm: {
+ tile: 48,
+ icon: '1.375rem',
+ halo: 6,
+ py: 5,
+ title: 'title-sm',
+ titleSize: '1rem',
+ },
+ md: {
+ tile: 68,
+ icon: '1.875rem',
+ halo: 10,
+ py: 8,
+ title: 'title-md',
+ titleSize: '1.25rem',
+ },
+}
+
+const ActionButton = ({ action, ...buttonProps }) => {
+ const { label, to, onClick, ...rest } = action
+ return (
+
+ )
+}
+
+ActionButton.propTypes = {
+ action: PropTypes.object.isRequired,
+}
+
+const EmptyState = ({
+ variant = 'empty',
+ icon,
+ title,
+ description,
+ primaryAction,
+ secondaryAction,
+ size = 'md',
+ fullHeight = false,
+ sx,
+ ...rest
+}) => {
+ const tone = TONES[variant] || TONES.empty
+ const dimensions = SIZES[size] || SIZES.md
+ const buttonSize = size === 'sm' ? 'sm' : 'md'
+
+ return (
+
+ {icon && (
+
+ svg': { fontSize: dimensions.icon },
+ }}
+ >
+ {icon}
+
+
+ )}
+
+
+ {title}
+
+
+ {description && (
+
+ {description}
+
+ )}
+
+ {(primaryAction || secondaryAction) && (
+
+ {primaryAction && (
+
+ )}
+ {secondaryAction && (
+
+ )}
+
+ )}
+
+ )
+}
+
+EmptyState.propTypes = {
+ variant: PropTypes.oneOf(['empty', 'no-results', 'error']),
+ icon: PropTypes.node,
+ title: PropTypes.node.isRequired,
+ description: PropTypes.node,
+ primaryAction: PropTypes.shape({
+ label: PropTypes.node.isRequired,
+ onClick: PropTypes.func,
+ to: PropTypes.string,
+ startDecorator: PropTypes.node,
+ }),
+ secondaryAction: PropTypes.shape({
+ label: PropTypes.node.isRequired,
+ onClick: PropTypes.func,
+ to: PropTypes.string,
+ startDecorator: PropTypes.node,
+ }),
+ size: PropTypes.oneOf(['sm', 'md']),
+ fullHeight: PropTypes.bool,
+ sx: PropTypes.object,
+}
+
+export default EmptyState
diff --git a/src/views/Chores/ArchivedTasks.jsx b/src/views/Chores/ArchivedTasks.jsx
index dc79a35..c14a952 100644
--- a/src/views/Chores/ArchivedTasks.jsx
+++ b/src/views/Chores/ArchivedTasks.jsx
@@ -7,6 +7,7 @@ import {
Label,
Person,
PriorityHigh,
+ SearchOff,
SelectAll,
Unarchive,
ViewAgenda,
@@ -27,6 +28,7 @@ import { useQueryClient } from '@tanstack/react-query'
import Fuse from 'fuse.js'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
+import EmptyState from '../../components/common/EmptyState'
import FilterBar from '../../components/common/FilterBar'
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
@@ -1004,45 +1006,37 @@ const ArchivedTasks = () => {
{/* Content */}
{finalChores.length === 0 ? (
-
-
-
- {searchTerm || hasActiveFilters
- ? 'No archived tasks found'
- : 'No archived tasks'}
-
-
- {searchTerm || hasActiveFilters
- ? 'Try adjusting your search or filters'
- : 'Archived tasks will appear here when you archive them from the main task list'}
-
- {(searchTerm || hasActiveFilters) && (
-
- {searchTerm && (
-
- )}
- {hasActiveFilters && (
-
- )}
-
- )}
-
+ searchTerm || hasActiveFilters ? (
+ }
+ title='No archived tasks match'
+ description={
+ searchTerm
+ ? `Nothing in the archive matches "${searchTerm}".`
+ : 'There are archived tasks, but none fit the filters that are currently on.'
+ }
+ primaryAction={
+ searchTerm
+ ? { label: 'Clear search', onClick: handleSearchClose }
+ : { label: 'Clear filters', onClick: clearAll }
+ }
+ secondaryAction={
+ searchTerm && hasActiveFilters
+ ? { label: 'Clear filters', onClick: clearAll }
+ : undefined
+ }
+ />
+ ) : (
+ }
+ title='Nothing archived'
+ description='Archiving hides a task without deleting it. Anything you archive from your task list shows up here, ready to restore.'
+ primaryAction={{ label: 'Back to tasks', to: '/chores' }}
+ />
+ )
) : (
diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx
index fd6afac..2e133a1 100644
--- a/src/views/Chores/MyChores.jsx
+++ b/src/views/Chores/MyChores.jsx
@@ -2,18 +2,18 @@ import {
Add,
Bolt,
CalendarMonth,
+ CloudOff,
EditCalendar,
+ SearchOff,
ExpandCircleDown,
PriorityHigh,
Style,
} from '@mui/icons-material'
-import Logo from '../../Logo'
import {
Accordion,
AccordionDetails,
AccordionGroup,
Box,
- Button,
Chip,
Container,
Divider,
@@ -33,6 +33,7 @@ import IconButtonWithMenu from './IconButtonWithMenu'
import { useMediaQuery } from '@mui/material'
import { useQueryClient } from '@tanstack/react-query'
+import EmptyState from '../../components/common/EmptyState'
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
import { useFilter } from '../../hooks/useFilter'
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
@@ -217,8 +218,7 @@ const MyChores = () => {
)
case 'Due Later':
return (
- d !== null &&
- d > new Date(now.getTime() + 24 * 60 * 60 * 1000)
+ d !== null && d > new Date(now.getTime() + 24 * 60 * 60 * 1000)
)
case 'No Due Date':
return item.nextDueDate === null
@@ -637,7 +637,8 @@ const MyChores = () => {
selectedChores,
addTaskModalOpen,
searchTerm,
- searchFilter: hasQuickFilters || searchTerm?.length > 0 ? 'filtered' : 'All',
+ searchFilter:
+ hasQuickFilters || searchTerm?.length > 0 ? 'filtered' : 'All',
filteredChores: getFilteredChores,
choreSections,
openChoreSections,
@@ -826,10 +827,12 @@ const MyChores = () => {
}
const toggleViewMode = value => {
- const newMode = value ?? (() => {
- const modes = ['default', 'compact', 'calendar']
- return modes[(modes.indexOf(viewMode) + 1) % modes.length]
- })()
+ const newMode =
+ value ??
+ (() => {
+ const modes = ['default', 'compact', 'calendar']
+ return modes[(modes.indexOf(viewMode) + 1) % modes.length]
+ })()
setViewMode(newMode)
localStorage.setItem('choreCardViewMode', newMode)
if (newMode !== 'calendar') {
@@ -905,9 +908,28 @@ const MyChores = () => {
[getFilteredChores],
)
+
+ // "Narrowed" means the user actively cut the list down (search, quick
+ // filters, a saved filter). Picking a project is not narrowing: an empty
+ // project is an empty place, not a filtered-away result.
+ const isNarrowed = Boolean(
+ searchTerm?.length > 0 || hasQuickFilters || activeFilterId,
+ )
+ const isCustomProjectSelected = Boolean(
+ selectedProject && selectedProject.id !== 'default',
+ )
+
+ const clearNarrowing = () => {
+ clearQuickFilters()
+ setSearchTerm('')
+ clearActiveFilter()
+ updateFilterUrl(null, null)
+ }
+
const appendChore = (prev, newChore) => {
let newChores = [...prev, newChore]
+
if (impersonatedUser) {
newChores = newChores.filter(
chore => chore.assignedTo === impersonatedUser.userId,
@@ -930,40 +952,23 @@ const MyChores = () => {
if (choresError || membersError) {
return (
-
-
-
-
-
- Unable to communicate with server
-
-
- {choresErrorDetails?.message ||
- 'The server is currently unavailable. Please check your connection and try again.'}
-
-
-
+ },
+ }}
+ />
)
}
@@ -1118,50 +1123,79 @@ const MyChores = () => {
}
/>
- {/* Show "Nothing scheduled" when appropriate based on current view mode */}
- {(searchTerm?.length > 0 || hasQuickFilters || activeFilterId
+ {/* Empty state. Three different situations, three different messages:
+ nothing created yet, nothing left after narrowing, or an empty
+ project. Only the middle one is about filters. */}
+ {(isNarrowed
? getFilteredChores.length === 0
: projectFilteredChores.length === 0) &&
// only if not in calendar view:
- viewMode !== 'calendar' && (
- }
+ title='No tasks yet'
+ description='Create your first task and Donetick keeps track of when it is due, whose turn it is, and what comes next.'
+ primaryAction={{
+ label: 'Create a task',
+ startDecorator: ,
+ onClick: () => setAddTaskModalOpen(true),
}}
- >
-
-
- Nothing scheduled
-
- {chores.length > 0 && (
- <>
-
- >
- )}
-
- )}
+ secondaryAction={{
+ label: 'More options',
+ onClick: () => Navigate('/chores/create'),
+ }}
+ />
+ ) : isNarrowed ? (
+ }
+ title='No tasks match this view'
+ description={
+ searchTerm?.length > 0
+ ? `Nothing matches "${searchTerm}". Try a different search, or clear what is narrowing the list.`
+ : 'You have tasks, but none of them fit the filters that are currently on.'
+ }
+ primaryAction={{
+ label:
+ searchTerm?.length > 0 ? 'Clear search' : 'Clear filters',
+ onClick: clearNarrowing,
+ }}
+ />
+ ) : isCustomProjectSelected ? (
+ }
+ title={`Nothing in ${selectedProject.name} yet`}
+ description='Tasks you add to this project show up here. Your other tasks are still where you left them.'
+ primaryAction={{
+ label: 'Add a task here',
+ startDecorator: ,
+ onClick: () => setAddTaskModalOpen(true),
+ }}
+ secondaryAction={{
+ label: 'See tasks outside projects',
+ onClick: () => setSelectedProjectWithCache(null),
+ }}
+ />
+ ) : (
+ }
+ title='No tasks here yet'
+ description='Tasks that do not belong to a project live here. Add one, or switch projects to see what is in them.'
+ primaryAction={{
+ label: 'Create a task',
+ startDecorator: ,
+ onClick: () => setAddTaskModalOpen(true),
+ }}
+ />
+ ))}
{searchTerm?.length > 0 && viewMode !== 'calendar' && (
{
}}
>
{getChoresForDate(selectedCalendarDate).length === 0 ? (
- }
+ title='Nothing scheduled'
+ description='This day is free. Add a task if you want something to land here.'
+ primaryAction={{
+ label: 'Add task',
+ startDecorator: ,
+ onClick: () => setAddTaskModalOpen(true),
}}
- >
- No tasks scheduled for this date
-
+ />
) : (
{
mb: 1,
}}
>
-
-
- No assigned tasks found
-
+ }
+ title='No one has tasks yet'
+ description='Assign a task to someone in your circle and their workload shows up here.'
+ />
)
}
diff --git a/src/views/Filters/FilterView.jsx b/src/views/Filters/FilterView.jsx
index c097057..b7706f3 100644
--- a/src/views/Filters/FilterView.jsx
+++ b/src/views/Filters/FilterView.jsx
@@ -29,6 +29,7 @@ import {
StarBorder,
Task,
} from '@mui/icons-material'
+import EmptyState from '../../components/common/EmptyState'
import { useChores } from '../../queries/ChoreQueries'
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
import { getFilterCount, getFilterOverdueCount } from '../../utils/FilterEngine'
@@ -417,29 +418,17 @@ const FilterView = () => {
}}
>
{savedFilters.length === 0 ? (
- }
+ title='No saved filters yet'
+ description='Save a set of conditions once, like "overdue and assigned to me", and jump straight back to it from anywhere.'
+ primaryAction={{
+ label: 'Create a filter',
+ startDecorator: ,
+ onClick: handleAddFilter,
}}
- >
-
-
- No saved filters yet
-
-
- Create custom filters to quickly access your most used chore
-
-
+ />
) : (
{savedFilters.map(filter => {
diff --git a/src/views/History/ChoreHistory.jsx b/src/views/History/ChoreHistory.jsx
index 3298408..cf5906c 100644
--- a/src/views/History/ChoreHistory.jsx
+++ b/src/views/History/ChoreHistory.jsx
@@ -28,10 +28,11 @@ import {
} from '@mui/icons-material'
import DeleteIcon from '@mui/icons-material/Delete'
import EditIcon from '@mui/icons-material/Edit'
-import { Box, Button, Card, Container, Grid, Sheet, Typography } from '@mui/joy'
+import { Box, Card, Container, Grid, Sheet, Typography } from '@mui/joy'
import moment from 'moment'
import { useEffect, useMemo, useState } from 'react'
-import { Link, useParams } from 'react-router-dom'
+import { useParams } from 'react-router-dom'
+import EmptyState from '../../components/common/EmptyState'
import FilterBar from '../../components/common/FilterBar'
import { useLocalization } from '../../contexts/LocalizationContext'
import useConfirmationModal from '../../hooks/useConfirmationModal'
@@ -303,36 +304,14 @@ const ChoreHistory = () => {
}
if (!choreHistory.length) {
return (
-
-
+ }
+ title='No history yet'
+ description='Every time this task gets completed or skipped, it lands here with who did it and when. Nothing has happened yet.'
+ primaryAction={{ label: 'Back to tasks', to: '/chores' }}
/>
-
-
- No History Yet
-
-
- You haven't completed any tasks. Once you start finishing tasks,
- they'll show up here.
-
-
)
}
@@ -441,27 +420,13 @@ const ChoreHistory = () => {
/>
{sortedHistory.length === 0 && activeFilterCount > 0 && (
-
-
-
- No results match your filters
-
-
- Try adjusting or clearing the active filters.
-
-
-
+ }
+ title='No history matches these filters'
+ description='There is history here, but none of it fits the filters that are currently on.'
+ primaryAction={{ label: 'Clear filters', onClick: clearAll }}
+ />
)}
{sortedHistory.length > 0 && (
diff --git a/src/views/Labels/LabelView.jsx b/src/views/Labels/LabelView.jsx
index 6191207..1f05020 100644
--- a/src/views/Labels/LabelView.jsx
+++ b/src/views/Labels/LabelView.jsx
@@ -21,7 +21,8 @@ import {
TrailingActions,
} from '@meauxt/react-swipeable-list'
import '@meauxt/react-swipeable-list/dist/styles.css'
-import { Add, MoreVert } from '@mui/icons-material'
+import { Add, MoreVert, Style } from '@mui/icons-material'
+import EmptyState from '../../components/common/EmptyState'
import { useQueryClient } from '@tanstack/react-query'
import { useUserProfile } from '../../queries/UserQueries'
import { getTextColorFromBackgroundColor } from '../../utils/Colors'
@@ -258,19 +259,17 @@ const LabelView = () => {
}}
>
{userLabels.length === 0 && (
- }
+ title='No labels yet'
+ description='Labels group tasks across your circle, like "kitchen" or "bills", so you can filter down to them in one tap.'
+ primaryAction={{
+ label: 'Create a label',
+ startDecorator: ,
+ onClick: handleAddLabel,
}}
- >
-
- No labels available. Add a new label to get started.
-
-
+ />
)}
{userLabels.map(label => (
diff --git a/src/views/Modals/Inputs/AttachmentBrowserModal.jsx b/src/views/Modals/Inputs/AttachmentBrowserModal.jsx
index 8165a17..76401bf 100644
--- a/src/views/Modals/Inputs/AttachmentBrowserModal.jsx
+++ b/src/views/Modals/Inputs/AttachmentBrowserModal.jsx
@@ -8,6 +8,7 @@ import {
Typography,
} from '@mui/joy'
import { useEffect, useState } from 'react'
+import EmptyState from '../../../components/common/EmptyState'
import ModalActions from '../../../components/common/ModalActions'
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
import { GetChoreAttachments } from '../../../utils/Fetcher'
@@ -95,12 +96,12 @@ function AttachmentBrowserModal({ choreId, isOpen, onClose }) {
) : attachments.length === 0 ? (
-
- No attachments found.
-
+ }
+ title='No attachments'
+ description='Photos and files added to this task will show up here.'
+ />
) : (
{attachments.map((attachment, index) => (
diff --git a/src/views/Things/ThingsHistory.jsx b/src/views/Things/ThingsHistory.jsx
index 854355f..0b53c8e 100644
--- a/src/views/Things/ThingsHistory.jsx
+++ b/src/views/Things/ThingsHistory.jsx
@@ -2,6 +2,7 @@ import {
Analytics,
BarChart,
CallReceived,
+ CloudOff,
EventBusy,
Schedule,
Speed,
@@ -25,7 +26,7 @@ import {
} from '@mui/joy'
import { useTheme } from '@mui/joy/styles'
import moment from 'moment'
-import { Link, useParams } from 'react-router-dom'
+import { useParams } from 'react-router-dom'
import { useLocalization } from '../../contexts/LocalizationContext'
import {
Line,
@@ -35,6 +36,7 @@ import {
XAxis,
YAxis,
} from 'recharts'
+import EmptyState from '../../components/common/EmptyState'
import { useThingHistory } from '../../queries/ThingQueries'
import LoadingComponent from '../components/Loading'
@@ -49,6 +51,7 @@ const ThingsHistory = () => {
fetchNextPage,
hasNextPage,
isFetchingNextPage,
+ refetch,
} = useThingHistory(id)
// Flatten all pages of history data
@@ -152,35 +155,23 @@ const ThingsHistory = () => {
if (error || !thingsHistory || thingsHistory.length === 0) {
return (
-
-
+ : }
+ title={error ? "Couldn't load this history" : 'No history yet'}
+ description={
+ error
+ ? 'We could not reach the server. Check your connection and try again.'
+ : "Each time this thing's value changes, the change is recorded here."
+ }
+ primaryAction={
+ error
+ ? { label: 'Try again', onClick: () => refetch() }
+ : { label: 'Back to things', to: '/things' }
+ }
/>
-
-
- No history found
-
-
- It looks like there is no history for this thing yet.
-
-
)
}
diff --git a/src/views/Things/ThingsView.jsx b/src/views/Things/ThingsView.jsx
index 3ab615a..a8ae371 100644
--- a/src/views/Things/ThingsView.jsx
+++ b/src/views/Things/ThingsView.jsx
@@ -28,6 +28,7 @@ import {
} from '@mui/joy'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
+import EmptyState from '../../components/common/EmptyState'
import { useNotification } from '../../service/NotificationProvider'
import {
CreateThing,
@@ -405,25 +406,20 @@ const ThingsView = () => {
}}
>
{things.length === 0 && (
- }
+ title='No things yet'
+ description='A thing tracks a value, like a counter or a switch, that other tasks can react to. Create one to trigger tasks automatically.'
+ primaryAction={{
+ label: 'Create a thing',
+ startDecorator: ,
+ onClick: () => {
+ setCreateModalThing(null)
+ setIsShowCreateThingModal(true)
+ },
}}
- >
-
-
- No things has been created/found
-
-
+ />
)}
{things.map(thing => (
diff --git a/src/views/Timer/TimerDetails.jsx b/src/views/Timer/TimerDetails.jsx
index e41aac7..7c2997a 100644
--- a/src/views/Timer/TimerDetails.jsx
+++ b/src/views/Timer/TimerDetails.jsx
@@ -38,6 +38,7 @@ import {
import moment from 'moment'
import { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
+import EmptyState from '../../components/common/EmptyState'
import { useLocalization } from '../../contexts/LocalizationContext'
import {
useChoreTimer,
@@ -1204,9 +1205,12 @@ const TimerDetails = () => {
)}
{(!timerData.pauseLog || timerData.pauseLog.length === 0) && (
-
- No work sessions found for this timer.
-
+ }
+ title='No work sessions yet'
+ description='Start the timer on this task and each session lands here.'
+ />
)}
) : (
diff --git a/src/views/User/UserActivities.jsx b/src/views/User/UserActivities.jsx
index 663aed8..60f47bf 100644
--- a/src/views/User/UserActivities.jsx
+++ b/src/views/User/UserActivities.jsx
@@ -20,17 +20,16 @@ import {
import {
Avatar,
Box,
- Button,
Card,
Chip,
Container,
Divider,
Grid,
- Link,
Stack,
Typography,
} from '@mui/joy'
import React, { useEffect, useMemo, useState } from 'react'
+import EmptyState from '../../components/common/EmptyState'
import FilterBar from '../../components/common/FilterBar'
import { useFilter } from '../../hooks/useFilter'
@@ -992,54 +991,21 @@ const UserActivites = () => {
{/* Conditional Content Based on Data Availability */}
{!choresData.res?.length > 0 || !choresHistory?.length > 0 ? (
-
-
-
-
- No activities found
-
-
- No activities found for{' '}
-
- {selectedUser === undefined || selectedUser === 'all'
- ? 'All Users'
- : circleUsers.find(user => user.userId === selectedUser)
- ?.displayName || 'Unknown User'}
- {' '}
- in the{' '}
-
- {tabValue === 365 ? 'All Time' : `Last ${tabValue} Days`}
-
- .
-
-
- Try selecting a different time period or user filter above.
-
-
-
+ }
+ title='No activity in this range'
+ description={`Nothing was completed by ${
+ selectedUser === undefined || selectedUser === 'all'
+ ? 'anyone in your circle'
+ : circleUsers.find(user => user.userId === selectedUser)
+ ?.displayName || 'this member'
+ } ${
+ tabValue === 365 ? 'so far' : `in the last ${tabValue} days`
+ }. Try a wider time range or a different member.`}
+ primaryAction={{ label: 'Back to tasks', to: '/chores' }}
+ />
) : (
<>
{/* Main Content Area - Mobile: Stack vertically, Desktop: Side by side */}
diff --git a/src/views/components/NotFound.jsx b/src/views/components/NotFound.jsx
index 8bc1a2e..f89d13b 100644
--- a/src/views/components/NotFound.jsx
+++ b/src/views/components/NotFound.jsx
@@ -1,61 +1,23 @@
-import { HomeRounded, Login } from '@mui/icons-material'
-import { Box, Button, CircularProgress, Container } from '@mui/joy'
-import { Typography } from '@mui/material'
-import { Link } from 'react-router-dom' // Assuming you are using React Router
-import Logo from '../../Logo'
+import { Explore, HomeRounded } from '@mui/icons-material'
+import { Container } from '@mui/joy'
+import EmptyState from '../../components/common/EmptyState'
const NotFound = () => {
return (
-
-
+ }
+ title='Page not found'
+ description='This link does not lead anywhere. It may have moved, or the address has a typo in it.'
+ primaryAction={{
+ label: 'Go to my tasks',
+ to: '/chores',
+ startDecorator: ,
}}
- >
-
-
-
-
- Page Not Found
-
-
- Sorry, I could be wrong but I think you are lost.
-
- }
- >
- Home
-
- }
- >
- Login
-
-
+ secondaryAction={{ label: 'Log in', to: '/login' }}
+ />
)
}