diff --git a/packages/twenty-apps/public/call-recorder/css.d.ts b/packages/twenty-apps/public/call-recorder/css.d.ts new file mode 100644 index 0000000000..35306c6fc9 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/css.d.ts @@ -0,0 +1 @@ +declare module '*.css'; diff --git a/packages/twenty-apps/public/call-recorder/package.json b/packages/twenty-apps/public/call-recorder/package.json index 4ada0a95fe..26127ae322 100644 --- a/packages/twenty-apps/public/call-recorder/package.json +++ b/packages/twenty-apps/public/call-recorder/package.json @@ -34,8 +34,9 @@ "oxlint": "^0.16.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "twenty-client-sdk": "^2.16.0", - "twenty-sdk": "^2.16.0", + "twenty-client-sdk": "^2.18.0", + "twenty-sdk": "^2.18.0", + "twenty-ui": "^1.0.0-alpha.1", "typescript": "^5.9.3", "vite-tsconfig-paths": "^4.2.1", "vitest": "^4.1.9" diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx index 76eae30dc5..d5eae72046 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/calendar-event-recording.front-component.tsx @@ -1,5 +1,7 @@ import { defineFrontComponent } from 'twenty-sdk/define'; +import 'twenty-ui/style.css'; + import { CALENDAR_EVENT_RECORDING_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/calendar-event-recording-front-component-universal-identifier'; import { CalendarEventRecording } from 'src/front-components/components/CalendarEventRecording'; diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx index ce05393821..586a783700 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecording.tsx @@ -1,20 +1,20 @@ import styled from '@emotion/styled'; import { isUndefined } from '@sniptt/guards'; import { useSelectedRecordIds } from 'twenty-sdk/front-component'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { CalendarEventRecordingContent } from 'src/front-components/components/CalendarEventRecordingContent'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; const StyledCenteredState = styled.div` align-items: center; box-sizing: border-box; - color: ${recordingThemeCssVariables.font.colorTertiary}; + color: ${() => themeCssVariables.font.color.tertiary}; display: flex; - font-family: ${recordingThemeCssVariables.font.family}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; height: 100%; justify-content: center; - padding: ${recordingThemeCssVariables.spacing[4]}; + padding: ${() => themeCssVariables.spacing[4]}; `; export const CalendarEventRecording = () => { diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx index 7847f43409..e537770fe9 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventRecordingBody.tsx @@ -1,29 +1,30 @@ import styled from '@emotion/styled'; import { isUndefined } from '@sniptt/guards'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { RecordingSkeletonLoader } from 'src/front-components/components/RecordingSkeletonLoader'; import { RecordingTranscript } from 'src/front-components/components/RecordingTranscript'; import { RecordingVideoPlayer } from 'src/front-components/components/RecordingVideoPlayer'; import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type'; const StyledCenteredState = styled.div` align-items: center; box-sizing: border-box; - color: ${recordingThemeCssVariables.font.colorTertiary}; + color: ${() => themeCssVariables.font.color.tertiary}; display: flex; - font-family: ${recordingThemeCssVariables.font.family}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; justify-content: center; min-height: 240px; - padding: ${recordingThemeCssVariables.spacing[4]}; + padding: ${() => themeCssVariables.spacing[4]}; `; const StyledRecordingContainer = styled.div<{ $hasVideo?: boolean; }>` display: grid; - gap: ${recordingThemeCssVariables.spacing[2]}; + gap: ${() => themeCssVariables.spacing[2]}; grid-template-rows: ${({ $hasVideo }) => $hasVideo ? 'auto minmax(0, 1fr)' : 'minmax(0, 1fr)'}; min-height: 0; @@ -37,6 +38,7 @@ type CalendarEventRecordingBodyProps = { currentTimeSeconds: number; calendarEventParticipants: CalendarEventRecordingParticipant[]; onVideoTimeUpdate: (videoCurrentTimeSeconds: number) => void; + onVideoRetry: () => void; }; export const CalendarEventRecordingBody = ({ @@ -47,6 +49,7 @@ export const CalendarEventRecordingBody = ({ currentTimeSeconds, calendarEventParticipants, onVideoTimeUpdate, + onVideoRetry, }: CalendarEventRecordingBodyProps) => { const hasVideo = !isUndefined(videoFileUrl); @@ -60,14 +63,7 @@ export const CalendarEventRecordingBody = ({ } if (isCalendarEventRecordingQueryLoading) { - return ( - - - - ); + return ; } if (isUndefined(transcript) && !hasVideo) { @@ -84,6 +80,7 @@ export const CalendarEventRecordingBody = ({ )} themeCssVariables.background.primary}; border: 1px solid transparent; border-bottom: 1px solid transparent; - border-radius: ${recordingThemeCssVariables.border.radiusMd}; + border-radius: ${() => themeCssVariables.border.radius.md}; box-sizing: border-box; - font-family: ${recordingThemeCssVariables.font.family}; - padding: ${recordingThemeCssVariables.spacing[4]}; + font-family: ${() => themeCssVariables.font.family}; + padding: ${() => themeCssVariables.spacing[4]}; position: relative; width: 100%; `; @@ -24,31 +24,31 @@ const StyledRecordingHeader = styled.div` align-items: center; box-sizing: border-box; display: flex; - height: ${recordingThemeCssVariables.spacing[6]}; + height: ${() => themeCssVariables.spacing[6]}; `; const StyledRecordingTitle = styled.h2` - color: ${recordingThemeCssVariables.font.colorPrimary}; + color: ${() => themeCssVariables.font.color.primary}; flex: 1; - font-size: ${recordingThemeCssVariables.font.sizeMd}; - font-weight: ${recordingThemeCssVariables.font.weightMedium}; + font-size: ${() => themeCssVariables.font.size.md}; + font-weight: ${() => themeCssVariables.font.weight.medium}; margin: 0; overflow: hidden; - padding-inline: ${recordingThemeCssVariables.spacing[1]}; + padding-inline: ${() => themeCssVariables.spacing[1]}; user-select: none; `; const StyledRecordingBody = styled.div` box-sizing: border-box; - margin-top: ${recordingThemeCssVariables.spacing[2]}; + margin-top: ${() => themeCssVariables.spacing[2]}; `; const StyledRecordingContentFrame = styled.div` - background-color: ${recordingThemeCssVariables.background.secondary}; - border: 1px solid ${recordingThemeCssVariables.border.colorMedium}; - border-radius: ${recordingThemeCssVariables.border.radiusMd}; + background-color: ${() => themeCssVariables.background.secondary}; + border: 1px solid ${() => themeCssVariables.border.color.medium}; + border-radius: ${() => themeCssVariables.border.radius.md}; box-sizing: border-box; - padding: ${recordingThemeCssVariables.spacing[2]}; + padding: ${() => themeCssVariables.spacing[2]}; `; type CalendarEventRecordingContentProps = { @@ -59,31 +59,35 @@ export const CalendarEventRecordingContent = ({ calendarEventId, }: CalendarEventRecordingContentProps) => { const [currentTimeSeconds, setCurrentTimeSeconds] = useState(0); - const updateCurrentTimeSeconds = useCallback( - (videoCurrentTimeSeconds: number) => { - const nextCurrentTimeSeconds = - Math.floor( - videoCurrentTimeSeconds / TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS, - ) * TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS; - setCurrentTimeSeconds((previousCurrentTimeSeconds) => - previousCurrentTimeSeconds === nextCurrentTimeSeconds - ? previousCurrentTimeSeconds - : nextCurrentTimeSeconds, - ); - }, - [], - ); + const updateCurrentTimeSeconds = (videoCurrentTimeSeconds: number) => { + const nextCurrentTimeSeconds = + Math.floor( + videoCurrentTimeSeconds / TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS, + ) * TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS; + + setCurrentTimeSeconds((previousCurrentTimeSeconds) => + previousCurrentTimeSeconds === nextCurrentTimeSeconds + ? previousCurrentTimeSeconds + : nextCurrentTimeSeconds, + ); + }; const { transcript, videoFile, isCalendarEventRecordingQueryLoading, errorMessage, + refetchCalendarEventRecording, } = useCalendarEventRecording(calendarEventId); const { calendarEventParticipants } = useCalendarEventParticipants(calendarEventId); + const handleVideoRetry = () => { + setCurrentTimeSeconds(0); + refetchCalendarEventRecording(); + }; + const videoFileUrl = videoFile?.url ?? undefined; return ( @@ -103,6 +107,7 @@ export const CalendarEventRecordingContent = ({ currentTimeSeconds={currentTimeSeconds} calendarEventParticipants={calendarEventParticipants} onVideoTimeUpdate={updateCurrentTimeSeconds} + onVideoRetry={handleVideoRetry} /> diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx index 2b26364925..ffeed13539 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummary.tsx @@ -1,20 +1,20 @@ import styled from '@emotion/styled'; import { isUndefined } from '@sniptt/guards'; import { useSelectedRecordIds } from 'twenty-sdk/front-component'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { CalendarEventSummaryContent } from 'src/front-components/components/CalendarEventSummaryContent'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; const StyledCenteredState = styled.div` align-items: center; box-sizing: border-box; - color: ${recordingThemeCssVariables.font.colorTertiary}; + color: ${() => themeCssVariables.font.color.tertiary}; display: flex; - font-family: ${recordingThemeCssVariables.font.family}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; height: 100%; justify-content: center; - padding: ${recordingThemeCssVariables.spacing[4]}; + padding: ${() => themeCssVariables.spacing[4]}; `; export const CalendarEventSummary = () => { diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx index 379712a5df..4cd4417862 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryBody.tsx @@ -1,20 +1,20 @@ import styled from '@emotion/styled'; import { isUndefined } from '@sniptt/guards'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { SummaryMarkdown } from 'src/front-components/components/SummaryMarkdown'; import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; const StyledCenteredState = styled.div` align-items: center; box-sizing: border-box; - color: ${recordingThemeCssVariables.font.colorTertiary}; + color: ${() => themeCssVariables.font.color.tertiary}; display: flex; - font-family: ${recordingThemeCssVariables.font.family}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; justify-content: center; min-height: 240px; - padding: ${recordingThemeCssVariables.spacing[4]}; + padding: ${() => themeCssVariables.spacing[4]}; `; type CalendarEventSummaryBodyProps = { diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx index 572a8f573b..6656ff3067 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/CalendarEventSummaryContent.tsx @@ -1,16 +1,16 @@ import styled from '@emotion/styled'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { CalendarEventSummaryBody } from 'src/front-components/components/CalendarEventSummaryBody'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; import { useCalendarEventSummary } from 'src/front-components/hooks/use-calendar-event-summary'; const StyledSummaryShell = styled.div` - background: ${recordingThemeCssVariables.background.primary}; + background: ${() => themeCssVariables.background.primary}; border: 1px solid transparent; - border-radius: ${recordingThemeCssVariables.border.radiusMd}; + border-radius: ${() => themeCssVariables.border.radius.md}; box-sizing: border-box; - font-family: ${recordingThemeCssVariables.font.family}; - padding: ${recordingThemeCssVariables.spacing[4]}; + font-family: ${() => themeCssVariables.font.family}; + padding: ${() => themeCssVariables.spacing[4]}; position: relative; width: 100%; `; @@ -19,31 +19,31 @@ const StyledSummaryHeader = styled.div` align-items: center; box-sizing: border-box; display: flex; - height: ${recordingThemeCssVariables.spacing[6]}; + height: ${() => themeCssVariables.spacing[6]}; `; const StyledSummaryTitle = styled.h2` - color: ${recordingThemeCssVariables.font.colorPrimary}; + color: ${() => themeCssVariables.font.color.primary}; flex: 1; - font-size: ${recordingThemeCssVariables.font.sizeMd}; - font-weight: ${recordingThemeCssVariables.font.weightMedium}; + font-size: ${() => themeCssVariables.font.size.md}; + font-weight: ${() => themeCssVariables.font.weight.medium}; margin: 0; overflow: hidden; - padding-inline: ${recordingThemeCssVariables.spacing[1]}; + padding-inline: ${() => themeCssVariables.spacing[1]}; user-select: none; `; const StyledSummaryBody = styled.div` box-sizing: border-box; - margin-top: ${recordingThemeCssVariables.spacing[2]}; + margin-top: ${() => themeCssVariables.spacing[2]}; `; const StyledSummaryContentFrame = styled.div` - background-color: ${recordingThemeCssVariables.background.secondary}; - border: 1px solid ${recordingThemeCssVariables.border.colorMedium}; - border-radius: ${recordingThemeCssVariables.border.radiusMd}; + background-color: ${() => themeCssVariables.background.secondary}; + border: 1px solid ${() => themeCssVariables.border.color.medium}; + border-radius: ${() => themeCssVariables.border.radius.md}; box-sizing: border-box; - padding: ${recordingThemeCssVariables.spacing[3]}; + padding: ${() => themeCssVariables.spacing[3]}; `; type CalendarEventSummaryContentProps = { diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingSkeletonLoader.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingSkeletonLoader.tsx new file mode 100644 index 0000000000..7984851dd8 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingSkeletonLoader.tsx @@ -0,0 +1,98 @@ +import { keyframes } from '@emotion/react'; +import styled from '@emotion/styled'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +const SKELETON_ENTRY_COUNT = 3; +const SKELETON_BAR_HEIGHT = '16px'; +const SKELETON_BORDER_RADIUS = '4px'; + +const skeletonHighlightSweep = keyframes` + 100% { + transform: translateX(100%); + } +`; + +const StyledSkeletonBar = styled.div` + background-color: ${() => themeCssVariables.background.tertiary}; + border-radius: ${SKELETON_BORDER_RADIUS}; + height: ${SKELETON_BAR_HEIGHT}; + overflow: hidden; + position: relative; + + &::after { + animation: ${skeletonHighlightSweep} 1.5s ease-in-out infinite; + background-image: linear-gradient( + 90deg, + transparent, + ${() => themeCssVariables.background.transparent.lighter}, + transparent + ); + content: ''; + inset: 0; + position: absolute; + transform: translateX(-100%); + } +`; + +const StyledSkeletonAvatar = styled(StyledSkeletonBar)` + border-radius: 50px; + flex-shrink: 0; + width: ${SKELETON_BAR_HEIGHT}; +`; + +const StyledSkeletonSpeakerBar = styled(StyledSkeletonBar)` + width: 96px; +`; + +const StyledSkeletonTextBar = styled(StyledSkeletonBar)<{ $width: string }>` + width: ${({ $width }) => $width}; +`; + +const StyledSkeletonEntry = styled.div` + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: ${() => themeCssVariables.spacing[2]}; + padding: ${() => themeCssVariables.spacing[2]}; + width: 100%; +`; + +const StyledSkeletonEntryHeader = styled.div` + align-items: center; + display: flex; + gap: ${() => themeCssVariables.spacing[2]}; + min-height: ${() => themeCssVariables.spacing[6]}; +`; + +const StyledSkeletonContainer = styled.div` + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: ${() => themeCssVariables.spacing[2]}; + min-height: 240px; + width: 100%; +`; + +export const RecordingSkeletonLoader = () => { + const skeletonEntries = Array.from( + { length: SKELETON_ENTRY_COUNT }, + (_, skeletonEntryIndex) => skeletonEntryIndex, + ); + + return ( + + ); +}; diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx index 5666e2f5e0..0cd53258e9 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingTranscript.tsx @@ -1,10 +1,10 @@ import styled from '@emotion/styled'; import { isUndefined } from '@sniptt/guards'; import { useMemo } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { TranscriptEntryList } from 'src/front-components/components/TranscriptEntryList'; import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type'; import { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util'; import { parseTranscriptMarker } from 'src/logic-functions/domain/parse-transcript-marker.util'; @@ -12,10 +12,10 @@ import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string. const StyledTranscriptCenteredState = styled.div` align-items: center; - color: ${recordingThemeCssVariables.font.colorTertiary}; + color: ${() => themeCssVariables.font.color.tertiary}; display: flex; flex: 1; - font-size: ${recordingThemeCssVariables.font.sizeSm}; + font-size: ${() => themeCssVariables.font.size.sm}; justify-content: center; `; diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx index 40a60ab04a..057217f8d3 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/RecordingVideoPlayer.tsx @@ -1,52 +1,170 @@ import styled from '@emotion/styled'; -import { memo, type SyntheticEvent } from 'react'; - -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; +import { useState, type SyntheticEvent } from 'react'; +import { CircularProgressBar } from 'twenty-ui/feedback'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; const DEFAULT_VIDEO_ASPECT_RATIO = '16 / 9'; +// Forces the browser to decode the first frame under preload="metadata" +// (Safari otherwise paints nothing until play). Fragments stay client-side, +// so the signed file URL is unaffected. +const FIRST_FRAME_SEEK_FRAGMENT = '#t=0.001'; + +type RecordingVideoLoadState = 'awaiting-first-frame' | 'ready' | 'errored'; + const StyledVideoViewport = styled.div` aspect-ratio: ${DEFAULT_VIDEO_ASPECT_RATIO}; - background: ${recordingThemeCssVariables.background.primary}; - border-radius: ${recordingThemeCssVariables.border.radiusSm}; + background: ${() => themeCssVariables.background.primary}; + border-radius: ${() => themeCssVariables.border.radius.sm}; overflow: hidden; + position: relative; width: 100%; `; -const StyledVideo = styled.video` - accent-color: ${recordingThemeCssVariables.accent.primary}; - background: ${recordingThemeCssVariables.background.primary}; +const StyledVideo = styled.video<{ $isFirstFrameReady: boolean }>` + accent-color: ${() => themeCssVariables.accent.accent9}; + background: ${() => themeCssVariables.background.primary}; color-scheme: light dark; display: block; height: 100%; object-fit: contain; + opacity: ${({ $isFirstFrameReady }) => ($isFirstFrameReady ? 1 : 0)}; + transition: opacity 200ms ease; width: 100%; + + @media (prefers-reduced-motion: reduce) { + transition: none; + } +`; + +const StyledBufferingOverlay = styled.div<{ $isVisible: boolean }>` + align-items: center; + color: ${() => themeCssVariables.font.color.tertiary}; + display: flex; + inset: 0; + justify-content: center; + opacity: ${({ $isVisible }) => ($isVisible ? 1 : 0)}; + pointer-events: none; + position: absolute; + transition: opacity 150ms ease + ${({ $isVisible }) => ($isVisible ? '300ms' : '0ms')}; + + @media (prefers-reduced-motion: reduce) { + transition-duration: 0ms; + } +`; + +const StyledPlaybackErrorState = styled.div` + align-items: center; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: ${() => themeCssVariables.spacing[2]}; + height: 100%; + justify-content: center; + padding: ${() => themeCssVariables.spacing[4]}; +`; + +const StyledPlaybackErrorTitle = styled.span` + color: ${() => themeCssVariables.font.color.primary}; + font-size: ${() => themeCssVariables.font.size.sm}; + font-weight: ${() => themeCssVariables.font.weight.medium}; +`; + +const StyledPlaybackErrorDescription = styled.span` + color: ${() => themeCssVariables.font.color.secondary}; + font-size: ${() => themeCssVariables.font.size.xs}; +`; + +const StyledRetryButton = styled.button` + background: ${() => themeCssVariables.background.secondary}; + border: 1px solid ${() => themeCssVariables.border.color.medium}; + border-radius: ${() => themeCssVariables.border.radius.sm}; + color: ${() => themeCssVariables.font.color.secondary}; + cursor: pointer; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; + font-weight: ${() => themeCssVariables.font.weight.medium}; + margin-top: ${() => themeCssVariables.spacing[1]}; + padding: ${() => themeCssVariables.spacing[1]} + ${() => themeCssVariables.spacing[3]}; + + &:hover { + background: ${() => themeCssVariables.background.tertiary}; + } `; type RecordingVideoPlayerProps = { - src: string | undefined; + src: string; onTimeUpdate: (currentTimeSeconds: number) => void; + onRetry: () => void; }; -const RecordingVideoPlayerComponent = ({ +export const RecordingVideoPlayer = ({ src, onTimeUpdate, + onRetry, }: RecordingVideoPlayerProps) => { + const [loadState, setLoadState] = useState( + 'awaiting-first-frame', + ); + const [isPlaybackStalled, setIsPlaybackStalled] = useState(false); + + const markFirstFrameReady = () => { + setLoadState((previousLoadState) => + previousLoadState === 'awaiting-first-frame' + ? 'ready' + : previousLoadState, + ); + setIsPlaybackStalled(false); + }; + const handleTimeUpdate = (event: SyntheticEvent) => { + markFirstFrameReady(); onTimeUpdate(event.currentTarget.currentTime); }; + const handleWaiting = () => setIsPlaybackStalled(true); + + const handlePause = () => setIsPlaybackStalled(false); + + const handleError = () => setLoadState('errored'); + + if (loadState === 'errored') { + return ( + + + Playback failed + + The recording could not be loaded. + + Retry + + + ); + } + return ( + + + ); }; - -export const RecordingVideoPlayer = memo(RecordingVideoPlayerComponent); diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx index eb27ac70f0..4008facb2c 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryInlineSegments.tsx @@ -1,12 +1,12 @@ import styled from '@emotion/styled'; import { Fragment } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type'; const StyledBold = styled.strong` - color: ${recordingThemeCssVariables.font.colorPrimary}; - font-weight: ${recordingThemeCssVariables.font.weightMedium}; + color: ${() => themeCssVariables.font.color.primary}; + font-weight: ${() => themeCssVariables.font.weight.medium}; `; type SummaryInlineSegmentsProps = { diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx index 0d97c002ce..a663b22b31 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/SummaryMarkdown.tsx @@ -1,31 +1,31 @@ import styled from '@emotion/styled'; import { useMemo } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { SummaryInlineSegments } from 'src/front-components/components/SummaryInlineSegments'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; import { parseSummaryMarkdownBlocks } from 'src/front-components/utils/parse-summary-markdown-blocks.util'; const TOP_LEVEL_HEADING_MAX = 2; const StyledSummary = styled.div` - color: ${recordingThemeCssVariables.font.colorSecondary}; + color: ${() => themeCssVariables.font.color.secondary}; display: flex; flex-direction: column; - font-family: ${recordingThemeCssVariables.font.family}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; - gap: ${recordingThemeCssVariables.spacing[2]}; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; + gap: ${() => themeCssVariables.spacing[2]}; line-height: 1.5; `; const StyledHeading = styled.h3<{ $isTopLevel: boolean }>` - color: ${recordingThemeCssVariables.font.colorPrimary}; + color: ${() => themeCssVariables.font.color.primary}; font-size: ${({ $isTopLevel }) => $isTopLevel - ? recordingThemeCssVariables.font.sizeMd - : recordingThemeCssVariables.font.sizeSm}; - font-weight: ${recordingThemeCssVariables.font.weightMedium}; + ? themeCssVariables.font.size.md + : themeCssVariables.font.size.sm}; + font-weight: ${() => themeCssVariables.font.weight.medium}; margin: 0; - margin-top: ${recordingThemeCssVariables.spacing[1]}; + margin-top: ${() => themeCssVariables.spacing[1]}; `; const StyledParagraph = styled.p` @@ -35,9 +35,9 @@ const StyledParagraph = styled.p` const StyledList = styled.ul` display: flex; flex-direction: column; - gap: ${recordingThemeCssVariables.spacing[1]}; + gap: ${() => themeCssVariables.spacing[1]}; margin: 0; - padding-left: ${recordingThemeCssVariables.spacing[3]}; + padding-left: ${() => themeCssVariables.spacing[3]}; `; type SummaryMarkdownProps = { diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx index 43f4b7a479..fcab6228a0 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryList.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; import { useMemo } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { TranscriptEntryListItem } from 'src/front-components/components/TranscriptEntryListItem'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type'; import { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type'; import { buildCalendarEventParticipantBySpeakerName } from 'src/front-components/utils/build-calendar-event-participant-by-speaker-name.util'; @@ -13,7 +13,7 @@ const StyledTranscriptContainer = styled.div` display: flex; flex: 1; flex-direction: column; - gap: ${recordingThemeCssVariables.spacing[2]}; + gap: ${() => themeCssVariables.spacing[2]}; min-height: 0; `; diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx index c8b3af6af7..40fd0a304b 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptEntryListItem.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; import { isUndefined } from '@sniptt/guards'; +import { Avatar, Chip, ChipVariant } from 'twenty-ui/data-display'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { TranscriptSpeakerChip } from 'src/front-components/components/TranscriptSpeakerChip'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type'; import { type TranscriptEntry, @@ -14,15 +14,15 @@ const StyledEntry = styled.div<{ $isActive: boolean }>` align-items: flex-start; background: ${({ $isActive }) => $isActive - ? recordingThemeCssVariables.background.transparentBlue + ? themeCssVariables.background.transparent.blue : 'transparent'}; - border-radius: ${recordingThemeCssVariables.border.radiusSm}; + border-radius: ${() => themeCssVariables.border.radius.sm}; box-sizing: border-box; display: flex; flex-direction: column; - gap: ${recordingThemeCssVariables.spacing[2]}; + gap: ${() => themeCssVariables.spacing[2]}; justify-content: center; - padding: ${recordingThemeCssVariables.spacing[2]}; + padding: ${() => themeCssVariables.spacing[2]}; width: 100%; `; @@ -30,21 +30,21 @@ const StyledEntryHeader = styled.div` align-items: center; align-self: stretch; display: flex; - gap: ${recordingThemeCssVariables.spacing[2]}; - min-height: ${recordingThemeCssVariables.spacing[6]}; + gap: ${() => themeCssVariables.spacing[2]}; + min-height: ${() => themeCssVariables.spacing[6]}; min-width: 0; `; const StyledTimestamp = styled.span` - color: ${recordingThemeCssVariables.font.colorTertiary}; - font-size: ${recordingThemeCssVariables.font.sizeXs}; + color: ${() => themeCssVariables.font.color.tertiary}; + font-size: ${() => themeCssVariables.font.size.xs}; line-height: 1.4; `; const StyledEntryText = styled.p` align-self: stretch; - color: ${recordingThemeCssVariables.font.colorSecondary}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; + color: ${() => themeCssVariables.font.color.secondary}; + font-size: ${() => themeCssVariables.font.size.sm}; line-height: 1.4; margin: 0; `; @@ -52,8 +52,8 @@ const StyledEntryText = styled.p` const StyledWord = styled.span<{ $isSpoken: boolean }>` color: ${({ $isSpoken }) => $isSpoken - ? recordingThemeCssVariables.font.colorPrimary - : recordingThemeCssVariables.font.colorSecondary}; + ? themeCssVariables.font.color.primary + : themeCssVariables.font.color.secondary}; line-height: 1.4; transition: color 0.15s ease; `; @@ -77,11 +77,22 @@ export const TranscriptEntryListItem = ({ return ( - } /> {!isUndefined(entry.startSeconds) && ( diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx index 2808bd3f97..29020b4bb7 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx +++ b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptErrorBox.tsx @@ -1,33 +1,32 @@ import styled from '@emotion/styled'; - -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; const StyledStateContainer = styled.div` box-sizing: border-box; - font-family: ${recordingThemeCssVariables.font.family}; + font-family: ${() => themeCssVariables.font.family}; height: 100%; - padding: ${recordingThemeCssVariables.spacing[4]}; + padding: ${() => themeCssVariables.spacing[4]}; `; const StyledErrorBox = styled.div` - background: ${recordingThemeCssVariables.background.transparentDanger}; - border: 1px solid ${recordingThemeCssVariables.border.colorDanger}; - border-radius: ${recordingThemeCssVariables.border.radiusMd}; + background: ${() => themeCssVariables.background.transparent.danger}; + border: 1px solid ${() => themeCssVariables.border.color.danger}; + border-radius: ${() => themeCssVariables.border.radius.md}; display: flex; flex-direction: column; - gap: ${recordingThemeCssVariables.spacing[1]}; - padding: ${recordingThemeCssVariables.spacing[3]}; + gap: ${() => themeCssVariables.spacing[1]}; + padding: ${() => themeCssVariables.spacing[3]}; `; const StyledErrorTitle = styled.span` - color: ${recordingThemeCssVariables.font.colorDanger}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; - font-weight: ${recordingThemeCssVariables.font.weightMedium}; + color: ${() => themeCssVariables.font.color.danger}; + font-size: ${() => themeCssVariables.font.size.sm}; + font-weight: ${() => themeCssVariables.font.weight.medium}; `; const StyledErrorDescription = styled.span` - color: ${recordingThemeCssVariables.font.colorSecondary}; - font-size: ${recordingThemeCssVariables.font.sizeSm}; + color: ${() => themeCssVariables.font.color.secondary}; + font-size: ${() => themeCssVariables.font.size.sm}; `; type TranscriptErrorBoxProps = { diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerAvatar.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerAvatar.tsx deleted file mode 100644 index 620bf5d32c..0000000000 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerAvatar.tsx +++ /dev/null @@ -1,141 +0,0 @@ -// Duplicates minimal twenty-ui Avatar logic for this app. -// Remove once twenty-ui can be imported safely in front components. -import styled from '@emotion/styled'; -import { useState } from 'react'; - -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; -import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util'; - -const AVATAR_COLOR_NAMES = [ - 'red', - 'ruby', - 'crimson', - 'tomato', - 'orange', - 'amber', - 'yellow', - 'lime', - 'grass', - 'green', - 'jade', - 'mint', - 'turquoise', - 'cyan', - 'sky', - 'blue', - 'iris', - 'violet', - 'purple', - 'plum', - 'pink', - 'bronze', - 'gold', - 'brown', - 'gray', -] as const; - -const StyledAvatar = styled.div<{ - $backgroundColor: string; - $color: string; -}>` - align-items: center; - background: ${({ $backgroundColor }) => $backgroundColor}; - border-radius: 50px; - box-sizing: border-box; - color: ${({ $color }) => $color}; - display: flex; - flex-shrink: 0; - font-size: ${recordingThemeCssVariables.font.sizeXs}; - font-weight: ${recordingThemeCssVariables.font.weightMedium}; - height: 16px; - justify-content: center; - line-height: 15px; - overflow: hidden; - width: 16px; -`; - -const StyledAvatarImage = styled.img` - height: 100%; - object-fit: cover; - width: 100%; -`; - -type TranscriptSpeakerAvatarProps = { - speakerName: string; - avatarUrl: string | undefined; - placeholderColorSeed: string; -}; - -const getSpeakerInitial = (speakerName: string) => - speakerName.trim().charAt(0).toUpperCase() || '-'; - -export const TranscriptSpeakerAvatar = ({ - speakerName, - avatarUrl, - placeholderColorSeed, -}: TranscriptSpeakerAvatarProps) => { - const [erroredAvatarUrl, setErroredAvatarUrl] = useState( - undefined, - ); - - const shouldShowAvatarImage = - isNonEmptyString(avatarUrl) && erroredAvatarUrl !== avatarUrl; - - const handleAvatarImageError = () => { - if (isNonEmptyString(avatarUrl)) { - setErroredAvatarUrl(avatarUrl); - } - }; - - const avatarPlaceholderColor = getAvatarPlaceholderColor({ - placeholderColorSeed, - variant: 12, - }); - const avatarPlaceholderBackgroundColor = getAvatarPlaceholderColor({ - placeholderColorSeed, - variant: 4, - }); - - return ( - - ); -}; - -const getAvatarPlaceholderColor = ({ - placeholderColorSeed, - variant, -}: { - placeholderColorSeed: string; - variant: 4 | 12; -}): string => { - const avatarColorName = - AVATAR_COLOR_NAMES[ - Math.abs(hashString(placeholderColorSeed)) % AVATAR_COLOR_NAMES.length - ]; - - return `var(--t-color-${avatarColorName}${variant})`; -}; - -const hashString = (value: string): number => { - let hash = 0; - - for (let valueIndex = 0; valueIndex < value.length; valueIndex++) { - hash = value.charCodeAt(valueIndex) + ((hash << 5) - hash); - } - - return hash; -}; diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerChip.tsx b/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerChip.tsx deleted file mode 100644 index d7863eb42a..0000000000 --- a/packages/twenty-apps/public/call-recorder/src/front-components/components/TranscriptSpeakerChip.tsx +++ /dev/null @@ -1,51 +0,0 @@ -// Duplicates minimal twenty-ui Chip logic for this app. -// Remove once twenty-ui can be imported safely in front components. -import styled from '@emotion/styled'; - -import { TranscriptSpeakerAvatar } from 'src/front-components/components/TranscriptSpeakerAvatar'; -import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables'; - -const StyledSpeakerChip = styled.span` - align-items: center; - border-radius: ${recordingThemeCssVariables.border.radiusSm}; - color: ${recordingThemeCssVariables.font.colorPrimary}; - display: inline-flex; - font-size: ${recordingThemeCssVariables.font.sizeSm}; - font-weight: ${recordingThemeCssVariables.font.weightMedium}; - gap: ${recordingThemeCssVariables.spacing[1]}; - line-height: 1.4; - max-width: 100%; - min-width: 0; - text-decoration: none; - white-space: nowrap; -`; - -const StyledSpeakerName = styled.span` - flex-shrink: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -`; - -type TranscriptSpeakerChipProps = { - speakerName: string; - avatarUrl: string | undefined; - placeholderColorSeed: string; -}; - -export const TranscriptSpeakerChip = ({ - speakerName, - avatarUrl, - placeholderColorSeed, -}: TranscriptSpeakerChipProps) => { - return ( - - - {speakerName} - - ); -}; diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/constants/recording-theme-css-variables.ts b/packages/twenty-apps/public/call-recorder/src/front-components/constants/recording-theme-css-variables.ts deleted file mode 100644 index a8cce2347f..0000000000 --- a/packages/twenty-apps/public/call-recorder/src/front-components/constants/recording-theme-css-variables.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Avoid the SDK UI entrypoint until its bundle is safe for the browser runtime. -export const recordingThemeCssVariables = { - accent: { - primary: 'var(--t-accent-accent9)', - }, - background: { - primary: 'var(--t-background-primary)', - secondary: 'var(--t-background-secondary)', - transparentBlue: 'var(--t-background-transparent-blue)', - transparentDanger: 'var(--t-background-transparent-danger)', - }, - border: { - colorDanger: 'var(--t-border-color-danger)', - colorLight: 'var(--t-border-color-light)', - colorMedium: 'var(--t-border-color-medium)', - radiusMd: 'var(--t-border-radius-md)', - radiusSm: 'var(--t-border-radius-sm)', - }, - boxShadow: { - light: 'var(--t-box-shadow-light)', - }, - font: { - colorDanger: 'var(--t-font-color-danger)', - colorPrimary: 'var(--t-font-color-primary)', - colorSecondary: 'var(--t-font-color-secondary)', - colorTertiary: 'var(--t-font-color-tertiary)', - family: 'var(--t-font-family)', - sizeMd: 'var(--t-font-size-md)', - sizeSm: 'var(--t-font-size-sm)', - sizeXs: 'var(--t-font-size-xs)', - weightMedium: 'var(--t-font-weight-medium)', - }, - spacing: { - 1: 'var(--t-spacing-1)', - 2: 'var(--t-spacing-2)', - 3: 'var(--t-spacing-3)', - 4: 'var(--t-spacing-4)', - 6: 'var(--t-spacing-6)', - }, -} as const; diff --git a/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts b/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts index b6c986fc82..63954c1ecf 100644 --- a/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts +++ b/packages/twenty-apps/public/call-recorder/src/front-components/hooks/use-calendar-event-recording.ts @@ -9,6 +9,10 @@ type CalendarEventRecordingState = { errorMessage: string | undefined; }; +type CalendarEventRecordingResult = CalendarEventRecordingState & { + refetchCalendarEventRecording: () => void; +}; + type CalendarEventRecordingVideoFile = { fileId: string; label: string | null; @@ -31,13 +35,18 @@ const CALENDAR_EVENT_RECORDING_ERROR_MESSAGE = 'Please try again later.'; export const useCalendarEventRecording = ( calendarEventId: string | undefined, -): CalendarEventRecordingState => { +): CalendarEventRecordingResult => { const [state, setState] = useState({ transcript: undefined, videoFile: undefined, isCalendarEventRecordingQueryLoading: !isUndefined(calendarEventId), errorMessage: undefined, }); + const [refetchToken, setRefetchToken] = useState(0); + + const refetchCalendarEventRecording = () => { + setRefetchToken((previousRefetchToken) => previousRefetchToken + 1); + }; useEffect(() => { if (isUndefined(calendarEventId)) { @@ -123,9 +132,9 @@ export const useCalendarEventRecording = ( return () => { cancelled = true; }; - }, [calendarEventId]); + }, [calendarEventId, refetchToken]); - return state; + return { ...state, refetchCalendarEventRecording }; }; const hasTranscript = ( diff --git a/packages/twenty-apps/public/call-recorder/tsconfig.spec.json b/packages/twenty-apps/public/call-recorder/tsconfig.spec.json index ea69a6c010..b780a60a65 100644 --- a/packages/twenty-apps/public/call-recorder/tsconfig.spec.json +++ b/packages/twenty-apps/public/call-recorder/tsconfig.spec.json @@ -4,6 +4,6 @@ "composite": true, "types": ["vitest/globals", "node"] }, - "include": ["src/**/*.ts", "src/**/*.tsx"], + "include": ["css.d.ts", "src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules", "dist"] } diff --git a/packages/twenty-apps/public/call-recorder/yarn.lock b/packages/twenty-apps/public/call-recorder/yarn.lock index 5a23521880..287c923884 100644 --- a/packages/twenty-apps/public/call-recorder/yarn.lock +++ b/packages/twenty-apps/public/call-recorder/yarn.lock @@ -81,7 +81,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.29.2": version: 7.29.7 resolution: "@babel/runtime@npm:7.29.7" checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e @@ -124,6 +124,51 @@ __metadata: languageName: node linkType: hard +"@base-ui/react@npm:^1.5.0": + version: 1.6.0 + resolution: "@base-ui/react@npm:1.6.0" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@base-ui/utils": "npm:0.3.1" + "@floating-ui/react-dom": "npm:^2.1.8" + "@floating-ui/utils": "npm:^0.2.11" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@date-fns/tz": ^1.2.0 + "@types/react": ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@date-fns/tz": + optional: true + "@types/react": + optional: true + date-fns: + optional: true + checksum: 10c0/5e5994dc782710eac900d68cc0691aea70a47fabc6ef8d2ea943103d9aa96c4b44f9fd76ba65d29fa2667a5fbd7ea7a1cc02fe111c4bf639d4e3e65a1c281118 + languageName: node + linkType: hard + +"@base-ui/utils@npm:0.3.1": + version: 0.3.1 + resolution: "@base-ui/utils@npm:0.3.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@floating-ui/utils": "npm:^0.2.11" + reselect: "npm:^5.2.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4de858c3e2c8a4486a549d51c5e887bba6921e5a7f8db54439ba558feaf1656c3592a873928aee8a1711ee9210be3a65ca43b26713e97f649695ed2fa8c3af2c + languageName: node + linkType: hard + "@emnapi/core@npm:1.10.0": version: 1.10.0 resolution: "@emnapi/core@npm:1.10.0" @@ -489,6 +534,44 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.7.5": + version: 1.7.5 + resolution: "@floating-ui/core@npm:1.7.5" + dependencies: + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/f9c52205e198b231d63a387b09c659aab08c46a1899e0b0bbe147b8b4f048b546f15ba17cb5d2a471da9534f1883d979425e13e5c4ceee67be63e4b0abd4db5d + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.7.6": + version: 1.7.6 + resolution: "@floating-ui/dom@npm:1.7.6" + dependencies: + "@floating-ui/core": "npm:^1.7.5" + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/5c098e0d7b58c9bc769f276cca1766994c2c9c70c92d091a61bba8b3e9be53c011e0a79a8457fc2fb2f3d91697a26eb52e0a4962ef936dc963b45f58613c212f + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.1.8": + version: 2.1.8 + resolution: "@floating-ui/react-dom@npm:2.1.8" + dependencies: + "@floating-ui/dom": "npm:^1.7.6" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/26260ca4bb23b57c73b824062505abf977a008ce6e0463bdacca74f7e49853c4cd1d2bbf1a77c6caa17fa37dfffda2c6c4cd07a8737ebd7474aaff7818401d75 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.11": + version: 0.2.11 + resolution: "@floating-ui/utils@npm:0.2.11" + checksum: 10c0/f4bcea1559bdbb721ecc8e8ead423ac58d6a5b6e70b602cf0810ba6ad4ed1c77211b207faa88b278a9042f0c743133de08a203ed6741c1b6443423332884d5b3 + languageName: node + linkType: hard + "@genql/runtime@npm:^2.10.0": version: 2.10.0 resolution: "@genql/runtime@npm:2.10.0" @@ -1020,6 +1103,28 @@ __metadata: languageName: node linkType: hard +"@monaco-editor/loader@npm:^1.5.0": + version: 1.7.0 + resolution: "@monaco-editor/loader@npm:1.7.0" + dependencies: + state-local: "npm:^1.0.6" + checksum: 10c0/1fd3579cb09b669493cf553dfc0723a93483140a44353ce9a739827edfa7b2c6541b254024d15c48176ece749ef666b6d16cce6cf0e4396c7fa1c5a48012d08a + languageName: node + linkType: hard + +"@monaco-editor/react@npm:^4.7.0": + version: 4.7.0 + resolution: "@monaco-editor/react@npm:4.7.0" + dependencies: + "@monaco-editor/loader": "npm:^1.5.0" + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/a4e91c6eda1a71f5fd23871bd801de435490ccbc43934d23cba65cefe2be7e9d02c9a57c4ef80fc9fe99e4d4deea51e5db19cb4e0ecf3f51818dda0eb9cdbf12 + languageName: node + linkType: hard + "@napi-rs/wasm-runtime@npm:^1.1.4": version: 1.1.5 resolution: "@napi-rs/wasm-runtime@npm:1.1.5" @@ -1095,6 +1200,20 @@ __metadata: languageName: node linkType: hard +"@radix-ui/colors@npm:^3.0.0": + version: 3.0.0 + resolution: "@radix-ui/colors@npm:3.0.0" + checksum: 10c0/612c6bb4efe987f2d49aaca2f785687d08150da59a248328bc2b4e187ece4ef731c1dc5c6a9430fffcac4af9e65cf2f89ceb9806f8809940c9eb238b79d2cf2f + languageName: node + linkType: hard + +"@remix-run/router@npm:1.23.3": + version: 1.23.3 + resolution: "@remix-run/router@npm:1.23.3" + checksum: 10c0/73622465dd9e9a2c5a7ced7d1151ea6e9195a8a979c99b4f70a67093eeff7f339daf03a7c6304709a9c27deb2081a8fff6737663aacb33529c1a12a0a0827a17 + languageName: node + linkType: hard + "@rolldown/binding-android-arm64@npm:1.0.3": version: 1.0.3 resolution: "@rolldown/binding-android-arm64@npm:1.0.3" @@ -1225,6 +1344,24 @@ __metadata: languageName: node linkType: hard +"@tabler/icons-react@npm:^3.31.0": + version: 3.44.0 + resolution: "@tabler/icons-react@npm:3.44.0" + dependencies: + "@tabler/icons": "npm:3.44.0" + peerDependencies: + react: ">= 16" + checksum: 10c0/9a4bb6f1121d001049be8bb5757160ce4d5f2430671479ee94763f88ef43a69ab8d89b835d6c6451de22f3071793b779fefeed43db49d7993de1ea65c50145c9 + languageName: node + linkType: hard + +"@tabler/icons@npm:3.44.0": + version: 3.44.0 + resolution: "@tabler/icons@npm:3.44.0" + checksum: 10c0/0d5c1f9d6e68aa04c4a661e96035190e0884e0bc0022d31922efad3cfba210a25b2f013c0b6c0ab6aeb3d5a402a9b85a168966bd11a30ba6087f614f8521ccf3 + languageName: node + linkType: hard + "@twentyhq/call-recorder@workspace:.": version: 0.0.0-use.local resolution: "@twentyhq/call-recorder@workspace:." @@ -1239,8 +1376,9 @@ __metadata: react: "npm:^19.0.0" react-dom: "npm:^19.0.0" sharp: "npm:^0.34.5" - twenty-client-sdk: "npm:^2.16.0" - twenty-sdk: "npm:^2.16.0" + twenty-client-sdk: "npm:^2.18.0" + twenty-sdk: "npm:^2.18.0" + twenty-ui: "npm:^1.0.0-alpha.1" typescript: "npm:^5.9.3" vite-tsconfig-paths: "npm:^4.2.1" vitest: "npm:^4.1.9" @@ -1691,6 +1829,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + "code-excerpt@npm:^4.0.0": version: 4.0.0 resolution: "code-excerpt@npm:4.0.0" @@ -1750,6 +1895,13 @@ __metadata: languageName: node linkType: hard +"css-mediaquery@npm:^0.1.2": + version: 0.1.2 + resolution: "css-mediaquery@npm:0.1.2" + checksum: 10c0/b7825a78f52ce8a8198e004fcad0d7be1d3c9a0463ecd05ba31a0f2c94fb81468ad6f4d7bf715a6ca775696e7a17500c2a339b5216a6d0f789cbf78f9454d048 + languageName: node + linkType: hard + "csstype@npm:^3.0.2, csstype@npm:^3.2.2": version: 3.2.3 resolution: "csstype@npm:3.2.3" @@ -2239,6 +2391,13 @@ __metadata: languageName: node linkType: hard +"hyphenate-style-name@npm:^1.0.0": + version: 1.1.0 + resolution: "hyphenate-style-name@npm:1.1.0" + checksum: 10c0/bfe88deac2414a41a0d08811e277c8c098f23993d6a1eb17f14a0f11b54c4d42865a63d3cfe1914668eefb9a188e2de58f38b55a179a238fd1fef606893e194f + languageName: node + linkType: hard + "iconv-lite@npm:^0.7.2": version: 0.7.2 resolution: "iconv-lite@npm:0.7.2" @@ -2384,7 +2543,7 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^4.0.0": +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed @@ -2548,6 +2707,17 @@ __metadata: languageName: node linkType: hard +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + "magic-string@npm:^0.30.21": version: 0.30.21 resolution: "magic-string@npm:0.30.21" @@ -2557,6 +2727,15 @@ __metadata: languageName: node linkType: hard +"matchmediaquery@npm:^0.3.0": + version: 0.3.1 + resolution: "matchmediaquery@npm:0.3.1" + dependencies: + css-mediaquery: "npm:^0.1.2" + checksum: 10c0/19a254e0a1eb639fcc6aa7428a6e2d36cb5646a165579897e771580653e470b43d7f4a224c522700dbf1d8f19fb7c63a9719e0a6017727ade967155a2ae89fea + languageName: node + linkType: hard + "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -2671,6 +2850,13 @@ __metadata: languageName: node linkType: hard +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + "obug@npm:^2.1.1": version: 2.1.3 resolution: "obug@npm:2.1.3" @@ -2820,6 +3006,17 @@ __metadata: languageName: node linkType: hard +"prop-types@npm:^15.6.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + "proxy-from-env@npm:^2.1.0": version: 2.1.0 resolution: "proxy-from-env@npm:2.1.0" @@ -2838,7 +3035,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.7.0": +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 @@ -2856,6 +3053,44 @@ __metadata: languageName: node linkType: hard +"react-responsive@npm:^9.0.2": + version: 9.0.2 + resolution: "react-responsive@npm:9.0.2" + dependencies: + hyphenate-style-name: "npm:^1.0.0" + matchmediaquery: "npm:^0.3.0" + prop-types: "npm:^15.6.1" + shallow-equal: "npm:^1.2.1" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/c58b30d8b8ff993edbc73f7de59a2d86d8abd6380b06a9b41667c3bdd5401e5e710fa1d0fdac21368759ab9b958e8b479a111329a06e9b0241ee08f500b37a20 + languageName: node + linkType: hard + +"react-router-dom@npm:^6.4.4": + version: 6.30.4 + resolution: "react-router-dom@npm:6.30.4" + dependencies: + "@remix-run/router": "npm:1.23.3" + react-router: "npm:6.30.4" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10c0/1b25ab26a288da852f7b58eec5c14b3f37919e6773a557cd846d3a05d7a7d890c8d49bda93c0a7f497f240df1df8b0ad50635f990aafb55f2fc545ad8269a822 + languageName: node + linkType: hard + +"react-router@npm:6.30.4": + version: 6.30.4 + resolution: "react-router@npm:6.30.4" + dependencies: + "@remix-run/router": "npm:1.23.3" + peerDependencies: + react: ">=16.8" + checksum: 10c0/fb6de7d1002bcab9ea12c4072d93792eca494f1e4f30cfec334ccb6523756d23ce3e093c7c1241399296cebed94789a3fb89f96ee76004e0e746458a8f6bab33 + languageName: node + linkType: hard + "react@npm:^19.0.0, react@npm:^19.2.0": version: 19.2.7 resolution: "react@npm:19.2.7" @@ -2870,6 +3105,13 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^5.2.0": + version: 5.2.0 + resolution: "reselect@npm:5.2.0" + checksum: 10c0/d0a8497e404b25a769fe792eacae88b9b576c30870450cd243d76ec9427d91a59c4a2cc00d824d283448b444c4c698a8f961d771c8ae39c759313afb31950e2e + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -3021,6 +3263,13 @@ __metadata: languageName: node linkType: hard +"shallow-equal@npm:^1.2.1": + version: 1.2.1 + resolution: "shallow-equal@npm:1.2.1" + checksum: 10c0/51e03abadd97c9ebe590547d92db9148446962a3f23a3a0fb1ba2fccab80af881eef0ff1f8ccefd3f066c0bc5a4c8ca53706194813b95c8835fa66448a843a26 + languageName: node + linkType: hard + "sharp@npm:^0.34.5": version: 0.34.5 resolution: "sharp@npm:0.34.5" @@ -3166,6 +3415,13 @@ __metadata: languageName: node linkType: hard +"state-local@npm:^1.0.6": + version: 1.0.7 + resolution: "state-local@npm:1.0.7" + checksum: 10c0/8dc7daeac71844452fafb514a6d6b6f40d7e2b33df398309ea1c7b3948d6110c57f112b7196500a10c54fdde40291488c52c875575670fb5c819602deca48bd9 + languageName: node + linkType: hard + "std-env@npm:^4.0.0-rc.1": version: 4.1.0 resolution: "std-env@npm:4.1.0" @@ -3332,22 +3588,22 @@ __metadata: languageName: node linkType: hard -"twenty-client-sdk@npm:2.16.0, twenty-client-sdk@npm:^2.16.0": - version: 2.16.0 - resolution: "twenty-client-sdk@npm:2.16.0" +"twenty-client-sdk@npm:2.18.0, twenty-client-sdk@npm:^2.18.0": + version: 2.18.0 + resolution: "twenty-client-sdk@npm:2.18.0" dependencies: "@genql/runtime": "npm:^2.10.0" esbuild: "npm:^0.28.1" graphql: "npm:^16.8.1" lodash: "npm:^4.17.21" prettier: "npm:^3.8.3" - checksum: 10c0/f990220bec103a04ca50dc8bb38c295e94a4e51b27a3188f4a0ae515c204b76bf87f5591739eb705b97e7aa5a9aa6a215d0d4da05297e50a0392801ea64a2234 + checksum: 10c0/3b7d6ca4e7b59c04cd2348af0f2ba8de153e70d565b4a38c2744b44998dfcc6c2bdb84ba6315cec208953d8c6fa7573ab33c17355f971f279299d470c57be06e languageName: node linkType: hard -"twenty-sdk@npm:^2.16.0": - version: 2.16.0 - resolution: "twenty-sdk@npm:2.16.0" +"twenty-sdk@npm:^2.18.0": + version: 2.18.0 + resolution: "twenty-sdk@npm:2.18.0" dependencies: "@sniptt/guards": "npm:^0.2.0" axios: "npm:^1.16.0" @@ -3366,12 +3622,41 @@ __metadata: semver: "npm:7.6.3" sharp: "npm:^0.34.5" tinyglobby: "npm:^0.2.15" - twenty-client-sdk: "npm:2.16.0" + twenty-client-sdk: "npm:2.18.0" typescript: "npm:^5.9.3" uuid: "npm:^13.0.2" bin: twenty: dist/cli.cjs - checksum: 10c0/528f275f8bda022061a52c3563af514d2257f291fc5a232be90ca6011c50ff18a250984b6c68fc8e4c690644c92e1ccb0b89c511b8a19d832455c8860f2751a8 + checksum: 10c0/7e6d300894c427029e4124047614b55988671c914332613acb38f830cee11ecc480ff2a586b6153756aab1b7fa7f4520cf072c9db1428cd9ce9b9f7a62dfcd64 + languageName: node + linkType: hard + +"twenty-ui@npm:^1.0.0-alpha.1": + version: 1.0.0-alpha.1 + resolution: "twenty-ui@npm:1.0.0-alpha.1" + dependencies: + "@base-ui/react": "npm:^1.5.0" + "@monaco-editor/react": "npm:^4.7.0" + "@radix-ui/colors": "npm:^3.0.0" + "@sniptt/guards": "npm:^0.2.0" + "@tabler/icons-react": "npm:^3.31.0" + clsx: "npm:^2.1.1" + react-responsive: "npm:^9.0.2" + react-router-dom: "npm:^6.4.4" + type-fest: "npm:^4.10.1" + zod: "npm:^4.1.11" + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^19.0.0 + react-dom: ^19.0.0 + checksum: 10c0/213582e582949d6ffe90a7a8ab89a1d4e9dc724d34ed1b80bd7d50e834a8cb8539723556d41b39dc658f76f176bd8490ae0da8a3d6a713e79e6573b732084179 + languageName: node + linkType: hard + +"type-fest@npm:^4.10.1": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 languageName: node linkType: hard @@ -3432,6 +3717,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b + languageName: node + linkType: hard + "utility-types@npm:^3.10.0": version: 3.11.0 resolution: "utility-types@npm:3.11.0" @@ -3725,3 +4019,10 @@ __metadata: checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8 languageName: node linkType: hard + +"zod@npm:^4.1.11": + version: 4.4.3 + resolution: "zod@npm:4.4.3" + checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3 + languageName: node + linkType: hard