New Empty States (#3465)

New empty states
---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Kanav Arora
2024-01-30 15:31:56 +05:30
committed by GitHub
parent 96bcddc056
commit b07d67c624
27 changed files with 315 additions and 212 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -9,32 +9,13 @@ import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttac
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
const StyledTaskGroupEmptyContainer = styled.div` import {
align-items: center; StyledEmptyContainer,
align-self: stretch; StyledEmptySubTitle,
display: flex; StyledEmptyTextContainer,
flex: 1 0 0; StyledEmptyTitle,
flex-direction: column; } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
gap: ${({ theme }) => theme.spacing(2)};
justify-content: center;
height: 100%;
`;
const StyledEmptyTaskGroupTitle = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
`;
const StyledEmptyTaskGroupSubTitle = styled.div`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledAttachmentsContainer = styled.div` const StyledAttachmentsContainer = styled.div`
display: flex; display: flex;
@ -85,23 +66,26 @@ export const Attachments = ({
onUploadFile={onUploadFile} onUploadFile={onUploadFile}
/> />
) : ( ) : (
<StyledTaskGroupEmptyContainer> <StyledEmptyContainer>
<AnimatedPlaceholder type="noFile" />
<StyledEmptyTextContainer>
<StyledEmptyTitle>No Files</StyledEmptyTitle>
<StyledEmptySubTitle>
There are no associated files with this record.
</StyledEmptySubTitle>
</StyledEmptyTextContainer>
<StyledFileInput <StyledFileInput
ref={inputFileRef} ref={inputFileRef}
onChange={handleFileChange} onChange={handleFileChange}
type="file" type="file"
/> />
<StyledEmptyTaskGroupTitle>No files yet</StyledEmptyTaskGroupTitle>
<StyledEmptyTaskGroupSubTitle>
Upload one:
</StyledEmptyTaskGroupSubTitle>
<Button <Button
Icon={IconPlus} Icon={IconPlus}
title="Add file" title="Add file"
variant="secondary" variant="secondary"
onClick={handleUploadFileClick} onClick={handleUploadFileClick}
/> />
</StyledTaskGroupEmptyContainer> </StyledEmptyContainer>
)} )}
</StyledDropZoneContainer> </StyledDropZoneContainer>
); );

View File

@ -6,35 +6,13 @@ import { useNotes } from '@/activities/notes/hooks/useNotes';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
const StyledTaskGroupEmptyContainer = styled.div` import {
align-items: center; StyledEmptyContainer,
align-self: stretch; StyledEmptySubTitle,
display: flex; StyledEmptyTextContainer,
flex: 1 0 0; StyledEmptyTitle,
flex-direction: column; } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
gap: ${({ theme }) => theme.spacing(2)};
justify-content: center;
padding-bottom: ${({ theme }) => theme.spacing(16)};
padding-left: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(4)};
padding-top: ${({ theme }) => theme.spacing(3)};
`;
const StyledEmptyTaskGroupTitle = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
`;
const StyledEmptyTaskGroupSubTitle = styled.div`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledNotesContainer = styled.div` const StyledNotesContainer = styled.div`
display: flex; display: flex;
@ -55,9 +33,14 @@ export const Notes = ({
if (notes?.length === 0) { if (notes?.length === 0) {
return ( return (
<StyledTaskGroupEmptyContainer> <StyledEmptyContainer>
<StyledEmptyTaskGroupTitle>No note yet</StyledEmptyTaskGroupTitle> <AnimatedPlaceholder type="noNote" />
<StyledEmptyTaskGroupSubTitle>Create one:</StyledEmptyTaskGroupSubTitle> <StyledEmptyTextContainer>
<StyledEmptyTitle>No notes</StyledEmptyTitle>
<StyledEmptySubTitle>
There are no associated notes with this record.
</StyledEmptySubTitle>
</StyledEmptyTextContainer>
<Button <Button
Icon={IconPlus} Icon={IconPlus}
title="New note" title="New note"
@ -69,7 +52,7 @@ export const Notes = ({
}) })
} }
/> />
</StyledTaskGroupEmptyContainer> </StyledEmptyContainer>
); );
} }

View File

