diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 5950c81d5cc1..7f053fc6b10f 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -141,6 +141,7 @@ export enum CaptchaDriverType { export type ClientConfig = { __typename?: 'ClientConfig'; + analyticsEnabled: Scalars['Boolean']; api: ApiConfig; authProviders: AuthProviders; billing: Billing; @@ -1599,7 +1600,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; -export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } }; +export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, analyticsEnabled: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } }; export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>; @@ -2765,6 +2766,7 @@ export const GetClientConfigDocument = gql` signInPrefilled signUpDisabled debugMode + analyticsEnabled support { supportDriver supportFrontChatId diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 7a7de0807f1b..ae13d831fb7a 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -32,6 +32,8 @@ import { import { isDefined } from '~/utils/isDefined'; import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates'; +import { DateFormat } from '@/localization/constants/DateFormat'; +import { TimeFormat } from '@/localization/constants/TimeFormat'; import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState'; import { detectDateFormat } from '@/localization/utils/detectDateFormat'; import { detectTimeFormat } from '@/localization/utils/detectTimeFormat'; @@ -143,12 +145,12 @@ export const useAuth = () => { ? getDateFormatFromWorkspaceDateFormat( user.workspaceMember.dateFormat, ) - : detectDateFormat(), + : DateFormat[detectDateFormat()], timeFormat: isDefined(user.workspaceMember.timeFormat) ? getTimeFormatFromWorkspaceTimeFormat( user.workspaceMember.timeFormat, ) - : detectTimeFormat(), + : TimeFormat[detectTimeFormat()], }); } diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx index 9eccbeb98e10..ed06d3f0ee69 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx @@ -1,23 +1,24 @@ -import { useEffect } from 'react'; -import { useRecoilState, useSetRecoilState } from 'recoil'; - import { apiConfigState } from '@/client-config/states/apiConfigState'; import { authProvidersState } from '@/client-config/states/authProvidersState'; import { billingState } from '@/client-config/states/billingState'; import { captchaProviderState } from '@/client-config/states/captchaProviderState'; import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState'; +import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState'; import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState'; import { isDebugModeState } from '@/client-config/states/isDebugModeState'; import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState'; import { sentryConfigState } from '@/client-config/states/sentryConfigState'; import { supportChatState } from '@/client-config/states/supportChatState'; +import { useEffect } from 'react'; +import { useRecoilState, useSetRecoilState } from 'recoil'; import { useGetClientConfigQuery } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; export const ClientConfigProviderEffect = () => { const setAuthProviders = useSetRecoilState(authProvidersState); const setIsDebugMode = useSetRecoilState(isDebugModeState); + const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState); const setIsSignInPrefilled = useSetRecoilState(isSignInPrefilledState); const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState); @@ -50,6 +51,7 @@ export const ClientConfigProviderEffect = () => { magicLink: false, }); setIsDebugMode(data?.clientConfig.debugMode); + setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled); setIsSignInPrefilled(data?.clientConfig.signInPrefilled); setIsSignUpDisabled(data?.clientConfig.signUpDisabled); @@ -84,6 +86,7 @@ export const ClientConfigProviderEffect = () => { setCaptchaProvider, setChromeExtensionId, setApiConfig, + setIsAnalyticsEnabled, ]); return <>; diff --git a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts index e702acefa4f1..9a060b0d7b2b 100644 --- a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts @@ -16,6 +16,7 @@ export const GET_CLIENT_CONFIG = gql` signInPrefilled signUpDisabled debugMode + analyticsEnabled support { supportDriver supportFrontChatId diff --git a/packages/twenty-front/src/modules/client-config/states/isAnalyticsEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isAnalyticsEnabledState.ts new file mode 100644 index 000000000000..50c0f5c89c25 --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/states/isAnalyticsEnabledState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const isAnalyticsEnabledState = createState({ + key: 'isAnalyticsEnabled', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/localization/constants/DateFormatWithoutYear.ts b/packages/twenty-front/src/modules/localization/constants/DateFormatWithoutYear.ts new file mode 100644 index 000000000000..a1c7f2af3b72 --- /dev/null +++ b/packages/twenty-front/src/modules/localization/constants/DateFormatWithoutYear.ts @@ -0,0 +1,11 @@ +import { DateFormat } from '@/localization/constants/DateFormat'; + +type DateFormatWithoutYear = { + [K in keyof typeof DateFormat]: string; +}; +export const DATE_FORMAT_WITHOUT_YEAR: DateFormatWithoutYear = { + SYSTEM: 'SYSTEM', + MONTH_FIRST: 'MMM d', + DAY_FIRST: 'd MMM', + YEAR_FIRST: 'MMM d', +}; diff --git a/packages/twenty-front/src/modules/localization/utils/__tests__/detectDateFormat.test.ts b/packages/twenty-front/src/modules/localization/utils/__tests__/detectDateFormat.test.ts index 2b641f302a63..b267622bf0cc 100644 --- a/packages/twenty-front/src/modules/localization/utils/__tests__/detectDateFormat.test.ts +++ b/packages/twenty-front/src/modules/localization/utils/__tests__/detectDateFormat.test.ts @@ -1,8 +1,7 @@ -import { DateFormat } from '@/localization/constants/DateFormat'; import { detectDateFormat } from '@/localization/utils/detectDateFormat'; describe('detectDateFormat', () => { - it('should return DateFormat.MONTH_FIRST if the detected format starts with month', () => { + it('should return MONTH_FIRST if the detected format starts with month', () => { // Mock the Intl.DateTimeFormat to return a specific format const mockDateTimeFormat = jest.fn().mockReturnValue({ formatToParts: () => [ @@ -16,10 +15,10 @@ describe('detectDateFormat', () => { const result = detectDateFormat(); - expect(result).toBe(DateFormat.MONTH_FIRST); + expect(result).toBe('MONTH_FIRST'); }); - it('should return DateFormat.DAY_FIRST if the detected format starts with day', () => { + it('should return DAY_FIRST if the detected format starts with day', () => { // Mock the Intl.DateTimeFormat to return a specific format const mockDateTimeFormat = jest.fn().mockReturnValue({ formatToParts: () => [ @@ -32,10 +31,10 @@ describe('detectDateFormat', () => { const result = detectDateFormat(); - expect(result).toBe(DateFormat.DAY_FIRST); + expect(result).toBe('DAY_FIRST'); }); - it('should return DateFormat.YEAR_FIRST if the detected format starts with year', () => { + it('should return YEAR_FIRST if the detected format starts with year', () => { // Mock the Intl.DateTimeFormat to return a specific format const mockDateTimeFormat = jest.fn().mockReturnValue({ formatToParts: () => [ @@ -48,10 +47,10 @@ describe('detectDateFormat', () => { const result = detectDateFormat(); - expect(result).toBe(DateFormat.YEAR_FIRST); + expect(result).toBe('YEAR_FIRST'); }); - it('should return DateFormat.MONTH_FIRST by default if the detected format does not match any specific order', () => { + it('should return MONTH_FIRST by default if the detected format does not match any specific order', () => { // Mock the Intl.DateTimeFormat to return a specific format const mockDateTimeFormat = jest.fn().mockReturnValue({ formatToParts: () => [ @@ -64,6 +63,6 @@ describe('detectDateFormat', () => { const result = detectDateFormat(); - expect(result).toBe(DateFormat.MONTH_FIRST); + expect(result).toBe('MONTH_FIRST'); }); }); diff --git a/packages/twenty-front/src/modules/localization/utils/__tests__/detectTimeFormat.test.ts b/packages/twenty-front/src/modules/localization/utils/__tests__/detectTimeFormat.test.ts index 6433495789ee..9445068a5f7f 100644 --- a/packages/twenty-front/src/modules/localization/utils/__tests__/detectTimeFormat.test.ts +++ b/packages/twenty-front/src/modules/localization/utils/__tests__/detectTimeFormat.test.ts @@ -1,8 +1,7 @@ -import { TimeFormat } from '@/localization/constants/TimeFormat'; import { detectTimeFormat } from '@/localization/utils/detectTimeFormat'; describe('detectTimeFormat', () => { - it('should return TimeFormat.HOUR_12 if the hour format is 12-hour', () => { + it('should return HOUR_12 if the hour format is 12-hour', () => { // Mock the resolvedOptions method to return hour12 as true const mockResolvedOptions = jest.fn(() => ({ hour12: true })); Intl.DateTimeFormat = jest.fn().mockImplementation(() => ({ @@ -11,11 +10,11 @@ describe('detectTimeFormat', () => { const result = detectTimeFormat(); - expect(result).toBe(TimeFormat.HOUR_12); + expect(result).toBe('HOUR_12'); expect(mockResolvedOptions).toHaveBeenCalled(); }); - it('should return TimeFormat.HOUR_24 if the hour format is 24-hour', () => { + it('should return HOUR_24 if the hour format is 24-hour', () => { // Mock the resolvedOptions method to return hour12 as false const mockResolvedOptions = jest.fn(() => ({ hour12: false })); Intl.DateTimeFormat = jest.fn().mockImplementation(() => ({ @@ -24,7 +23,7 @@ describe('detectTimeFormat', () => { const result = detectTimeFormat(); - expect(result).toBe(TimeFormat.HOUR_24); + expect(result).toBe('HOUR_24'); expect(mockResolvedOptions).toHaveBeenCalled(); }); }); diff --git a/packages/twenty-front/src/modules/localization/utils/__tests__/formatDateISOStringToDateTimeSimplified.test.js b/packages/twenty-front/src/modules/localization/utils/__tests__/formatDateISOStringToDateTimeSimplified.test.js new file mode 100644 index 000000000000..4caee3aedf0d --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/__tests__/formatDateISOStringToDateTimeSimplified.test.js @@ -0,0 +1,90 @@ +import { detectDateFormat } from '@/localization/utils/detectDateFormat'; +import { formatDateISOStringToDateTimeSimplified } from '@/localization/utils/formatDateISOStringToDateTimeSimplified'; +import { formatInTimeZone } from 'date-fns-tz'; +// Mock the imported modules +jest.mock('@/localization/utils/detectDateFormat'); +jest.mock('date-fns-tz'); + +describe('formatDateISOStringToDateTimeSimplified', () => { + const mockDate = new Date('2023-08-15T10:30:00Z'); + const mockTimeZone = 'America/New_York'; + const mockTimeFormat = 'HH:mm'; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should format the date correctly when DATE_FORMAT is MONTH_FIRST', () => { + detectDateFormat.mockReturnValue('MONTH_FIRST'); + formatInTimeZone.mockReturnValue('Oct 15 · 06:30'); + + const result = formatDateISOStringToDateTimeSimplified( + mockDate, + mockTimeZone, + mockTimeFormat, + ); + + expect(detectDateFormat).toHaveBeenCalled(); + expect(formatInTimeZone).toHaveBeenCalledWith( + mockDate, + mockTimeZone, + 'MMM d · HH:mm', + ); + expect(result).toBe('Oct 15 · 06:30'); + }); + + it('should format the date correctly when DATE_FORMAT is DAY_FIRST', () => { + detectDateFormat.mockReturnValue('DAY_FIRST'); + formatInTimeZone.mockReturnValue('15 Oct · 06:30'); + + const result = formatDateISOStringToDateTimeSimplified( + mockDate, + mockTimeZone, + mockTimeFormat, + ); + + expect(detectDateFormat).toHaveBeenCalled(); + expect(formatInTimeZone).toHaveBeenCalledWith( + mockDate, + mockTimeZone, + 'd MMM · HH:mm', + ); + expect(result).toBe('15 Oct · 06:30'); + }); + + it('should use the provided time format', () => { + detectDateFormat.mockReturnValue('MONTH_FIRST'); + formatInTimeZone.mockReturnValue('Oct 15 · 6:30 AM'); + + const result = formatDateISOStringToDateTimeSimplified( + mockDate, + mockTimeZone, + 'h:mm aa', + ); + + expect(formatInTimeZone).toHaveBeenCalledWith( + mockDate, + mockTimeZone, + 'MMM d · h:mm aa', + ); + expect(result).toBe('Oct 15 · 6:30 AM'); + }); + + it('should handle different time zones', () => { + detectDateFormat.mockReturnValue('MONTH_FIRST'); + formatInTimeZone.mockReturnValue('Oct 16 · 02:30'); + + const result = formatDateISOStringToDateTimeSimplified( + mockDate, + 'Asia/Tokyo', + mockTimeFormat, + ); + + expect(formatInTimeZone).toHaveBeenCalledWith( + mockDate, + 'Asia/Tokyo', + 'MMM d · HH:mm', + ); + expect(result).toBe('Oct 16 · 02:30'); + }); +}); diff --git a/packages/twenty-front/src/modules/localization/utils/detectDateFormat.ts b/packages/twenty-front/src/modules/localization/utils/detectDateFormat.ts index b503ef826e60..e38b018df445 100644 --- a/packages/twenty-front/src/modules/localization/utils/detectDateFormat.ts +++ b/packages/twenty-front/src/modules/localization/utils/detectDateFormat.ts @@ -1,6 +1,6 @@ import { DateFormat } from '@/localization/constants/DateFormat'; -export const detectDateFormat = (): DateFormat => { +export const detectDateFormat = (): keyof typeof DateFormat => { const date = new Date(); const formatter = new Intl.DateTimeFormat(navigator.language); const parts = formatter.formatToParts(date); @@ -9,9 +9,9 @@ export const detectDateFormat = (): DateFormat => { .filter((part) => ['year', 'month', 'day'].includes(part.type)) .map((part) => part.type); - if (partOrder[0] === 'month') return DateFormat.MONTH_FIRST; - if (partOrder[0] === 'day') return DateFormat.DAY_FIRST; - if (partOrder[0] === 'year') return DateFormat.YEAR_FIRST; + if (partOrder[0] === 'month') return 'MONTH_FIRST'; + if (partOrder[0] === 'day') return 'DAY_FIRST'; + if (partOrder[0] === 'year') return 'YEAR_FIRST'; - return DateFormat.MONTH_FIRST; + return 'MONTH_FIRST'; }; diff --git a/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts b/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts index 01bad17167a5..d6d914d83637 100644 --- a/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts +++ b/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts @@ -1,14 +1,14 @@ import { TimeFormat } from '@/localization/constants/TimeFormat'; import { isDefined } from '~/utils/isDefined'; -export const detectTimeFormat = () => { +export const detectTimeFormat = (): keyof typeof TimeFormat => { const isHour12 = Intl.DateTimeFormat(navigator.language, { hour: 'numeric', }).resolvedOptions().hour12; if (isDefined(isHour12) && isHour12) { - return TimeFormat.HOUR_12; + return 'HOUR_12'; } - return TimeFormat.HOUR_24; + return 'HOUR_24'; }; diff --git a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDateTimeSimplified.ts b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDateTimeSimplified.ts new file mode 100644 index 000000000000..c96d9f2f885d --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToDateTimeSimplified.ts @@ -0,0 +1,18 @@ +import { DATE_FORMAT_WITHOUT_YEAR } from '@/localization/constants/DateFormatWithoutYear'; +import { TimeFormat } from '@/localization/constants/TimeFormat'; +import { detectDateFormat } from '@/localization/utils/detectDateFormat'; +import { formatInTimeZone } from 'date-fns-tz'; + +export const formatDateISOStringToDateTimeSimplified = ( + date: Date, + timeZone: string, + timeFormat: TimeFormat, +) => { + const simplifiedDateFormat = DATE_FORMAT_WITHOUT_YEAR[detectDateFormat()]; + + return formatInTimeZone( + date, + timeZone, + `${simplifiedDateFormat} · ${timeFormat}`, + ); +}; diff --git a/packages/twenty-front/src/modules/localization/utils/getDateFormatFromWorkspaceDateFormat.ts b/packages/twenty-front/src/modules/localization/utils/getDateFormatFromWorkspaceDateFormat.ts index f32bdbb93355..09293fbb8ec8 100644 --- a/packages/twenty-front/src/modules/localization/utils/getDateFormatFromWorkspaceDateFormat.ts +++ b/packages/twenty-front/src/modules/localization/utils/getDateFormatFromWorkspaceDateFormat.ts @@ -7,7 +7,7 @@ export const getDateFormatFromWorkspaceDateFormat = ( ) => { switch (workspaceDateFormat) { case WorkspaceMemberDateFormatEnum.System: - return detectDateFormat(); + return DateFormat[detectDateFormat()]; case WorkspaceMemberDateFormatEnum.MonthFirst: return DateFormat.MONTH_FIRST; case WorkspaceMemberDateFormatEnum.DayFirst: diff --git a/packages/twenty-front/src/modules/localization/utils/getTimeFormatFromWorkspaceTimeFormat.ts b/packages/twenty-front/src/modules/localization/utils/getTimeFormatFromWorkspaceTimeFormat.ts index f6aebb43779b..7519d0cb4068 100644 --- a/packages/twenty-front/src/modules/localization/utils/getTimeFormatFromWorkspaceTimeFormat.ts +++ b/packages/twenty-front/src/modules/localization/utils/getTimeFormatFromWorkspaceTimeFormat.ts @@ -7,7 +7,7 @@ export const getTimeFormatFromWorkspaceTimeFormat = ( ) => { switch (workspaceTimeFormat) { case WorkspaceMemberTimeFormatEnum.System: - return detectTimeFormat(); + return TimeFormat[detectTimeFormat()]; case WorkspaceMemberTimeFormatEnum.Hour_24: return TimeFormat.HOUR_24; case WorkspaceMemberTimeFormatEnum.Hour_12: diff --git a/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookTooltip.tsx b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookTooltip.tsx new file mode 100644 index 000000000000..40925c5d3830 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookTooltip.tsx @@ -0,0 +1,89 @@ +import { formatDateISOStringToDateTimeSimplified } from '@/localization/utils/formatDateISOStringToDateTimeSimplified'; +import { UserContext } from '@/users/contexts/UserContext'; +import styled from '@emotion/styled'; +import { Point } from '@nivo/line'; +import { ReactElement, useContext } from 'react'; + +const StyledTooltipContainer = styled.div` + align-items: center; + border-radius: ${({ theme }) => theme.border.radius.md}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + display: flex; + width: 128px; + flex-direction: column; + justify-content: center; + background: ${({ theme }) => theme.background.transparent.secondary}; + box-shadow: ${({ theme }) => theme.boxShadow.light}; + backdrop-filter: ${({ theme }) => theme.blur.medium}; +`; + +const StyledTooltipDateContainer = styled.div` + align-items: flex-start; + align-self: stretch; + display: flex; + justify-content: center; + font-weight: ${({ theme }) => theme.font.weight.medium}; + font-family: ${({ theme }) => theme.font.family}; + gap: ${({ theme }) => theme.spacing(2)}; + color: ${({ theme }) => theme.font.color.secondary}; + padding: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledTooltipDataRow = styled.div` + align-items: flex-start; + align-self: stretch; + display: flex; + justify-content: space-between; + color: ${({ theme }) => theme.font.color.tertiary}; + padding: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledLine = styled.div` + background-color: ${({ theme }) => theme.border.color.medium}; + height: 1px; + width: 100%; +`; +const StyledColorPoint = styled.div<{ color: string }>` + background-color: ${({ color }) => color}; + border-radius: 50%; + height: 8px; + width: 8px; + display: inline-block; +`; +const StyledDataDefinition = styled.div` + display: flex; + align-items: center; + gap: ${({ theme }) => theme.spacing(2)}; +`; +const StyledSpan = styled.span` + color: ${({ theme }) => theme.font.color.primary}; +`; +type SettingsDevelopersWebhookTooltipProps = { + point: Point; +}; +export const SettingsDevelopersWebhookTooltip = ({ + point, +}: SettingsDevelopersWebhookTooltipProps): ReactElement => { + const { timeFormat, timeZone } = useContext(UserContext); + const windowInterval = new Date(point.data.x); + const windowIntervalDate = formatDateISOStringToDateTimeSimplified( + windowInterval, + timeZone, + timeFormat, + ); + return ( + + + {windowIntervalDate} + + + + + + {String(point.serieId)} + + {String(point.data.y)} + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx index eb2e359fff16..9626c6712eef 100644 --- a/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx +++ b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx @@ -1,8 +1,13 @@ +import { SettingsDevelopersWebhookTooltip } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookTooltip'; +import { useGraphData } from '@/settings/developers/webhook/hooks/useGraphData'; import { webhookGraphDataState } from '@/settings/developers/webhook/states/webhookGraphDataState'; +import { Select } from '@/ui/input/components/Select'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { ResponsiveLine } from '@nivo/line'; import { Section } from '@react-email/components'; -import { useRecoilValue } from 'recoil'; +import { useState } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { H2Title } from 'twenty-ui'; export type NivoLineInput = { @@ -14,22 +19,102 @@ export type NivoLineInput = { }>; }; const StyledGraphContainer = styled.div` - height: 200px; - width: 100%; + background-color: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.md}; + height: 199px; + + padding: ${({ theme }) => theme.spacing(4, 2, 2, 2)}; + width: 496px; +`; +const StyledTitleContainer = styled.div` + align-items: flex-start; + display: flex; + justify-content: space-between; `; -export const SettingsDeveloppersWebhookUsageGraph = () => { + +type SettingsDevelopersWebhookUsageGraphProps = { + webhookId: string; +}; + +export const SettingsDevelopersWebhookUsageGraph = ({ + webhookId, +}: SettingsDevelopersWebhookUsageGraphProps) => { const webhookGraphData = useRecoilValue(webhookGraphDataState); + const setWebhookGraphData = useSetRecoilState(webhookGraphDataState); + const theme = useTheme(); + + const [windowLengthGraphOption, setWindowLengthGraphOption] = useState< + '7D' | '1D' | '12H' | '4H' + >('7D'); + + const { fetchGraphData } = useGraphData(webhookId); return ( <> {webhookGraphData.length ? (
- + + + Boolean) debugMode: boolean; + @Field(() => Boolean) + analyticsEnabled: boolean; + @Field(() => Support) support: Support; diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts index 3615066a4390..f6ba1aaf4abd 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts @@ -48,6 +48,7 @@ export class ClientConfigResolver { 'MUTATION_MAXIMUM_AFFECTED_RECORDS', ), }, + analyticsEnabled: this.environmentService.get('ANALYTICS_ENABLED'), }; return Promise.resolve(clientConfig);