openActivityRightDrawer(event.linkedRecordId)}
>
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx
index 943473d8c573..affd62affaee 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx
@@ -10,7 +10,7 @@ import {
formatToHumanReadableDay,
formatToHumanReadableMonth,
formatToHumanReadableTime,
-} from '~/utils';
+} from '~/utils/format/formatDate';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@@ -121,7 +121,7 @@ export const EventCardCalendarEvent = ({
return Calendar event not found
;
}
- return Error loading message
;
+ return Error loading calendar event
;
}
if (loading || isUndefined(calendarEvent)) {
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx
index 53447661f3c6..c1ffd1094ef3 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx
@@ -2,14 +2,12 @@ import { useState } from 'react';
import styled from '@emotion/styled';
import { EventCardCalendarEvent } from '@/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent';
-import {
- EventCard,
- EventCardToggleButton,
-} from '@/activities/timelineActivities/rows/components/EventCard';
+import { EventCard } from '@/activities/timelineActivities/rows/components/EventCard';
+import { EventCardToggleButton } from '@/activities/timelineActivities/rows/components/EventCardToggleButton';
import {
EventRowDynamicComponentProps,
- StyledItemAction,
- StyledItemAuthorText,
+ StyledEventRowItemAction,
+ StyledEventRowItemColumn,
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
type EventRowCalendarEventProps = EventRowDynamicComponentProps;
@@ -26,7 +24,7 @@ const StyledRowContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
-export const EventRowCalendarEvent: React.FC = ({
+export const EventRowCalendarEvent = ({
event,
authorFullName,
labelIdentifierValue,
@@ -34,24 +32,17 @@ export const EventRowCalendarEvent: React.FC = ({
const [, eventAction] = event.name.split('.');
const [isOpen, setIsOpen] = useState(false);
- const renderRow = () => {
- switch (eventAction) {
- case 'linked': {
- return (
-
- linked a calendar event with {labelIdentifierValue}
-
- );
- }
- default:
- throw new Error('Invalid event action for calendarEvent event type.');
- }
- };
+ if (['linked'].includes(eventAction) === false) {
+ throw new Error('Invalid event action for calendarEvent event type.');
+ }
+
return (
- {authorFullName}
- {renderRow()}
+ {authorFullName}
+
+ linked a calendar event with {labelIdentifierValue}
+
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/__stories__/EventCardCalendarEvent.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/__stories__/EventCardCalendarEvent.stories.tsx
new file mode 100644
index 000000000000..b0c3fd7ca54c
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/__stories__/EventCardCalendarEvent.stories.tsx
@@ -0,0 +1,68 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { graphql, HttpResponse } from 'msw';
+import { ComponentDecorator } from 'twenty-ui';
+
+import { EventCardCalendarEvent } from '@/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent';
+import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
+import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
+
+const meta: Meta = {
+ title: 'Modules/TimelineActivities/Rows/CalendarEvent/EventCardCalendarEvent',
+ component: EventCardCalendarEvent,
+ decorators: [
+ ComponentDecorator,
+ ObjectMetadataItemsDecorator,
+ SnackBarDecorator,
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ calendarEventId: '1',
+ },
+ parameters: {
+ msw: {
+ handlers: [
+ graphql.query('FindOneCalendarEvent', () => {
+ return HttpResponse.json({
+ data: {
+ calendarEvent: {
+ id: '1',
+ title: 'Mock title',
+ startsAt: '2022-01-01T00:00:00Z',
+ endsAt: '2022-01-01T01:00:00Z',
+ },
+ },
+ });
+ }),
+ ],
+ },
+ },
+};
+
+export const NotShared: Story = {
+ args: {
+ calendarEventId: '1',
+ },
+ parameters: {
+ msw: {
+ handlers: [
+ graphql.query('FindOneCalendarEvent', () => {
+ return HttpResponse.json({
+ errors: [
+ {
+ message: 'Forbidden',
+ extensions: {
+ code: 'FORBIDDEN',
+ },
+ },
+ ],
+ });
+ }),
+ ],
+ },
+ },
+};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx
index b802c3047d15..baed69eb738a 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx
@@ -1,7 +1,5 @@
import styled from '@emotion/styled';
-import { IconChevronDown, IconChevronUp } from 'twenty-ui';
-import { IconButton } from '@/ui/input/button/components/IconButton';
import { Card } from '@/ui/layout/card/components/Card';
type EventCardProps = {
@@ -9,15 +7,6 @@ type EventCardProps = {
isOpen: boolean;
};
-type EventCardToggleButtonProps = {
- isOpen: boolean;
- setIsOpen: (isOpen: boolean) => void;
-};
-
-const StyledButtonContainer = styled.div`
- border-radius: ${({ theme }) => theme.border.radius.sm};
-`;
-
const StyledCardContainer = styled.div`
align-items: flex-start;
display: flex;
@@ -52,19 +41,3 @@ export const EventCard = ({ children, isOpen }: EventCardProps) => {
)
);
};
-
-export const EventCardToggleButton = ({
- isOpen,
- setIsOpen,
-}: EventCardToggleButtonProps) => {
- return (
-
- setIsOpen(!isOpen)}
- size="small"
- variant="secondary"
- />
-
- );
-};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCardToggleButton.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCardToggleButton.tsx
new file mode 100644
index 000000000000..44a2cb4066a2
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCardToggleButton.tsx
@@ -0,0 +1,29 @@
+import styled from '@emotion/styled';
+import { IconChevronDown, IconChevronUp } from 'twenty-ui';
+
+import { IconButton } from '@/ui/input/button/components/IconButton';
+
+type EventCardToggleButtonProps = {
+ isOpen: boolean;
+ setIsOpen: (isOpen: boolean) => void;
+};
+
+const StyledButtonContainer = styled.div`
+ border-radius: ${({ theme }) => theme.border.radius.sm};
+`;
+
+export const EventCardToggleButton = ({
+ isOpen,
+ setIsOpen,
+}: EventCardToggleButtonProps) => {
+ return (
+
+ setIsOpen(!isOpen)}
+ size="small"
+ variant="secondary"
+ />
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventIconDynamicComponent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventIconDynamicComponent.tsx
new file mode 100644
index 000000000000..d1b35d7f2293
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventIconDynamicComponent.tsx
@@ -0,0 +1,26 @@
+import { IconCirclePlus, IconEditCircle, useIcons } from 'twenty-ui';
+
+import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+
+export const EventIconDynamicComponent = ({
+ event,
+ linkedObjectMetadataItem,
+}: {
+ event: TimelineActivity;
+ linkedObjectMetadataItem: ObjectMetadataItem | null;
+}) => {
+ const { getIcon } = useIcons();
+ const [, eventAction] = event.name.split('.');
+
+ if (eventAction === 'created') {
+ return ;
+ }
+ if (eventAction === 'updated') {
+ return ;
+ }
+
+ const IconComponent = getIcon(linkedObjectMetadataItem?.icon);
+
+ return ;
+};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx
index 0c51cdf65876..955450d67ca9 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx
@@ -1,13 +1,11 @@
import styled from '@emotion/styled';
-import { IconCirclePlus, IconEditCircle, useIcons } from 'twenty-ui';
import { EventRowActivity } from '@/activities/timelineActivities/rows/activity/components/EventRowActivity';
import { EventRowCalendarEvent } from '@/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent';
-import { EventRowMainObject } from '@/activities/timelineActivities/rows/mainObject/components/EventRowMainObject';
+import { EventRowMainObject } from '@/activities/timelineActivities/rows/main-object/components/EventRowMainObject';
import { EventRowMessage } from '@/activities/timelineActivities/rows/message/components/EventRowMessage';
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
-import { isDefined } from '~/utils/isDefined';
export interface EventRowDynamicComponentProps {
labelIdentifierValue: string;
@@ -17,7 +15,7 @@ export interface EventRowDynamicComponentProps {
authorFullName: string;
}
-const StyledItemColumn = styled.div`
+export const StyledEventRowItemColumn = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
@@ -25,23 +23,10 @@ const StyledItemColumn = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
-export const StyledItemAuthorText = styled(StyledItemColumn)``;
-
-export const StyledItemLabelIdentifier = styled(StyledItemColumn)``;
-
-export const StyledItemAction = styled(StyledItemColumn)`
+export const StyledEventRowItemAction = styled(StyledEventRowItemColumn)`
color: ${({ theme }) => theme.font.color.secondary};
`;
-const eventRowComponentMap: {
- [key: string]: React.FC;
-} = {
- calendarEvent: EventRowCalendarEvent,
- message: EventRowMessage,
- task: EventRowActivity,
- note: EventRowActivity,
-};
-
export const EventRowDynamicComponent = ({
labelIdentifierValue,
event,
@@ -50,53 +35,52 @@ export const EventRowDynamicComponent = ({
authorFullName,
}: EventRowDynamicComponentProps) => {
const [eventName] = event.name.split('.');
- const EventRowComponent = eventRowComponentMap[eventName];
-
- if (isDefined(EventRowComponent)) {
- return (
-
- );
- }
-
- if (eventName === mainObjectMetadataItem?.nameSingular) {
- return (
-
- );
- }
-
- throw new Error(`Cannot find event component for event name ${eventName}`);
-};
-export const EventIconDynamicComponent = ({
- event,
- linkedObjectMetadataItem,
-}: {
- event: TimelineActivity;
- linkedObjectMetadataItem: ObjectMetadataItem | null;
-}) => {
- const { getIcon } = useIcons();
- const [, eventAction] = event.name.split('.');
-
- if (eventAction === 'created') {
- return ;
- }
- if (eventAction === 'updated') {
- return ;
+ switch (eventName) {
+ case 'calendarEvent':
+ return (
+
+ );
+ case 'message':
+ return (
+
+ );
+ case 'task':
+ case 'note':
+ return (
+
+ );
+ case mainObjectMetadataItem?.nameSingular:
+ return (
+
+ );
+ default:
+ throw new Error(
+ `Cannot find event component for event name ${eventName}`,
+ );
}
-
- const IconComponent = getIcon(linkedObjectMetadataItem?.icon);
-
- return ;
};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiff.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiff.tsx
similarity index 76%
rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiff.tsx
rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiff.tsx
index d948b1c4ce28..1f55059a9c03 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiff.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiff.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
-import { EventFieldDiffLabel } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel';
-import { EventFieldDiffValue } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue';
-import { EventFieldDiffValueEffect } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValueEffect';
+import { EventFieldDiffLabel } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel';
+import { EventFieldDiffValue } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue';
+import { EventFieldDiffValueEffect } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffValueEffect';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@@ -10,7 +10,7 @@ type EventFieldDiffProps = {
diffRecord: Record;
mainObjectMetadataItem: ObjectMetadataItem;
fieldMetadataItem: FieldMetadataItem | undefined;
- forgedRecordId: string;
+ diffArtificialRecordStoreId: string;
};
const StyledEventFieldDiffContainer = styled.div`
@@ -26,23 +26,23 @@ export const EventFieldDiff = ({
diffRecord,
mainObjectMetadataItem,
fieldMetadataItem,
- forgedRecordId,
+ diffArtificialRecordStoreId,
}: EventFieldDiffProps) => {
if (!fieldMetadataItem) {
- return null;
+ throw new Error('fieldMetadataItem is required');
}
return (
→
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer.tsx
new file mode 100644
index 000000000000..3b4cf60396f8
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer.tsx
@@ -0,0 +1,39 @@
+import { EventFieldDiff } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiff';
+import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+
+type EventFieldDiffContainerProps = {
+ mainObjectMetadataItem: ObjectMetadataItem;
+ diffKey: string;
+ diffValue: any;
+ eventId: string;
+ fieldMetadataItemMap: Record;
+};
+
+export const EventFieldDiffContainer = ({
+ mainObjectMetadataItem,
+ diffKey,
+ diffValue,
+ eventId,
+ fieldMetadataItemMap,
+}: EventFieldDiffContainerProps) => {
+ const fieldMetadataItem = fieldMetadataItemMap[diffKey];
+
+ if (!fieldMetadataItem) {
+ throw new Error(
+ `Cannot find field metadata item for field name ${diffKey} on object ${mainObjectMetadataItem.nameSingular}`,
+ );
+ }
+
+ const diffArtificialRecordStoreId = eventId + '--' + fieldMetadataItem.id;
+
+ return (
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel.tsx
similarity index 87%
rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel.tsx
rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel.tsx
index 9ee7393ef6a6..6bb302ae9638 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel.tsx
@@ -22,8 +22,6 @@ const StyledUpdatedFieldIconContainer = styled.div`
width: 14px;
`;
-const StyledUpdatedFieldLabel = styled.div``;
-
export const EventFieldDiffLabel = ({
fieldMetadataItem,
}: EventFieldDiffLabelProps) => {
@@ -38,9 +36,7 @@ export const EventFieldDiffLabel = ({
-
- {fieldMetadataItem.label}
-
+ {fieldMetadataItem.label}
);
};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue.tsx
similarity index 93%
rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue.tsx
rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue.tsx
index e37cbdec3f8f..0cfc1c6b74ce 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue.tsx
@@ -7,7 +7,7 @@ import { FieldDisplay } from '@/object-record/record-field/components/FieldDispl
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
type EventFieldDiffValueProps = {
- forgedRecordId: string;
+ diffArtificialRecordStoreId: string;
mainObjectMetadataItem: ObjectMetadataItem;
fieldMetadataItem: FieldMetadataItem;
};
@@ -18,7 +18,7 @@ const StyledEventFieldDiffValue = styled.div`
`;
export const EventFieldDiffValue = ({
- forgedRecordId,
+ diffArtificialRecordStoreId,
mainObjectMetadataItem,
fieldMetadataItem,
}: EventFieldDiffValueProps) => {
@@ -26,7 +26,7 @@ export const EventFieldDiffValue = ({
| undefined;
mainObjectMetadataItem: ObjectMetadataItem;
fieldMetadataItem: FieldMetadataItem;
}) => {
const setEntityFields = useSetRecoilState(
- recordStoreFamilyState(forgedRecordId),
+ recordStoreFamilyState(diffArtificialRecordStoreId),
);
useEffect(() => {
@@ -25,14 +25,14 @@ export const EventFieldDiffValueEffect = ({
const forgedObjectRecord = {
__typename: mainObjectMetadataItem.nameSingular,
- id: forgedRecordId,
+ id: diffArtificialRecordStoreId,
[fieldMetadataItem.name]: diffRecord,
};
setEntityFields(forgedObjectRecord);
}, [
diffRecord,
- forgedRecordId,
+ diffArtificialRecordStoreId,
fieldMetadataItem.name,
mainObjectMetadataItem.nameSingular,
setEntityFields,
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObject.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObject.tsx
similarity index 75%
rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObject.tsx
rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObject.tsx
index f2aa06509847..f2dcc69055fa 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObject.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObject.tsx
@@ -2,11 +2,10 @@ import styled from '@emotion/styled';
import {
EventRowDynamicComponentProps,
- StyledItemAction,
- StyledItemAuthorText,
- StyledItemLabelIdentifier,
+ StyledEventRowItemAction,
+ StyledEventRowItemColumn,
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
-import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated';
+import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated';
type EventRowMainObjectProps = EventRowDynamicComponentProps;
@@ -28,11 +27,11 @@ export const EventRowMainObject = ({
case 'created': {
return (
-
+
{labelIdentifierValue}
-
- was created by
- {authorFullName}
+
+ was created by
+ {authorFullName}
);
}
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated.tsx
similarity index 55%
rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated.tsx
rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated.tsx
index 435a834bb5ad..30e6343bd708 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated.tsx
@@ -1,15 +1,13 @@
import { useState } from 'react';
import styled from '@emotion/styled';
+import { EventCard } from '@/activities/timelineActivities/rows/components/EventCard';
+import { EventCardToggleButton } from '@/activities/timelineActivities/rows/components/EventCardToggleButton';
import {
- EventCard,
- EventCardToggleButton,
-} from '@/activities/timelineActivities/rows/components/EventCard';
-import {
- StyledItemAction,
- StyledItemAuthorText,
+ StyledEventRowItemAction,
+ StyledEventRowItemColumn,
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
-import { EventFieldDiff } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiff';
+import { EventFieldDiffContainer } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer';
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@@ -33,34 +31,6 @@ const StyledEventRowMainObjectUpdatedContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
-const renderUpdateDescription = (
- mainObjectMetadataItem: ObjectMetadataItem,
- diffKey: string,
- diffValue: any,
- eventId: string,
- fieldMetadataItemMap: Record,
-) => {
- const fieldMetadataItem = fieldMetadataItemMap[diffKey];
-
- if (!fieldMetadataItem) {
- throw new Error(
- `Cannot find field metadata item for field name ${diffKey} on object ${mainObjectMetadataItem.nameSingular}`,
- );
- }
-
- const forgedRecordId = eventId + '--' + fieldMetadataItem.id;
-
- return (
-
- );
-};
-
export const EventRowMainObjectUpdated = ({
authorFullName,
labelIdentifierValue,
@@ -86,17 +56,18 @@ export const EventRowMainObjectUpdated = ({
return (
- {authorFullName}
-
+ {authorFullName}
+
updated
- {diffEntries.length === 1 &&
- renderUpdateDescription(
- mainObjectMetadataItem,
- diffEntries[0][0],
- diffEntries[0][1].after,
- event.id,
- fieldMetadataItemMap,
- )}
+ {diffEntries.length === 1 && (
+
+ )}
{diffEntries.length > 1 && (
<>
@@ -105,19 +76,20 @@ export const EventRowMainObjectUpdated = ({
>
)}
-
+
{diffEntries.length > 1 && (
- {diffEntries.map(([diffKey, diffValue]) =>
- renderUpdateDescription(
- mainObjectMetadataItem,
- diffKey,
- diffValue.after,
- event.id,
- fieldMetadataItemMap,
- ),
- )}
+ {diffEntries.map(([diffKey, diffValue]) => (
+
+ ))}
)}
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx
new file mode 100644
index 000000000000..0cb945bdc4b2
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx
@@ -0,0 +1,51 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
+
+import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated';
+import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
+import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
+import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
+import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata';
+
+const meta: Meta = {
+ title: 'Modules/TimelineActivities/Rows/MainObject/EventRowMainObjectUpdated',
+ component: EventRowMainObjectUpdated,
+ args: {
+ authorFullName: 'John Doe',
+ labelIdentifierValue: 'Mock',
+ event: {
+ id: '1',
+ name: 'mock.updated',
+ properties: {
+ diff: {
+ jobTitle: {
+ after: 'mock job title',
+ before: '',
+ },
+ linkedinLink: {
+ after: {
+ url: 'mock.linkedin',
+ label: 'mock linkedin url',
+ },
+ before: {
+ url: '',
+ label: '',
+ },
+ },
+ },
+ },
+ } as TimelineActivity,
+ mainObjectMetadataItem: mockedPersonObjectMetadataItem,
+ },
+ decorators: [
+ ComponentDecorator,
+ ObjectMetadataItemsDecorator,
+ SnackBarDecorator,
+ RouterDecorator,
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx
index 4f85b7463cea..d39f46c677ab 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx
@@ -101,7 +101,7 @@ export const EventCardMessage = ({
return Loading...
;
}
- const messageParticipantHandles = message?.messageParticipants
+ const messageParticipantHandles = message.messageParticipants
.map((participant) => participant.handle)
.join(', ');
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx
index 95e34fa8c159..83513994517f 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx
@@ -1,15 +1,12 @@
import { useState } from 'react';
import styled from '@emotion/styled';
-import {
- EventCard,
- EventCardToggleButton,
-} from '@/activities/timelineActivities/rows/components/EventCard';
+import { EventCard } from '@/activities/timelineActivities/rows/components/EventCard';
+import { EventCardToggleButton } from '@/activities/timelineActivities/rows/components/EventCardToggleButton';
import {
EventRowDynamicComponentProps,
- StyledItemAction,
- StyledItemAuthorText,
- StyledItemLabelIdentifier,
+ StyledEventRowItemAction,
+ StyledEventRowItemColumn,
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
import { EventCardMessage } from '@/activities/timelineActivities/rows/message/components/EventCardMessage';
@@ -27,36 +24,28 @@ const StyledRowContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
-export const EventRowMessage: React.FC = ({
- labelIdentifierValue,
+export const EventRowMessage = ({
event,
authorFullName,
+ labelIdentifierValue,
}: EventRowMessageProps) => {
const [, eventAction] = event.name.split('.');
const [isOpen, setIsOpen] = useState(false);
- const renderRow = () => {
- switch (eventAction) {
- case 'linked': {
- return (
- <>
- {authorFullName}
- linked an email with
-
- {labelIdentifierValue}
-
- >
- );
- }
- default:
- throw new Error('Invalid event action for message event type.');
- }
- };
+ if (['linked'].includes(eventAction) === false) {
+ throw new Error('Invalid event action for message event type.');
+ }
return (
- {renderRow()}
+ {authorFullName}
+
+ linked an email with
+
+
+ {labelIdentifierValue}
+
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/__stories__/EventCardMessage.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/__stories__/EventCardMessage.stories.tsx
new file mode 100644
index 000000000000..3e8e08cd06dc
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/__stories__/EventCardMessage.stories.tsx
@@ -0,0 +1,82 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { graphql, HttpResponse } from 'msw';
+import { ComponentDecorator } from 'twenty-ui';
+
+import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext';
+import { EventCardMessage } from '@/activities/timelineActivities/rows/message/components/EventCardMessage';
+import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
+import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
+
+const meta: Meta = {
+ title: 'Modules/TimelineActivities/Rows/Message/EventCardMessage',
+ component: EventCardMessage,
+ decorators: [
+ ComponentDecorator,
+ ObjectMetadataItemsDecorator,
+ SnackBarDecorator,
+ (Story) => {
+ return (
+
+
+
+ );
+ },
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ messageId: '1',
+ authorFullName: 'John Doe',
+ },
+ parameters: {
+ msw: {
+ handlers: [
+ graphql.query('FindOneMessage', () => {
+ return HttpResponse.json({
+ data: {
+ message: {
+ id: '1',
+ subject: 'Mock title',
+ text: 'Mock body',
+ messageParticipants: [],
+ },
+ },
+ });
+ }),
+ ],
+ },
+ },
+};
+
+export const NotShared: Story = {
+ args: {
+ messageId: '1',
+ authorFullName: 'John Doe',
+ },
+ parameters: {
+ msw: {
+ handlers: [
+ graphql.query('FindOneMessage', () => {
+ return HttpResponse.json({
+ errors: [
+ {
+ message: 'Forbidden',
+ extensions: {
+ code: 'FORBIDDEN',
+ },
+ },
+ ],
+ });
+ }),
+ ],
+ },
+ },
+};
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/utils/__tests__/getTimelineActivityAuthorFullName.test.ts b/packages/twenty-front/src/modules/activities/timelineActivities/utils/__tests__/getTimelineActivityAuthorFullName.test.ts
new file mode 100644
index 000000000000..85ac636ea096
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/utils/__tests__/getTimelineActivityAuthorFullName.test.ts
@@ -0,0 +1,63 @@
+import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
+import { getTimelineActivityAuthorFullName } from '@/activities/timelineActivities/utils/getTimelineActivityAuthorFullName';
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+
+describe('getTimelineActivityAuthorFullName', () => {
+ it('should return "You" if the current workspace member is the author', () => {
+ const event = {
+ workspaceMember: {
+ id: '123',
+ name: {
+ firstName: 'John',
+ lastName: 'Doe',
+ },
+ },
+ };
+ const currentWorkspaceMember = {
+ id: '123',
+ };
+
+ const result = getTimelineActivityAuthorFullName(
+ event as TimelineActivity,
+ currentWorkspaceMember as CurrentWorkspaceMember,
+ );
+
+ expect(result).toBe('You');
+ });
+
+ it('should return the full name of the workspace member if they are not the current workspace member', () => {
+ const event = {
+ workspaceMember: {
+ id: '456',
+ name: {
+ firstName: 'Jane',
+ lastName: 'Smith',
+ },
+ },
+ };
+ const currentWorkspaceMember = {
+ id: '123',
+ };
+
+ const result = getTimelineActivityAuthorFullName(
+ event as TimelineActivity,
+ currentWorkspaceMember as CurrentWorkspaceMember,
+ );
+
+ expect(result).toBe('Jane Smith');
+ });
+
+ it('should return "Twenty" if the workspace member is not defined', () => {
+ const event = {};
+ const currentWorkspaceMember = {
+ id: '123',
+ };
+
+ const result = getTimelineActivityAuthorFullName(
+ event as TimelineActivity,
+ currentWorkspaceMember as CurrentWorkspaceMember,
+ );
+
+ expect(result).toBe('Twenty');
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/utils/getTimelineActivityAuthorFullName.ts b/packages/twenty-front/src/modules/activities/timelineActivities/utils/getTimelineActivityAuthorFullName.ts
new file mode 100644
index 000000000000..e97b27fa9450
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/utils/getTimelineActivityAuthorFullName.ts
@@ -0,0 +1,15 @@
+import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { isDefined } from '~/utils/isDefined';
+
+export const getTimelineActivityAuthorFullName = (
+ event: TimelineActivity,
+ currentWorkspaceMember: CurrentWorkspaceMember,
+) => {
+ if (isDefined(event.workspaceMember)) {
+ return currentWorkspaceMember.id === event.workspaceMember.id
+ ? 'You'
+ : `${event.workspaceMember?.name.firstName} ${event.workspaceMember?.name.lastName}`;
+ }
+ return 'Twenty';
+};
diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts b/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts
index f4baef7cb995..c50755a51f92 100644
--- a/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts
+++ b/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts
@@ -3638,7 +3638,7 @@ export const getObjectMetadataItemsMock = () => {
},
{
__typename: 'object',
- id: '20202020-049d-4d0c-9e7c-e74fee3f88b2',
+ id: '20202020-6736-4337-b5c4-8b39fae325a5',
dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
nameSingular: 'timelineActivity',
namePlural: 'timelineActivities',
@@ -3654,6 +3654,24 @@ export const getObjectMetadataItemsMock = () => {
updatedAt: '2023-11-30T11:13:15.206Z',
fields: [],
},
+ {
+ __typename: 'object',
+ id: '20202020-3f6b-4425-80ab-e468899ab4b2',
+ dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1',
+ nameSingular: 'message',
+ namePlural: 'messages',
+ labelSingular: 'Message',
+ labelPlural: 'Messages',
+ description: 'A message',
+ icon: 'IconMessage',
+ isCustom: false,
+ isRemote: false,
+ isActive: true,
+ isSystem: true,
+ createdAt: '2023-11-30T11:13:15.206Z',
+ updatedAt: '2023-11-30T11:13:15.206Z',
+ fields: [],
+ },
];
// Todo fix typing here (the backend is not in sync with the frontend)
diff --git a/packages/twenty-front/src/utils/format/__tests__/formatDate.test.ts b/packages/twenty-front/src/utils/format/__tests__/formatDate.test.ts
new file mode 100644
index 000000000000..a80c11d3e7f5
--- /dev/null
+++ b/packages/twenty-front/src/utils/format/__tests__/formatDate.test.ts
@@ -0,0 +1,29 @@
+import {
+ formatToHumanReadableDay,
+ formatToHumanReadableMonth,
+ formatToHumanReadableTime,
+} from '../formatDate';
+
+describe('formatToHumanReadableMonth', () => {
+ it('should format the date to a human-readable month', () => {
+ const date = new Date('2022-01-01');
+ const result = formatToHumanReadableMonth(date);
+ expect(result).toBe('Jan');
+ });
+});
+
+describe('formatToHumanReadableDay', () => {
+ it('should format the date to a human-readable day', () => {
+ const date = new Date('2022-01-01');
+ const result = formatToHumanReadableDay(date);
+ expect(result).toBe('1');
+ });
+});
+
+describe('formatToHumanReadableTime', () => {
+ it('should format the date to a human-readable time', () => {
+ const date = new Date('2022-01-01T12:30:00');
+ const result = formatToHumanReadableTime(date);
+ expect(result).toBe('12:30 PM');
+ });
+});
diff --git a/packages/twenty-front/src/utils/format/formatDate.ts b/packages/twenty-front/src/utils/format/formatDate.ts
new file mode 100644
index 000000000000..7bed724a89ad
--- /dev/null
+++ b/packages/twenty-front/src/utils/format/formatDate.ts
@@ -0,0 +1,26 @@
+import { parseDate } from '~/utils/date-utils';
+
+export const formatToHumanReadableMonth = (date: Date | string) => {
+ const parsedJSDate = parseDate(date).toJSDate();
+
+ return new Intl.DateTimeFormat(undefined, {
+ month: 'short',
+ }).format(parsedJSDate);
+};
+
+export const formatToHumanReadableDay = (date: Date | string) => {
+ const parsedJSDate = parseDate(date).toJSDate();
+
+ return new Intl.DateTimeFormat(undefined, {
+ day: 'numeric',
+ }).format(parsedJSDate);
+};
+
+export const formatToHumanReadableTime = (date: Date | string) => {
+ const parsedJSDate = parseDate(date).toJSDate();
+
+ return new Intl.DateTimeFormat(undefined, {
+ hour: 'numeric',
+ minute: 'numeric',
+ }).format(parsedJSDate);
+};
diff --git a/packages/twenty-front/src/utils/index.ts b/packages/twenty-front/src/utils/index.ts
index 25015d069f44..9edcbb6b973d 100644
--- a/packages/twenty-front/src/utils/index.ts
+++ b/packages/twenty-front/src/utils/index.ts
@@ -22,31 +22,6 @@ export const formatToHumanReadableDateTime = (date: Date | string) => {
}).format(parsedJSDate);
};
-export const formatToHumanReadableMonth = (date: Date | string) => {
- const parsedJSDate = parseDate(date).toJSDate();
-
- return new Intl.DateTimeFormat(undefined, {
- month: 'short',
- }).format(parsedJSDate);
-};
-
-export const formatToHumanReadableDay = (date: Date | string) => {
- const parsedJSDate = parseDate(date).toJSDate();
-
- return new Intl.DateTimeFormat(undefined, {
- day: 'numeric',
- }).format(parsedJSDate);
-};
-
-export const formatToHumanReadableTime = (date: Date | string) => {
- const parsedJSDate = parseDate(date).toJSDate();
-
- return new Intl.DateTimeFormat(undefined, {
- hour: 'numeric',
- minute: 'numeric',
- }).format(parsedJSDate);
-};
-
export const sanitizeURL = (link: string | null | undefined) => {
return link
? link.replace(/(https?:\/\/)|(www\.)/g, '').replace(/\/$/, '')