@ -7,40 +7,18 @@ import { useTasks } from '@/activities/tasks/hooks/useTasks';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
StyledEmptyContainer,
StyledEmptySubTitle,
StyledEmptyTextContainer,
StyledEmptyTitle,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { AddTaskButton } from './AddTaskButton'; import { AddTaskButton } from './AddTaskButton';
import { TaskList } from './TaskList'; import { TaskList } from './TaskList';
const StyledTaskGroupEmptyContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
flex: 1 0 0;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: center;
padding-bottom: ${({ theme }) => theme.spacing(16)};
padding-left: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(4)};
padding-top: ${({ theme }) => theme.spacing(3)};
`;
const StyledEmptyTaskGroupTitle = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
`;
const StyledEmptyTaskGroupSubTitle = styled.div`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledContainer = styled.div` const StyledContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -80,9 +58,14 @@ export const TaskGroups = ({
(activeTabId === 'done' && completedTasks?.length === 0) (activeTabId === 'done' && completedTasks?.length === 0)
) { ) {
return ( return (
<StyledTaskGroupEmptyContainer> <StyledEmptyContainer>
<StyledEmptyTaskGroupTitle>No task yet</StyledEmptyTaskGroupTitle> <AnimatedPlaceholder type="noTask" />
<StyledEmptyTaskGroupSubTitle>Create one:</StyledEmptyTaskGroupSubTitle> <StyledEmptyTextContainer>
<StyledEmptyTitle>No Task</StyledEmptyTitle>
<StyledEmptySubTitle>
There are no associated tasks with this record
</StyledEmptySubTitle>
</StyledEmptyTextContainer>
<Button <Button
Icon={IconPlus} Icon={IconPlus}
title="New task" title="New task"
@ -94,7 +77,7 @@ export const TaskGroups = ({
}) })
} }
/> />
</StyledTaskGroupEmptyContainer> </StyledEmptyContainer>
); );
} }

View File

