diff --git a/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts b/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts index 9bbbab5acd1..b03faa81795 100644 --- a/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts +++ b/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts @@ -1,14 +1,16 @@ -import { DEFAULT_WORKFLOW_PREFERENCES, PreferencesTypeEnum, StepTypeEnum, WorkflowOriginEnum } from '@novu/shared'; -import { ControlValuesEntity, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal'; -import { GetPreferencesResponseDto } from '@novu/application-generic'; - import { ControlsSchema, + DEFAULT_WORKFLOW_PREFERENCES, PreferencesResponseDto, + PreferencesTypeEnum, StepResponseDto, + StepTypeEnum, WorkflowListResponseDto, -} from '../dto/workflow-commons-fields'; -import { WorkflowResponseDto } from '../dto/workflow-response-dto'; + WorkflowOriginEnum, + WorkflowResponseDto, +} from '@novu/shared'; +import { ControlValuesEntity, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal'; +import { GetPreferencesResponseDto } from '@novu/application-generic'; export function toResponseWorkflowDto( template: NotificationTemplateEntity, diff --git a/apps/api/src/app/workflows-v2/params/get-list-query-params.ts b/apps/api/src/app/workflows-v2/params/get-list-query-params.ts index bc26126c377..53f395b9be8 100644 --- a/apps/api/src/app/workflows-v2/params/get-list-query-params.ts +++ b/apps/api/src/app/workflows-v2/params/get-list-query-params.ts @@ -1,6 +1,4 @@ -import { LimitOffsetPaginationDto } from '@novu/shared'; - -import { WorkflowResponseDto } from '../dto/workflow-response-dto'; +import { LimitOffsetPaginationDto, WorkflowResponseDto } from '@novu/shared'; export class GetListQueryParams extends LimitOffsetPaginationDto { query?: string; diff --git a/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts index 6729440b28b..f2d391090f2 100644 --- a/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts @@ -6,12 +6,11 @@ import { NotificationStepEntity, NotificationTemplateRepository, } from '@novu/dal'; -import { ControlVariablesLevelEnum } from '@novu/shared'; +import { ControlVariablesLevelEnum, WorkflowResponseDto } from '@novu/shared'; import { GetPreferences, GetPreferencesCommand } from '@novu/application-generic'; import { GetWorkflowCommand } from './get-workflow.command'; import { WorkflowNotFoundException } from '../../exceptions/workflow-not-found-exception'; -import { WorkflowResponseDto } from '../../dto/workflow-response-dto'; import { toResponseWorkflowDto } from '../../mappers/notification-template-mapper'; @Injectable() diff --git a/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts index 03c6425f98b..fc54259789b 100644 --- a/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { NotificationTemplateRepository } from '@novu/dal'; +import { ListWorkflowResponse } from '@novu/shared'; import { ListWorkflowsCommand } from './list-workflows.command'; -import { ListWorkflowResponse } from '../../dto/workflow-commons-fields'; import { toWorkflowsMinifiedDtos } from '../../mappers/notification-template-mapper'; @Injectable() diff --git a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.command.ts b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.command.ts index d86421c44cd..9c7ba3d60ae 100644 --- a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.command.ts @@ -1,6 +1,5 @@ import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; -import { CreateWorkflowDto } from '../../dto/create-workflow-dto'; -import { UpdateWorkflowDto } from '../../dto/update-workflow-dto'; +import { CreateWorkflowDto, UpdateWorkflowDto } from '@novu/shared'; export class UpsertWorkflowCommand extends EnvironmentWithUserObjectCommand { workflowDatabaseIdForUpdate?: string; diff --git a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts index 41d10353f71..0678780617d 100644 --- a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts @@ -23,13 +23,19 @@ import { UpsertPreferences, UpsertUserWorkflowPreferencesCommand, } from '@novu/application-generic'; -import { WorkflowCreationSourceEnum, WorkflowOriginEnum, WorkflowTypeEnum } from '@novu/shared'; +import { + CreateWorkflowDto, + StepCreateDto, + StepDto, + StepUpdateDto, + WorkflowCreationSourceEnum, + WorkflowOriginEnum, + WorkflowResponseDto, + WorkflowTypeEnum, +} from '@novu/shared'; import { UpsertWorkflowCommand } from './upsert-workflow.command'; import { WorkflowAlreadyExistException } from '../../exceptions/workflow-already-exist'; -import { StepCreateDto, StepDto, StepUpdateDto } from '../../dto/workflow-commons-fields'; import { StepUpsertMechanismFailedMissingIdException } from '../../exceptions/step-upsert-mechanism-failed-missing-id.exception'; -import { CreateWorkflowDto } from '../../dto/create-workflow-dto'; -import { WorkflowResponseDto } from '../../dto/workflow-response-dto'; import { toResponseWorkflowDto } from '../../mappers/notification-template-mapper'; function buildUpsertControlValuesCommand(command: UpsertWorkflowCommand, persistedStep, persistedWorkflow, stepInDto) { diff --git a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts index f9482963dcb..c2d8c946ca0 100644 --- a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts +++ b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts @@ -1,19 +1,21 @@ import { expect } from 'chai'; import { UserSession } from '@novu/testing'; -import { DEFAULT_WORKFLOW_PREFERENCES, StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; -import { randomBytes } from 'crypto'; -import { JsonSchema } from '@novu/framework'; import { + CreateWorkflowDto, + DEFAULT_WORKFLOW_PREFERENCES, ListWorkflowResponse, StepCreateDto, StepDto, + StepTypeEnum, StepUpdateDto, + UpdateWorkflowDto, WorkflowCommonsFields, + WorkflowCreationSourceEnum, WorkflowListResponseDto, -} from './dto/workflow-commons-fields'; -import { WorkflowResponseDto } from './dto/workflow-response-dto'; -import { UpdateWorkflowDto } from './dto/update-workflow-dto'; -import { CreateWorkflowDto } from './dto/create-workflow-dto'; + WorkflowResponseDto, +} from '@novu/shared'; +import { randomBytes } from 'crypto'; +import { JsonSchema } from '@novu/framework'; const v2Prefix = '/v2'; const PARTIAL_UPDATED_NAME = 'Updated'; diff --git a/apps/api/src/app/workflows-v2/workflow.controller.ts b/apps/api/src/app/workflows-v2/workflow.controller.ts index 01928afe5e2..b9dba7d505c 100644 --- a/apps/api/src/app/workflows-v2/workflow.controller.ts +++ b/apps/api/src/app/workflows-v2/workflow.controller.ts @@ -15,7 +15,14 @@ import { } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { DirectionEnum, UserSessionData } from '@novu/shared'; +import { + CreateWorkflowDto, + DirectionEnum, + ListWorkflowResponse, + UpdateWorkflowDto, + UserSessionData, + WorkflowResponseDto, +} from '@novu/shared'; import { ExternalApiAccessible, UserAuthGuard, UserSession } from '@novu/application-generic'; import { ApiCommonResponses } from '../shared/framework/response.decorator'; import { UserAuthentication } from '../shared/framework/swagger/api.key.security'; @@ -25,13 +32,9 @@ import { UpsertWorkflowCommand } from './usecases/upsert-workflow/upsert-workflo import { GetWorkflowUseCase } from './usecases/get-workflow/get-workflow.usecase'; import { ListWorkflowsUseCase } from './usecases/list-workflows/list-workflow.usecase'; import { ListWorkflowsCommand } from './usecases/list-workflows/list-workflows.command'; -import { ListWorkflowResponse } from './dto/workflow-commons-fields'; import { DeleteWorkflowUseCase } from './usecases/delete-workflow/delete-workflow.usecase'; import { DeleteWorkflowCommand } from './usecases/delete-workflow/delete-workflow.command'; import { GetListQueryParams } from './params/get-list-query-params'; -import { CreateWorkflowDto } from './dto/create-workflow-dto'; -import { UpdateWorkflowDto } from './dto/update-workflow-dto'; -import { WorkflowResponseDto } from './dto/workflow-response-dto'; @ApiCommonResponses() @Controller({ path: `/workflows`, version: '2' }) diff --git a/apps/dashboard/src/components/primitives/badge.tsx b/apps/dashboard/src/components/primitives/badge.tsx index daf68db1eea..e6bbda2eab7 100644 --- a/apps/dashboard/src/components/primitives/badge.tsx +++ b/apps/dashboard/src/components/primitives/badge.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { cva, type VariantProps } from 'class-variance-authority'; - import { cn } from '@/utils/ui'; const badgeVariants = cva( @@ -8,7 +7,7 @@ const badgeVariants = cva( { variants: { variant: { - default: 'border-transparent bg-muted text-muted-foreground', + default: 'border-transparent bg-primary/5 text-primary/50', destructive: 'border-transparent bg-destructive/10 text-destructive', success: 'border-transparent bg-success/10 text-success', warning: 'border-transparent bg-warning/10 text-warning', diff --git a/apps/dashboard/src/components/primitives/button.tsx b/apps/dashboard/src/components/primitives/button.tsx index e0154f479d5..da2648cfe5a 100644 --- a/apps/dashboard/src/components/primitives/button.tsx +++ b/apps/dashboard/src/components/primitives/button.tsx @@ -13,7 +13,7 @@ const buttonVariants = cva( novu: 'bg-gradient-to-b from-novu/90 to-novu text-novu-foreground shadow-[inset_0_-4px_2px_-2px_hsl(var(--novu)),inset_0_0_0_1px_rgba(255,255,255,0.16),0_0_0_1px_hsl(var(--novu)),0px_1px_2px_0px_#0E121B3D] after:content-[""] after:absolute after:w-full after:h-full after:bg-gradient-to-b after:from-background/10 after:opacity-0 hover:after:opacity-100 after:rounded-lg after:transition-opacity after:duration-300', destructive: 'bg-gradient-to-b from-destructive/90 to-destructive text-destructive-foreground shadow-[inset_0_-4px_2px_-2px_hsl(var(--destructive)),inset_0_0_0_1px_rgba(255,255,255,0.16),0_0_0_1px_hsl(var(--destructive)),0px_1px_2px_0px_#0E121B3D] after:content-[""] after:absolute after:w-full after:h-full after:bg-gradient-to-b after:from-background/10 after:opacity-0 hover:after:opacity-100 after:rounded-lg after:transition-opacity after:duration-300', - outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + outline: 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, diff --git a/apps/dashboard/src/components/primitives/step.tsx b/apps/dashboard/src/components/primitives/step.tsx index ee786a19185..826e158df6a 100644 --- a/apps/dashboard/src/components/primitives/step.tsx +++ b/apps/dashboard/src/components/primitives/step.tsx @@ -4,15 +4,15 @@ import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/utils/ui'; const stepVariants = cva( - 'inline-flex items-center rounded-full border-1.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + 'inline-flex items-center rounded-full border text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', { variants: { variant: { - default: 'border-muted-foreground/5 bg-muted/50 text-muted-foreground/50', - feature: 'border-feature/30 bg-transparent text-feature/30', - information: 'border-information/30 bg-transparent text-information/30', - highlighted: 'border-highlighted/30 bg-transparent text-highlighted/30', - stable: 'border-stable/30 bg-transparent text-stable/30', + default: 'border-primary/5 bg-primary/2.5 text-primary/30', + feature: 'border-feature/30 bg-foreground/2.5 text-feature/30', + information: 'border-information/30 bg-foreground/2.5 text-information/30', + highlighted: 'border-highlighted/30 bg-foreground/2.5 text-highlighted/30', + stable: 'border-stable/30 bg-foreground/2.5 text-stable/30', }, size: { default: 'p-2 [&>svg]:size-3.5', diff --git a/apps/dashboard/src/components/primitives/table.tsx b/apps/dashboard/src/components/primitives/table.tsx index 210b1644d2e..aa3185a04b9 100644 --- a/apps/dashboard/src/components/primitives/table.tsx +++ b/apps/dashboard/src/components/primitives/table.tsx @@ -4,7 +4,7 @@ import { cn } from '@/utils/ui'; const Table = React.forwardRef>( ({ className, ...props }, ref) => ( -
+
) @@ -30,7 +30,7 @@ const TableRow = React.forwardRef ( ) @@ -42,7 +42,7 @@ const TableHead = React.forwardRef[role=checkbox]]:translate-y-[2px]', + 'text-foreground/60 h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className )} {...props} @@ -58,7 +58,7 @@ TableCell.displayName = 'TableCell'; const TableCaption = React.forwardRef>( ({ className, ...props }, ref) => ( -
+ ) ); TableCaption.displayName = 'TableCaption'; diff --git a/apps/dashboard/src/components/primitives/tag.tsx b/apps/dashboard/src/components/primitives/tag.tsx index 12cc475948f..e5194e70cb2 100644 --- a/apps/dashboard/src/components/primitives/tag.tsx +++ b/apps/dashboard/src/components/primitives/tag.tsx @@ -8,7 +8,7 @@ const tagVariants = cva( { variants: { variant: { - default: 'border-transparent bg-muted text-muted-foreground', + default: 'border-transparent bg-primary/5 text-primary/50', feature: 'border-transparent bg-feature/10 text-feature', information: 'border-transparent bg-information/10 text-information', }, diff --git a/apps/dashboard/src/index.css b/apps/dashboard/src/index.css index 4ee7c729314..c1527db1d17 100644 --- a/apps/dashboard/src/index.css +++ b/apps/dashboard/src/index.css @@ -18,9 +18,6 @@ --primary: 222 32% 8%; --primary-foreground: 0 0% 100%; - --muted: 0 0% 96.1%; - --muted-foreground: 0 0% 45.1%; - --accent: 0 0% 96.1%; --accent-foreground: 222 32% 8%; diff --git a/apps/dashboard/tailwind.config.js b/apps/dashboard/tailwind.config.js index 2d270f31ea9..6ba90fc0420 100644 --- a/apps/dashboard/tailwind.config.js +++ b/apps/dashboard/tailwind.config.js @@ -3,9 +3,14 @@ export default { darkMode: ['class'], content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], theme: { + boxShadow: { + xs: '0px 1px 2px 0px #0A0D1408', + sm: '0px 1px 2px 0px #1018280F,0px 1px 3px 0px #1018281A', + DEFAULT: '0px 16px 32px -12px #0E121B1A', + }, extend: { - borderWidth: { - 1.5: 1.5, + opacity: { + 2.5: 0.025, }, borderRadius: { lg: 'var(--radius)', @@ -31,10 +36,6 @@ export default { DEFAULT: 'hsl(var(--novu))', foreground: 'hsl(var(--novu-foreground))', }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))', - }, accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))', diff --git a/apps/web/src/api/api.client.ts b/apps/web/src/api/api.client.ts index 472a4109fe7..25c7fafc3f5 100644 --- a/apps/web/src/api/api.client.ts +++ b/apps/web/src/api/api.client.ts @@ -8,13 +8,18 @@ interface IOptions { absoluteUrl: boolean; } +axios.interceptors.request.use(async (config) => { + config.headers.set('Novu-Environment-Id', getEnvironmentId()); + config.headers.set('Authorization', `Bearer ${await getToken()}`); + + return config; +}); + // @deprecated Migrate all api methods to the new buildApiHttpClient that allows runtime configuration on the client object. export const api = { get(url: string, options: IOptions = { absoluteUrl: false }) { return axios - .get(buildUrl(url, options.absoluteUrl), { - headers: getHeaders(), - }) + .get(buildUrl(url, options.absoluteUrl)) .then((response) => { return response.data?.data; }) @@ -24,10 +29,7 @@ export const api = { }, getFullResponse(url: string, params?: { [key: string]: string | string[] | number }) { return axios - .get(`${API_ROOT}${url}`, { - params, - headers: getHeaders(), - }) + .get(`${API_ROOT}${url}`, { params }) .then((response) => response.data) .catch((error) => { return Promise.reject(error?.response?.data || error?.response || error); @@ -35,9 +37,7 @@ export const api = { }, put(url: string, payload) { return axios - .put(`${API_ROOT}${url}`, payload, { - headers: getHeaders(), - }) + .put(`${API_ROOT}${url}`, payload) .then((response) => response.data?.data) .catch((error) => { return Promise.reject(error?.response?.data || error?.response || error); @@ -45,17 +45,15 @@ export const api = { }, post(url: string, payload, params?: CustomDataType) { return axios - .post(`${API_ROOT}${url}`, payload, { params, headers: getHeaders() }) + .post(`${API_ROOT}${url}`, payload, { params }) .then((response) => response.data?.data) .catch((error) => { return Promise.reject(error?.response?.data || error?.response || error); }); }, - patch(url: string, payload) { + patch(url: string, payload, params?: CustomDataType) { return axios - .patch(`${API_ROOT}${url}`, payload, { - headers: getHeaders(), - }) + .patch(`${API_ROOT}${url}`, payload, { params }) .then((response) => response.data?.data) .catch((error) => { return Promise.reject(error?.response?.data || error?.response || error); @@ -63,10 +61,7 @@ export const api = { }, delete(url: string, payload = {}) { return axios - .delete(`${API_ROOT}${url}`, { - ...payload, - headers: getHeaders(), - }) + .delete(`${API_ROOT}${url}`, payload) .then((response) => response.data?.data) .catch((error) => { return Promise.reject(error?.response?.data || error?.response || error); @@ -78,46 +73,39 @@ function buildUrl(url: string, absoluteUrl: boolean) { return absoluteUrl ? url : `${API_ROOT}${url}`; } -function getHeaders() { - // TODO: change the way we get the clerk token - const token = getToken(); - const lastEnvironmentId = getEnvironmentId(); - - return token - ? { - Authorization: `Bearer ${token}`, - 'Novu-Environment-Id': lastEnvironmentId || '', - } - : {}; -} - // WIP: The static API client needs to be replaced by a dynamic API client where api keys are injected. export function buildApiHttpClient({ baseURL = API_ROOT || 'https://api.novu.co', secretKey, - jwt, - environmentId, + environmentId = getEnvironmentId(), }: { baseURL?: string; secretKey?: string; - jwt?: string; environmentId?: string; }) { - if (!secretKey && !jwt) { - throw new Error('A secretKey or jwt is required to create a Novu API client.'); - } - - const authHeader = jwt ? `Bearer ${jwt}` : `ApiKey ${secretKey}`; - const httpClient = axios.create({ baseURL, headers: { - Authorization: authHeader, 'Content-Type': 'application/json', - 'Novu-Environment-Id': environmentId, }, }); + httpClient.interceptors.request.use(async (config) => { + let authHeaderValue = ''; + + if (secretKey) { + authHeaderValue = `ApiKey ${secretKey}`; + } else { + const token = await getToken(); + authHeaderValue = `Bearer ${token}`; + config.headers.set('Novu-Environment-Id', environmentId); + } + + config.headers.set('Authorization', authHeaderValue); + + return config; + }); + const get = async (url, params?: Record) => { // eslint-disable-next-line no-useless-catch try { diff --git a/apps/web/src/components/nav/RootNavMenu.tsx b/apps/web/src/components/nav/RootNavMenu.tsx index e24e2fab576..cc28f2641d2 100644 --- a/apps/web/src/components/nav/RootNavMenu.tsx +++ b/apps/web/src/components/nav/RootNavMenu.tsx @@ -46,8 +46,7 @@ export const RootNavMenu: React.FC = () => { const segment = useSegment(); const { updateOnboardingStatus, showOnboarding, isLoading: isLoadingOnboardingStatus } = useUserOnboardingStatus(); const { readonly: isEnvReadonly, environment } = useEnvironment(); - const isV2Enabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_V2_ENABLED); - const isV2ExperienceEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_V2_ENABLED); + const isV2Enabled = false; const [isLocalStudioModalOpen, toggleLocalStudioModalOpen] = useToggle(); const { navigateToLocalStudio } = useNavigateToLocalStudio({ fallbackFn: toggleLocalStudioModalOpen }); @@ -159,7 +158,7 @@ export const RootNavMenu: React.FC = () => { > - {isV2ExperienceEnabled ? ( + {isV2Enabled ? ( <> diff --git a/apps/web/src/components/providers/AuthProvider.tsx b/apps/web/src/components/providers/AuthProvider.tsx index 3c85ddc9c44..80553064476 100644 --- a/apps/web/src/components/providers/AuthProvider.tsx +++ b/apps/web/src/components/providers/AuthProvider.tsx @@ -1,8 +1,6 @@ import { useContext } from 'react'; -import { IOrganizationEntity, IUserEntity, IJwtClaims } from '@novu/shared'; -import jwtDecode from 'jwt-decode'; +import { IOrganizationEntity, IUserEntity } from '@novu/shared'; import { IS_EE_AUTH_ENABLED } from '../../config/index'; -import { eeAuthTokenCookie } from '../../utils/cookies'; import { CommunityAuthContext, CommunityAuthProvider, @@ -60,14 +58,10 @@ export const useAuth = () => { return value; }; -export function getToken(): string { - const token = IS_EE_AUTH_ENABLED ? eeAuthTokenCookie.get() : getCommunityAuthToken(); - - return token || ''; -} - -export function getTokenClaims(): IJwtClaims | null { - const token = getToken(); +export async function getToken() { + if (IS_EE_AUTH_ENABLED) { + return (await window?.Clerk?.session?.getToken()) || ''; + } - return token ? jwtDecode(token) : null; + return getCommunityAuthToken() || ''; } diff --git a/apps/web/src/ee/clerk/providers/EnterpriseAuthProvider.tsx b/apps/web/src/ee/clerk/providers/EnterpriseAuthProvider.tsx index 77ad94eece3..9a7d3a4f6e0 100644 --- a/apps/web/src/ee/clerk/providers/EnterpriseAuthProvider.tsx +++ b/apps/web/src/ee/clerk/providers/EnterpriseAuthProvider.tsx @@ -9,7 +9,6 @@ import { type AuthContextValue } from '../../../components/providers/AuthProvide import { DEFAULT_AUTH_CONTEXT_VALUE } from '../../../components/providers/constants'; import { useSegment } from '../../../components/providers/SegmentProvider'; import { ROUTES } from '../../../constants/routes'; -import { useGetDefaultLocale } from '../../translations/hooks/useGetDefaultLocale'; const asyncNoop = async () => {}; @@ -19,7 +18,6 @@ EnterpriseAuthContext.displayName = 'EnterpriseAuthProvider'; export const EnterpriseAuthProvider = ({ children }: { children: React.ReactNode }) => { const { signOut, orgId } = useAuth(); - const { defaultLocale } = useGetDefaultLocale(); const { user: clerkUser, isLoaded: isUserLoaded } = useUser(); const { organization: clerkOrganization, isLoaded: isOrganizationLoaded } = useOrganization(); // TODO @ChmaraX: Can we use setActive from useSession, useSignIn, or useSignUp to avoid loading the list? @@ -112,8 +110,8 @@ export const EnterpriseAuthProvider = ({ children }: { children: React.ReactNode const currentUser = useMemo(() => (clerkUser ? toUserEntity(clerkUser) : undefined), [clerkUser]); const currentOrganization = useMemo( - () => (clerkOrganization ? toOrganizationEntity(clerkOrganization, defaultLocale) : undefined), - [clerkOrganization, defaultLocale] + () => (clerkOrganization ? toOrganizationEntity(clerkOrganization) : undefined), + [clerkOrganization] ); // refetch queries on organization switch @@ -179,10 +177,7 @@ const toUserEntity = (clerkUser: UserResource): IUserEntity => { }; }; -const toOrganizationEntity = ( - clerkOrganization: OrganizationResource, - defaultLocale: string | undefined -): IOrganizationEntity => { +const toOrganizationEntity = (clerkOrganization: OrganizationResource): IOrganizationEntity => { /* * When mapping to IOrganizationEntity, we have 2 cases: * - user exists and has signed in @@ -202,7 +197,6 @@ const toOrganizationEntity = ( name: clerkOrganization.name, createdAt: clerkOrganization.createdAt.toISOString(), updatedAt: clerkOrganization.updatedAt.toISOString(), - defaultLocale, domain: clerkOrganization.publicMetadata.domain, productUseCases: clerkOrganization.publicMetadata.productUseCases, language: clerkOrganization.publicMetadata.language, diff --git a/apps/web/src/ee/translations/components/CreateGroupSidebar.tsx b/apps/web/src/ee/translations/components/CreateGroupSidebar.tsx index 68d2cce27e3..af8f77213bb 100644 --- a/apps/web/src/ee/translations/components/CreateGroupSidebar.tsx +++ b/apps/web/src/ee/translations/components/CreateGroupSidebar.tsx @@ -1,17 +1,26 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Button, Sidebar, Title, errorMessage } from '@novu/design-system'; import { Group } from '@mantine/core'; import slugify from 'slugify'; import { Control, FormProvider, useForm } from 'react-hook-form'; -import { useAuth, useEnvironment } from '../../../hooks'; +import { useEnvironment } from '../../../hooks'; import { api } from '../../../api'; +import { useGetDefaultLocale } from '../hooks/useGetDefaultLocale'; import { TranslationFolderIconSmall } from '../icons'; import { GroupFormCommonFields } from './GroupFormCommonFields'; import { ICreateGroup } from './shared'; +function defaultValues(defaultLocale = '') { + return { + name: '', + identifier: '', + locales: [defaultLocale], + }; +} + export const CreateGroupSidebar = ({ open, onClose, @@ -21,10 +30,9 @@ export const CreateGroupSidebar = ({ onClose: () => void; onGroupCreated: (id: string) => void; }) => { - const { currentOrganization } = useAuth(); const queryClient = useQueryClient(); - const { readonly } = useEnvironment(); + const { defaultLocale } = useGetDefaultLocale(); const { mutateAsync: createTranslationGroup, isLoading: isSaving } = useMutation< any, @@ -43,31 +51,33 @@ export const CreateGroupSidebar = ({ const methods = useForm({ mode: 'onChange', - defaultValues: { - name: '', - identifier: '', - locales: currentOrganization?.defaultLocale ? [currentOrganization?.defaultLocale] : [], - }, + defaultValues: defaultValues(defaultLocale), }); + const { control, handleSubmit, watch, setValue, formState: { isValid, isDirty }, + reset, } = methods; + const name = watch('name'); const identifier = watch('identifier'); const localesForm = watch('locales'); useEffect(() => { - if (!currentOrganization?.defaultLocale) return; + if (defaultLocale) { + reset(defaultValues(defaultLocale)); + } + }, [defaultLocale, reset]); - if (localesForm.length === 0) { - setValue('locales', [currentOrganization?.defaultLocale]); + useEffect(() => { + if (defaultLocale && localesForm.length === 0) { + setValue('locales', [defaultLocale]); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentOrganization?.defaultLocale, localesForm]); + }, [defaultLocale, localesForm, setValue]); useEffect(() => { const newIdentifier = slugify(name, { diff --git a/apps/web/src/ee/translations/components/DefaultLocaleModal/index.tsx b/apps/web/src/ee/translations/components/DefaultLocaleModal/index.tsx index 4206fd584f6..5c9927dc999 100644 --- a/apps/web/src/ee/translations/components/DefaultLocaleModal/index.tsx +++ b/apps/web/src/ee/translations/components/DefaultLocaleModal/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { Group, Stack } from '@mantine/core'; import { Controller, useForm } from 'react-hook-form'; import { useMutation, useQueryClient } from '@tanstack/react-query'; @@ -18,7 +18,7 @@ import { api } from '../../../../api'; import { useAuth } from '../../../../hooks/useAuth'; import { LocaleIcon } from '../../icons/LocaleIcon'; -import { useFetchLocales } from '../../hooks/useFetchLocales'; +import { useFetchLocales, useGetDefaultLocale } from '../../hooks'; import { FlagIcon, SelectItem } from '../shared'; import { GlobeIcon } from '../../icons/GlobeIcon'; @@ -33,14 +33,15 @@ export const DefaultLocaleModal = ({ }) => { const queryClient = useQueryClient(); const { locales, isLoading } = useFetchLocales(); - const { currentOrganization, reloadOrganization } = useAuth(); + const { defaultLocale } = useGetDefaultLocale(); + const { currentOrganization } = useAuth(); const { mutateAsync: saveDefaultLocale, isLoading: isSaving } = useMutation( (args) => api.patch('/v1/translations/language', args), { onSuccess: async () => { - await reloadOrganization(); - queryClient.refetchQueries([`translationGroups`]); + queryClient.refetchQueries([`translations/defaultLocale, ${currentOrganization?._id}`]); + queryClient.refetchQueries(['translationGroups']); queryClient.refetchQueries(['changesCount']); successMessage(`Default language has been set`); @@ -73,13 +74,13 @@ export const DefaultLocaleModal = ({ }; useEffect(() => { - if (currentOrganization?.defaultLocale) { + if (defaultLocale) { reset({ - defaultLocale: currentOrganization.defaultLocale, + defaultLocale, }); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentOrganization?.defaultLocale]); + }, [defaultLocale]); return ( - {currentOrganization?.defaultLocale ? 'Change' : 'Specify'} default language + {defaultLocale ? 'Change' : 'Specify'} default language } onClose={onClose} @@ -100,7 +101,7 @@ export const DefaultLocaleModal = ({ Notifications without specified translations will use the default language for all notifications within the current currentOrganization. - +
@@ -150,7 +151,7 @@ export const DefaultLocaleModal = ({ Cancel diff --git a/apps/web/src/ee/translations/components/DeleteLocaleModal.tsx b/apps/web/src/ee/translations/components/DeleteLocaleModal.tsx index 7ac842e0f2b..2ea6c25e8ce 100644 --- a/apps/web/src/ee/translations/components/DeleteLocaleModal.tsx +++ b/apps/web/src/ee/translations/components/DeleteLocaleModal.tsx @@ -1,11 +1,10 @@ -import React from 'react'; import { Group } from '@mantine/core'; import { useFormContext } from 'react-hook-form'; import { Modal, Text, colors, Button } from '@novu/design-system'; -import { useAuth } from '../../../hooks'; import { useFetchLocales } from '../hooks/useFetchLocales'; import { FlagIcon } from './shared'; import { Warning } from '../icons'; +import { useGetDefaultLocale } from '../hooks/useGetDefaultLocale'; export const DeleteLocaleModal = ({ localeToDelete, @@ -17,7 +16,7 @@ export const DeleteLocaleModal = ({ readonly: boolean; }) => { const { getLocale } = useFetchLocales(); - const { currentOrganization } = useAuth(); + const { defaultLocale } = useGetDefaultLocale(); const form = useFormContext(); if (!localeToDelete) { @@ -41,7 +40,7 @@ export const DeleteLocaleModal = ({ Deleting a language removes its JSON file, and notifications using keys from that file will switch to the {} - default {getLocale(currentOrganization!.defaultLocale!)?.langName} language. + default {getLocale(defaultLocale!)?.langName} language.