feat(billing): refacto billing (#14243)

… prices for metered billing

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Antoine Moreaux
2025-09-19 11:25:53 +02:00
committed by GitHub
parent 163890f6c8
commit 43e0cd5d05
351 changed files with 16091 additions and 6101 deletions
@@ -2,7 +2,7 @@ import React from 'react';
import { useRecoilValue } from 'recoil';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState';
import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat';
import { UserContext } from '@/users/contexts/UserContext';
import { useLocation } from 'react-router-dom';
import { AppPath } from 'twenty-shared/types';
@@ -13,7 +13,7 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
const isCurrentUserLoaded = useRecoilValue(isCurrentUserLoadedState);
const location = useLocation();
const dateTimeFormat = useRecoilValue(dateTimeFormatState);
const { dateFormat, timeFormat, timeZone } = useDateTimeFormat();
return !isCurrentUserLoaded &&
!isMatchingLocation(location, AppPath.Verify) &&
@@ -23,9 +23,9 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
) : (
<UserContext.Provider
value={{
dateFormat: dateTimeFormat.dateFormat,
timeFormat: dateTimeFormat.timeFormat,
timeZone: dateTimeFormat.timeZone,
dateFormat,
timeFormat,
timeZone,
}}
>
{children}
@@ -9,14 +9,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
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';
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDateFormatFromWorkspaceDateFormat';
import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat';
import { useInitializeFormatPreferences } from '@/localization/hooks/useInitializeFormatPreferences';
import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util';
import { coreViewsState } from '@/views/states/coreViewState';
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
@@ -46,7 +39,7 @@ export const UserProviderEffect = () => {
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState);
const setAvailableWorkspaces = useSetRecoilState(availableWorkspacesState);
const setDateTimeFormat = useSetRecoilState(dateTimeFormatState);
const { initializeFormatPreferences } = useInitializeFormatPreferences();
const isLoggedIn = useIsLogged();
const updateLocaleCatalog = useRecoilCallback(
@@ -155,19 +148,8 @@ export const UserProviderEffect = () => {
updateLocaleCatalog(updatedWorkspaceMember.locale);
// TODO: factorize
setDateTimeFormat({
timeZone:
workspaceMember.timeZone && workspaceMember.timeZone !== 'system'
? workspaceMember.timeZone
: detectTimeZone(),
dateFormat: isDefined(workspaceMember.dateFormat)
? getDateFormatFromWorkspaceDateFormat(workspaceMember.dateFormat)
: DateFormat[detectDateFormat()],
timeFormat: isDefined(workspaceMember.timeFormat)
? getTimeFormatFromWorkspaceTimeFormat(workspaceMember.timeFormat)
: TimeFormat[detectTimeFormat()],
});
// Initialize format preferences from workspace member
initializeFormatPreferences(updatedWorkspaceMember);
dynamicActivate(
(workspaceMember.locale as keyof typeof APP_LOCALES) ?? SOURCE_LOCALE,
@@ -195,7 +177,7 @@ export const UserProviderEffect = () => {
setCurrentWorkspace,
setCurrentWorkspaceMember,
setIsCurrentUserLoaded,
setDateTimeFormat,
initializeFormatPreferences,
setCurrentWorkspaceMembersWithDeleted,
updateLocaleCatalog,
setCoreViews,
@@ -0,0 +1,17 @@
import { gql } from '@apollo/client';
import { BILLING_SUBSCRIPTION_SCHEDULE_PHASE_ITEM_FRAGMENT } from '@/billing/graphql/fragments/billingSubscriptionSchedulePhaseItemFragment';
import { BILLING_SUBSCRIPTION_SCHEDULE_PHASE_FRAGMENT } from '@/billing/graphql/fragments/billingSubscriptionSchedulePhaseFragment';
export const BILLING_SUBSCRIPTION_FRAGMENT = gql`
fragment BillingSubscriptionFragment on BillingSubscription {
id
status
metadata
phases {
...BillingSubscriptionSchedulePhaseFragment
}
}
${BILLING_SUBSCRIPTION_SCHEDULE_PHASE_ITEM_FRAGMENT}
${BILLING_SUBSCRIPTION_SCHEDULE_PHASE_FRAGMENT}
`;
@@ -0,0 +1,35 @@
import { gql } from '@apollo/client';
import { BILLING_SUBSCRIPTION_SCHEDULE_PHASE_FRAGMENT } from '@/billing/graphql/fragments/billingSubscriptionSchedulePhaseFragment';
import { BILLING_SUBSCRIPTION_SCHEDULE_PHASE_ITEM_FRAGMENT } from '@/billing/graphql/fragments/billingSubscriptionSchedulePhaseItemFragment';
export const CURRENT_BILLING_SUBSCRIPTION_FRAGMENT = gql`
fragment CurrentBillingSubscriptionFragment on BillingSubscription {
id
status
interval
metadata
currentPeriodEnd
phases {
...BillingSubscriptionSchedulePhaseFragment
}
billingSubscriptionItems {
id
hasReachedCurrentPeriodCap
quantity
stripePriceId
billingProduct {
name
description
images
metadata {
productKey
planKey
priceUsageBased
}
}
}
}
${BILLING_SUBSCRIPTION_SCHEDULE_PHASE_ITEM_FRAGMENT}
${BILLING_SUBSCRIPTION_SCHEDULE_PHASE_FRAGMENT}
`;
@@ -10,6 +10,8 @@ import { DELETED_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/grap
import { PARTIAL_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/partialWorkspaceMemberQueryFragment';
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
import { gql } from '@apollo/client';
import { CURRENT_BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/currentBillingSubscriptionFragement';
import { BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/billingSubscriptionsFragment';
export const USER_QUERY_FRAGMENT = gql`
fragment UserQueryFragment on User {
@@ -65,31 +67,10 @@ export const USER_QUERY_FRAGMENT = gql`
}
metadataVersion
currentBillingSubscription {
id
status
interval
metadata
currentPeriodEnd
billingSubscriptionItems {
id
hasReachedCurrentPeriodCap
quantity
stripePriceId
billingProduct {
name
description
metadata {
planKey
priceUsageBased
productKey
}
}
}
...CurrentBillingSubscriptionFragment
}
billingSubscriptions {
id
status
metadata
...BillingSubscriptionFragment
}
workspaceMembersCount
defaultRole {
@@ -118,4 +99,6 @@ export const USER_QUERY_FRAGMENT = gql`
${VIEW_FRAGMENT}
${AVAILABLE_WORKSPACES_FOR_AUTH_FRAGMENT}
${AVAILABLE_WORKSPACE_FOR_AUTH_FRAGMENT}
${CURRENT_BILLING_SUBSCRIPTION_FRAGMENT}
${BILLING_SUBSCRIPTION_FRAGMENT}
`;
@@ -6,14 +6,7 @@ import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMemb
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
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';
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDateFormatFromWorkspaceDateFormat';
import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat';
import { useInitializeFormatPreferences } from '@/localization/hooks/useInitializeFormatPreferences';
import { coreViewsState } from '@/views/states/coreViewState';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
@@ -38,7 +31,7 @@ export const useLoadCurrentUser = () => {
currentWorkspaceMembersState,
);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setDateTimeFormat = useSetRecoilState(dateTimeFormatState);
const { initializeFormatPreferences } = useInitializeFormatPreferences();
const setCoreViews = useSetRecoilState(coreViewsState);
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
@@ -91,23 +84,8 @@ export const useLoadCurrentUser = () => {
setCurrentWorkspaceMember(workspaceMember);
// TODO: factorize with UserProviderEffect
setDateTimeFormat({
timeZone:
workspaceMember.timeZone && workspaceMember.timeZone !== 'system'
? workspaceMember.timeZone
: detectTimeZone(),
dateFormat: isDefined(user.workspaceMember.dateFormat)
? getDateFormatFromWorkspaceDateFormat(
user.workspaceMember.dateFormat,
)
: DateFormat[detectDateFormat()],
timeFormat: isDefined(user.workspaceMember.timeFormat)
? getTimeFormatFromWorkspaceTimeFormat(
user.workspaceMember.timeFormat,
)
: TimeFormat[detectTimeFormat()],
});
// Initialize unified format preferences state
initializeFormatPreferences(workspaceMember);
dynamicActivate(
(workspaceMember.locale as keyof typeof APP_LOCALES) ?? SOURCE_LOCALE,
);
@@ -143,7 +121,7 @@ export const useLoadCurrentUser = () => {
setCurrentWorkspace,
setCurrentWorkspaceMember,
setCurrentWorkspaceMembers,
setDateTimeFormat,
initializeFormatPreferences,
setLastAuthenticateWorkspaceDomain,
]);