diff --git a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx
index 7032895aad..5bc8afa217 100644
--- a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx
+++ b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx
@@ -9,6 +9,7 @@ import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
+import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@@ -18,6 +19,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
+ EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { Section } from '@/ui/layout/section/components/Section';
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
@@ -74,14 +76,16 @@ export const Calendar = ({
} = useCalendarEvents(timelineCalendarEvents || []);
if (firstQueryLoading) {
- // TODO: implement loader
- return;
+ return ;
}
if (!firstQueryLoading && !timelineCalendarEvents?.length) {
// TODO: change animated placeholder
return (
-
+
diff --git a/packages/twenty-front/src/modules/activities/timeline/components/TimelineSkeletonLoader.tsx b/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx
similarity index 62%
rename from packages/twenty-front/src/modules/activities/timeline/components/TimelineSkeletonLoader.tsx
rename to packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx
index f328fda440..21478ed73c 100644
--- a/packages/twenty-front/src/modules/activities/timeline/components/TimelineSkeletonLoader.tsx
+++ b/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx
@@ -18,18 +18,14 @@ const StyledSkeletonSubSection = styled.div`
gap: ${({ theme }) => theme.spacing(4)};
`;
-const StyledSkeletonColumn = styled.div`
+const StyledSkeletonSubSectionContent = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
justify-content: center;
`;
-const StyledSkeletonLoader = ({
- isSecondColumn,
-}: {
- isSecondColumn: boolean;
-}) => {
+const SkeletonColumnLoader = ({ height }: { height: number }) => {
const theme = useTheme();
return (
-
+
);
};
-export const TimelineSkeletonLoader = () => {
+export const SkeletonLoader = ({
+ withSubSections = false,
+}: {
+ withSubSections?: boolean;
+}) => {
const theme = useTheme();
const skeletonItems = Array.from({ length: 3 }).map((_, index) => ({
id: `skeleton-item-${index}`,
@@ -56,16 +56,17 @@ export const TimelineSkeletonLoader = () => {
>
- {skeletonItems.map(({ id }, index) => (
-
-
-
-
-
- {index === 1 && }
-
-
- ))}
+ {withSubSections &&
+ skeletonItems.map(({ id }, index) => (
+
+
+
+
+
+ {index === 1 && }
+
+
+ ))}
);
diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx
index d0766c517b..53038755dc 100644
--- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx
+++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx
@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { H1Title, H1TitleFontColor } from 'twenty-ui';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
-import { EmailLoader } from '@/activities/emails/components/EmailLoader';
+import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging';
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
@@ -16,6 +16,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
+ EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { Card } from '@/ui/layout/card/components/Card';
import { Section } from '@/ui/layout/section/components/Section';
@@ -61,12 +62,15 @@ export const EmailThreads = ({
const { totalNumberOfThreads, timelineThreads } = data?.[queryName] ?? {};
if (firstQueryLoading) {
- return ;
+ return ;
}
if (!firstQueryLoading && !timelineThreads?.length) {
return (
-
+
diff --git a/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx b/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx
index aacc0ec6ad..482b8e18f8 100644
--- a/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx
+++ b/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx
@@ -1,8 +1,8 @@
import { ChangeEvent, useRef, useState } from 'react';
import styled from '@emotion/styled';
-import { isNonEmptyArray } from '@sniptt/guards';
import { IconPlus } from 'twenty-ui';
+import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { AttachmentList } from '@/activities/files/components/AttachmentList';
import { DropZone } from '@/activities/files/components/DropZone';
import { useAttachments } from '@/activities/files/hooks/useAttachments';
@@ -15,6 +15,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
+ EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { isDefined } from '~/utils/isDefined';
@@ -41,7 +42,7 @@ export const Attachments = ({
targetableObject: ActivityTargetableObject;
}) => {
const inputFileRef = useRef(null);
- const { attachments } = useAttachments(targetableObject);
+ const { attachments, loading } = useAttachments(targetableObject);
const { uploadAttachmentFile } = useUploadAttachmentFile();
const [isDraggingFile, setIsDraggingFile] = useState(false);
@@ -58,7 +59,13 @@ export const Attachments = ({
await uploadAttachmentFile(file, targetableObject);
};
- if (!isNonEmptyArray(attachments)) {
+ const isAttachmentsEmpty = !attachments || attachments.length === 0;
+
+ if (loading && isAttachmentsEmpty) {
+ return ;
+ }
+
+ if (isAttachmentsEmpty) {
return (
setIsDraggingFile(true)}>
{isDraggingFile ? (
@@ -67,7 +74,10 @@ export const Attachments = ({
onUploadFile={onUploadFile}
/>
) : (
-
+
diff --git a/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx b/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx
index cb8a1837d8..a91eb40313 100644
--- a/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx
+++ b/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx
@@ -4,13 +4,12 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
-// do we need to test this?
export const useAttachments = (targetableObject: ActivityTargetableObject) => {
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
});
- const { records: attachments } = useFindManyRecords({
+ const { records: attachments, loading } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.Attachment,
filter: {
[targetableObjectFieldIdName]: {
@@ -26,5 +25,6 @@ export const useAttachments = (targetableObject: ActivityTargetableObject) => {
return {
attachments,
+ loading,
};
};
diff --git a/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx b/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx
index 173bda4ede..4f3e83656e 100644
--- a/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx
+++ b/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { IconPlus } from 'twenty-ui';
+import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { NoteList } from '@/activities/notes/components/NoteList';
import { useNotes } from '@/activities/notes/hooks/useNotes';
@@ -12,6 +13,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
+ EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
const StyledNotesContainer = styled.div`
@@ -27,13 +29,22 @@ export const Notes = ({
}: {
targetableObject: ActivityTargetableObject;
}) => {
- const { notes } = useNotes(targetableObject);
+ const { notes, loading } = useNotes(targetableObject);
const openCreateActivity = useOpenCreateActivityDrawer();
- if (notes?.length === 0) {
+ const isNotesEmpty = !notes || notes.length === 0;
+
+ if (loading && isNotesEmpty) {
+ return ;
+ }
+
+ if (isNotesEmpty) {
return (
-
+
@@ -62,7 +73,7 @@ export const Notes = ({
;
+ }
+
+ if (isTasksEmpty) {
return (
-
+
diff --git a/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts b/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts
index f7938d17bc..e560e26079 100644
--- a/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts
+++ b/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts
@@ -107,17 +107,19 @@ export const useTasks = ({
setCurrentIncompleteTaskQueryVariables,
]);
- const { activities: completeTasksData } = useActivities({
- targetableObjects,
- activitiesFilters: completedQueryVariables.filter ?? {},
- activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
- });
+ const { activities: completeTasksData, loading: completeTasksLoading } =
+ useActivities({
+ targetableObjects,
+ activitiesFilters: completedQueryVariables.filter ?? {},
+ activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
+ });
- const { activities: incompleteTaskData } = useActivities({
- targetableObjects,
- activitiesFilters: incompleteQueryVariables.filter ?? {},
- activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
- });
+ const { activities: incompleteTaskData, loading: incompleteTasksLoading } =
+ useActivities({
+ targetableObjects,
+ activitiesFilters: incompleteQueryVariables.filter ?? {},
+ activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
+ });
const todayOrPreviousTasks = incompleteTaskData?.filter((task) => {
if (!task.dueAt) {
@@ -148,5 +150,7 @@ export const useTasks = ({
upcomingTasks: (upcomingTasks ?? []) as Activity[],
unscheduledTasks: (unscheduledTasks ?? []) as Activity[],
completedTasks: (completedTasks ?? []) as Activity[],
+ completeTasksLoading,
+ incompleteTasksLoading,
};
};
diff --git a/packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx b/packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx
index 8ee6765759..a6b2fd9b07 100644
--- a/packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx
+++ b/packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
+import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
-import { TimelineSkeletonLoader } from '@/activities/timeline/components/TimelineSkeletonLoader';
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
@@ -11,6 +11,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
+ EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
@@ -40,12 +41,15 @@ export const Timeline = ({
);
if (loading) {
- return ;
+ return ;
}
if (timelineActivitiesForGroup.length === 0) {
return (
-
+
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx
index 9b0dca3e8a..b64f624e33 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx
@@ -1,7 +1,7 @@
import styled from '@emotion/styled';
-import { isNonEmptyArray } from '@sniptt/guards';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
+import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
import { EventList } from '@/activities/timelineActivities/components/EventList';
import { useTimelineActivities } from '@/activities/timelineActivities/hooks/useTimelineActivities';
@@ -12,6 +12,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
+ EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
@@ -40,9 +41,19 @@ export const TimelineActivities = ({
const { timelineActivities, loading, fetchMoreRecords } =
useTimelineActivities(targetableObject);
- if (!isNonEmptyArray(timelineActivities)) {
+ const isTimelineActivitiesEmpty =
+ !timelineActivities || timelineActivities.length === 0;
+
+ if (loading && isTimelineActivitiesEmpty) {
+ return ;
+ }
+
+ if (isTimelineActivitiesEmpty) {
return (
-
+
diff --git a/packages/twenty-front/src/modules/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled.tsx b/packages/twenty-front/src/modules/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled.tsx
index 9d98e821f6..d088ec7e53 100644
--- a/packages/twenty-front/src/modules/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled.tsx
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
+import { motion } from 'framer-motion';
-const StyledEmptyContainer = styled.div`
+const StyledEmptyContainer = styled(motion.div)`
align-items: center;
width: 100%;
height: 100%;
@@ -13,6 +14,14 @@ const StyledEmptyContainer = styled.div`
export { StyledEmptyContainer as AnimatedPlaceholderEmptyContainer };
+export const EMPTY_PLACEHOLDER_TRANSITION_PROPS = {
+ initial: { opacity: 0 },
+ animate: { opacity: 1 },
+ transition: {
+ duration: 0.15,
+ },
+};
+
const StyledEmptyTextContainer = styled.div`
align-items: center;
display: flex;