New Empty States (#3465)
New empty states --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>Server’s 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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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%;
|
||||||
|
`;
|
||||||
@ -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;
|
||||||
|
`;
|
||||||
@ -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',
|
||||||
|
};
|
||||||
@ -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 />
|
||||||
</>
|
</>
|
||||||
|
|||||||