@ -4,6 +4,13 @@ import { ActivityCreateButton } from '@/activities/components/ActivityCreateButt
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { useTimelineActivities } from '@/activities/timeline/hooks/useTimelineActivities'; import { useTimelineActivities } from '@/activities/timeline/hooks/useTimelineActivities';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
StyledEmptyContainer,
StyledEmptySubTitle,
StyledEmptyTextContainer,
StyledEmptyTitle,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { TimelineItemsContainer } from './TimelineItemsContainer'; import { TimelineItemsContainer } from './TimelineItemsContainer';
@ -20,31 +27,6 @@ const StyledMainContainer = styled.div`
justify-content: center; justify-content: center;
`; `;
const StyledTimelineEmptyContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
flex: 1 0 0;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: center;
`;
const StyledEmptyTimelineTitle = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
`;
const StyledEmptyTimelineSubTitle = styled.div`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
export const Timeline = ({ export const Timeline = ({
targetableObject, targetableObject,
}: { }: {
@ -67,9 +49,14 @@ export const Timeline = ({
if (showEmptyState) { if (showEmptyState) {
return ( return (
<StyledTimelineEmptyContainer> <StyledEmptyContainer>
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle> <AnimatedPlaceholder type="emptyTimeline" />
<StyledEmptyTimelineSubTitle>Create one:</StyledEmptyTimelineSubTitle> <StyledEmptyTextContainer>
<StyledEmptyTitle>Add your first Activity</StyledEmptyTitle>
<StyledEmptySubTitle>
There are no activities associated with this record.{' '}
</StyledEmptySubTitle>
</StyledEmptyTextContainer>
<ActivityCreateButton <ActivityCreateButton
onNoteClick={() => onNoteClick={() =>
openCreateActivity({ openCreateActivity({
@ -84,7 +71,7 @@ export const Timeline = ({
}) })
} }
/> />
</StyledTimelineEmptyContainer> </StyledEmptyContainer>
); );
} }

View File

@ -1,4 +1,14 @@
import { FallbackProps } from 'react-error-boundary'; import { FallbackProps } from 'react-error-boundary';
import { Button } from 'tsup.ui.index';
import { IconRefresh } from '@/ui/display/icon';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
StyledEmptyContainer,
StyledEmptySubTitle,
StyledEmptyTextContainer,
StyledEmptyTitle,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
type GenericErrorFallbackProps = FallbackProps; type GenericErrorFallbackProps = FallbackProps;
@ -7,20 +17,18 @@ export const GenericErrorFallback = ({
resetErrorBoundary, resetErrorBoundary,
}: GenericErrorFallbackProps) => { }: GenericErrorFallbackProps) => {
return ( return (
<div <StyledEmptyContainer>
style={{ <AnimatedPlaceholder type="errorIndex" />
color: 'red', <StyledEmptyTextContainer>
display: 'flex', <StyledEmptyTitle>Servers on a coffee break</StyledEmptyTitle>
flexDirection: 'column', <StyledEmptySubTitle>{error.message}</StyledEmptySubTitle>
gap: '20px', </StyledEmptyTextContainer>
alignItems: 'center', <Button
justifyContent: 'center', Icon={IconRefresh}
width: '100%', title="Reload"
height: '100%', variant={'secondary'}
}} onClick={() => resetErrorBoundary()}
> />
<div>{error.message}</div> </StyledEmptyContainer>
<button onClick={() => resetErrorBoundary()}>Retry</button>
</div>
); );
}; };

View File

@ -9,6 +9,13 @@ import { EntityDeleteContext } from '@/object-record/record-table/contexts/Entit
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
StyledEmptyContainer,
StyledEmptySubTitle,
StyledEmptyTextContainer,
StyledEmptyTitle,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useViewFields } from '@/views/hooks/internal/useViewFields'; import { useViewFields } from '@/views/hooks/internal/useViewFields';
@ -19,35 +26,6 @@ import { useRecordTable } from '../hooks/useRecordTable';
import { RecordTableInternalEffect } from './RecordTableInternalEffect'; import { RecordTableInternalEffect } from './RecordTableInternalEffect';
const StyledObjectEmptyContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
flex: 1 0 0;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: center;
padding-bottom: ${({ theme }) => theme.spacing(16)};
padding-left: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(4)};
padding-top: ${({ theme }) => theme.spacing(3)};
`;
const StyledEmptyObjectTitle = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
`;
const StyledEmptyObjectSubTitle = styled.div`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.xxl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledTableWithHeader = styled.div` const StyledTableWithHeader = styled.div`
display: flex; display: flex;
flex: 1; flex: 1;
@ -130,20 +108,24 @@ export const RecordTableWithWrappers = ({
tableBodyRef={tableBodyRef} tableBodyRef={tableBodyRef}
/> />
{!isRecordTableInitialLoading && numberOfTableRows === 0 && ( {!isRecordTableInitialLoading && numberOfTableRows === 0 && (
<StyledObjectEmptyContainer> <StyledEmptyContainer>
<StyledEmptyObjectTitle> <AnimatedPlaceholder type="noRecord" />
No {foundObjectMetadataItem?.namePlural} <StyledEmptyTextContainer>
</StyledEmptyObjectTitle> <StyledEmptyTitle>
<StyledEmptyObjectSubTitle> Add your first {foundObjectMetadataItem?.namePlural}
Create one: </StyledEmptyTitle>
</StyledEmptyObjectSubTitle> <StyledEmptySubTitle>
Use our API or add your first{' '}
{foundObjectMetadataItem?.namePlural} manually
</StyledEmptySubTitle>
</StyledEmptyTextContainer>
<Button <Button
Icon={IconPlus} Icon={IconPlus}
title={`Add a ${foundObjectMetadataItem?.nameSingular}`} title={`Add a ${foundObjectMetadataItem?.nameSingular}`}
variant={'secondary'} variant={'secondary'}
onClick={createRecord} onClick={createRecord}
/> />
</StyledObjectEmptyContainer> </StyledEmptyContainer>
)} )}
</StyledTableContainer> </StyledTableContainer>
</StyledTableWithHeader> </StyledTableWithHeader>

View File

@ -0,0 +1,86 @@
import { useEffect } from 'react';
import styled from '@emotion/styled';
import { motion, useMotionValue, useTransform } from 'framer-motion';
import {
Background,
MovingImage,
} from '@/ui/layout/animated-placeholder/constants/AnimatedImages';
const StyledContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
position: relative;
`;
interface StyledImageProps {
type: string;
}
const StyledBackgroundImage = styled.img<StyledImageProps>`
max-height: ${({ type }) =>
type === 'error500' || type === 'error404' ? '245px' : '160px'};
max-width: ${({ type }) =>
type === 'error500' || type === 'error404' ? '245px' : '160px'};
`;
const StyledMovingImage = styled(motion.img)<StyledImageProps>`
position: absolute;
max-width: ${({ type }) =>
type === 'error500' || type === 'error404' ? '185px' : '130px'};
max-height: ${({ type }) =>
type === 'error500' || type === 'error404' ? '185px' : '130px'};
z-index: 2;
`;
interface AnimatedPlaceholderProps {
type: keyof typeof Background | keyof typeof MovingImage;
}
const AnimatedPlaceholder = ({ type }: AnimatedPlaceholderProps) => {
const x = useMotionValue(window.innerWidth / 2);
const y = useMotionValue(window.innerHeight / 2);
const translateX = useTransform(x, [0, window.innerWidth], [-2, 2]);
const translateY = useTransform(y, [0, window.innerHeight], [-2, 2]);
useEffect(() => {
const handleMove = (event: MouseEvent | TouchEvent) => {
const clientX =
'touches' in event ? event.touches[0].clientX : event.clientX;
const clientY =
'touches' in event ? event.touches[0].clientY : event.clientY;
x.set(clientX);
y.set(clientY);
};
window.addEventListener('mousemove', handleMove);
window.addEventListener('touchmove', handleMove);
return () => {
window.removeEventListener('mousemove', handleMove);
window.removeEventListener('touchmove', handleMove);
};
}, [x, y]);
return (
<StyledContainer>
<StyledBackgroundImage
src={Background[type]}
alt="Background"
type={type}
/>
<StyledMovingImage
src={MovingImage[type]}
alt="Moving"
style={{ translateX, translateY }}
transition={{ type: 'spring', stiffness: 100, damping: 10 }}
type={type}
/>
</StyledContainer>
);
};
export default AnimatedPlaceholder;

View File

@ -0,0 +1,40 @@
import styled from '@emotion/styled';
export const StyledEmptyContainer = styled.div`
align-items: center;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(6)};
justify-content: center;
text-align: center;
margin: ${({ theme }) => theme.spacing(16)} 0px;
`;
export const StyledEmptyTextContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
justify-content: center;
text-align: center;
width: 100%;
`;
export const StyledEmptyTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.lg};
`;
export const StyledEmptySubTitle = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
line-height: ${({ theme }) => theme.text.lineHeight.md};
max-height: 2.4em;
overflow: hidden;
width: 50%;
`;

View File

@ -0,0 +1,38 @@
import styled from '@emotion/styled';
export const StyledErrorContainer = styled.div`
align-items: center;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(8)};
justify-content: center;
text-align: center;
`;
export const StyledErrorTextContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
justify-content: center;
text-align: center;
width: 100%;
`;
export const StyledErrorTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.lg};
`;
export const StyledErrorSubTitle = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.regular};
line-height: ${({ theme }) => theme.text.lineHeight.md};
max-height: 2.4em;
overflow: hidden;
`;

View File

@ -0,0 +1,21 @@
export const Background: Record<string, string> = {
noFile: '/images/placeholders/background/no_file_bg.png',
noNote: '/images/placeholders/background/no_note_bg.png',
noRecord: '/images/placeholders/background/no_record_bg.png',
noTask: '/images/placeholders/background/no_task_bg.png',
errorIndex: '/images/placeholders/background/error_index_bg.png',
emptyTimeline: '/images/placeholders/background/empty_timeline_bg.png',
error404: '/images/placeholders/background/404_bg.png',
error500: '/images/placeholders/background/500_bg.png',
};
export const MovingImage: Record<string, string> = {
noFile: '/images/placeholders/moving-image/no_file.png',
noNote: '/images/placeholders/moving-image/no_note.png',
noRecord: '/images/placeholders/moving-image/no_record.png',
noTask: '/images/placeholders/moving-image/no_task.png',
errorIndex: '/images/placeholders/moving-image/error_index.png',
emptyTimeline: '/images/placeholders/moving-image/empty_timeline.png',
error404: '/images/placeholders/moving-image/404.png',
error500: '/images/placeholders/moving-image/500.png',
};

View File

@ -3,7 +3,13 @@ import styled from '@emotion/styled';
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage'; import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
import { MainButton } from '@/ui/input/button/components/MainButton'; import { MainButton } from '@/ui/input/button/components/MainButton';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import { StyledEmptyTextContainer } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import {
StyledErrorContainer,
StyledErrorSubTitle,
StyledErrorTitle,
} from '@/ui/layout/animated-placeholder/components/ErrorPlaceholderStyled';
const StyledBackDrop = styled.div` const StyledBackDrop = styled.div`
align-items: center; align-items: center;
@ -20,48 +26,33 @@ const StyledBackDrop = styled.div`
z-index: 10000; z-index: 10000;
`; `;
const StyledTextContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
padding: ${({ theme }) => theme.spacing(15)};
`;
const StyledButtonContainer = styled.div` const StyledButtonContainer = styled.div`
width: 200px; width: 200px;
`; `;
type StyledInfoProps = {
color: 'dark' | 'light';
};
const StyledInfo = styled.div<StyledInfoProps>`
color: ${(props) =>
props.color === 'light'
? props.theme.font.color.extraLight
: props.theme.font.color.primary};
font-size: ${() => (useIsMobile() ? '2.5rem' : '4rem')};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
`;
export const NotFound = () => { export const NotFound = () => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
<StyledBackDrop> <StyledBackDrop>
<StyledTextContainer> <StyledErrorContainer>
<StyledInfo color="dark">404</StyledInfo> <AnimatedPlaceholder type="error404" />
<StyledInfo color="light">Page not found</StyledInfo> <StyledEmptyTextContainer>
</StyledTextContainer> <StyledErrorTitle>Off the beaten path</StyledErrorTitle>
<StyledButtonContainer> <StyledErrorSubTitle>
<MainButton The page you're seeking is either gone or never was. Let's get you
title="Back to content" back on track
fullWidth </StyledErrorSubTitle>
onClick={() => navigate('/')} </StyledEmptyTextContainer>
/> <StyledButtonContainer>
</StyledButtonContainer> <MainButton
title="Back to content"
fullWidth
onClick={() => navigate('/')}
/>
</StyledButtonContainer>
</StyledErrorContainer>
</StyledBackDrop> </StyledBackDrop>
<SignInBackgroundMockPage /> <SignInBackgroundMockPage />
</> </>