From d5036d4e5e61e55a6e6d44f198edc74a7667021f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Tymczuk?= Date: Tue, 3 Dec 2024 15:42:43 +0100 Subject: [PATCH 01/18] fix(dashboard): step form values were not updated when switching between steps (#7201) --- .../src/components/workflow-editor/nodes.tsx | 7 +++- .../steps/configure-step-form.tsx | 8 ++--- .../steps/configure-step-template-form.tsx | 4 --- .../workflow-editor/steps/configure-step.tsx | 12 +++++-- .../workflow-editor/steps/step-provider.tsx | 14 ++++---- apps/dashboard/src/hooks/use-fetch-step.tsx | 32 +++++++++++++------ 6 files changed, 50 insertions(+), 27 deletions(-) diff --git a/apps/dashboard/src/components/workflow-editor/nodes.tsx b/apps/dashboard/src/components/workflow-editor/nodes.tsx index 51f397a0bdc..7ba79e26dcb 100644 --- a/apps/dashboard/src/components/workflow-editor/nodes.tsx +++ b/apps/dashboard/src/components/workflow-editor/nodes.tsx @@ -12,6 +12,7 @@ import { STEP_TYPE_TO_COLOR } from '@/utils/color'; import { useWorkflow } from '@/components/workflow-editor/workflow-provider'; import { WorkflowOriginEnum } from '@novu/shared'; import { createStep } from '@/components/workflow-editor/steps/step-provider'; +import { getEncodedId, STEP_DIVIDER } from '@/utils/step'; export type NodeData = { name?: string; @@ -52,7 +53,11 @@ const StepNode = (props: StepNodeProps) => { stepSlug: string; }>(); - return ; + const isSelected = + getEncodedId({ slug: stepSlug ?? '', divider: STEP_DIVIDER }) === + getEncodedId({ slug: data.stepSlug ?? '', divider: STEP_DIVIDER }); + + return ; }; export const EmailNode = ({ data }: NodeProps) => { diff --git a/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx index 9e3b188a548..90fa58e293f 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx @@ -43,10 +43,11 @@ type ConfigureStepFormProps = { environment: IEnvironment; step: StepDataDto; update: (data: UpdateWorkflowDto) => void; + updateStepCache: (step: Partial) => void; }; export const ConfigureStepForm = (props: ConfigureStepFormProps) => { - const { step, workflow, update, environment } = props; + const { step, workflow, update, updateStepCache, environment } = props; const navigate = useNavigate(); const isCodeCreatedWorkflow = workflow.origin === WorkflowOriginEnum.EXTERNAL; @@ -64,10 +65,6 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => { const form = useForm>({ defaultValues, - values: { - ...defaultValues, - ...step.controls.values, - }, resolver: zodResolver(stepSchema), shouldFocusError: false, }); @@ -78,6 +75,7 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => { isReadOnly: isCodeCreatedWorkflow, save: (data) => { update(updateStepInWorkflow(workflow, data)); + updateStepCache(data); }, }); diff --git a/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx index 9232ca870fb..59898b51468 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx @@ -60,10 +60,6 @@ export const ConfigureStepTemplateForm = (props: ConfigureStepTemplateFormProps) const form = useForm({ resolver: zodResolver(schema), defaultValues, - values: { - ...defaultValues, - ...step.controls.values, - }, shouldFocusError: false, }); diff --git a/apps/dashboard/src/components/workflow-editor/steps/configure-step.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-step.tsx index a3765c8ffae..616cc048374 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/configure-step.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-step.tsx @@ -4,12 +4,20 @@ import { useWorkflow } from '@/components/workflow-editor/workflow-provider'; import { useEnvironment } from '@/context/environment/hooks'; export const ConfigureStep = () => { - const { step } = useStep(); + const { step, updateStepCache } = useStep(); const { workflow, update } = useWorkflow(); const { currentEnvironment } = useEnvironment(); if (!currentEnvironment || !step || !workflow) { return null; } - return ; + return ( + + ); }; diff --git a/apps/dashboard/src/components/workflow-editor/steps/step-provider.tsx b/apps/dashboard/src/components/workflow-editor/steps/step-provider.tsx index e9428a20f2d..27ea449b85c 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/step-provider.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/step-provider.tsx @@ -1,7 +1,6 @@ import { createContext, useMemo, type ReactNode } from 'react'; import { useParams } from 'react-router-dom'; -import { useWorkflow } from '@/components/workflow-editor/workflow-provider'; import { useFetchStep } from '@/hooks/use-fetch-step'; import { StepDataDto, StepTypeEnum } from '@novu/shared'; import { QueryObserverResult, RefetchOptions } from '@tanstack/react-query'; @@ -12,22 +11,25 @@ export type StepEditorContextType = { isPending: boolean; step?: StepDataDto; refetch: (options?: RefetchOptions) => Promise>; + updateStepCache: (step: Partial) => void; }; export const StepContext = createContext({} as StepEditorContextType); export const StepProvider = ({ children }: { children: ReactNode }) => { - const { workflow } = useWorkflow(); - const { stepSlug = '' } = useParams<{ + const { stepSlug = '', workflowSlug = '' } = useParams<{ workflowSlug: string; stepSlug: string; }>(); - const { step, isPending, refetch } = useFetchStep({ - workflowSlug: workflow?.slug, + const { step, isPending, refetch, updateStepCache } = useFetchStep({ + workflowSlug, stepSlug, }); - const value = useMemo(() => ({ isPending, step, refetch }), [isPending, step, refetch]); + const value = useMemo( + () => ({ isPending, step, refetch, updateStepCache }), + [isPending, step, refetch, updateStepCache] + ); return {children}; }; diff --git a/apps/dashboard/src/hooks/use-fetch-step.tsx b/apps/dashboard/src/hooks/use-fetch-step.tsx index 01859963a57..14c0cab48cb 100644 --- a/apps/dashboard/src/hooks/use-fetch-step.tsx +++ b/apps/dashboard/src/hooks/use-fetch-step.tsx @@ -1,29 +1,43 @@ -import { useQuery } from '@tanstack/react-query'; +import { useCallback, useMemo } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import type { StepDataDto } from '@novu/shared'; + import { QueryKeys } from '@/utils/query-keys'; import { useEnvironment } from '@/context/environment/hooks'; import { fetchStep } from '@/api/steps'; -import { getEncodedId, STEP_DIVIDER } from '@/utils/step'; +import { getEncodedId, STEP_DIVIDER, WORKFLOW_DIVIDER } from '@/utils/step'; -export const useFetchStep = ({ workflowSlug, stepSlug }: { workflowSlug?: string; stepSlug?: string }) => { +export const useFetchStep = ({ workflowSlug, stepSlug }: { workflowSlug: string; stepSlug: string }) => { + const client = useQueryClient(); const { currentEnvironment } = useEnvironment(); - - const { data, isPending, isRefetching, error, refetch } = useQuery({ - queryKey: [ + const queryKey = useMemo( + () => [ QueryKeys.fetchWorkflow, currentEnvironment?._id, - workflowSlug, - getEncodedId({ slug: stepSlug!, divider: STEP_DIVIDER }), + getEncodedId({ slug: workflowSlug, divider: WORKFLOW_DIVIDER }), + getEncodedId({ slug: stepSlug, divider: STEP_DIVIDER }), ], - queryFn: () => fetchStep({ workflowSlug: workflowSlug!, stepSlug: stepSlug! }), + [currentEnvironment?._id, workflowSlug, stepSlug] + ); + + const { data, isPending, isRefetching, error, refetch } = useQuery({ + queryKey, + queryFn: () => fetchStep({ workflowSlug: workflowSlug, stepSlug: stepSlug }), enabled: !!currentEnvironment?._id && !!stepSlug && !!workflowSlug, }); + const updateStepCache = useCallback( + (newStep: Partial) => + client.setQueryData(queryKey, (oldData: StepDataDto | undefined) => ({ ...oldData, ...newStep })), + [client, queryKey] + ); + return { step: data, isPending, isRefetching, error, refetch, + updateStepCache, }; }; From dc829deb9cf30fbdc35a502c4ecdde80b969bd26 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Tue, 3 Dec 2024 17:08:10 +0200 Subject: [PATCH 02/18] chore(api): Enable preview deployments (#7200) --- apps/api/package.json | 2 +- apps/api/src/.env.development | 2 - apps/api/src/app.module.ts | 4 +- apps/api/src/config/cors.config.spec.ts | 69 ++++++------------------- apps/api/src/config/cors.config.ts | 32 +++--------- apps/web/env-config.js | 0 packages/node/CHANGELOG.md | 10 ++++ packages/node/package.json | 2 +- 8 files changed, 36 insertions(+), 85 deletions(-) delete mode 100644 apps/web/env-config.js diff --git a/apps/api/package.json b/apps/api/package.json index ad6acc52d24..e71ab926c85 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "@novu/api", - "version": "2.1.0", + "version": "2.1.1", "description": "description", "author": "", "private": "true", diff --git a/apps/api/src/.env.development b/apps/api/src/.env.development index c642de2b7e4..4e82600e495 100644 --- a/apps/api/src/.env.development +++ b/apps/api/src/.env.development @@ -81,8 +81,6 @@ API_RATE_LIMIT_MAXIMUM_UNLIMITED_TRIGGER= API_RATE_LIMIT_MAXIMUM_UNLIMITED_CONFIGURATION= API_RATE_LIMIT_MAXIMUM_UNLIMITED_GLOBAL= -PR_PREVIEW_ROOT_URL=dev-web-novu.netlify.app - HUBSPOT_INVITE_NUDGE_EMAIL_USER_LIST_ID= HUBSPOT_PRIVATE_APP_ACCESS_TOKEN= diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index f53e8aab558..585fbd3626c 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -160,7 +160,9 @@ modules.push( client: new Client({ secretKey: process.env.NOVU_INTERNAL_SECRET_KEY, strictAuthentication: - process.env.NODE_ENV === 'production' || process.env.NOVU_STRICT_AUTHENTICATION_ENABLED === 'true', + process.env.NODE_ENV === 'production' || + process.env.NODE_ENV === 'dev' || + process.env.NOVU_STRICT_AUTHENTICATION_ENABLED === 'true', }), controllerDecorators: [ApiExcludeController()], workflows: [usageLimitsWorkflow], diff --git a/apps/api/src/config/cors.config.spec.ts b/apps/api/src/config/cors.config.spec.ts index 6691d7e4e72..d77d334028b 100644 --- a/apps/api/src/config/cors.config.spec.ts +++ b/apps/api/src/config/cors.config.spec.ts @@ -1,6 +1,6 @@ import { spy } from 'sinon'; import { expect } from 'chai'; -import { corsOptionsDelegate, isPermittedDeployPreviewOrigin } from './cors.config'; +import { corsOptionsDelegate } from './cors.config'; describe('CORS Configuration', () => { describe('Local Environment', () => { @@ -32,7 +32,6 @@ describe('CORS Configuration', () => { process.env.FRONT_BASE_URL = 'https://test.com'; process.env.LEGACY_STAGING_DASHBOARD_URL = 'https://test-legacy-staging-dashboard.com'; process.env.WIDGET_BASE_URL = 'https://widget.com'; - process.env.PR_PREVIEW_ROOT_URL = 'https://pr-preview.com'; }); afterEach(() => { @@ -43,14 +42,26 @@ describe('CORS Configuration', () => { const callbackSpy = spy(); // @ts-expect-error - corsOptionsDelegate is not typed correctly - corsOptionsDelegate({ url: '/v1/test' }, callbackSpy); + corsOptionsDelegate( + { + url: '/v1/test', + headers: { + origin: 'https://test.novu.com', + }, + }, + callbackSpy + ); expect(callbackSpy.calledOnce).to.be.ok; expect(callbackSpy.firstCall.firstArg).to.be.null; - expect(callbackSpy.firstCall.lastArg.origin.length).to.equal(3); + expect(callbackSpy.firstCall.lastArg.origin.length).to.equal(environment === 'dev' ? 4 : 3); expect(callbackSpy.firstCall.lastArg.origin[0]).to.equal(process.env.FRONT_BASE_URL); expect(callbackSpy.firstCall.lastArg.origin[1]).to.equal(process.env.LEGACY_STAGING_DASHBOARD_URL); expect(callbackSpy.firstCall.lastArg.origin[2]).to.equal(process.env.WIDGET_BASE_URL); + + if (environment === 'dev') { + expect(callbackSpy.firstCall.lastArg.origin[3]).to.equal('https://test.novu.com'); + } }); it('widget routes should be wildcarded', () => { @@ -74,56 +85,6 @@ describe('CORS Configuration', () => { expect(callbackSpy.firstCall.firstArg).to.be.null; expect(callbackSpy.firstCall.lastArg.origin).to.equal('*'); }); - - if (environment === 'dev') { - it('should allow all origins for dev environment from pr preview', () => { - const callbackSpy = spy(); - - // @ts-expect-error - corsOptionsDelegate is not typed correctly - corsOptionsDelegate( - { - url: '/v1/test', - headers: { - origin: `https://test--${process.env.PR_PREVIEW_ROOT_URL}`, - }, - }, - callbackSpy - ); - - expect(callbackSpy.calledOnce).to.be.ok; - expect(callbackSpy.firstCall.firstArg).to.be.null; - expect(callbackSpy.firstCall.lastArg.origin).to.equal('*'); - }); - } - }); - }); - - describe('isPermittedDeployPreviewOrigin', () => { - afterEach(() => { - process.env.NODE_ENV = 'test'; - }); - - it('should return false when NODE_ENV is not dev', () => { - process.env.NODE_ENV = 'production'; - expect(isPermittedDeployPreviewOrigin('https://someorigin.com')).to.be.false; - }); - - it('should return false when PR_PREVIEW_ROOT_URL is not set', () => { - process.env.NODE_ENV = 'dev'; - delete process.env.PR_PREVIEW_ROOT_URL; - expect(isPermittedDeployPreviewOrigin('https://someorigin.com')).to.be.false; - }); - - it('should return false for origins not matching PR_PREVIEW_ROOT_URL (string)', () => { - process.env.NODE_ENV = 'dev'; - process.env.PR_PREVIEW_ROOT_URL = 'https://pr-preview.com'; - expect(isPermittedDeployPreviewOrigin('https://anotherorigin.com')).to.be.false; - }); - - it('should return true for origin matching PR_PREVIEW_ROOT_URL', () => { - process.env.NODE_ENV = 'dev'; - process.env.PR_PREVIEW_ROOT_URL = 'https://pr-preview.com'; - expect(isPermittedDeployPreviewOrigin('https://netlify-https://pr-preview.com')).to.be.true; }); }); }); diff --git a/apps/api/src/config/cors.config.ts b/apps/api/src/config/cors.config.ts index 2445e3c690a..338326e6679 100644 --- a/apps/api/src/config/cors.config.ts +++ b/apps/api/src/config/cors.config.ts @@ -10,8 +10,6 @@ export const corsOptionsDelegate: Parameters[0] methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], }; - const origin = extractOrigin(req); - if (enableWildcard(req)) { corsOptions.origin = '*'; } else { @@ -29,27 +27,17 @@ export const corsOptionsDelegate: Parameters[0] if (process.env.WIDGET_BASE_URL) { corsOptions.origin.push(process.env.WIDGET_BASE_URL); } + // Enable preview deployments in staging environment for Netlify and Vercel + if (process.env.NODE_ENV === 'dev') { + corsOptions.origin.push(origin(req)); + } } - const shouldDisableCorsForPreviewUrls = isPermittedDeployPreviewOrigin(origin); - - Logger.verbose(`Should allow deploy preview? ${shouldDisableCorsForPreviewUrls ? 'Yes' : 'No'}.`, { - curEnv: process.env.NODE_ENV, - previewUrlRoot: process.env.PR_PREVIEW_ROOT_URL, - origin, - }); - callback(null as unknown as Error, corsOptions); }; function enableWildcard(req: Request): boolean { - return ( - isSandboxEnvironment() || - isWidgetRoute(req.url) || - isInboxRoute(req.url) || - isBlueprintRoute(req.url) || - isPermittedDeployPreviewOrigin(extractOrigin(req)) - ); + return isSandboxEnvironment() || isWidgetRoute(req.url) || isInboxRoute(req.url) || isBlueprintRoute(req.url); } function isWidgetRoute(url: string): boolean { @@ -68,14 +56,6 @@ function isSandboxEnvironment(): boolean { return ['test', 'local'].includes(process.env.NODE_ENV); } -export function isPermittedDeployPreviewOrigin(origin: string | string[]): boolean { - if (!process.env.PR_PREVIEW_ROOT_URL || process.env.NODE_ENV !== 'dev') { - return false; - } - - return origin.includes(process.env.PR_PREVIEW_ROOT_URL); -} - -function extractOrigin(req: Request): string { +function origin(req: Request): string { return (req.headers as any)?.origin || ''; } diff --git a/apps/web/env-config.js b/apps/web/env-config.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 5eb6758e571..4b0693e859d 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.0.5 (2024-12-02) + +### 🩹 Fixes + +- **node:** Allow setting includeInactiveChannels to false ([129355e269](https://github.com/novuhq/novu/commit/129355e269)) + +### ❤️ Thank You + +- Sokratis Vidros @SokratisVidros + ## 2.0.4 (2024-11-29) ### 🚀 Features diff --git a/packages/node/package.json b/packages/node/package.json index 2cb0d567e13..c832a06ac94 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@novu/node", - "version": "2.0.4", + "version": "2.0.5", "description": "Notification Management Framework", "main": "build/main/index.js", "typings": "build/main/index.d.ts", From 8a2352086a027f0ee5775f58f980ccb1c3064047 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Tue, 3 Dec 2024 17:08:36 +0200 Subject: [PATCH 03/18] fix(js): Remove @novu/shared dependency (#6906) --- packages/js/jest.config.cjs | 5 ++ packages/js/jest.setup.ts | 2 - packages/js/package.json | 1 - packages/js/src/api/http-client.ts | 119 ++++++++++++++++++++++++++ packages/js/src/api/inbox-service.ts | 31 +++---- packages/js/src/base-module.test.ts | 71 ++++++++++++++++ packages/js/src/global.d.ts | 11 +-- packages/js/src/novu.test.ts | 121 ++++++++++----------------- packages/js/src/novu.ts | 8 +- packages/js/src/session/session.ts | 1 - packages/js/tsconfig.json | 2 +- packages/js/tsup.config.ts | 5 ++ pnpm-lock.yaml | 3 - 13 files changed, 270 insertions(+), 110 deletions(-) create mode 100644 packages/js/src/api/http-client.ts create mode 100644 packages/js/src/base-module.test.ts diff --git a/packages/js/jest.config.cjs b/packages/js/jest.config.cjs index bde0ef6b7f8..a03abac029d 100644 --- a/packages/js/jest.config.cjs +++ b/packages/js/jest.config.cjs @@ -1,4 +1,9 @@ module.exports = { preset: 'ts-jest', setupFiles: ['./jest.setup.ts'], + globals: { + NOVU_API_VERSION: '2024-06-26', + PACKAGE_NAME: '@novu/js', + PACKAGE_VERSION: 'test', + }, }; diff --git a/packages/js/jest.setup.ts b/packages/js/jest.setup.ts index 85cf83a3672..e69de29bb2d 100644 --- a/packages/js/jest.setup.ts +++ b/packages/js/jest.setup.ts @@ -1,2 +0,0 @@ -global.PACKAGE_VERSION = 'test-version'; -global.PACKAGE_NAME = 'test-package'; diff --git a/packages/js/package.json b/packages/js/package.json index 18b739b6201..02b14a5fea2 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -126,7 +126,6 @@ }, "dependencies": { "@floating-ui/dom": "^1.6.7", - "@novu/client": "workspace:*", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "mitt": "^3.0.1", diff --git a/packages/js/src/api/http-client.ts b/packages/js/src/api/http-client.ts new file mode 100644 index 00000000000..b0cb50566ee --- /dev/null +++ b/packages/js/src/api/http-client.ts @@ -0,0 +1,119 @@ +export type HttpClientOptions = { + apiVersion?: string; + backendUrl?: string; + userAgent?: string; +}; + +const DEFAULT_API_VERSION = 'v1'; +const DEFAULT_BACKEND_URL = 'https://api.novu.co'; +const DEFAULT_USER_AGENT = `${PACKAGE_NAME}@${PACKAGE_VERSION}`; + +export class HttpClient { + private backendUrl: string; + private apiVersion: string; + private headers: Record; + + constructor(options: HttpClientOptions = {}) { + const { + apiVersion = DEFAULT_API_VERSION, + backendUrl = DEFAULT_BACKEND_URL, + userAgent = DEFAULT_USER_AGENT, + } = options || {}; + this.apiVersion = apiVersion; + this.backendUrl = `${backendUrl}/${this.apiVersion}`; + this.headers = { + 'Novu-API-Version': NOVU_API_VERSION, + 'Content-Type': 'application/json', + 'User-Agent': userAgent, + }; + } + + setAuthorizationToken(token: string) { + this.headers.Authorization = `Bearer ${token}`; + } + + setHeaders(headers: Record) { + this.headers = { + ...this.headers, + ...headers, + }; + } + + async get(path: string, searchParams?: URLSearchParams, unwrapEnvelope = true) { + return this.doFetch({ + path, + searchParams, + options: { + method: 'GET', + }, + unwrapEnvelope, + }); + } + + async post(path: string, body?: any) { + return this.doFetch({ + path, + options: { + method: 'POST', + body, + }, + }); + } + + async patch(path: string, body?: any) { + return this.doFetch({ + path, + options: { + method: 'PATCH', + body, + }, + }); + } + + async delete(path: string, body?: any) { + return this.doFetch({ + path, + options: { + method: 'DELETE', + body, + }, + }); + } + + private async doFetch({ + path, + searchParams, + options, + unwrapEnvelope = true, + }: { + path: string; + searchParams?: URLSearchParams; + options?: RequestInit; + unwrapEnvelope?: boolean; + }) { + const fullUrl = combineUrl(this.backendUrl, path, searchParams ? `?${searchParams.toString()}` : ''); + const reqInit = { + method: options?.method || 'GET', + headers: { ...this.headers, ...(options?.headers || {}) }, + body: options?.body ? JSON.stringify(options.body) : undefined, + }; + + const response = await fetch(fullUrl, reqInit); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(`${this.headers['User-Agent']} error. Status: ${response.status}, Message: ${errorData.message}`); + } + if (response.status === 204) { + return undefined as unknown as T; + } + + const res = await response.json(); + + return (unwrapEnvelope ? res.data : res) as Promise; + } +} + +function combineUrl(...args: string[]): string { + return args.map((part) => part.replace(/^\/+|\/+$/g, '')).join('/'); +} diff --git a/packages/js/src/api/inbox-service.ts b/packages/js/src/api/inbox-service.ts index e375934c3ac..70573f4f439 100644 --- a/packages/js/src/api/inbox-service.ts +++ b/packages/js/src/api/inbox-service.ts @@ -1,4 +1,3 @@ -import { ApiOptions, HttpClient } from '@novu/client'; import type { ActionTypeEnum, ChannelPreference, @@ -7,10 +6,10 @@ import type { PreferencesResponse, Session, } from '../types'; +import { HttpClient, HttpClientOptions } from './http-client'; -export type InboxServiceOptions = ApiOptions; +export type InboxServiceOptions = HttpClientOptions; -const NOVU_API_VERSION = '2024-06-26'; const INBOX_ROUTE = '/inbox'; const INBOX_NOTIFICATIONS_ROUTE = `${INBOX_ROUTE}/notifications`; @@ -20,10 +19,6 @@ export class InboxService { constructor(options: InboxServiceOptions = {}) { this.#httpClient = new HttpClient(options); - this.#httpClient.updateHeaders({ - 'Novu-API-Version': NOVU_API_VERSION, - 'Novu-User-Agent': options.userAgent || '@novu/js', - }); } async initializeSession({ @@ -61,24 +56,24 @@ export class InboxService { after?: string; offset?: number; }): Promise<{ data: InboxNotification[]; hasMore: boolean; filter: NotificationFilter }> { - const queryParams = new URLSearchParams(`limit=${limit}`); + const searchParams = new URLSearchParams(`limit=${limit}`); if (after) { - queryParams.append('after', after); + searchParams.append('after', after); } if (offset) { - queryParams.append('offset', `${offset}`); + searchParams.append('offset', `${offset}`); } if (tags) { - tags.forEach((tag) => queryParams.append('tags[]', tag)); + tags.forEach((tag) => searchParams.append('tags[]', tag)); } if (read !== undefined) { - queryParams.append('read', `${read}`); + searchParams.append('read', `${read}`); } if (archived !== undefined) { - queryParams.append('archived', `${archived}`); + searchParams.append('archived', `${archived}`); } - return this.#httpClient.getFullResponse(`${INBOX_NOTIFICATIONS_ROUTE}?${queryParams.toString()}`); + return this.#httpClient.get(INBOX_NOTIFICATIONS_ROUTE, searchParams, false); } count({ filters }: { filters: Array<{ tags?: string[]; read?: boolean; archived?: boolean }> }): Promise<{ @@ -87,7 +82,13 @@ export class InboxService { filter: NotificationFilter; }>; }> { - return this.#httpClient.getFullResponse(`${INBOX_NOTIFICATIONS_ROUTE}/count?filters=${JSON.stringify(filters)}`); + return this.#httpClient.get( + `${INBOX_NOTIFICATIONS_ROUTE}/count`, + new URLSearchParams({ + filters: JSON.stringify(filters), + }), + false + ); } read(notificationId: string): Promise { diff --git a/packages/js/src/base-module.test.ts b/packages/js/src/base-module.test.ts new file mode 100644 index 00000000000..58f2a43287d --- /dev/null +++ b/packages/js/src/base-module.test.ts @@ -0,0 +1,71 @@ +import { InboxService } from './api'; +import { BaseModule } from './base-module'; +import { NovuEventEmitter } from './event-emitter'; + +beforeAll(() => jest.spyOn(global, 'fetch')); +afterAll(() => jest.restoreAllMocks()); + +describe('callWithSession(fn)', () => { + test('should invoke callback function immediately if session is initialized', async () => { + const emitter = new NovuEventEmitter(); + const bm = new BaseModule({ + inboxServiceInstance: { + isSessionInitialized: true, + } as InboxService, + eventEmitterInstance: emitter, + }); + + const cb = jest.fn(); + bm.callWithSession(cb); + expect(cb).toHaveBeenCalled(); + }); + + test('should invoke callback function as soon as session is initialized', async () => { + const emitter = new NovuEventEmitter(); + const bm = new BaseModule({ + inboxServiceInstance: {} as InboxService, + eventEmitterInstance: emitter, + }); + + const cb = jest.fn(); + + bm.callWithSession(cb); + expect(cb).not.toHaveBeenCalled(); + + emitter.emit('session.initialize.resolved', { + args: { + applicationIdentifier: 'foo', + subscriberId: 'bar', + }, + data: { + token: 'cafebabe', + totalUnreadCount: 10, + removeNovuBranding: true, + }, + }); + + expect(cb).toHaveBeenCalled(); + }); + + test('should return an error if session initialization failed', async () => { + const emitter = new NovuEventEmitter(); + const bm = new BaseModule({ + inboxServiceInstance: {} as InboxService, + eventEmitterInstance: emitter, + }); + + emitter.emit('session.initialize.resolved', { + args: { + applicationIdentifier: 'foo', + subscriberId: 'bar', + }, + error: new Error('Failed to initialize session'), + }); + + const cb = jest.fn(); + const result = await bm.callWithSession(cb); + expect(result).toEqual({ + error: new Error('Failed to initialize session, please contact the support'), + }); + }); +}); diff --git a/packages/js/src/global.d.ts b/packages/js/src/global.d.ts index 9741972a4ed..2ea80da406b 100644 --- a/packages/js/src/global.d.ts +++ b/packages/js/src/global.d.ts @@ -1,10 +1,11 @@ -/* eslint-disable vars-on-top */ -/* eslint-disable no-var */ -import { Novu } from './novu'; +import type { Novu } from './novu'; + +export {}; declare global { - var PACKAGE_NAME: string; - var PACKAGE_VERSION: string; + const NOVU_API_VERSION: string; + const PACKAGE_NAME: string; + const PACKAGE_VERSION: string; interface Window { Novu: typeof Novu; } diff --git a/packages/js/src/novu.test.ts b/packages/js/src/novu.test.ts index 49fa73342b6..5e134a646ed 100644 --- a/packages/js/src/novu.test.ts +++ b/packages/js/src/novu.test.ts @@ -1,6 +1,6 @@ -import { ListNotificationsArgs } from './notifications'; import { Novu } from './novu'; -import { NovuError } from './utils/errors'; + +const mockSessionResponse = { data: { token: 'cafebabe' } }; const mockNotificationsResponse = { data: [], @@ -8,100 +8,71 @@ const mockNotificationsResponse = { filter: { tags: [], read: false, archived: false }, }; -const post = jest.fn().mockResolvedValue({ token: 'token', profile: 'profile' }); -const getFullResponse = jest.fn(() => mockNotificationsResponse); -const updateHeaders = jest.fn(); -const setAuthorizationToken = jest.fn(); - -jest.mock('@novu/client', () => ({ - ...jest.requireActual('@novu/client'), - HttpClient: jest.fn().mockImplementation(() => { - const httpClient = { - post, - getFullResponse, - updateHeaders, - setAuthorizationToken, +async function mockFetch(url: string, reqInit: Request) { + if (url.includes('/session')) { + return { + ok: true, + status: 200, + json: async () => mockSessionResponse, }; + } + if (url.includes('/notifications')) { + return { + ok: true, + status: 200, + json: async () => mockNotificationsResponse, + }; + } + throw new Error(`Unmocked request: ${url}`); +} - return httpClient; - }), -})); +beforeAll(() => jest.spyOn(global, 'fetch')); +afterAll(() => jest.restoreAllMocks()); describe('Novu', () => { + const applicationIdentifier = 'foo'; + const subscriberId = 'bar'; + beforeEach(() => { - jest.clearAllMocks(); + // @ts-ignore + global.fetch.mockImplementation(mockFetch) as jest.Mock; }); - describe('lazy session initialization', () => { - test('should call the queued notifications.list after the session is initialized', async () => { + describe('http client', () => { + test('should call the notifications.list after the session is initialized', async () => { const options = { limit: 10, offset: 0, }; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - const { data } = await novu.notifications.list(options); - expect(post).toHaveBeenCalledTimes(1); - expect(getFullResponse).toHaveBeenCalledWith('/inbox/notifications?limit=10'); - expect(data).toEqual({ - notifications: mockNotificationsResponse.data, - hasMore: mockNotificationsResponse.hasMore, - filter: mockNotificationsResponse.filter, + const novu = new Novu({ applicationIdentifier, subscriberId }); + expect(fetch).toHaveBeenNthCalledWith(1, 'https://api.novu.co/v1/inbox/session/', { + method: 'POST', + body: JSON.stringify({ applicationIdentifier, subscriberId }), + headers: { + 'Content-Type': 'application/json', + 'Novu-API-Version': '2024-06-26', + 'User-Agent': '@novu/js@test', + }, }); - }); - test('should call the notifications.list right away when session is already initialized', async () => { - const options: ListNotificationsArgs = { - limit: 10, - offset: 0, - }; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => { - setTimeout(resolve, 10); + const { data } = await novu.notifications.list(options); + expect(fetch).toHaveBeenNthCalledWith(2, 'https://api.novu.co/v1/inbox/notifications/?limit=10', { + method: 'GET', + body: undefined, + headers: { + Authorization: 'Bearer cafebabe', + 'Content-Type': 'application/json', + 'Novu-API-Version': '2024-06-26', + 'User-Agent': '@novu/js@test', + }, }); - const { data } = await novu.notifications.list({ limit: 10, offset: 0 }); - - expect(post).toHaveBeenCalledTimes(1); - expect(getFullResponse).toHaveBeenCalledWith('/inbox/notifications?limit=10'); expect(data).toEqual({ notifications: mockNotificationsResponse.data, hasMore: mockNotificationsResponse.hasMore, filter: mockNotificationsResponse.filter, }); }); - - test('should reject the queued notifications.list if session initialization fails', async () => { - const options = { - limit: 10, - offset: 0, - }; - const expectedError = 'reason'; - post.mockRejectedValueOnce(expectedError); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - - const { error } = await novu.notifications.list(options); - - expect(error).toEqual(new NovuError('Failed to initialize session, please contact the support', expectedError)); - }); - - test('should reject the notifications.list right away when session initialization has failed', async () => { - const options = { - limit: 10, - offset: 0, - }; - const expectedError = 'reason'; - post.mockRejectedValueOnce(expectedError); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => { - setTimeout(resolve, 10); - }); - - const { error } = await novu.notifications.list(options); - - expect(error).toEqual(new NovuError('Failed to initialize session, please contact the support', expectedError)); - }); }); }); diff --git a/packages/js/src/novu.ts b/packages/js/src/novu.ts index 01c769e5e53..feebd4564c3 100644 --- a/packages/js/src/novu.ts +++ b/packages/js/src/novu.ts @@ -8,12 +8,6 @@ import { PRODUCTION_BACKEND_URL } from './utils/config'; import type { NovuOptions } from './types'; import { InboxService } from './api'; -// @ts-ignore -const version = PACKAGE_VERSION; -// @ts-ignore -const name = PACKAGE_NAME; -const userAgent = `${name}@${version}`; - export class Novu implements Pick { #emitter: NovuEventEmitter; #session: Session; @@ -32,7 +26,7 @@ export class Novu implements Pick { constructor(options: NovuOptions) { this.#inboxService = new InboxService({ backendUrl: options.backendUrl ?? PRODUCTION_BACKEND_URL, - userAgent: options.__userAgent ?? userAgent, + userAgent: options.__userAgent, }); this.#emitter = new NovuEventEmitter(); this.#session = new Session( diff --git a/packages/js/src/session/session.ts b/packages/js/src/session/session.ts index bef8f3ac154..1e72a768606 100644 --- a/packages/js/src/session/session.ts +++ b/packages/js/src/session/session.ts @@ -21,7 +21,6 @@ export class Session { try { const { applicationIdentifier, subscriberId, subscriberHash } = this.#options; this.#emitter.emit('session.initialize.pending', { args: this.#options }); - const response = await this.#inboxService.initializeSession({ applicationIdentifier, subscriberId, diff --git a/packages/js/tsconfig.json b/packages/js/tsconfig.json index 0e5d3b81e19..4a83a390ddf 100644 --- a/packages/js/tsconfig.json +++ b/packages/js/tsconfig.json @@ -19,5 +19,5 @@ "removeComments": false }, "include": ["src/**/*", "src/**/*.d.ts"], - "exclude": ["src/**/*.test.ts", "src/*.test.ts", "node_modules", "**/node_modules/*"] + "exclude": ["node_modules", "**/node_modules/*"] } diff --git a/packages/js/tsup.config.ts b/packages/js/tsup.config.ts index 52cae64f1e5..4d0cc27ba70 100644 --- a/packages/js/tsup.config.ts +++ b/packages/js/tsup.config.ts @@ -42,6 +42,11 @@ const baseModuleConfig: Options = { 'themes/index': './src/ui/themes/index.ts', 'internal/index': './src/ui/internal/index.ts', }, + define: { + NOVU_API_VERSION: `"2024-06-26"`, + PACKAGE_NAME: `"${name}"`, + PACKAGE_VERSION: `"${version}"`, + }, }; export default defineConfig((config: Options) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6f796e7c77..2d697eeaed6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3638,9 +3638,6 @@ importers: '@floating-ui/dom': specifier: ^1.6.7 version: 1.6.7 - '@novu/client': - specifier: workspace:* - version: link:../client class-variance-authority: specifier: ^0.7.0 version: 0.7.0 From df292438b3ff674585b164a39a171dc2cfe93ea1 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Tue, 3 Dec 2024 19:23:48 +0200 Subject: [PATCH 04/18] feat(dashboard): Getting started page (#7132) --- .cspell.json | 3 +- .source | 2 +- .../images/welcome/calendar_schedule.png | Bin 0 -> 4149 bytes .../public/images/welcome/compliance.png | Bin 0 -> 6554 bytes .../public/images/welcome/view_code.png | Bin 0 -> 2728 bytes apps/dashboard/src/api/integrations.ts | 8 + .../src/components/primitives/scroll-area.tsx | 38 ++++ .../side-navigation/free-trial-card.tsx | 2 +- .../getting-started-menu-item.tsx | 55 ++++++ .../side-navigation/navigation-link.tsx | 56 ++++++ .../side-navigation/side-navigation.tsx | 166 +++++++----------- .../src/components/welcome/icons.tsx | 49 ++++++ .../welcome/progress-section.animations.ts | 77 ++++++++ .../components/welcome/progress-section.tsx | 148 ++++++++++++++++ .../src/components/welcome/resources-list.tsx | 107 +++++++++++ .../src/components/workflow-list.tsx | 43 ++--- apps/dashboard/src/hooks/use-integrations.ts | 19 ++ .../src/hooks/use-onboarding-steps.ts | 115 ++++++++++++ apps/dashboard/src/hooks/use-workflows.ts | 32 ++++ apps/dashboard/src/main.tsx | 5 + apps/dashboard/src/pages/index.ts | 1 + .../src/pages/usecase-select-page.tsx | 4 +- apps/dashboard/src/pages/welcome-page.tsx | 93 ++++++++++ apps/dashboard/src/utils/query-keys.ts | 1 + apps/dashboard/src/utils/routes.ts | 1 + apps/dashboard/src/utils/telemetry.ts | 3 + .../shared/src/entities/integration/index.ts | 1 + .../integration/integration.interface.ts | 38 ++++ packages/shared/src/types/feature-flags.ts | 1 + 29 files changed, 930 insertions(+), 138 deletions(-) create mode 100644 apps/dashboard/public/images/welcome/calendar_schedule.png create mode 100644 apps/dashboard/public/images/welcome/compliance.png create mode 100644 apps/dashboard/public/images/welcome/view_code.png create mode 100644 apps/dashboard/src/api/integrations.ts create mode 100644 apps/dashboard/src/components/primitives/scroll-area.tsx create mode 100644 apps/dashboard/src/components/side-navigation/getting-started-menu-item.tsx create mode 100644 apps/dashboard/src/components/side-navigation/navigation-link.tsx create mode 100644 apps/dashboard/src/components/welcome/icons.tsx create mode 100644 apps/dashboard/src/components/welcome/progress-section.animations.ts create mode 100644 apps/dashboard/src/components/welcome/progress-section.tsx create mode 100644 apps/dashboard/src/components/welcome/resources-list.tsx create mode 100644 apps/dashboard/src/hooks/use-integrations.ts create mode 100644 apps/dashboard/src/hooks/use-onboarding-steps.ts create mode 100644 apps/dashboard/src/hooks/use-workflows.ts create mode 100644 apps/dashboard/src/pages/welcome-page.tsx create mode 100644 packages/shared/src/entities/integration/integration.interface.ts diff --git a/.cspell.json b/.cspell.json index 93c9b7b81ff..5dbaf060f75 100644 --- a/.cspell.json +++ b/.cspell.json @@ -708,7 +708,8 @@ "rstrip", "truncatewords", "xmlschema", - "jsonify" + "jsonify", + "touchpoint" ], "flagWords": [], "patterns": [ diff --git a/.source b/.source index e37e9d3f03e..61941c5bd9f 160000 --- a/.source +++ b/.source @@ -1 +1 @@ -Subproject commit e37e9d3f03e2574565e00f8ed52c4ea11bfd37aa +Subproject commit 61941c5bd9f09f22a68620f2a51e36bcdc0f3abe diff --git a/apps/dashboard/public/images/welcome/calendar_schedule.png b/apps/dashboard/public/images/welcome/calendar_schedule.png new file mode 100644 index 0000000000000000000000000000000000000000..7cdc9ef53d35d7669ec89a9a1f43382be596c2a1 GIT binary patch literal 4149 zcmV-55X$d~P)1ik(L}b}shJNPIK+gQO%% z)C?(!A^`TUN;|V7k`f6tx&ibgb|aX^>Si+*?xu>7(#r7TO;?|qcXrEu@=-lJ<%6uG?Nf|4YmXuT%a^OZ24 zb}{N~1>L?_62pK(4*Vx8U)kr9G^R93$@e{)&zDN`ixJfMC?+&+Rw`ckn#r4=OVf5m zQ79kAJm~!p3Nn5Q&s?2C2`@Be@BSvw31xrvq|&*)eL-W-q0h4+E&v|ja(N?7ABU1S zCWg5N%5dweG37~N7|J>JLcRjhVF7G(0j|MA!8{_*Gh&#*IbptU18(rzCZ4=I()@;+9%I-nY%=QD+=J|vEFnQ;;KR%mQqT!34vRox-2{bh8Z38m$w#MX_3k>ZgvB`}nTfn$t}y zJ&!1LaF12!EX(D9)lI2f6YYjj#}R`GR}>`_M)9CpEyp&4oX020d5duvXmk}`fM*!X zfpE1l(YvisBHU--f$xHj1C`k}5LKR79p!N-%|66urI{J4@uqywCW6YvPxDtljTd0G z8BYw;4_h`aj2B=9OEA+DrGrh6WFT7vm|=Rt8dw`Noy)LQpfUDLyDSjk?yHHj+4XMM zot?Mk;JW&4S+p^mnejdMVGXPaedaO2BA~h4qaAYYqg+CRx!NN=hnt%vxwwX=ssi_k zP`c$(FW15@^oT&!9^*4|^Z|_q#{&~Qu{^1P?{06{6Kep3Me`dAQtD7|OzC*gayG1# zGQ(lT9GGMJnSS&SX3!NYOz1vYIpQQ$u8u=D9?Ue)ch!9O6!86`C{}*kw&AGI(Yaix z8>{=3I~S%0V*o1zxa*t-Ik@icS63T(x zjx9j!N~s0Bg2nX3o<3uVD$CT*j4qj(2xA zWnQeISEOYR3Fh;Yql&P1?71#Qs8(Uw93Ku8^T0pZIyoPE9;hXb9Nj_q+s#tH^q_4Z zqUqeICZ?UfSt)g8QuTpN0{4Y_#$hy`D(QX=QEHz_v~e^q_pUyp5y<9Q4kd z2zz4b0Bb6yFXe!5L(iF1QagqzDXdt)0_JPr(kq>8L#J#J*YsYy8XVBC31ah)RV>de zPkJfT7LtLfW1oXvDK+7g0^cSO%zg4bx5^?yqaYli(%agQJIYq9@zaAKBj2-4olW@+ zkLd7kBN@t}?l}J#?@Ia0{o}I-A@LX-^T$a*)*U3Ro6l|X7-B9>6h$f$O6Q#0u)2_q zv`YbkP7;}AIjzHJl$=ssab%-}VOWl^4ATMzc=Qq1^U6PHxcG_4Iz-ELtsc@@rQ(8x zhKbhdQPlT+HM*jaScsW@&@a+7RefVNt7eWGYG5eKK3;ge=D@n@UkI_|%*b^;HNpZ6 zI0zl&1(@klyV=%)`}p`sp1&<_r(=<`zAm)ZEMg%AjUNQGUOkeKniz9oG#UiB6mS;g zsZa}UX*HGtv0krL_@L))1=xno3u^=zTOLy1QV=XD3fHRfb{DkRtez(52dmXe_Kz9O z2>iL~6Y5n9 zu*SmMHyddu0mcmnqb4DHp4|)!<-8YjAR)Cc7PsIG!NNNwL{)O9>-S@u_J#+8H{s$C?@7K?STFz-4v-yDS zd$jjFHs`l|9}?U45-s<*X@{2IOW~~r*{ovewHs}hY^$zo;oNZBP|b}3v3dX4dCdbw z)f>dY1scCL&M)Zr>Sp6}pZLA4Q@4DDgLB1^-<{pOVb9$A?lag}HB<>~_5LOgS<5+% zYwdo=qQhtIy|-z_o%U#Kvxw$rG}^abX*NV?_faN)hvTfxS-RVBVxuD4sXL?CQ;mfZ z-hT7`?G9rt%*kS09Fbxyl>(-Qlohp201f!F@xz(y}pp92}Mz?y!3qE&SG(HbmgSo zGx;##XfYI7idf2smW$<}pTp=&EvWE&#tTtu;d(`Z>=Q!@G2(c;7TUWMZr8ZOOCbjI z=EBH}YJM!d%r5YWhup$P9zr}6S>7)c@DjMz)Vz_;4WWc(NlzADrU^`F#M(wxq}mV! zBcwmFCdMw!QngHl;01uUF7=Zb~_b|T5-CcRtq{)*>L+&j`ef8Dfl(WLCSHGd(y?IM--n^j?K6puAfAux} z_lF-g$JKM>Oyg=Brg2R-%6cJ=1=v^$rG5AFl*LIxKmGV4y?y(J{`2pD(Jw#$Os`-6 zmR74rm8o+VoXhEUFUSasV{AejN?F&u*h@xG^6OWx=hxGa9pC4%4Z@>MPKKtx1oBx+p@bcb?#F^t<^I6te zMp%FiM!8JN7gnQd53ElT?`77pxXHOS*k=tA58GFwUtxvzW^OXBzmRcTLKE z5~AD(_FNWV_Z2G_J#TFyY4pmIdc)<7=!)U zA=d_XcOF=PO-4!|95UdH0S_Q-3FVbJ-7x1+YO{JN51|5CD*Vc=l7;uulzF)6DzE?> zil`IMS0)vHLWs+nxYA5MNWWbA=tA>CYrDz>W8pO&ceM%+{#Zw@a8Yc%Q9=yocG*mx za8>olL~7wQk(wBbRMl~gDBINYOTpb#YGCvE;-vDqr%9q#_=jdD+tN+jJaMw}i`*;& zPRD8lrOAe1eGIyo+ZMYO*s33*3Nx&$7_jhO7OX9-*WuN|6;GbL+f1!Vi(PF?+p?l{(Gn8PwFTq zr4vTcJH!7E!GuJ0WxVF&3+ zy=f;&zZ8=Wn4g-}AvG|3Cy*EG8iO`!fVqJ!#myK-1IhrC>t*_68<}AYh6R{m42A`G zV5aR$&bt^x!vZ`oo;TgCc9$1I2ZtRw1_l`#5L~aKzNWG&i#ohs9dpbK2@CKQKy0;7 zcGRO~^ny)jhA|k{z$bzHQZyCU=D|_PK{!VFBv^oZqFe+~LNZM`Uiw17{Sb?NbDyUf zePI*2Cuy3g0%9z@mjl^fq9|5BnSN*|z-UCB&8alwPBs)sNuw)HRTNz}G!x*0;yBVg zj+8a;U}*ekQOX1r7j#1-Nhy?hn##H5jQ0;2&G0;5)mP3nvl28B;6e#*mZi#kMmQtG z=mb*sA~Y2JR3{-Gbrrg-7Lb=IY}GT*C}HE&IY7t3 z9cN-*RKmM`g<(jZd_cPAQBu_ai5UV^me3gl&}V6)gxK@EO8JOwrHc^+*@f9UCbj0p z90LzUci~ght|~4kM!5%fyTnezJAS86L2&%L)%ozM14D$~%aazvz00000NkvXXu0mjf*`DY< literal 0 HcmV?d00001 diff --git a/apps/dashboard/public/images/welcome/compliance.png b/apps/dashboard/public/images/welcome/compliance.png new file mode 100644 index 0000000000000000000000000000000000000000..9062c7872ea6bd8c6d575ca129e63481a930291b GIT binary patch literal 6554 zcmV;L8D-{)P)V)K(?8I4JjvDhIA82$gutge_SI-s;Nt6>kG{+x}&d~a1Suy z&2BT?W7XfIabklPudTZ$tkqrrTx{c>xai=v`#nwL*2)h3ecg6{J+8q!&;Zc%`|z$^ z=dWq{cLBz068FIQTu8@0`P}dx>5R{b-YzVGl}yMABeo&gnTclz$*@AqxSHaxy-x(&m3 zKd%j^ZN}}f|4+uyeE*+}p(X7;Zu9s1aTgmy;d4PBXlHkgF%cSS96{r~KxgbbV7uM8 zYt{)m#53Jk*0e%nfJ|l>5cBrcHQLL|OP%!!%MU@&E6?|GEhi6Q?EttM*zuh#{VMU(LslAKVY9 z#CtiQZ9jhe=-PVI6EanVIfBG-5HGH+#CutwKR$i>BMuhywjr=7`rNTK#9pV1rsn8>(#2Q1LFn&5ks6l96a&nIIFi;=!tUA2EW$6 zKN_$GsVCQ_IED$f>7`OFs8Sg$ny~79>2<1#+W?{(^sQ#FR;F<1b>L=I3HV%Kg8c8l z|J-+HWggm?b_e`nAM6S-CI+Hk8$GBJFf`pI`d7u}Kn^f#b;z9HxXYn?YELbUIA z`wfT_ljGN~UndrO8RP5hKmF>!&!0cdw{PDfckWbUf>jB)aqD0ew}#{ladcT@%5Hyn zdN)hI-rKLs22x7fPSryYG?t2>zCik+6HSUhZKEsosIp4!HLi-c!5OFqCzlvNn_cc$ z^^ta%YX^$VzyJQb=wO(!Of^Ix; zjWI0-Ay4 zYt;|FojE*p<~!S17w#8=9Cx$XocQ^Jh=2Y1<%m4h_D(WVlz!9**vb7>2QCK95S0`k zNQ`AoW7SseDg~_hx-h}lS4Yr{@dZGP@uTO3N+vn|p-UebYqTxV%{1NW3mJPt(-o z3yP55iB3mGppUY&^RzlJMH)9N4x$jjv^3;U3=yN=a}US`E5HisEUfLyY*ekR0UxK0h_OLA6|1B1xPt?6|`3pR-ulSL_Psr=_ z<&2%W0sT!GYoshILY-G~-ki9+1mw6B!#OkK58Rh2M{f;hJjooFBHm`Zaa=0QDpB!1 zK#D<%WfUso8vA;!t7fV8Rw)7$#aw{I3$96BSOe%BMxeOLaf5CRl;RMvL>|JsBYo=U zn#=nlz?c9LZ#J*y641>`S-`-hUJB6Agni&RG6^@W#{k_4@#zYM^?$Tp*bBY`j zp8y(_`@mUg+@fWf*4U7M!?k0%%v8QtTIcFEFA0OIct4!I#AO229T&c13e zX=IvM0{eZgTAY{-nVg&Jb<}ZPk?dh9iTk98jBCjCxxWOz;yHT+j=M4+1v;TTA}~H) zhQjNbP$S?H^j~RIB3B6qsjH4Yh{uu`V*&0=pTcD#a$;Jc2|lkR0%i%16dI9~t>QZH zT)5DjSl$8Pwqm0!YAx3}CHKXCTA~?kgyT$BMxvHno09+?HC{y>UVu8V0E58@)kg{9}ZPpVD{F0$M5hyLPa?l7mQMotin!x z8U>wkZ=6e_hQ18zG!R<4OIQQWB^dN;)C=%RO|ekZE#oo-Bj$ z#~*(<0tIpZ^Upu-9`Fl>Wm;Z~Z4mAE@82iS<#K7*d0qc~ z3_M&fLhOZ@D}A3O0fPW#mP!1fKr;y*92TNA&ci+yaRw+tGcOkJO@gP$T>=MoIk8e4 zwyVtdQJ@>pzrVd<1iEOL!-{^-ATX}=(wPJM$cgDnNy4TNH8~lGn~wjqa#Lg{u)hTN zMCiCFkqBZO915xqd;^ra4(yVs6|jy(0t}-*DAtChFmQg1;9?FLAy=hGR_yt=#%!h$ zQ}GB;6QSpFFkKU|3IiZC=*C(;%Bg#mk{8S8x=J5w+WSaQ|7g%Ak%zQoFbLriM9h0g zy_yJJE^i6voBp7R?xX&$w?MbJSwXIO6qiGAd5P<}OF`olm!?9LB07&aq;+7Q154Mb z{#sy$&{DFV+!_y^Ru;M~2`>bL^J99k1h1!|D+We0mE=mv5~5Kew)dk0%Tz@Gq#vvX zP|8Z3S1Pn=L^-j0c@2n8X2uN(nc$rS9K*6d@DqA=PQo^x|j5F{YFy%NO|wDKz{7dI;#)K%%b%g?+ z>n8`X_0Aln^%wdCK_qaPkr`29s|j+Sl%fD;7llY&@+7MPT>`!n|5_=ANkWb}WiUZN zm>92g%q18sRR_-1Uq=7Hy%k0*aNTd;zD4_lsgf8iRuamNMk-#bIz`R`ec#9gGvo@9 z-4K{4|NQgMsxn#$9ZdqYN-U}-ZF<|lx2ycn+ z^Bh=+uFNH*Let9TmMto-kK8&hx~U`!G!gnfGXv-n$V{OrAL>VhYECRnF4d5Ad$96g zYUsOxUHI6hJ`4wrFvBRFItU!cKBfjP8Aa>B?Rs_AtXZKC&xv-~q;=??wk}gXfmmgN28MUy{L;B@;iXj?v)ygRI=vOwhs*WV9cHppn&JU{I2KI=f|eOM2oUf+ zto>^9HeDkY#Fu__u_-Q}X+-7|)g1NsppzFM`T-zrdv z_&UI($>e)$tbKNK)d}x|6F(Zt6``G@`Px}OH@jDiD7Ty9^jxv6iu1vQd`w6H?%VWz zfMES>bwcB%qtcVwJtB(DO6i7+i^25z$hIF$Jr%p}jN>dfM)Ur_3?eibI^DPLh4nzZ zIRd7aWi>V4KU76%`k%U@VLjpQhnP$dF^hrKWhOn4fPJ=2J5*$}jyU1#oU$~#Vqs9S+%rn;`rEZ}b4SbogR0LpBg4G$t*O{aM)@d*W?BiI2?=;qp88eIm?IjX% zu_81O>RxU@A$S~CQ8Ns>-Cn&)`7^=t{}{NvzKiy?F6rJhGh=R|995aFJTA<@e&o8s zJJM+be{{2i!v>9`U%!5JgiOf~+uy%`FE_2MGE@mzreO6z6}+BYA=bgQZxD4J8*hm6 zIXlUm7n2R`3L=5sQtQ7D=r50eWnO3yDz@eNE)Aj-nZb3LqZ^zX#9U&fj7vc|9(eD# zFyI@obN{&pcYPxAFPrI@T{?z&o;*MNE)kwI!yw1%Wa@76?;MM+5Jo_9;yV4igz!~wCn|EOZUlP*qsjk zljcq^G8;Kx#P9XAG;c=7LF9eC{z09-(OZb_(e|V2$+sqVEGkyZ3kt#bjLBJU+ zG~gn4dLo9gr{X$5AAj#f{7;`gt4%H!(ScKt?>zp1z-Kr9vurv$%*n>74{+*PC&!c5 z2~H}`jnzzw-EVF?weRE{ny_Zszs(Y_FY1OB0&^E&`I~T(lZEP@)%RXk2kvL|X2?wG z-1_=izGmsTJi5-y9qaYWaotq(Mf@UjH=rXB)5wMr576An5@pfrz;GydS-qIecH`C< zzkK;z*L|-976xltUxjhsFV{8egbv*QgT3Nj=2qynM5<=ey^OP>*Ma@O*!Anb)+H3H zxE>g0hou)FCuVzH+T0E1)Qe}aILDwmZNVzI+ioiH-ViWKAyg27B{`HH&|k3(sss!% zuCz3s&j#mO*fiO7-V#&FR=f?W)9|+Ez8{6YQgJ>&cv#|Ld@n%%Ui`_m&lI2!?snUL zGEMY_^^`thw(w%f9dswoAit(l(y7oSYX0u6Z0>5`WD11lt0(fBg6{OTbyH z>=40IVrtn(N3mHcnaVVN%sf5u0^Y?j-43!M0i$lfd1+Y&miqa6VW)E1M~A8r>yQ{Q zHGJ@hTw8?cQ>pucJBMjhXP=WXV$Dq?6_f0jo+6x&)jU4;V)c65Ktz-et|0u_Oi%Ch|dnfElztUfQ=G4OvS#@LYVc z%qd+1PObx1PjnKH5)msWwM)MgDmEOt?zuhqhKVeB*!mz4>b;D-Vd+eKE_GG4Gtco$ zRz4NYhw43daWL_|cBK;K;$}8G^Si-YrRgj{SRg2LHaIS0V3)d463&Qa(#_5IS(!_! z;xga_yxZ-~%f}C@B%rJVSG`IDAX4Ul0?r{^_JOb0oX@=j!iE1jMe|C;`jEcuc1W|F zn^@0(iw6hlora(jH1ULgCPfVN~NO|-A8RT0M*l|JA7e;jPPtuKEQ zI*nAk9hgv|_cky{4$RD%ffW5E^r{mM1}tQPaO;*452A|8fw~s-tY)>u(s`V>S&R;Y zjS1x1alq9H_#Ds%u^Utrl)pjQn@hFjisBM(PWIo7MY6gvrDs@OkmhiC4fHBsr>AvQ z8aNm#E)U0nZR6`x_5Yj*H{`*N^)Ga>krNF2V8im4^#iaM!#h+xJg@@b(G#re7!@^S z#2mfHj@$B@!^u73*Kj+2P5IAdIIX7_&%B!U*!FC9Uq?GlM@wiCaH|txcv>unvCMZI z3ul|;PZqN`Iv?pxIgsqIjD-C@g|nRuKs&Q7pEZUro^j3OJ&q@xeGhZyb5HIOxh8y% zlXs20yL7uWhOU>Qf2?;Gi1lyRiFJF+)U550Y^U|_+#O@5rm0-rMSj2gmUm4E<$+GL zq4+gM+Gg96J*Q_4EfSn_JfjI5VUBNWvd@)!=RU)`uu8_ME38>gN15%gFI6iuxF0g; z!i-0C{|;uHtwK5&Unbb-h87tcs}uW8K-Uv1*tS9jb_)K< zxSX($lbc0zY<1=|98M6}9MPwbOIdt+Ph-P^WQmDpPJ}rAfbp1({BDyWD!cf1nk-^2^8f$< M07*qoM6N<$g7VXM!T~bROBRbqNjwruS(dU|EoJfe zC~?G$=)1ugzXc=W?RINK^5m6x6?LEI#*Hq_@xk{40pGy!bTAq&@9r`Wk!G2hj~@iS z9|-sccDubyCR6d5viSku2?TrtM`PXM*zcxNSNdcY+2DJDfUlsgvn~`xo^PMb%&K^v z=K|jg1pGzFvWZ4i#|Ugr`LI~XcC)q%T3MDFuOW=_S-qRcOi|bddpijD z3!(Gi2eVIoJRYniR*M`0I!@{|Mb{a0v5=sY+o7edpmX5R!UvNimH9lpQ+qrf&3^mF zP6wt_zgPWz0iR_Ib?s~zmcS>V`|KL&rXixcJ#+b6K)_>^uO7f}MyCOfkB>I8OxI%d zy|$~rS>|Wh4*-R@0JPt1O6{0s--%Xxd7tr^hk9(4&iEQ)Y z>g%F64xZl?EbtY|EAwC7bN&0*O7@3imEezfuVb2YxmrsSC-URRi?OzDJE8)vqTp!M zArSBt#w@VTb$|Z7GHzaGI$>gO@HYPU|3BpA`B4@RbLZb9M#NEYp~onPEpR=bn;P-b z#oo^(%jDT==duC&{ZUx0 z`WSSMt5I0+b`bCwXBQfoegdpGzY7+z#?Vm8vXXDETOVegR#m<*KTikIt=@<^+J_K{ zR{UHxUQM-6Oxs=q0gs@885&HWeQGwHnAPUVd7l@X{JW^Pjx?LSojk&Mi)aQp>;R<@|87p9c5YTlB1YCiDThJ-Ofb(<#0V`$a4m<||UqHap zN*X`M;ex+#e!3uFkxPOCb1Uzu_kc+#sX)LjGWEaGiIz&)_Qe!~FU1p&9P zSGlKycnxJ{yCC2eQH+3n$HuD@Y|}tXw9t-$fc?f=UHg0k=>Am$=YgQ%!MDZw&%IV}m(_Kn~ww6}k%oK2rqi#8&m!CUI03s~O^2>8sU zQy^ZiLA)Iha33tNw^SCG^V0?a_aLFcU-Z0aygDxQ1q9qfL+?$NMz_jYxKYZ z`%4uE&Be{yAmAQxoFHINsaQVaIke!XJ;g&^}V0Ein7Q7Z7lpsE){@z`KvsXM}n11qAGXQQkl5C@kFP zE(o|qJD~ymqE6&z0jmoFZqW!A3+x@74>t(dfo>(S7wi|*gLXrUxFDR;JE(=_1_3*u zz`K8FaE%spXAay20r${RKDK+o;YVYgPNnP*$1gD$>Hm^7D(}{$L6>vg#?SlpGQO~tPUG-e8bNTD%@|6dnzyE2TUu|*; z5v~jDh9Rv-$OYcjU0$x%GM`PXrQPjzl{aBr=}DH#et(dqvCLERILpF1^(-ENV^dyO8I?3#_L%d0=lpxI+uNx@Xc0~A72Yost8_a_x0Q;b686Js&Y_OV*?k!Ygp02hi$=p+zvMYyABDU{{;h+q}70d40{rnGx^H(;_$*nqqdtV1Wk- zPr%yAQ!@{Kd0rT|`OpY8O%wU=k7vouy*?M37^*mE1ndF<4-f?!4_z2MBlo zE^_7%Dqt;h#Gv{b1Ux{s4=msbh}R7Q9)JSx{y+xOFbLQ|yR#tR`%-c}q)`y?DD;|p z(+HSx)CmF}p~Qv0J9UaAxIw@J)DcarMBkTc{wrvB00cZj#Kpevj;$UNQ4e|u1U!N! z$me;E8+}KLqOdi-zzr=kLXsxd0*6>b>MNjCH8nModeFll;1M+XCq7$YP0xeRL)%;1 zJx*dSYBmBbbA`!dYApdG{yemu)pagRyKDOs#4!xM$pCey(a~Q!k#;D3tes19ZFPPO zA4@%5(UpH4FsIX5V|@{^8w5N;y_J+2!Jf*KY`0s}sisYd7Zq_WWIEXU&;>#juC}Q@ z)_ZN|G@ID#IpIE!6BWBXOROW*^_I0`sdd;DxwXKPNthQ2^BHYlb(_`lY8%((e;6Ex zLBKaq2XpAX-gdh}4yLo!78on>ezZN+0_#mZoy=I?x=j)9brA3k*nQyg5?X29y;Eu3 zYOTW@;&qgKp`MD^eBU^-w>tM1@m_~H@C{U#TDjCFwpT!iONq-^%GP35{hG^b%T5e_ z)cou}dx`h0-djE2{+!qE6X$2t-`G74aePMqd2xLH(05**KVQtH_x}E|i*#clyYbDn zSldPT;5HEOjr3Ct+Z~Qbt@qOxE0*_dAmBGrcd)i-JK~ntR}rw!l~H4K+OKiF{&(?j zzRUT4y))5%>^Knb+d#nYr0HnAY&TaY*EwAy-un0c>(8Ilh@$_ybT9H%&&h|5#q~b@ z-uCCW-|u?9{n_?79bUNe*jnG;KaTo45bDRkA{Icvu>S!NFbEhRU=T1sz#w3NfI+|j i0fT@60tNvC1k68|x`6X_HjXs_0000('/integrations'); + + return data; +} diff --git a/apps/dashboard/src/components/primitives/scroll-area.tsx b/apps/dashboard/src/components/primitives/scroll-area.tsx new file mode 100644 index 00000000000..f30c02dadd4 --- /dev/null +++ b/apps/dashboard/src/components/primitives/scroll-area.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'; + +import { cn } from '@/utils/ui'; + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + + +)); +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = 'vertical', ...props }, ref) => ( + + + +)); +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; + +export { ScrollArea, ScrollBar }; diff --git a/apps/dashboard/src/components/side-navigation/free-trial-card.tsx b/apps/dashboard/src/components/side-navigation/free-trial-card.tsx index 1ca715fed79..3f904360c61 100644 --- a/apps/dashboard/src/components/side-navigation/free-trial-card.tsx +++ b/apps/dashboard/src/components/side-navigation/free-trial-card.tsx @@ -25,7 +25,7 @@ export const FreeTrialCard = () => { return (
+ + + Getting started + + + + + + + {completedSteps}/{totalSteps} + + + + + ); +} diff --git a/apps/dashboard/src/components/side-navigation/navigation-link.tsx b/apps/dashboard/src/components/side-navigation/navigation-link.tsx new file mode 100644 index 00000000000..5646e5b44cc --- /dev/null +++ b/apps/dashboard/src/components/side-navigation/navigation-link.tsx @@ -0,0 +1,56 @@ +import { Link as RouterLink, useLocation } from 'react-router-dom'; +import { cva } from 'class-variance-authority'; +import { cn } from '@/utils/ui'; + +const linkVariants = cva( + `flex items-center gap-2 text-sm py-1.5 px-2 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer`, + { + variants: { + variant: { + default: 'text-foreground-600/95 transition ease-out duration-300 hover:bg-accent', + selected: 'text-foreground-950 bg-neutral-alpha-100 transition ease-out duration-300 hover:bg-accent', + disabled: 'text-foreground-300 cursor-help', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +); + +interface NavLinkProps { + to?: string; + isExternal?: boolean; + className?: string; + children: React.ReactNode; +} + +export function NavigationLink({ to, isExternal, className, children }: NavLinkProps) { + const { pathname } = useLocation(); + const isSelected = pathname === to; + const variant = isSelected ? 'selected' : 'default'; + const classNames = cn(linkVariants({ variant, className })); + + if (!to) { + return {children}; + } + + if (isExternal) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +} diff --git a/apps/dashboard/src/components/side-navigation/side-navigation.tsx b/apps/dashboard/src/components/side-navigation/side-navigation.tsx index 37ce6903e1c..2a039f99622 100644 --- a/apps/dashboard/src/components/side-navigation/side-navigation.tsx +++ b/apps/dashboard/src/components/side-navigation/side-navigation.tsx @@ -1,6 +1,4 @@ -import React, { ReactNode, useMemo } from 'react'; -import { Link as RouterLink, useLocation } from 'react-router-dom'; -import { cva } from 'class-variance-authority'; +import { ReactNode, useMemo } from 'react'; import { RiBarChartBoxLine, RiGroup2Line, @@ -10,68 +8,17 @@ import { RiStore3Line, RiUserAddLine, } from 'react-icons/ri'; -import { cn } from '@/utils/ui'; -import { EnvironmentDropdown } from './environment-dropdown'; import { useEnvironment } from '@/context/environment/hooks'; -import { OrganizationDropdown } from './organization-dropdown'; -import { FreeTrialCard } from './free-trial-card'; import { buildRoute, LEGACY_ROUTES, ROUTES } from '@/utils/routes'; -import { SubscribersStayTunedModal } from './subscribers-stay-tuned-modal'; import { TelemetryEvent } from '@/utils/telemetry'; import { useTelemetry } from '@/hooks/use-telemetry'; +import { EnvironmentDropdown } from './environment-dropdown'; +import { OrganizationDropdown } from './organization-dropdown'; +import { FreeTrialCard } from './free-trial-card'; +import { SubscribersStayTunedModal } from './subscribers-stay-tuned-modal'; import { SidebarContent } from '@/components/side-navigation/sidebar'; - -const linkVariants = cva( - `flex items-center gap-2 text-sm py-1.5 px-2 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer`, - { - variants: { - variant: { - default: 'text-foreground-600/95 transition ease-out duration-300 hover:bg-accent', - selected: 'text-foreground-950 bg-neutral-alpha-100 transition ease-out duration-300 hover:bg-accent', - disabled: 'text-foreground-300 cursor-help', - }, - }, - defaultVariants: { - variant: 'default', - }, - } -); - -type NavLinkProps = { - to?: string; - isExternal?: boolean; - className?: string; - children: React.ReactNode; -}; - -const NavigationLink = ({ to, isExternal, className, children }: NavLinkProps) => { - const { pathname } = useLocation(); - const isSelected = pathname === to; - const variant = isSelected ? 'selected' : 'default'; - - const classNames = cn(linkVariants({ variant, className })); - if (!to) { - return {children}; - } - - if (isExternal) { - return ( - - {children} - - ); - } - return ( - - {children} - - ); -}; +import { NavigationLink } from './navigation-link'; +import { GettingStartedMenuItem } from './getting-started-menu-item'; const NavigationGroup = ({ children, label }: { children: ReactNode; label?: string }) => { return ( @@ -92,54 +39,61 @@ export const SideNavigation = () => { }; return ( -
- {isFormValid && ( -
- -
- )} + + {isFormValid && ( + + + + )} +
diff --git a/apps/dashboard/src/components/icons/onboarding-arrow-left.tsx b/apps/dashboard/src/components/icons/onboarding-arrow-left.tsx index 976674e98b0..980a8356e7a 100644 --- a/apps/dashboard/src/components/icons/onboarding-arrow-left.tsx +++ b/apps/dashboard/src/components/icons/onboarding-arrow-left.tsx @@ -5,8 +5,8 @@ export function OnboardingArrowLeft(props: React.SVGProps) { id="Vector 5" d="M65 25C50.2472 14.5237 39.9203 8.99515 21.7996 12.3333C17.4016 13.1435 10.1759 13.9168 6.64552 17C4.64024 18.7513 1.53255 18.9147 5.43925 20.4074C8.65651 21.6367 13.3217 23.6667 16.8237 23.6667C18.0884 23.6667 5.70543 20.4243 1.89576 19.5926C-2.97483 18.5292 13.4803 3.61815 16.1451 0.999999" stroke="#1FC16B" - stroke-width="1.3" - stroke-linecap="round" + strokeWidth="1.3" + strokeLinecap="round" /> ); diff --git a/apps/dashboard/src/components/onboarding/animated-page.tsx b/apps/dashboard/src/components/onboarding/animated-page.tsx new file mode 100644 index 00000000000..ba871a0917d --- /dev/null +++ b/apps/dashboard/src/components/onboarding/animated-page.tsx @@ -0,0 +1,22 @@ +import { motion } from 'motion/react'; +import { ReactNode } from 'react'; +import { cn } from '../../utils/ui'; + +interface AnimatedPageProps { + children: ReactNode; + className?: string; +} + +export function AnimatedPage({ children, className }: AnimatedPageProps) { + return ( + + {children} + + ); +} diff --git a/apps/dashboard/src/components/primitives/code-block.tsx b/apps/dashboard/src/components/primitives/code-block.tsx new file mode 100644 index 00000000000..f87760ca371 --- /dev/null +++ b/apps/dashboard/src/components/primitives/code-block.tsx @@ -0,0 +1,146 @@ +import { useState } from 'react'; +import CodeMirror from '@uiw/react-codemirror'; +import { materialDark } from '@uiw/codemirror-theme-material'; +import { Check, Eye, EyeOff } from 'lucide-react'; +import { RiFileCopyLine } from 'react-icons/ri'; +import { cn } from '../../utils/ui'; +import { langs, loadLanguage } from '@uiw/codemirror-extensions-langs'; + +loadLanguage('tsx'); +loadLanguage('json'); +loadLanguage('shell'); +loadLanguage('typescript'); + +const languageMap = { + typescript: langs.typescript, + tsx: langs.tsx, + json: langs.json, + shell: langs.shell, +} as const; + +export type Language = keyof typeof languageMap; + +interface CodeBlockProps { + code: string; + language?: Language; + title?: string; + className?: string; + secretMask?: { + line: number; + maskStart?: number; + maskEnd?: number; + }[]; +} + +/** + * A code block component that supports syntax highlighting and secret masking. + * + * @example + * // Example 1: Basic usage with syntax highlighting + * + * + * @example + * // Example 2: Mask entire lines + * + * + * @example + * // Example 3: Mask specific parts of lines + * + */ +export function CodeBlock({ code, language = 'typescript', title, className, secretMask = [] }: CodeBlockProps) { + const [isCopied, setIsCopied] = useState(false); + const [showSecrets, setShowSecrets] = useState(false); + + const copyToClipboard = async () => { + await navigator.clipboard.writeText(code); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + }; + + const hasSecrets = secretMask.length > 0; + + const getMaskedCode = () => { + if (!hasSecrets || showSecrets) return code; + + const lines = code.split('\n'); + + secretMask.forEach(({ line, maskStart, maskEnd }) => { + if (line > lines.length) return; + + const lineIndex = line - 1; + const lineContent = lines[lineIndex]; + + if (maskStart !== undefined && maskEnd !== undefined) { + // Mask only part of the line + lines[lineIndex] = + lineContent.substring(0, maskStart) + '•'.repeat(maskEnd - maskStart) + lineContent.substring(maskEnd); + } else { + // Mask the entire line + lines[lineIndex] = '•'.repeat(lineContent.length); + } + }); + + return lines.join('\n'); + }; + + return ( +
+
+ {title && {title}} +
+ {hasSecrets && ( + + )} + +
+
+ +
+ ); +} diff --git a/apps/dashboard/src/components/primitives/color-picker.tsx b/apps/dashboard/src/components/primitives/color-picker.tsx new file mode 100644 index 00000000000..4f851b8778a --- /dev/null +++ b/apps/dashboard/src/components/primitives/color-picker.tsx @@ -0,0 +1,34 @@ +import { HexColorPicker } from 'react-colorful'; +import { Popover, PopoverContent, PopoverTrigger } from './popover'; +import { Input } from './input'; +import { cn } from '../../utils/ui'; + +interface ColorPickerProps { + value: string; + onChange: (color: string) => void; + className?: string; +} + +export function ColorPicker({ value, onChange, className }: ColorPickerProps) { + return ( +
+ onChange(e.target.value)} + /> + + +
+ + + + + +
+ ); +} diff --git a/apps/dashboard/src/components/primitives/inline-toast.tsx b/apps/dashboard/src/components/primitives/inline-toast.tsx new file mode 100644 index 00000000000..c7dcf04d16f --- /dev/null +++ b/apps/dashboard/src/components/primitives/inline-toast.tsx @@ -0,0 +1,85 @@ +import { cn } from '@/utils/ui'; +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; +import { Button } from './button'; +import { Loader2 } from 'lucide-react'; + +const inlineToastVariants = cva('flex items-center justify-between gap-3 rounded-lg border px-2 py-1.5', { + variants: { + variant: { + tip: 'border-neutral-100 bg-neutral-50', + warning: 'border-warning/20 bg-warning/10', + success: 'border-success/20 bg-success/10', + error: 'border-destructive/20 bg-destructive/10', + info: 'border-information/20 bg-information/10', + }, + }, + defaultVariants: { + variant: 'tip', + }, +}); + +const VARIANT_COLORS = { + tip: 'bg-[#717784]', + warning: 'bg-warning', + success: 'bg-success', + error: 'bg-destructive', + info: 'bg-information', +} as const; + +const BUTTON_COLORS = { + tip: 'text-[#DD2450]', + warning: 'text-warning', + success: 'text-success', + error: 'text-destructive', + info: 'text-information', +} as const; + +export interface InlineToastProps + extends React.HTMLAttributes, + VariantProps { + title?: string; + description?: string | React.ReactNode; + ctaLabel?: string; + onCtaClick?: () => void; + isCtaLoading?: boolean; +} + +export function InlineToast({ + className, + variant = 'tip', + title, + description, + ctaLabel, + onCtaClick, + isCtaLoading, + ...props +}: InlineToastProps) { + const barColorClass = VARIANT_COLORS[variant || 'tip']; + const buttonColorClass = BUTTON_COLORS[variant || 'tip']; + + return ( +
+
+
+
+ {title && {title}} + {title && description && ' '} + {description} +
+
+ {ctaLabel && ( + + )} +
+ ); +} diff --git a/apps/dashboard/src/components/primitives/sonner-helpers.tsx b/apps/dashboard/src/components/primitives/sonner-helpers.tsx index 20d3b52674e..be01ec71108 100644 --- a/apps/dashboard/src/components/primitives/sonner-helpers.tsx +++ b/apps/dashboard/src/components/primitives/sonner-helpers.tsx @@ -1,5 +1,5 @@ import { ExternalToast, toast } from 'sonner'; -import { Toast, ToastProps } from './sonner'; +import { Toast, ToastIcon, ToastProps } from './sonner'; import { ReactNode } from 'react'; export const showToast = ({ @@ -17,3 +17,27 @@ export const showToast = ({ ...options, }); }; + +export const showSuccessToast = (message: string, position: 'bottom-center' | 'top-center' = 'bottom-center') => { + showToast({ + children: () => ( + <> + + {message} + + ), + options: { position }, + }); +}; + +export const showErrorToast = (message: string, position: 'bottom-center' | 'top-center' = 'bottom-center') => { + showToast({ + children: () => ( + <> + + {message} + + ), + options: { position }, + }); +}; diff --git a/apps/dashboard/src/components/side-navigation/getting-started-menu-item.tsx b/apps/dashboard/src/components/side-navigation/getting-started-menu-item.tsx index 58bb8d96f6e..3271f31b4a9 100644 --- a/apps/dashboard/src/components/side-navigation/getting-started-menu-item.tsx +++ b/apps/dashboard/src/components/side-navigation/getting-started-menu-item.tsx @@ -1,19 +1,47 @@ +import { useUser } from '@clerk/clerk-react'; import { motion } from 'motion/react'; -import { RiQuestionLine, RiSparkling2Fill } from 'react-icons/ri'; +import { RiQuestionLine, RiSparkling2Fill, RiCloseLine } from 'react-icons/ri'; import { Badge } from '../primitives/badge'; import { buildRoute, ROUTES } from '@/utils/routes'; import { useEnvironment } from '@/context/environment/hooks'; import { useOnboardingSteps } from '../../hooks/use-onboarding-steps'; import { NavigationLink } from './navigation-link'; +import { useTelemetry } from '@/hooks/use-telemetry'; +import { TelemetryEvent } from '@/utils/telemetry'; +import { Button } from '../primitives/button'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'; import { useFeatureFlag } from '@/hooks/use-feature-flag'; import { FeatureFlagsKeysEnum } from '@novu/shared'; export function GettingStartedMenuItem() { - const { totalSteps, completedSteps } = useOnboardingSteps(); - const { currentEnvironment } = useEnvironment(); + const { totalSteps, completedSteps, steps } = useOnboardingSteps(); const isGettingStartedEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_NEW_DASHBOARD_GETTING_STARTED_ENABLED); - if (!isGettingStartedEnabled) { + const { currentEnvironment } = useEnvironment(); + const { user } = useUser(); + const track = useTelemetry(); + + const allStepsCompleted = completedSteps === totalSteps; + + const handleClose = async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + track(TelemetryEvent.WELCOME_MENU_HIDDEN, { + completedSteps: steps.filter((step) => step.status === 'completed').map((step) => step.id), + totalSteps, + allStepsCompleted, + }); + + await user?.update({ + unsafeMetadata: { + ...user.unsafeMetadata, + hideGettingStarted: true, + }, + }); + }; + + if (!isGettingStartedEnabled || user?.unsafeMetadata?.hideGettingStarted) { return null; } @@ -49,6 +77,31 @@ export function GettingStartedMenuItem() { {completedSteps}/{totalSteps} + + {allStepsCompleted && ( + + + + + + This will hide the Getting Started page + + + )} ); diff --git a/apps/dashboard/src/components/usecase-playground-header.tsx b/apps/dashboard/src/components/usecase-playground-header.tsx new file mode 100644 index 00000000000..4eed5b99e75 --- /dev/null +++ b/apps/dashboard/src/components/usecase-playground-header.tsx @@ -0,0 +1,38 @@ +import { RiArrowLeftSLine } from 'react-icons/ri'; +import { Button } from './primitives/button'; +import { useNavigate } from 'react-router-dom'; + +interface UsecasePlaygroundHeaderProps { + title: string; + description: string; + skipPath: string; + onSkip?: () => void; +} + +export function UsecasePlaygroundHeader({ title, description, skipPath, onSkip }: UsecasePlaygroundHeaderProps) { + const navigate = useNavigate(); + + const handleSkip = () => { + onSkip?.(); + navigate(skipPath); + }; + + return ( +
+
+ + +
+

{title}

+

{description}

+
+
+ + +
+ ); +} diff --git a/apps/dashboard/src/components/welcome/framework-guides.instructions.tsx b/apps/dashboard/src/components/welcome/framework-guides.instructions.tsx new file mode 100644 index 00000000000..f8b1bf4ca42 --- /dev/null +++ b/apps/dashboard/src/components/welcome/framework-guides.instructions.tsx @@ -0,0 +1,297 @@ +import { RiAngularjsFill, RiJavascriptFill, RiNextjsFill, RiReactjsFill, RiRemixRunFill } from 'react-icons/ri'; +import { Language } from '../primitives/code-block'; + +export interface Framework { + name: string; + icon: JSX.Element; + selected?: boolean; + installSteps: InstallationStep[]; +} + +export interface InstallationStep { + title: string; + description: string; + code?: string; + codeLanguage: Language; + codeTitle?: string; + tip?: { + title?: string; + description: string | React.ReactNode; + }; +} + +export const customizationTip = { + title: 'Tip:', + description: ( + <> + You can customize your inbox to match your app theme,{' '} + + learn more + + . + + ), +}; + +export const commonInstallStep = (packageName: string): InstallationStep => ({ + title: 'Install the package', + description: `${packageName} is the package that powers the notification center.`, + code: `npm install ${packageName}`, + codeLanguage: 'shell', + codeTitle: 'Terminal', +}); + +export const frameworks: Framework[] = [ + { + name: 'Next.js', + icon: , + selected: true, + installSteps: [ + commonInstallStep('@novu/react'), + { + title: 'Add the inbox code to your Next.js app', + description: 'Novu uses the router hook to make your notifications navigatable in Next.js.', + code: `'use client'; + +import { Inbox } from '@novu/react'; +import { useRouter } from 'next/navigation'; + +function Novu() { + const router = useRouter(); + + return ( + router.push(path)} + /> + ); +}`, + codeLanguage: 'tsx', + codeTitle: 'Inbox.tsx', + tip: customizationTip, + }, + ], + }, + { + name: 'React', + icon: , + installSteps: [ + commonInstallStep('@novu/react'), + { + title: 'Add the inbox code to your React app', + description: 'Novu uses the onNavigate prop to handle notification clicks in React.', + code: `import { Inbox } from '@novu/react'; +import { useNavigate } from 'react-router-dom'; + +function Novu() { + const navigate = useNavigate(); + + return ( + navigate(path)} + /> + ); +}`, + codeLanguage: 'tsx', + codeTitle: 'Inbox.tsx', + tip: customizationTip, + }, + ], + }, + { + name: 'Remix', + icon: , + installSteps: [ + commonInstallStep('@novu/react'), + { + title: 'Add the inbox code to your Remix app', + description: 'Implement the notification center in your Remix application.', + code: `import { Inbox } from '@novu/react'; +import { useNavigate } from '@remix-run/react'; + +function Novu() { + const navigate = useNavigate(); + + return ( + navigate(path)} + /> + ); +}`, + codeLanguage: 'tsx', + codeTitle: 'Inbox.tsx', + tip: customizationTip, + }, + ], + }, + { + name: 'Native', + icon: , + installSteps: [ + commonInstallStep('@novu/react-native'), + { + title: 'Add the inbox code to your React Native app', + description: 'Implement the notification center in your React Native application.', + code: `import { NovuProvider } from '@novu/react-native'; +import { YourCustomInbox } from './Inbox'; + +function Layout() { + return ( + + + + ); +}`, + codeLanguage: 'tsx', + codeTitle: 'App.tsx', + }, + { + title: 'Build your custom inbox component', + description: 'Build your custom inbox component to use within your app.', + code: `import { + FlatList, + View, + Text, + ActivityIndicator, + RefreshControl, +} from "react-native"; +import { useNotifications, Notification } from "@novu/react-native"; + +export function YourCustomInbox() { + const { notifications, isLoading, fetchMore, hasMore, refetch } = useNotifications(); + + const renderItem = ({ item }) => ( + + {item.body} + + ); + + const renderFooter = () => { + if (!hasMore) return null; + + return ( + + + + ); + }; + + const renderEmpty = () => ( + + No updates available + + ); + + if (isLoading) { + return ( + + + + ); + } + + return ( + item.id} + contentContainerStyle={styles.listContainer} + onEndReached={fetchMore} + onEndReachedThreshold={0.5} + ListFooterComponent={renderFooter} + ListEmptyComponent={renderEmpty} + refreshControl={ + + } + /> + ); +}`, + codeLanguage: 'tsx', + codeTitle: 'Inbox.tsx', + }, + ], + }, + { + name: 'Angular', + icon: , + installSteps: [ + commonInstallStep('@novu/js'), + { + title: 'Add the inbox code to your Angular app', + description: 'Currently, angular applications are supported with the Novu UI library.', + code: `import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { NovuUI } from '@novu/js/ui'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [RouterOutlet], + templateUrl: './app.component.html', + styleUrl: './app.component.css', +}) +export class AppComponent implements AfterViewInit { + @ViewChild('notificationInbox') notificationInbox!: ElementRef; + title = 'inbox-angular'; + + ngAfterViewInit() { + const novu = new NovuUI({ + options: { + applicationIdentifier: '123', + subscriberId: '456', + }, + }); + + novu.mountComponent({ + name: 'Inbox', + props: {}, + element: this.notificationInbox.nativeElement, + }); + } +}`, + codeLanguage: 'typescript', + tip: customizationTip, + }, + ], + }, + { + name: 'JavaScript', + icon: , + installSteps: [ + commonInstallStep('@novu/js'), + { + title: 'Add the inbox code to your JavaScript app', + description: + 'You can use the Novu UI library to implement the notification center in your vanilla JavaScript application or any other non-supported framework like Vue.', + code: `import { NovuUI } from '@novu/js/ui'; + + const novu = new NovuUI({ + options: { + applicationIdentifier: '123', + subscriberId: '456', + }, +}); + +novu.mountComponent({ + name: 'Inbox', + props: {}, + element: document.getElementById('notification-inbox'), +});`, + codeLanguage: 'typescript', + tip: customizationTip, + }, + ], + }, +]; diff --git a/apps/dashboard/src/components/welcome/framework-guides.tsx b/apps/dashboard/src/components/welcome/framework-guides.tsx new file mode 100644 index 00000000000..f1712cb042e --- /dev/null +++ b/apps/dashboard/src/components/welcome/framework-guides.tsx @@ -0,0 +1,133 @@ +import { motion, AnimatePresence } from 'motion/react'; +import { CodeBlock, Language } from '../primitives/code-block'; +import { InlineToast } from '../primitives/inline-toast'; +import { Framework, InstallationStep } from './framework-guides.instructions'; + +const fadeInAnimation = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, + transition: { duration: 0.2 }, +}; + +const stepAnimation = (index: number) => ({ + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0 }, + transition: { + duration: 0.3, + delay: index * 0.15, + ease: 'easeOut', + }, +}); + +const numberAnimation = (index: number) => ({ + initial: { scale: 0, opacity: 0 }, + animate: { scale: 1, opacity: 1 }, + transition: { + duration: 0.2, + delay: index * 0.15 + 0.1, + ease: 'easeOut', + }, +}); + +const codeBlockAnimation = (index: number) => ({ + initial: { opacity: 0, y: 10 }, + animate: { opacity: 1, y: 0 }, + transition: { + duration: 0.3, + delay: index * 0.15 + 0.2, + ease: 'easeOut', + }, +}); + +function StepNumber({ index }: { index: number }) { + return ( + + {index + 1} + + ); +} + +function StepContent({ + title, + description, + tip, +}: { + title: string; + description: string; + tip?: InstallationStep['tip']; +}) { + return ( +
+
+ {title} +
+

{description}

+ {tip && } +
+ ); +} + +function StepCodeBlock({ + code, + language, + title, + index, +}: { + code: string; + language: Language; + title?: string; + index: number; +}) { + return ( + + + + ); +} + +function InstallationStepRow({ + step, + index, + frameworkName, +}: { + step: InstallationStep; + index: number; + frameworkName: string; +}) { + return ( + + + + {step.code && ( + + )} + + ); +} + +export function FrameworkInstructions({ framework }: { framework: Framework }) { + return ( + + +
+ {framework.installSteps.map((step, index) => ( + + ))} +
+
+
+ ); +} diff --git a/apps/dashboard/src/components/welcome/inbox-connected-guide.tsx b/apps/dashboard/src/components/welcome/inbox-connected-guide.tsx new file mode 100644 index 00000000000..f9e8d4de8cf --- /dev/null +++ b/apps/dashboard/src/components/welcome/inbox-connected-guide.tsx @@ -0,0 +1,81 @@ +import { RiCheckboxCircleFill, RiLoader3Line, RiNotification2Fill } from 'react-icons/ri'; +import { Loader2 } from 'lucide-react'; +import { Button } from '../primitives/button'; +import { showErrorToast, showSuccessToast } from '../primitives/sonner-helpers'; +import { useTriggerWorkflow } from '@/hooks/use-trigger-workflow'; +import { ROUTES } from '../../utils/routes'; +import { useNavigate } from 'react-router-dom'; +import { ONBOARDING_DEMO_WORKFLOW_ID } from '../../config'; + +interface InboxConnectedGuideProps { + subscriberId: string; +} + +export function InboxConnectedGuide({ subscriberId }: InboxConnectedGuideProps) { + const navigate = useNavigate(); + const { triggerWorkflow, isPending } = useTriggerWorkflow(); + + async function handleSendNotification() { + try { + await triggerWorkflow({ + name: ONBOARDING_DEMO_WORKFLOW_ID, + to: subscriberId, + payload: { + subject: '**Welcome to Inbox!**', + body: 'This is your first notification. Customize and explore more features.', + primaryActionLabel: 'Add to your app', + secondaryActionLabel: '', + }, + }); + + showSuccessToast('Notification sent successfully!'); + navigate(ROUTES.INBOX_EMBED_SUCCESS); + } catch (error) { + showErrorToast('Failed to send notification'); + } + } + + return ( + <> +
+
+
+ + Amazing, you did it 🎉 +
+

Now, let the magic happen! Click send notification below.

+
+
+ +
+
+
+
+ +
+ +
+
+ Let the magic happen 🪄 +
+

+ Now, trigger a notification to see it pop up in your app! If it doesn't appear, double-check that the + subscriberId matches as above. +

+
+ +
+
+
+
+
+ + ); +} diff --git a/apps/dashboard/src/components/welcome/inbox-embed.tsx b/apps/dashboard/src/components/welcome/inbox-embed.tsx new file mode 100644 index 00000000000..4663bfedaa9 --- /dev/null +++ b/apps/dashboard/src/components/welcome/inbox-embed.tsx @@ -0,0 +1,55 @@ +import { useState, useEffect } from 'react'; +import { useIntegrations } from '../../hooks/use-integrations'; +import { useFetchEnvironments } from '../../context/environment/hooks'; +import { useAuth } from '../../context/auth/hooks'; +import { ChannelTypeEnum } from '@novu/shared'; +import ReactConfetti from 'react-confetti'; +import { InboxConnectedGuide } from './inbox-connected-guide'; +import { InboxFrameworkGuide } from './inbox-framework-guide'; +import { useSearchParams } from 'react-router-dom'; + +export function InboxEmbed(): JSX.Element | null { + const [showConfetti, setShowConfetti] = useState(false); + const auth = useAuth(); + const { integrations } = useIntegrations({ refetchInterval: 1000, refetchOnWindowFocus: true }); + const { environments } = useFetchEnvironments({ organizationId: auth?.currentOrganization?._id }); + const [searchParams] = useSearchParams(); + + const currentEnvironment = environments?.find((env) => !env._parentId); + const subscriberId = auth?.currentUser?._id; + + const foundIntegration = integrations?.find( + (integration) => + integration._environmentId === environments?.[0]?._id && integration.channel === ChannelTypeEnum.IN_APP + ); + + const primaryColor = searchParams.get('primaryColor') || '#DD2450'; + const foregroundColor = searchParams.get('foregroundColor') || '#0E121B'; + + useEffect(() => { + if (foundIntegration?.connected) { + setShowConfetti(true); + const timer = setTimeout(() => setShowConfetti(false), 10000); + return () => clearTimeout(timer); + } + }, [foundIntegration?.connected]); + + if (!subscriberId) return null; + + return ( +
+ {showConfetti && } + + {!foundIntegration?.connected && ( + + )} + + {foundIntegration?.connected && } +
+ ); +} diff --git a/apps/dashboard/src/components/welcome/inbox-framework-guide.tsx b/apps/dashboard/src/components/welcome/inbox-framework-guide.tsx new file mode 100644 index 00000000000..4145f55e906 --- /dev/null +++ b/apps/dashboard/src/components/welcome/inbox-framework-guide.tsx @@ -0,0 +1,203 @@ +import { Loader } from 'lucide-react'; +import { Card, CardContent } from '../primitives/card'; +import { useState, useEffect } from 'react'; +import { IEnvironment } from '@novu/shared'; +import { API_HOSTNAME, WEBSOCKET_HOSTNAME } from '../../config'; +import { motion } from 'motion/react'; +import { Framework, frameworks } from './framework-guides.instructions'; +import { FrameworkInstructions } from './framework-guides'; + +const containerVariants = { + hidden: {}, + show: { + transition: { + staggerChildren: 0.05, + delayChildren: 0.1, + }, + }, +}; + +const cardVariants = { + hidden: { + opacity: 0, + y: 10, + }, + show: { + opacity: 1, + y: 0, + transition: { + duration: 0.2, + ease: 'easeOut', + }, + }, +}; + +const iconVariants = { + initial: { + scale: 1, + }, + hover: { + scale: 1.1, + transition: { + scale: { + duration: 0.2, + ease: 'easeOut', + }, + }, + }, +}; + +interface InboxFrameworkGuideProps { + currentEnvironment: IEnvironment | undefined; + subscriberId: string; + primaryColor: string; + foregroundColor: string; +} + +function getUrlProps(isDefaultApi: boolean, isDefaultWs: boolean): string { + const props = [ + ...(isDefaultApi ? [] : [`backendUrl="${API_HOSTNAME}"`]), + ...(isDefaultWs ? [] : [`socketUrl="${WEBSOCKET_HOSTNAME}"`]), + ]; + + return props.length ? '\n ' + props.join('\n ') : ''; +} + +function generateInboxComponent( + environmentIdentifier: string, + subscriberId: string, + urlProps: string, + primaryColor: string, + foregroundColor: string +): string { + return ` router.push(path)}${urlProps} + appearance={{ + variables: { + colorPrimary: "${primaryColor}", + colorForeground: "${foregroundColor}" + } + }} + />`; +} + +function generateNovuProvider(environmentIdentifier: string, subscriberId: string, urlProps: string): string { + return ` + + `; +} + +function updateFrameworkCode( + framework: Framework, + inboxComponent: string, + novuProvider: string, + environmentIdentifier: string, + subscriberId: string +): Framework { + return { + ...framework, + installSteps: framework.installSteps.map((step) => ({ + ...step, + code: step.code + ?.replace(//, inboxComponent) + ?.replace(//, novuProvider) + ?.replace(/YOUR_APP_ID/g, environmentIdentifier) + ?.replace(/YOUR_APPLICATION_IDENTIFIER/g, environmentIdentifier) + ?.replace(/YOUR_SUBSCRIBER_ID/g, subscriberId), + })), + }; +} + +export function InboxFrameworkGuide({ + currentEnvironment, + subscriberId, + primaryColor, + foregroundColor, +}: InboxFrameworkGuideProps) { + const [selectedFramework, setSelectedFramework] = useState(frameworks.find((f) => f.selected) || frameworks[0]); + + useEffect(() => { + if (!currentEnvironment?.identifier || !subscriberId) return; + + const isDefaultApi = API_HOSTNAME === 'https://api.novu.co'; + const isDefaultWs = WEBSOCKET_HOSTNAME === 'https://ws.novu.co'; + + const urlProps = getUrlProps(isDefaultApi, isDefaultWs); + const inboxComponent = generateInboxComponent( + currentEnvironment.identifier, + subscriberId, + urlProps, + primaryColor, + foregroundColor + ); + const novuProvider = generateNovuProvider(currentEnvironment.identifier, subscriberId, urlProps); + + const updatedFrameworks = frameworks.map((framework) => + updateFrameworkCode(framework, inboxComponent, novuProvider, currentEnvironment.identifier, subscriberId) + ); + + setSelectedFramework(updatedFrameworks.find((f) => f.name === selectedFramework.name) || updatedFrameworks[0]); + }, [currentEnvironment?.identifier, subscriberId, selectedFramework.name, primaryColor, foregroundColor]); + + function handleFrameworkSelect(framework: Framework) { + setSelectedFramework(framework); + } + + return ( + <> + +
+
+ + + Watching for Inbox Integration + +
+

You're just a couple steps away from your first notification.

+
+
+ + {/* Framework Cards */} + + {frameworks.map((framework) => ( + + handleFrameworkSelect(framework)} + className={`flex h-[100px] w-[100px] flex-col items-center justify-center border-none p-6 shadow-none hover:cursor-pointer ${ + framework.name === selectedFramework.name ? 'bg-neutral-100' : '' + }`} + > + + + {framework.icon} + + {framework.name} + + + + ))} + + +
+ +
+ + ); +} diff --git a/apps/dashboard/src/components/welcome/progress-section.tsx b/apps/dashboard/src/components/welcome/progress-section.tsx index b114f9e7974..5fd845d3b92 100644 --- a/apps/dashboard/src/components/welcome/progress-section.tsx +++ b/apps/dashboard/src/components/welcome/progress-section.tsx @@ -126,7 +126,6 @@ function getStepRoute(stepId: StepIdEnum, environmentSlug: string = '') { isLegacy: false, }; case StepIdEnum.CONNECT_EMAIL_PROVIDER: - case StepIdEnum.CONNECT_IN_APP_PROVIDER: case StepIdEnum.CONNECT_PUSH_PROVIDER: case StepIdEnum.CONNECT_CHAT_PROVIDER: case StepIdEnum.CONNECT_SMS_PROVIDER: @@ -134,6 +133,11 @@ function getStepRoute(stepId: StepIdEnum, environmentSlug: string = '') { path: LEGACY_ROUTES.INTEGRATIONS, isLegacy: true, }; + case StepIdEnum.CONNECT_IN_APP_PROVIDER: + return { + path: ROUTES.INBOX_EMBED, + isLegacy: false, + }; case StepIdEnum.INVITE_TEAM_MEMBER: return { path: LEGACY_ROUTES.INVITE_TEAM_MEMBERS, diff --git a/apps/dashboard/src/components/welcome/resources-list.tsx b/apps/dashboard/src/components/welcome/resources-list.tsx index 326a6c20bc4..9dac80be81e 100644 --- a/apps/dashboard/src/components/welcome/resources-list.tsx +++ b/apps/dashboard/src/components/welcome/resources-list.tsx @@ -8,7 +8,7 @@ import { TelemetryEvent } from '@/utils/telemetry'; export interface Resource { title: string; - duration: string; + duration?: string; image: string; url: string; } @@ -70,30 +70,32 @@ export function ResourcesList({ resources, title, icon }: ResourcesListProps) { - + {resources.map((resource, index) => ( handleResourceClick(resource)}> - + {resource.title} - +

{resource.title}

-
- - {resource.duration} -
+ {resource.duration && ( +
+ + {resource.duration} +
+ )}
diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx index fe39c5311f3..ea47e02c28e 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; import { Editor } from '@/components/primitives/editor'; -import { FormControl, FormField, FormItem, FormMessage } from '@/components/primitives/form/form'; +import { FormControl, FormField, FormItem } from '@/components/primitives/form/form'; import { InputField } from '@/components/primitives/input'; import { completions } from '@/utils/liquid-autocomplete'; import { parseStepVariablesToLiquidVariables } from '@/utils/parseStepVariablesToLiquidVariables'; @@ -41,7 +41,6 @@ export const InAppBody = () => { /> - {`Type {{ for variables, or wrap text in ** for bold.`} )} /> diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor.tsx index d9095a049d5..b38715e73ef 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor.tsx @@ -4,6 +4,7 @@ import { Notification5Fill } from '@/components/icons'; import { Separator } from '@/components/primitives/separator'; import { getComponentByType } from '@/components/workflow-editor/steps/component-utils'; import { InAppTabsSection } from '@/components/workflow-editor/steps/in-app/in-app-tabs-section'; +import { FormMessage } from '../../../primitives/form/form'; const avatarKey = 'avatar'; const subjectKey = 'subject'; @@ -40,7 +41,12 @@ export const InAppEditor = ({ uiSchema }: { uiSchema?: UiSchema }) => { {subject && getComponentByType({ component: subject.component })}
)} - {body && getComponentByType({ component: body.component })} + {body && ( + <> + {getComponentByType({ component: body.component })} + {`Type {{ for variables, or wrap text in ** for bold.`} + + )} {(primaryAction || secondaryAction) && getComponentByType({ component: primaryAction.component || secondaryAction.component, diff --git a/apps/dashboard/src/components/workflow-editor/test-workflow/snippet-editor.tsx b/apps/dashboard/src/components/workflow-editor/test-workflow/snippet-editor.tsx index e13dd13ead9..d7184effbe5 100644 --- a/apps/dashboard/src/components/workflow-editor/test-workflow/snippet-editor.tsx +++ b/apps/dashboard/src/components/workflow-editor/test-workflow/snippet-editor.tsx @@ -4,7 +4,15 @@ import { loadLanguage, LanguageName } from '@uiw/codemirror-extensions-langs'; import { Editor } from '@/components/primitives/editor'; import type { SnippetLanguage } from './types'; -export const SnippetEditor = ({ language, value }: { language: SnippetLanguage; value: string }) => { +export const SnippetEditor = ({ + language, + value, + readOnly = false, +}: { + language: SnippetLanguage; + value: string; + readOnly?: boolean; +}) => { const editorLanguage: LanguageName = language === 'framework' ? 'typescript' : language; const extensions = useMemo(() => { @@ -18,6 +26,7 @@ export const SnippetEditor = ({ language, value }: { language: SnippetLanguage; return ( ({ queryKey: [QueryKeys.fetchIntegrations, currentEnvironment?._id], queryFn: getIntegrations, + refetchInterval, + refetchOnWindowFocus, }); return { diff --git a/apps/dashboard/src/hooks/use-onboarding-steps.ts b/apps/dashboard/src/hooks/use-onboarding-steps.ts index 43f08a38599..7dbbefad814 100644 --- a/apps/dashboard/src/hooks/use-onboarding-steps.ts +++ b/apps/dashboard/src/hooks/use-onboarding-steps.ts @@ -3,6 +3,7 @@ import { useWorkflows } from './use-workflows'; import { useOrganization } from '@clerk/clerk-react'; import { ChannelTypeEnum, IIntegration } from '@novu/shared'; import { useIntegrations } from './use-integrations'; +import { ONBOARDING_DEMO_WORKFLOW_ID } from '../config'; export enum StepIdEnum { ACCOUNT_CREATION = 'account-creation', @@ -10,7 +11,7 @@ export enum StepIdEnum { INVITE_TEAM_MEMBER = 'invite-team-member', SYNC_TO_PRODUCTION = 'sync-to-production', CONNECT_EMAIL_PROVIDER = 'connect-email-provider', - CONNECT_IN_APP_PROVIDER = 'connect-in-app-provider', + CONNECT_IN_APP_PROVIDER = 'connect-in_app-provider', CONNECT_PUSH_PROVIDER = 'connect-push-provider', CONNECT_CHAT_PROVIDER = 'connect-chat-provider', CONNECT_SMS_PROVIDER = 'connect-sms-provider', @@ -46,7 +47,7 @@ function getProviderTitle(providerType: ChannelTypeEnum): string { function getProviderDescription(providerType: ChannelTypeEnum): string { return providerType === ChannelTypeEnum.IN_APP - ? 'Add an Inbox to your app' + ? 'Embed a full-featured Inbox in your app in minutes' : `Connect your provider to send ${providerType} notifications with Novu.`; } @@ -59,7 +60,7 @@ function isActiveIntegration(integration: IIntegration, providerType: ChannelTyp } export function useOnboardingSteps(): OnboardingStepsResult { - const { data: workflows } = useWorkflows(); + const workflows = useWorkflows(); const { organization } = useOrganization(); const { integrations } = useIntegrations(); @@ -67,6 +68,13 @@ export function useOnboardingSteps(): OnboardingStepsResult { return (organization?.membersCount ?? 0) > 1; }, [organization?.membersCount]); + const hasCreatedWorkflow = useMemo(() => { + return ( + (workflows?.data?.workflows ?? []).filter((workflow) => workflow.workflowId !== ONBOARDING_DEMO_WORKFLOW_ID) + .length > 0 + ); + }, [workflows?.data?.workflows]); + const providerType = useMemo(() => { const metadata = organization?.publicMetadata as OrganizationMetadata; const useCases = metadata?.useCases ?? DEFAULT_USE_CASES; @@ -86,7 +94,7 @@ export function useOnboardingSteps(): OnboardingStepsResult { id: StepIdEnum.CREATE_A_WORKFLOW, title: 'Create a workflow', description: 'Workflows in Novu, orchestrate notifications across channels.', - status: workflows && workflows.totalCount > 0 ? 'completed' : 'in-progress', + status: hasCreatedWorkflow ? 'completed' : 'in-progress', }, { id: `connect-${providerType}-provider` as StepIdEnum, @@ -103,7 +111,7 @@ export function useOnboardingSteps(): OnboardingStepsResult { status: hasInvitedTeamMember ? 'completed' : 'pending', }, ], - [workflows, hasInvitedTeamMember, providerType, integrations] + [hasInvitedTeamMember, providerType, integrations, hasCreatedWorkflow] ); return { diff --git a/apps/dashboard/src/hooks/use-workflows.ts b/apps/dashboard/src/hooks/use-workflows.ts index 35f5e82411b..75028765a74 100644 --- a/apps/dashboard/src/hooks/use-workflows.ts +++ b/apps/dashboard/src/hooks/use-workflows.ts @@ -7,15 +7,18 @@ import { useEnvironment } from '../context/environment/hooks'; interface UseWorkflowsParams { limit?: number; offset?: number; + query?: string; } -export function useWorkflows({ limit = 12, offset = 0 }: UseWorkflowsParams = {}) { +export function useWorkflows({ limit = 12, offset = 0, query = '' }: UseWorkflowsParams = {}) { const { currentEnvironment } = useEnvironment(); const workflowsQuery = useQuery({ - queryKey: [QueryKeys.fetchWorkflows, currentEnvironment?._id, { limit, offset }], + queryKey: [QueryKeys.fetchWorkflows, currentEnvironment?._id, { limit, offset, query }], queryFn: async () => { - const { data } = await getV2<{ data: ListWorkflowResponse }>(`/workflows?limit=${limit}&offset=${offset}`); + const { data } = await getV2<{ data: ListWorkflowResponse }>( + `/workflows?limit=${limit}&offset=${offset}&query=${query}` + ); return data; }, placeholderData: keepPreviousData, diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx index e6ff1b5141a..9ae8ec6a49c 100644 --- a/apps/dashboard/src/main.tsx +++ b/apps/dashboard/src/main.tsx @@ -3,6 +3,7 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { createRoot } from 'react-dom/client'; import ErrorPage from '@/components/error-page'; import { RootRoute, AuthRoute, DashboardRoute, CatchAllRoute } from './routes'; +import { OnboardingParentRoute } from './routes/onboarding'; import { WorkflowsPage, SignInPage, @@ -18,10 +19,13 @@ import { EditWorkflowPage } from './pages/edit-workflow'; import { TestWorkflowPage } from './pages/test-workflow'; import { initializeSentry } from './utils/sentry'; import { overrideZodErrorMap } from './utils/validation'; +import { InboxUsecasePage } from './pages/inbox-usecase-page'; +import { InboxEmbedPage } from './pages/inbox-embed-page'; import { FeatureFlagsProvider } from '@/context/feature-flags-provider'; import { EditStepTemplate } from '@/components/workflow-editor/steps/edit-step-template'; import { ConfigureWorkflow } from '@/components/workflow-editor/configure-workflow'; import { EditStep } from '@/components/workflow-editor/steps/edit-step'; +import { InboxEmbedSuccessPage } from './pages/inbox-embed-success-page'; initializeSentry(); overrideZodErrorMap(); @@ -50,10 +54,28 @@ const router = createBrowserRouter([ path: ROUTES.SIGNUP_QUESTIONNAIRE, element: , }, + ], + }, + { + path: '/onboarding', + element: , + children: [ { path: ROUTES.USECASE_SELECT, element: , }, + { + path: ROUTES.INBOX_USECASE, + element: , + }, + { + path: ROUTES.INBOX_EMBED, + element: , + }, + { + path: ROUTES.INBOX_EMBED_SUCCESS, + element: , + }, ], }, { diff --git a/apps/dashboard/src/pages/inbox-embed-page.tsx b/apps/dashboard/src/pages/inbox-embed-page.tsx new file mode 100644 index 00000000000..0f9d8a3b644 --- /dev/null +++ b/apps/dashboard/src/pages/inbox-embed-page.tsx @@ -0,0 +1,41 @@ +import { AuthCard } from '../components/auth/auth-card'; +import { ROUTES } from '../utils/routes'; +import { InboxEmbed } from '../components/welcome/inbox-embed'; +import { UsecasePlaygroundHeader } from '../components/usecase-playground-header'; +import { useTelemetry } from '../hooks/use-telemetry'; +import { TelemetryEvent } from '../utils/telemetry'; +import { useEffect } from 'react'; +import { AnimatedPage } from '@/components/onboarding/animated-page'; + +export function InboxEmbedPage() { + const telemetry = useTelemetry(); + + useEffect(() => { + telemetry(TelemetryEvent.INBOX_EMBED_PAGE_VIEWED); + }, [telemetry]); + + return ( + + +
+
+ + telemetry(TelemetryEvent.SKIP_ONBOARDING_CLICKED, { + skippedFrom: 'inbox-embed', + }) + } + /> +
+ +
+ +
+
+
+
+ ); +} diff --git a/apps/dashboard/src/pages/inbox-embed-success-page.tsx b/apps/dashboard/src/pages/inbox-embed-success-page.tsx new file mode 100644 index 00000000000..50a48316526 --- /dev/null +++ b/apps/dashboard/src/pages/inbox-embed-success-page.tsx @@ -0,0 +1,56 @@ +import { AuthCard } from '../components/auth/auth-card'; +import { Button } from '../components/primitives/button'; +import { useNavigate } from 'react-router-dom'; +import { ROUTES } from '../utils/routes'; +import { useTelemetry } from '../hooks/use-telemetry'; +import { TelemetryEvent } from '../utils/telemetry'; +import { useEffect } from 'react'; +import { AnimatedPage } from '@/components/onboarding/animated-page'; + +export function InboxEmbedSuccessPage() { + const navigate = useNavigate(); + const telemetry = useTelemetry(); + + useEffect(() => { + telemetry(TelemetryEvent.INBOX_EMBED_SUCCESS_PAGE_VIEWED); + }, [telemetry]); + + function handleNavigateToDashboard() { + navigate(ROUTES.WELCOME); + } + + return ( + + +
+
+ Onboarding succcess hint to look for inbox +
+ +
+
+ Novu Logo + +
+

See how simple that was?

+

+ Robust and flexible building blocks for application notifications. +

+
+
+
+ +
+ +
+
+
+
+ ); +} diff --git a/apps/dashboard/src/pages/inbox-usecase-page.tsx b/apps/dashboard/src/pages/inbox-usecase-page.tsx new file mode 100644 index 00000000000..a8a41b89bf3 --- /dev/null +++ b/apps/dashboard/src/pages/inbox-usecase-page.tsx @@ -0,0 +1,24 @@ +import { AuthCard } from '../components/auth/auth-card'; +import { PageMeta } from '../components/page-meta'; +import { InboxPlayground } from '../components/auth/inbox-playground'; +import { useTelemetry } from '../hooks/use-telemetry'; +import { TelemetryEvent } from '../utils/telemetry'; +import { useEffect } from 'react'; +import { AnimatedPage } from '@/components/onboarding/animated-page'; + +export function InboxUsecasePage() { + const telemetry = useTelemetry(); + + useEffect(() => { + telemetry(TelemetryEvent.INBOX_USECASE_PAGE_VIEWED); + }, [telemetry]); + + return ( + + + + + + + ); +} diff --git a/apps/dashboard/src/pages/sign-in.tsx b/apps/dashboard/src/pages/sign-in.tsx index d28c9f8d210..2f20317ad85 100644 --- a/apps/dashboard/src/pages/sign-in.tsx +++ b/apps/dashboard/src/pages/sign-in.tsx @@ -7,7 +7,7 @@ import { clerkSignupAppearance } from '@/utils/clerk-appearance'; export const SignInPage = () => { return ( - <> +
@@ -16,6 +16,6 @@ export const SignInPage = () => {
- +
); }; diff --git a/apps/dashboard/src/pages/sign-up.tsx b/apps/dashboard/src/pages/sign-up.tsx index 2e01d974181..6febac2b13b 100644 --- a/apps/dashboard/src/pages/sign-up.tsx +++ b/apps/dashboard/src/pages/sign-up.tsx @@ -7,7 +7,7 @@ import { clerkSignupAppearance } from '@/utils/clerk-appearance'; export const SignUpPage = () => { return ( - <> +
@@ -21,6 +21,6 @@ export const SignUpPage = () => {
- + ); }; diff --git a/apps/dashboard/src/pages/usecase-select-page.tsx b/apps/dashboard/src/pages/usecase-select-page.tsx index bb3584c073f..58aa77a94f3 100644 --- a/apps/dashboard/src/pages/usecase-select-page.tsx +++ b/apps/dashboard/src/pages/usecase-select-page.tsx @@ -1,6 +1,6 @@ import { UsecaseSelectOnboarding } from '../components/auth/usecase-selector'; import { AuthCard } from '../components/auth/auth-card'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'motion/react'; import { Button } from '../components/primitives/button'; import { ROUTES } from '../utils/routes'; @@ -14,13 +14,44 @@ import { TelemetryEvent } from '../utils/telemetry'; import { channelOptions } from '../components/auth/usecases-list.utils'; import { useMutation } from '@tanstack/react-query'; import * as Sentry from '@sentry/react'; +import { useOrganization } from '@clerk/clerk-react'; +import { AnimatedPage } from '@/components/onboarding/animated-page'; + +const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + duration: 0.6, + ease: [0.22, 1, 0.36, 1], + staggerChildren: 0.1, + }, + }, +}; + +const itemVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, +}; export function UsecaseSelectPage() { + const { organization } = useOrganization(); const navigate = useNavigate(); const track = useTelemetry(); const [selectedUseCases, setSelectedUseCases] = useState([]); const [hoveredUseCase, setHoveredUseCase] = useState(null); + useEffect(() => { + track(TelemetryEvent.USECASE_SELECT_PAGE_VIEWED); + }, [track]); + + useEffect(() => { + console.log('organization', organization?.publicMetadata); + if (organization?.publicMetadata?.useCases) { + setSelectedUseCases(organization.publicMetadata.useCases as ChannelTypeEnum[]); + } + }, [organization]); + const displayedUseCase = hoveredUseCase || (selectedUseCases.length > 0 ? selectedUseCases[selectedUseCases.length - 1] : null); @@ -29,12 +60,18 @@ export function UsecaseSelectPage() { await updateClerkOrgMetadata({ useCases: selectedUseCases, }); + await organization?.reload(); }, onSuccess: () => { track(TelemetryEvent.USE_CASE_SELECTED, { useCases: selectedUseCases, }); - navigate(ROUTES.WELCOME); + + if (selectedUseCases.includes(ChannelTypeEnum.IN_APP)) { + navigate(ROUTES.INBOX_USECASE); + } else { + navigate(ROUTES.WELCOME); + } }, onError: (error) => { console.error('Failed to update use cases:', error); @@ -65,52 +102,68 @@ export function UsecaseSelectPage() { return ( <> - - -
-
-
- setHoveredUseCase(id)} - onClick={(id) => handleSelectUseCase(id)} - /> - -
- - -
-
-
-
- -
- - {displayedUseCase && ( - option.id === displayedUseCase)?.image}`} - alt={`${displayedUseCase}-usecase-illustration`} - className="h-auto max-h-[500px] w-full object-contain" - initial={{ opacity: 0 }} - animate={{ opacity: 1 }} - exit={{ opacity: 0 }} - transition={{ - duration: 0.2, - ease: 'easeInOut', - }} - /> - )} - - {!displayedUseCase && } - -
-
+ + + + +
+
+ setHoveredUseCase(id)} + onClick={(id) => handleSelectUseCase(id)} + /> + + + + + +
+
+
+ + + + {displayedUseCase && ( + option.id === displayedUseCase)?.image}`} + alt={`${displayedUseCase}-usecase-illustration`} + className="h-auto max-h-[500px] w-full object-contain" + initial={{ opacity: 0, scale: 0.95 }} + animate={{ opacity: 1, scale: 1 }} + exit={{ opacity: 0, scale: 0.95 }} + transition={{ + duration: 0.2, + ease: [0.22, 1, 0.36, 1], + }} + /> + )} + + {!displayedUseCase && } + + +
+
+
); } @@ -123,28 +176,46 @@ function EmptyStateView() { animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ - duration: 0.2, - ease: 'easeInOut', + duration: 0.4, + ease: [0.22, 1, 0.36, 1], }} > -
+ -
- - {/* Instruction Text */} -

+ + + Hover on the cards to visualize,
select all that apply. -

- - {/* Help Text */} -

+ + + This helps us understand your use-case better with the channels you'd use in your product to communicate with your users.

don't worry, you can always change later as you build. -

+ ); } diff --git a/apps/dashboard/src/pages/welcome-page.tsx b/apps/dashboard/src/pages/welcome-page.tsx index c7822192cd4..94ffe256671 100644 --- a/apps/dashboard/src/pages/welcome-page.tsx +++ b/apps/dashboard/src/pages/welcome-page.tsx @@ -11,31 +11,54 @@ import { TelemetryEvent } from '../utils/telemetry'; const helpfulResources: Resource[] = [ { - title: "Let's meet? Let's chat about notifications.", - duration: '15m meet', - image: 'calendar_schedule.png', - url: 'https://cal.com/novu/30min', + title: 'Documentation', + image: 'blog.svg', + url: 'https://docs.novu.co/', }, { title: 'Join our community on Discord', - duration: '30s', - image: 'calendar_schedule.png', + image: 'discord.svg', url: 'https://discord.gg/novu', }, { - title: 'Star us on GitHub', - duration: '10s for happiness', - image: 'view_code.png', + title: 'See our code on GitHub', + image: 'git.svg', url: 'https://github.com/novuhq/novu', }, { - title: 'Security & Compliance with Novu', - duration: '5m read', - image: 'compliance.png', + title: 'Security & Compliance', + image: 'security.svg', url: 'https://trust.novu.co/', }, ]; +const learnResources: Resource[] = [ + { + title: 'Manage Subscribers', + duration: '4m read', + image: 'subscribers.svg', + url: 'https://docs.novu.co/concepts/subscribers?utm_source=novu.co&utm_medium=welcome-page', + }, + { + title: 'Topics', + duration: '5m read', + image: 'topics.svg', + url: 'https://docs.novu.co/concepts/topics?utm_source=novu.co&utm_medium=welcome-page', + }, + { + title: 'Code First Workflows', + duration: '4m read', + image: 'code-first.svg', + url: 'https://docs.novu.co/workflow/introduction?utm_source=novu.co&utm_medium=welcome-page', + }, + { + title: 'Digest Engine', + duration: '3m read', + image: 'digest engine-1.svg', + url: 'https://docs.novu.co/workflow/digest?utm_source=novu.co&utm_medium=welcome-page', + }, +]; + export function WelcomePage(): ReactElement { const telemetry = useTelemetry(); @@ -84,7 +107,7 @@ export function WelcomePage(): ReactElement { - } resources={helpfulResources} /> + } resources={learnResources} /> diff --git a/apps/dashboard/src/routes/onboarding.tsx b/apps/dashboard/src/routes/onboarding.tsx new file mode 100644 index 00000000000..daca22e5a7e --- /dev/null +++ b/apps/dashboard/src/routes/onboarding.tsx @@ -0,0 +1,10 @@ +import { AnimatedOutlet } from '@/components/animated-outlet'; +import { AuthLayout } from '../components/auth-layout'; + +export const OnboardingParentRoute = () => { + return ( + + + + ); +}; diff --git a/apps/dashboard/src/utils/routes.ts b/apps/dashboard/src/utils/routes.ts index 0862cb077a0..340a1e204e2 100644 --- a/apps/dashboard/src/utils/routes.ts +++ b/apps/dashboard/src/utils/routes.ts @@ -2,8 +2,11 @@ export const ROUTES = { SIGN_IN: '/auth/sign-in', SIGN_UP: '/auth/sign-up', SIGNUP_ORGANIZATION_LIST: '/auth/organization-list', - SIGNUP_QUESTIONNAIRE: '/auth/questionnaire', - USECASE_SELECT: '/auth/usecase', + SIGNUP_QUESTIONNAIRE: '/onboarding/questionnaire', + USECASE_SELECT: '/onboarding/usecase', + INBOX_USECASE: '/onboarding/inbox', + INBOX_EMBED: '/onboarding/inbox/embed', + INBOX_EMBED_SUCCESS: '/onboarding/inbox/success', ROOT: '/', ENV: '/env', WORKFLOWS: '/env/:environmentSlug/workflows', diff --git a/apps/dashboard/src/utils/telemetry.ts b/apps/dashboard/src/utils/telemetry.ts index cf4d53c9a7f..e03747cb752 100644 --- a/apps/dashboard/src/utils/telemetry.ts +++ b/apps/dashboard/src/utils/telemetry.ts @@ -7,6 +7,17 @@ export enum TelemetryEvent { USE_CASE_SKIPPED = 'Use Case Skipped', RESOURCE_CLICKED = 'Resource clicked - [Welcome]', WORKFLOWS_PAGE_VISIT = 'Workflows page visit', - WELCOME_PAGE_VIEWED = 'Welcome page viewed', - WELCOME_STEP_CLICKED = 'Welcome step clicked', + WELCOME_PAGE_VIEWED = 'Welcome page viewed - [Welcome]', + WELCOME_STEP_CLICKED = 'Welcome step clicked - [Welcome]', + WELCOME_STEP_COMPLETED = 'Welcome step completed - [Welcome]', + WELCOME_MENU_HIDDEN = 'Welcome menu hidden - [Welcome]', + INBOX_NOTIFICATION_SENT = 'Inbox notification sent - [Onboarding]', + INBOX_CUSTOMIZATION_CHANGED = 'Inbox customization changed - [Onboarding]', + INBOX_IMPLEMENTATION_CLICKED = 'Inbox implementation clicked - [Onboarding]', + INBOX_PREVIEW_STYLE_CHANGED = 'Inbox preview style changed - [Onboarding]', + SKIP_ONBOARDING_CLICKED = 'Skip onboarding clicked - [Onboarding]', + USECASE_SELECT_PAGE_VIEWED = 'Use case select page viewed - [Onboarding]', + INBOX_USECASE_PAGE_VIEWED = 'Inbox use case page viewed - [Onboarding]', + INBOX_EMBED_PAGE_VIEWED = 'Inbox embed page viewed - [Onboarding]', + INBOX_EMBED_SUCCESS_PAGE_VIEWED = 'Inbox embed success page viewed - [Onboarding]', } diff --git a/apps/dashboard/tailwind.config.js b/apps/dashboard/tailwind.config.js index ebe39246f87..3b15bd41875 100644 --- a/apps/dashboard/tailwind.config.js +++ b/apps/dashboard/tailwind.config.js @@ -137,6 +137,10 @@ export default { boxShadow: '0 0 0 0 rgba(255, 82, 82, 0)', }, }, + gradient: { + '0%, 100%': { backgroundPosition: '0% 50%' }, + '50%': { backgroundPosition: '100% 50%' }, + }, 'pulse-subtle': { '0%, 100%': { opacity: '1' }, '50%': { opacity: '0.85' }, @@ -188,6 +192,7 @@ export default { 'pulse-subtle': 'pulse-subtle 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', swing: 'swing 3s ease-in-out', jingle: 'jingle 3s ease-in-out', + gradient: 'gradient 5s ease infinite', }, backgroundImage: { 'test-pattern': diff --git a/libs/dal/src/repositories/integration/integration.entity.ts b/libs/dal/src/repositories/integration/integration.entity.ts index 4f1a46e0d67..f9ef0439067 100644 --- a/libs/dal/src/repositories/integration/integration.entity.ts +++ b/libs/dal/src/repositories/integration/integration.entity.ts @@ -37,6 +37,8 @@ export class IntegrationEntity { conditions?: StepFilter[]; removeNovuBranding?: boolean; + + connected?: boolean; } export type ICredentialsEntity = ICredentials; diff --git a/libs/dal/src/repositories/integration/integration.schema.ts b/libs/dal/src/repositories/integration/integration.schema.ts index 8bee7315c12..851d823653a 100644 --- a/libs/dal/src/repositories/integration/integration.schema.ts +++ b/libs/dal/src/repositories/integration/integration.schema.ts @@ -94,6 +94,7 @@ const integrationSchema = new Schema( ], }, ], + connected: Schema.Types.Boolean, }, schemaOptions ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6f796e7c77..b9b5c56ea66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -766,6 +766,9 @@ importers: '@uiw/codemirror-extensions-langs': specifier: ^4.23.6 version: 4.23.6(@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3)(@lezer/common@1.2.3))(@codemirror/language-data@6.5.1(@codemirror/view@6.34.3))(@codemirror/language@6.10.3)(@codemirror/legacy-modes@6.4.1)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/javascript@1.4.19)(@lezer/lr@1.4.2) + '@uiw/codemirror-theme-material': + specifier: ^4.23.6 + version: 4.23.6(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3) '@uiw/codemirror-theme-white': specifier: ^4.23.6 version: 4.23.6(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3) @@ -820,6 +823,12 @@ importers: react: specifier: ^18.3.1 version: 18.3.1 + react-colorful: + specifier: ^5.6.1 + version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-confetti: + specifier: ^6.1.0 + version: 6.1.0(react@18.3.1) react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) @@ -17730,6 +17739,9 @@ packages: '@codemirror/language-data': '>=6.0.0' '@codemirror/legacy-modes': '>=6.0.0' + '@uiw/codemirror-theme-material@4.23.6': + resolution: {integrity: sha512-QmFXWseYRPXPJZXG7bNxCIfGhIUQr7OmaBC41uBKttFMNWo09R+xjo7vtkdNeJwGBXySC7ZC4k0FS13jjrPTZw==} + '@uiw/codemirror-theme-white@4.23.6': resolution: {integrity: sha512-plBzEU7QOh8pm3JIxJLJ4YWxIB8t0DPOQrTABzMfFxjdGoGIMe+a/J4zhYdVslSVmwGBXbKDAj5TFqnS/1wrzQ==} @@ -18365,7 +18377,7 @@ packages: hasBin: true add-px-to-style@1.0.0: - resolution: {integrity: sha512-YMyxSlXpPjD8uWekCQGuN40lV4bnZagUwqa2m/uFv1z/tNImSk9fnXVMUI5qwME/zzI3MMQRvjZ+69zyfSSyew==} + resolution: {integrity: sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo=} add-stream@1.0.0: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} @@ -18672,7 +18684,7 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} argv@0.0.2: - resolution: {integrity: sha512-dEamhpPEwRUBpLNHeuCm/v+g0anFByHahxodVO/BbAarHVBBg2MccCwf9K+o1Pof+2btdnkJelYVUWjW/VrATw==} + resolution: {integrity: sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=} engines: {node: '>=0.6.10'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. @@ -19230,7 +19242,7 @@ packages: engines: {node: '>=10.0.0'} batch@0.6.1: - resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + resolution: {integrity: sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=} bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -19440,7 +19452,7 @@ packages: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=} buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -19510,7 +19522,7 @@ packages: engines: {node: '>= 0.8'} bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + resolution: {integrity: sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=} engines: {node: '>= 0.8'} bytes@3.1.2: @@ -20209,7 +20221,7 @@ packages: resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} concat-stream@1.6.2: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} @@ -20314,7 +20326,7 @@ packages: resolution: {integrity: sha512-L2rLOcK0wzWSfSDA33YR+PUHDG10a8px7rUHKWbGLP4YfbsMed2KFUw5fczvDPbT98DDe3LEzviswl810apTEw==} cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + resolution: {integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw=} cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} @@ -21357,7 +21369,7 @@ packages: resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} dom-css@2.1.0: - resolution: {integrity: sha512-w9kU7FAbaSh3QKijL6n59ofAhkkmMJ31GclJIz/vyQdjogfyxcB6Zf8CZyibOERI5o0Hxz30VmJS7+7r5fEj2Q==} + resolution: {integrity: sha1-/bwtWgFdCj4YcuEUcrvQ57nmogI=} dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -21491,7 +21503,7 @@ packages: hasBin: true ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} @@ -24165,7 +24177,7 @@ packages: engines: {node: '>=12'} indexof@0.0.1: - resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==} + resolution: {integrity: sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=} individual@3.0.0: resolution: {integrity: sha512-rUY5vtT748NMRbEMrTNiFfy29BgGZwGXUi2NFUVMWQrogSLzlJvQV9eeMWi+g1aVaQ53tpyLAQtd5x/JH0Nh1g==} @@ -26315,7 +26327,7 @@ packages: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} map-stream@0.0.7: - resolution: {integrity: sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==} + resolution: {integrity: sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=} map-visit@1.0.0: resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} @@ -26535,7 +26547,7 @@ packages: resolution: {integrity: sha512-88ZRGcNxAq4EH38cQ4D85PM57pikCwS8Z99EWHODxN7KBY+UuPiqzRTtZzS8KTXO/ywSWbdjjJST2Hly/EQxLw==} media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} engines: {node: '>= 0.6'} mediaquery-text@1.2.0: @@ -28297,7 +28309,7 @@ packages: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} pause@0.0.1: - resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + resolution: {integrity: sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=} peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} @@ -29402,7 +29414,7 @@ packages: engines: {node: '>=10'} prefix-style@2.0.1: - resolution: {integrity: sha512-gdr1MBNVT0drzTq95CbSNdsrBDoHGlb2aDJP/FoY+1e+jSDPOb1Cv554gH2MGiSr2WTcXi/zu+NaFzfcHQkfBQ==} + resolution: {integrity: sha1-ZrupqHDP2jCKXcIOhekSCTLJWgY=} prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} @@ -29801,7 +29813,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.10.4: @@ -30168,6 +30179,12 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + react-confetti@6.1.0: + resolution: {integrity: sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==} + engines: {node: '>=10.18'} + peerDependencies: + react: ^16.3.0 || ^17.0.1 || ^18.0.0 + react-css-theme-switcher@0.3.0: resolution: {integrity: sha512-RV+fJ6mSbtsLOgIgeL4Q8MEH4Hyl72tQvGpCFBbk3ia6ie3KzXO1gfbKTV2q1ryP3hBpmyy1qrX+6E1f937A1A==} engines: {node: '>=10'} @@ -30175,7 +30192,7 @@ packages: react: '>=16' react-custom-scrollbars@4.2.1: - resolution: {integrity: sha512-VtJTUvZ7kPh/auZWIbBRceGPkE30XBYe+HktFxuMWBR2eVQQ+Ur6yFJMoaYcNpyGq22uYJ9Wx4UAEcC0K+LNPQ==} + resolution: {integrity: sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts=} peerDependencies: react: ^0.14.0 || ^15.0.0 || ^16.0.0 react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 @@ -30824,7 +30841,7 @@ packages: resolution: {integrity: sha512-zEMsvb4GgxVKBBTHgy2tte67RYBZx2Kyg9mTYpg+JfATHDqYJqhuC3zG1VoiYhDVP5JaB5+mPKcAvdnT0n3jxA==} remove-accents@0.4.2: - resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} + resolution: {integrity: sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=} remove-markdown@0.3.0: resolution: {integrity: sha512-5392eIuy1mhjM74739VunOlsOYKjsH82rQcTBlJ1bkICVC3dQ3ksQzTHh4jGHQFnM+1xzLzcFOMH+BofqXhroQ==} @@ -31172,7 +31189,7 @@ packages: hasBin: true run-p@0.0.0: - resolution: {integrity: sha512-ZLiUUVOXJcM/S1hMnm6Ooc1zAgAx98Mmn1qyA+y3WNeK7hOTGAusVR5r3uOQJ0NuUxZt7J9vNusYNNVgKPSbww==} + resolution: {integrity: sha1-cWpVvRICd6nZDaX4IzO3C5GAiPI=} run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -31323,7 +31340,7 @@ packages: engines: {node: '>=4'} secure-compare@3.0.1: - resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + resolution: {integrity: sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=} secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -32761,7 +32778,7 @@ packages: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} to-camel-case@1.0.0: - resolution: {integrity: sha512-nD8pQi5H34kyu1QDMFjzEIYqk0xa9Alt6ZfrdEMuHCFOfTLhDG5pgTu/aAM9Wt9lXILwlXmWP43b8sav0GNE8Q==} + resolution: {integrity: sha1-GlYFSy+daWKYzmamCJcyK29CPkY=} to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} @@ -33216,6 +33233,9 @@ packages: resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} engines: {node: '>= 0.8.0'} + tween-functions@1.2.0: + resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==} + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -57656,6 +57676,14 @@ snapshots: - '@lezer/javascript' - '@lezer/lr' + '@uiw/codemirror-theme-material@4.23.6(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3)': + dependencies: + '@uiw/codemirror-themes': 4.23.6(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + '@uiw/codemirror-theme-white@4.23.6(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3)': dependencies: '@uiw/codemirror-themes': 4.23.6(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3) @@ -75557,6 +75585,11 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-confetti@6.1.0(react@18.3.1): + dependencies: + react: 18.3.1 + tween-functions: 1.2.0 + react-css-theme-switcher@0.3.0(react@18.3.1): dependencies: react: 18.3.1 @@ -79993,6 +80026,8 @@ snapshots: tv4@1.3.0: {} + tween-functions@1.2.0: {} + tweetnacl@0.14.5: {} tweetnacl@1.0.3: {} From 184c54905b0ef71234344f35bee26c4d6fe43b9f Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Wed, 4 Dec 2024 13:52:00 +0200 Subject: [PATCH 07/18] fix(api): session wrap connected true --- apps/api/src/app/inbox/usecases/session/session.usecase.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/inbox/usecases/session/session.usecase.ts b/apps/api/src/app/inbox/usecases/session/session.usecase.ts index f63b72fb0ec..5804dfd3602 100644 --- a/apps/api/src/app/inbox/usecases/session/session.usecase.ts +++ b/apps/api/src/app/inbox/usecases/session/session.usecase.ts @@ -111,7 +111,9 @@ export class Session { _environmentId: environment._id, }, { - connected: true, + $set: { + connected: true, + }, } ); } From d1c386585c731ec5b91b0384eb62157ea8045865 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Wed, 4 Dec 2024 14:16:59 +0200 Subject: [PATCH 08/18] Revert "Revert "fix(js): Remove @novu/shared dependency" (#7206)" This reverts commit adde8dd899d826abad39e9ab13c2b62f6321ba69. --- packages/js/jest.config.cjs | 5 ++ packages/js/jest.setup.ts | 2 - packages/js/package.json | 1 - packages/js/src/api/http-client.ts | 119 ++++++++++++++++++++++++++ packages/js/src/api/inbox-service.ts | 31 +++---- packages/js/src/base-module.test.ts | 71 ++++++++++++++++ packages/js/src/global.d.ts | 11 +-- packages/js/src/novu.test.ts | 121 ++++++++++----------------- packages/js/src/novu.ts | 8 +- packages/js/src/session/session.ts | 1 - packages/js/tsconfig.json | 2 +- packages/js/tsup.config.ts | 9 +- pnpm-lock.yaml | 3 - 13 files changed, 272 insertions(+), 112 deletions(-) create mode 100644 packages/js/src/api/http-client.ts create mode 100644 packages/js/src/base-module.test.ts diff --git a/packages/js/jest.config.cjs b/packages/js/jest.config.cjs index bde0ef6b7f8..a03abac029d 100644 --- a/packages/js/jest.config.cjs +++ b/packages/js/jest.config.cjs @@ -1,4 +1,9 @@ module.exports = { preset: 'ts-jest', setupFiles: ['./jest.setup.ts'], + globals: { + NOVU_API_VERSION: '2024-06-26', + PACKAGE_NAME: '@novu/js', + PACKAGE_VERSION: 'test', + }, }; diff --git a/packages/js/jest.setup.ts b/packages/js/jest.setup.ts index 85cf83a3672..e69de29bb2d 100644 --- a/packages/js/jest.setup.ts +++ b/packages/js/jest.setup.ts @@ -1,2 +0,0 @@ -global.PACKAGE_VERSION = 'test-version'; -global.PACKAGE_NAME = 'test-package'; diff --git a/packages/js/package.json b/packages/js/package.json index 18b739b6201..02b14a5fea2 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -126,7 +126,6 @@ }, "dependencies": { "@floating-ui/dom": "^1.6.7", - "@novu/client": "workspace:*", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "mitt": "^3.0.1", diff --git a/packages/js/src/api/http-client.ts b/packages/js/src/api/http-client.ts new file mode 100644 index 00000000000..b0cb50566ee --- /dev/null +++ b/packages/js/src/api/http-client.ts @@ -0,0 +1,119 @@ +export type HttpClientOptions = { + apiVersion?: string; + backendUrl?: string; + userAgent?: string; +}; + +const DEFAULT_API_VERSION = 'v1'; +const DEFAULT_BACKEND_URL = 'https://api.novu.co'; +const DEFAULT_USER_AGENT = `${PACKAGE_NAME}@${PACKAGE_VERSION}`; + +export class HttpClient { + private backendUrl: string; + private apiVersion: string; + private headers: Record; + + constructor(options: HttpClientOptions = {}) { + const { + apiVersion = DEFAULT_API_VERSION, + backendUrl = DEFAULT_BACKEND_URL, + userAgent = DEFAULT_USER_AGENT, + } = options || {}; + this.apiVersion = apiVersion; + this.backendUrl = `${backendUrl}/${this.apiVersion}`; + this.headers = { + 'Novu-API-Version': NOVU_API_VERSION, + 'Content-Type': 'application/json', + 'User-Agent': userAgent, + }; + } + + setAuthorizationToken(token: string) { + this.headers.Authorization = `Bearer ${token}`; + } + + setHeaders(headers: Record) { + this.headers = { + ...this.headers, + ...headers, + }; + } + + async get(path: string, searchParams?: URLSearchParams, unwrapEnvelope = true) { + return this.doFetch({ + path, + searchParams, + options: { + method: 'GET', + }, + unwrapEnvelope, + }); + } + + async post(path: string, body?: any) { + return this.doFetch({ + path, + options: { + method: 'POST', + body, + }, + }); + } + + async patch(path: string, body?: any) { + return this.doFetch({ + path, + options: { + method: 'PATCH', + body, + }, + }); + } + + async delete(path: string, body?: any) { + return this.doFetch({ + path, + options: { + method: 'DELETE', + body, + }, + }); + } + + private async doFetch({ + path, + searchParams, + options, + unwrapEnvelope = true, + }: { + path: string; + searchParams?: URLSearchParams; + options?: RequestInit; + unwrapEnvelope?: boolean; + }) { + const fullUrl = combineUrl(this.backendUrl, path, searchParams ? `?${searchParams.toString()}` : ''); + const reqInit = { + method: options?.method || 'GET', + headers: { ...this.headers, ...(options?.headers || {}) }, + body: options?.body ? JSON.stringify(options.body) : undefined, + }; + + const response = await fetch(fullUrl, reqInit); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(`${this.headers['User-Agent']} error. Status: ${response.status}, Message: ${errorData.message}`); + } + if (response.status === 204) { + return undefined as unknown as T; + } + + const res = await response.json(); + + return (unwrapEnvelope ? res.data : res) as Promise; + } +} + +function combineUrl(...args: string[]): string { + return args.map((part) => part.replace(/^\/+|\/+$/g, '')).join('/'); +} diff --git a/packages/js/src/api/inbox-service.ts b/packages/js/src/api/inbox-service.ts index e375934c3ac..70573f4f439 100644 --- a/packages/js/src/api/inbox-service.ts +++ b/packages/js/src/api/inbox-service.ts @@ -1,4 +1,3 @@ -import { ApiOptions, HttpClient } from '@novu/client'; import type { ActionTypeEnum, ChannelPreference, @@ -7,10 +6,10 @@ import type { PreferencesResponse, Session, } from '../types'; +import { HttpClient, HttpClientOptions } from './http-client'; -export type InboxServiceOptions = ApiOptions; +export type InboxServiceOptions = HttpClientOptions; -const NOVU_API_VERSION = '2024-06-26'; const INBOX_ROUTE = '/inbox'; const INBOX_NOTIFICATIONS_ROUTE = `${INBOX_ROUTE}/notifications`; @@ -20,10 +19,6 @@ export class InboxService { constructor(options: InboxServiceOptions = {}) { this.#httpClient = new HttpClient(options); - this.#httpClient.updateHeaders({ - 'Novu-API-Version': NOVU_API_VERSION, - 'Novu-User-Agent': options.userAgent || '@novu/js', - }); } async initializeSession({ @@ -61,24 +56,24 @@ export class InboxService { after?: string; offset?: number; }): Promise<{ data: InboxNotification[]; hasMore: boolean; filter: NotificationFilter }> { - const queryParams = new URLSearchParams(`limit=${limit}`); + const searchParams = new URLSearchParams(`limit=${limit}`); if (after) { - queryParams.append('after', after); + searchParams.append('after', after); } if (offset) { - queryParams.append('offset', `${offset}`); + searchParams.append('offset', `${offset}`); } if (tags) { - tags.forEach((tag) => queryParams.append('tags[]', tag)); + tags.forEach((tag) => searchParams.append('tags[]', tag)); } if (read !== undefined) { - queryParams.append('read', `${read}`); + searchParams.append('read', `${read}`); } if (archived !== undefined) { - queryParams.append('archived', `${archived}`); + searchParams.append('archived', `${archived}`); } - return this.#httpClient.getFullResponse(`${INBOX_NOTIFICATIONS_ROUTE}?${queryParams.toString()}`); + return this.#httpClient.get(INBOX_NOTIFICATIONS_ROUTE, searchParams, false); } count({ filters }: { filters: Array<{ tags?: string[]; read?: boolean; archived?: boolean }> }): Promise<{ @@ -87,7 +82,13 @@ export class InboxService { filter: NotificationFilter; }>; }> { - return this.#httpClient.getFullResponse(`${INBOX_NOTIFICATIONS_ROUTE}/count?filters=${JSON.stringify(filters)}`); + return this.#httpClient.get( + `${INBOX_NOTIFICATIONS_ROUTE}/count`, + new URLSearchParams({ + filters: JSON.stringify(filters), + }), + false + ); } read(notificationId: string): Promise { diff --git a/packages/js/src/base-module.test.ts b/packages/js/src/base-module.test.ts new file mode 100644 index 00000000000..58f2a43287d --- /dev/null +++ b/packages/js/src/base-module.test.ts @@ -0,0 +1,71 @@ +import { InboxService } from './api'; +import { BaseModule } from './base-module'; +import { NovuEventEmitter } from './event-emitter'; + +beforeAll(() => jest.spyOn(global, 'fetch')); +afterAll(() => jest.restoreAllMocks()); + +describe('callWithSession(fn)', () => { + test('should invoke callback function immediately if session is initialized', async () => { + const emitter = new NovuEventEmitter(); + const bm = new BaseModule({ + inboxServiceInstance: { + isSessionInitialized: true, + } as InboxService, + eventEmitterInstance: emitter, + }); + + const cb = jest.fn(); + bm.callWithSession(cb); + expect(cb).toHaveBeenCalled(); + }); + + test('should invoke callback function as soon as session is initialized', async () => { + const emitter = new NovuEventEmitter(); + const bm = new BaseModule({ + inboxServiceInstance: {} as InboxService, + eventEmitterInstance: emitter, + }); + + const cb = jest.fn(); + + bm.callWithSession(cb); + expect(cb).not.toHaveBeenCalled(); + + emitter.emit('session.initialize.resolved', { + args: { + applicationIdentifier: 'foo', + subscriberId: 'bar', + }, + data: { + token: 'cafebabe', + totalUnreadCount: 10, + removeNovuBranding: true, + }, + }); + + expect(cb).toHaveBeenCalled(); + }); + + test('should return an error if session initialization failed', async () => { + const emitter = new NovuEventEmitter(); + const bm = new BaseModule({ + inboxServiceInstance: {} as InboxService, + eventEmitterInstance: emitter, + }); + + emitter.emit('session.initialize.resolved', { + args: { + applicationIdentifier: 'foo', + subscriberId: 'bar', + }, + error: new Error('Failed to initialize session'), + }); + + const cb = jest.fn(); + const result = await bm.callWithSession(cb); + expect(result).toEqual({ + error: new Error('Failed to initialize session, please contact the support'), + }); + }); +}); diff --git a/packages/js/src/global.d.ts b/packages/js/src/global.d.ts index 9741972a4ed..2ea80da406b 100644 --- a/packages/js/src/global.d.ts +++ b/packages/js/src/global.d.ts @@ -1,10 +1,11 @@ -/* eslint-disable vars-on-top */ -/* eslint-disable no-var */ -import { Novu } from './novu'; +import type { Novu } from './novu'; + +export {}; declare global { - var PACKAGE_NAME: string; - var PACKAGE_VERSION: string; + const NOVU_API_VERSION: string; + const PACKAGE_NAME: string; + const PACKAGE_VERSION: string; interface Window { Novu: typeof Novu; } diff --git a/packages/js/src/novu.test.ts b/packages/js/src/novu.test.ts index 49fa73342b6..5e134a646ed 100644 --- a/packages/js/src/novu.test.ts +++ b/packages/js/src/novu.test.ts @@ -1,6 +1,6 @@ -import { ListNotificationsArgs } from './notifications'; import { Novu } from './novu'; -import { NovuError } from './utils/errors'; + +const mockSessionResponse = { data: { token: 'cafebabe' } }; const mockNotificationsResponse = { data: [], @@ -8,100 +8,71 @@ const mockNotificationsResponse = { filter: { tags: [], read: false, archived: false }, }; -const post = jest.fn().mockResolvedValue({ token: 'token', profile: 'profile' }); -const getFullResponse = jest.fn(() => mockNotificationsResponse); -const updateHeaders = jest.fn(); -const setAuthorizationToken = jest.fn(); - -jest.mock('@novu/client', () => ({ - ...jest.requireActual('@novu/client'), - HttpClient: jest.fn().mockImplementation(() => { - const httpClient = { - post, - getFullResponse, - updateHeaders, - setAuthorizationToken, +async function mockFetch(url: string, reqInit: Request) { + if (url.includes('/session')) { + return { + ok: true, + status: 200, + json: async () => mockSessionResponse, }; + } + if (url.includes('/notifications')) { + return { + ok: true, + status: 200, + json: async () => mockNotificationsResponse, + }; + } + throw new Error(`Unmocked request: ${url}`); +} - return httpClient; - }), -})); +beforeAll(() => jest.spyOn(global, 'fetch')); +afterAll(() => jest.restoreAllMocks()); describe('Novu', () => { + const applicationIdentifier = 'foo'; + const subscriberId = 'bar'; + beforeEach(() => { - jest.clearAllMocks(); + // @ts-ignore + global.fetch.mockImplementation(mockFetch) as jest.Mock; }); - describe('lazy session initialization', () => { - test('should call the queued notifications.list after the session is initialized', async () => { + describe('http client', () => { + test('should call the notifications.list after the session is initialized', async () => { const options = { limit: 10, offset: 0, }; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - const { data } = await novu.notifications.list(options); - expect(post).toHaveBeenCalledTimes(1); - expect(getFullResponse).toHaveBeenCalledWith('/inbox/notifications?limit=10'); - expect(data).toEqual({ - notifications: mockNotificationsResponse.data, - hasMore: mockNotificationsResponse.hasMore, - filter: mockNotificationsResponse.filter, + const novu = new Novu({ applicationIdentifier, subscriberId }); + expect(fetch).toHaveBeenNthCalledWith(1, 'https://api.novu.co/v1/inbox/session/', { + method: 'POST', + body: JSON.stringify({ applicationIdentifier, subscriberId }), + headers: { + 'Content-Type': 'application/json', + 'Novu-API-Version': '2024-06-26', + 'User-Agent': '@novu/js@test', + }, }); - }); - test('should call the notifications.list right away when session is already initialized', async () => { - const options: ListNotificationsArgs = { - limit: 10, - offset: 0, - }; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => { - setTimeout(resolve, 10); + const { data } = await novu.notifications.list(options); + expect(fetch).toHaveBeenNthCalledWith(2, 'https://api.novu.co/v1/inbox/notifications/?limit=10', { + method: 'GET', + body: undefined, + headers: { + Authorization: 'Bearer cafebabe', + 'Content-Type': 'application/json', + 'Novu-API-Version': '2024-06-26', + 'User-Agent': '@novu/js@test', + }, }); - const { data } = await novu.notifications.list({ limit: 10, offset: 0 }); - - expect(post).toHaveBeenCalledTimes(1); - expect(getFullResponse).toHaveBeenCalledWith('/inbox/notifications?limit=10'); expect(data).toEqual({ notifications: mockNotificationsResponse.data, hasMore: mockNotificationsResponse.hasMore, filter: mockNotificationsResponse.filter, }); }); - - test('should reject the queued notifications.list if session initialization fails', async () => { - const options = { - limit: 10, - offset: 0, - }; - const expectedError = 'reason'; - post.mockRejectedValueOnce(expectedError); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - - const { error } = await novu.notifications.list(options); - - expect(error).toEqual(new NovuError('Failed to initialize session, please contact the support', expectedError)); - }); - - test('should reject the notifications.list right away when session initialization has failed', async () => { - const options = { - limit: 10, - offset: 0, - }; - const expectedError = 'reason'; - post.mockRejectedValueOnce(expectedError); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => { - setTimeout(resolve, 10); - }); - - const { error } = await novu.notifications.list(options); - - expect(error).toEqual(new NovuError('Failed to initialize session, please contact the support', expectedError)); - }); }); }); diff --git a/packages/js/src/novu.ts b/packages/js/src/novu.ts index 01c769e5e53..feebd4564c3 100644 --- a/packages/js/src/novu.ts +++ b/packages/js/src/novu.ts @@ -8,12 +8,6 @@ import { PRODUCTION_BACKEND_URL } from './utils/config'; import type { NovuOptions } from './types'; import { InboxService } from './api'; -// @ts-ignore -const version = PACKAGE_VERSION; -// @ts-ignore -const name = PACKAGE_NAME; -const userAgent = `${name}@${version}`; - export class Novu implements Pick { #emitter: NovuEventEmitter; #session: Session; @@ -32,7 +26,7 @@ export class Novu implements Pick { constructor(options: NovuOptions) { this.#inboxService = new InboxService({ backendUrl: options.backendUrl ?? PRODUCTION_BACKEND_URL, - userAgent: options.__userAgent ?? userAgent, + userAgent: options.__userAgent, }); this.#emitter = new NovuEventEmitter(); this.#session = new Session( diff --git a/packages/js/src/session/session.ts b/packages/js/src/session/session.ts index bef8f3ac154..1e72a768606 100644 --- a/packages/js/src/session/session.ts +++ b/packages/js/src/session/session.ts @@ -21,7 +21,6 @@ export class Session { try { const { applicationIdentifier, subscriberId, subscriberHash } = this.#options; this.#emitter.emit('session.initialize.pending', { args: this.#options }); - const response = await this.#inboxService.initializeSession({ applicationIdentifier, subscriberId, diff --git a/packages/js/tsconfig.json b/packages/js/tsconfig.json index 0e5d3b81e19..4a83a390ddf 100644 --- a/packages/js/tsconfig.json +++ b/packages/js/tsconfig.json @@ -19,5 +19,5 @@ "removeComments": false }, "include": ["src/**/*", "src/**/*.d.ts"], - "exclude": ["src/**/*.test.ts", "src/*.test.ts", "node_modules", "**/node_modules/*"] + "exclude": ["node_modules", "**/node_modules/*"] } diff --git a/packages/js/tsup.config.ts b/packages/js/tsup.config.ts index 52cae64f1e5..ab3d4638c22 100644 --- a/packages/js/tsup.config.ts +++ b/packages/js/tsup.config.ts @@ -22,14 +22,13 @@ const buildCSS = async () => { fs.writeFileSync(destinationCssFilePath, processedCss); }; -const isProd = process.env?.NODE_ENV === 'production'; +const isProd = process.env.NODE_ENV === 'production'; const baseConfig: Options = { splitting: true, sourcemap: false, clean: true, esbuildPlugins: [solidPlugin()], - define: { PACKAGE_NAME: `"${name}"`, PACKAGE_VERSION: `"${version}"`, __DEV__: `${!isProd}` }, }; const baseModuleConfig: Options = { @@ -42,6 +41,12 @@ const baseModuleConfig: Options = { 'themes/index': './src/ui/themes/index.ts', 'internal/index': './src/ui/internal/index.ts', }, + define: { + NOVU_API_VERSION: `"2024-06-26"`, + PACKAGE_NAME: `"${name}"`, + PACKAGE_VERSION: `"${version}"`, + __DEV__: `${isProd ? false : true}`, + }, }; export default defineConfig((config: Options) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9b5c56ea66..e7421d217e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3647,9 +3647,6 @@ importers: '@floating-ui/dom': specifier: ^1.6.7 version: 1.6.7 - '@novu/client': - specifier: workspace:* - version: link:../client class-variance-authority: specifier: ^0.7.0 version: 0.7.0 From 17eaa7508459b0bf3e2c33f545042c834233bb07 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Wed, 4 Dec 2024 14:50:03 +0200 Subject: [PATCH 09/18] fix(dashboard): Eliminate React warnings on SVG props --- .../src/components/icons/flags/eu.tsx | 2 +- .../src/components/icons/opt-in-arrow.tsx | 2 +- apps/dashboard/src/components/icons/plug.tsx | 36 +++++++++---------- .../src/components/icons/shield-zap.tsx | 6 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/dashboard/src/components/icons/flags/eu.tsx b/apps/dashboard/src/components/icons/flags/eu.tsx index 82f0243a6cf..396c7579f6c 100644 --- a/apps/dashboard/src/components/icons/flags/eu.tsx +++ b/apps/dashboard/src/components/icons/flags/eu.tsx @@ -1,7 +1,7 @@ export function EuFlag(props: React.SVGProps) { return ( - + ) => { ); diff --git a/apps/dashboard/src/components/icons/plug.tsx b/apps/dashboard/src/components/icons/plug.tsx index f91d1f6702f..8136ad86f1f 100644 --- a/apps/dashboard/src/components/icons/plug.tsx +++ b/apps/dashboard/src/components/icons/plug.tsx @@ -7,49 +7,49 @@ export function Plug(props: React.ComponentPropsWithoutRef<'svg'>) { id="Vector" d="M1 24.9906L3.95687 22.0337" stroke="#DD2450" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" /> diff --git a/apps/dashboard/src/components/icons/shield-zap.tsx b/apps/dashboard/src/components/icons/shield-zap.tsx index 4c9cb20f2a0..9267a4b6fda 100644 --- a/apps/dashboard/src/components/icons/shield-zap.tsx +++ b/apps/dashboard/src/components/icons/shield-zap.tsx @@ -13,9 +13,9 @@ export function ShieldZap(props: React.ComponentPropsWithoutRef<'svg'>) { id="Icon" d="M14.083 8.11513L10.833 11.3651L15.1663 13.5318L11.9163 16.7818M21.6663 12.9901C21.6663 18.3076 15.8662 22.1751 13.7558 23.4063C13.516 23.5462 13.3961 23.6161 13.2268 23.6524C13.0955 23.6806 12.9039 23.6806 12.7725 23.6524C12.6033 23.6161 12.4834 23.5462 12.2435 23.4063C10.1331 22.1751 4.33301 18.3076 4.33301 12.9901V7.8092C4.33301 6.94306 4.33301 6.51 4.47466 6.13773C4.59981 5.80887 4.80316 5.51544 5.06714 5.28279C5.36596 5.01944 5.77146 4.86738 6.58245 4.56326L12.3911 2.38503C12.6163 2.30057 12.7289 2.25835 12.8447 2.24161C12.9475 2.22676 13.0519 2.22676 13.1546 2.24161C13.2705 2.25835 13.3831 2.30057 13.6083 2.38503L19.4169 4.56326C20.2279 4.86738 20.6334 5.01944 20.9322 5.28279C21.1962 5.51544 21.3995 5.80887 21.5247 6.13773C21.6663 6.51 21.6663 6.94306 21.6663 7.8092V12.9901Z" stroke="#DD2450" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" /> From 780c05c232515491faa7abb002a1c86987b585b7 Mon Sep 17 00:00:00 2001 From: George Djabarov <39195835+djabarovgeorge@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:55:50 +0200 Subject: [PATCH 10/18] fix(api): step naming (#7140) --- .../app/bridge/usecases/sync/sync.usecase.ts | 2 +- .../delete-notification-template.usecase.ts | 16 +----- .../get-notification-template.usecase.ts | 2 +- .../build-step-data.command.ts | 5 +- .../build-step-data.usecase.ts | 12 ++-- .../build-workflow-test-data.command.ts | 2 +- .../build-workflow-test-data.usecase.ts | 2 +- .../workflow-test-data.command.ts | 5 ++ .../generate-preview.command.ts | 4 +- .../generate-preview.usecase.ts | 6 +- .../get-workflow/get-workflow.command.ts | 2 +- .../get-workflow/get-workflow.usecase.ts | 2 +- .../patch-step-data/patch-step.command.ts | 7 +-- .../patch-step-data/patch-step.usecase.ts | 10 ++-- .../patch-workflow/patch-workflow.command.ts | 3 +- .../patch-workflow/patch-workflow.usecase.ts | 4 +- .../sync-to-environment.command.ts | 3 +- .../sync-to-environment.usecase.ts | 10 ++-- .../upsert-workflow.command.ts | 4 +- .../upsert-workflow.usecase.ts | 15 +++-- .../app/workflows-v2/workflow.controller.ts | 55 +++++++++++-------- .../create-workflow.usecase.ts | 2 +- .../delete-workflow.command.ts | 2 +- .../delete-workflow.usecase.ts | 2 +- .../get-workflow-by-ids.command.ts | 2 +- .../get-workflow-by-ids.usecase.ts | 8 +-- .../update-workflow.usecase.ts | 4 +- .../dto/workflows/workflow-response.dto.ts | 2 - 28 files changed, 94 insertions(+), 99 deletions(-) create mode 100644 apps/api/src/app/workflows-v2/usecases/build-workflow-test-data/workflow-test-data.command.ts diff --git a/apps/api/src/app/bridge/usecases/sync/sync.usecase.ts b/apps/api/src/app/bridge/usecases/sync/sync.usecase.ts index 5d11e2d8931..41d87e35c98 100644 --- a/apps/api/src/app/bridge/usecases/sync/sync.usecase.ts +++ b/apps/api/src/app/bridge/usecases/sync/sync.usecase.ts @@ -134,7 +134,7 @@ export class Sync { environmentId: command.environmentId, organizationId: command.organizationId, userId: command.userId, - identifierOrInternalId: workflow._id, + workflowIdOrInternalId: workflow._id, }) ) ); diff --git a/apps/api/src/app/workflows-v1/usecases/delete-notification-template/delete-notification-template.usecase.ts b/apps/api/src/app/workflows-v1/usecases/delete-notification-template/delete-notification-template.usecase.ts index 5b72d957980..bf8681082ec 100644 --- a/apps/api/src/app/workflows-v1/usecases/delete-notification-template/delete-notification-template.usecase.ts +++ b/apps/api/src/app/workflows-v1/usecases/delete-notification-template/delete-notification-template.usecase.ts @@ -26,15 +26,14 @@ export class DeleteNotificationTemplate { private changeRepository: ChangeRepository, private analyticsService: AnalyticsService, private deleteWorkflowUseCase: DeleteWorkflowUseCase, - private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase, private notificationTemplateRepository: NotificationTemplateRepository ) {} async execute(command: DeleteNotificationTemplateCommand) { try { - const workflowEntity = await this.getWorkflowByIdsUseCase.execute( - GetWorkflowByIdsCommand.create({ - identifierOrInternalId: command.templateId, + await this.deleteWorkflowUseCase.execute( + DeleteWorkflowCommand.create({ + workflowIdOrInternalId: command.templateId, environmentId: command.environmentId, organizationId: command.organizationId, userId: command.userId, @@ -47,15 +46,6 @@ export class DeleteNotificationTemplate { command.templateId ); - await this.deleteWorkflowUseCase.execute( - DeleteWorkflowCommand.create({ - identifierOrInternalId: command.templateId, - environmentId: command.environmentId, - organizationId: command.organizationId, - userId: command.userId, - }) - ); - const item: NotificationTemplateEntity = ( await this.notificationTemplateRepository.findDeleted({ _environmentId: command.environmentId, diff --git a/apps/api/src/app/workflows-v1/usecases/get-notification-template/get-notification-template.usecase.ts b/apps/api/src/app/workflows-v1/usecases/get-notification-template/get-notification-template.usecase.ts index 03fd7050a94..9181b76f8f5 100644 --- a/apps/api/src/app/workflows-v1/usecases/get-notification-template/get-notification-template.usecase.ts +++ b/apps/api/src/app/workflows-v1/usecases/get-notification-template/get-notification-template.usecase.ts @@ -15,7 +15,7 @@ export class GetNotificationTemplate { async execute(command: GetNotificationTemplateCommand): Promise { const workflow = await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ - identifierOrInternalId: command.workflowIdOrIdentifier, + workflowIdOrInternalId: command.workflowIdOrIdentifier, environmentId: command.environmentId, organizationId: command.organizationId, userId: command.userId, diff --git a/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.command.ts b/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.command.ts index 4df980d5e1e..c49ab48f19f 100644 --- a/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.command.ts @@ -1,13 +1,12 @@ import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; import { IsNotEmpty, IsString } from 'class-validator'; -import { IdentifierOrInternalId } from '@novu/shared'; export class BuildStepDataCommand extends EnvironmentWithUserObjectCommand { @IsString() @IsNotEmpty() - identifierOrInternalId: IdentifierOrInternalId; + workflowIdOrInternalId: string; @IsString() @IsNotEmpty() - stepId: IdentifierOrInternalId; + stepIdOrInternalId: string; } diff --git a/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts b/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts index 8225b0ce2c0..07037857af9 100644 --- a/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts @@ -11,7 +11,7 @@ export class BuildStepDataUsecase { constructor( private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase, private controlValuesRepository: ControlValuesRepository, - private buildAvailableVariableSchemaUsecase: BuildAvailableVariableSchemaUsecase // Dependency injection for new use case + private buildAvailableVariableSchemaUsecase: BuildAvailableVariableSchemaUsecase ) {} @InstrumentUsecase() @@ -38,7 +38,7 @@ export class BuildStepDataUsecase { variables: this.buildAvailableVariableSchemaUsecase.execute({ stepDatabaseId: currentStep._templateId, workflow, - }), // Use the new use case to build variables schema + }), name: currentStep.name, _id: currentStep._templateId, stepId: currentStep.stepId, @@ -53,7 +53,7 @@ export class BuildStepDataUsecase { @Instrument() private async fetchWorkflow(command: BuildStepDataCommand) { return await this.getWorkflowByIdsUseCase.execute({ - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, @@ -76,14 +76,14 @@ export class BuildStepDataUsecase { @Instrument() private async loadStepsFromDb(command: BuildStepDataCommand, workflow: NotificationTemplateEntity) { const currentStep = workflow.steps.find( - (stepItem) => stepItem._id === command.stepId || stepItem.stepId === command.stepId + (stepItem) => stepItem._id === command.stepIdOrInternalId || stepItem.stepId === command.stepIdOrInternalId ); if (!currentStep) { throw new BadRequestException({ message: 'No step found', - stepId: command.stepId, - workflowId: command.identifierOrInternalId, + stepId: command.stepIdOrInternalId, + workflowId: command.workflowIdOrInternalId, }); } diff --git a/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.command.ts b/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.command.ts index 34c92963f32..1c3b20e6518 100644 --- a/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.command.ts @@ -4,5 +4,5 @@ import { IsDefined, IsString } from 'class-validator'; export class WorkflowTestDataCommand extends EnvironmentWithUserObjectCommand { @IsString() @IsDefined() - identifierOrInternalId: string; + workflowIdOrInternalId: string; } diff --git a/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts b/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts index 04a1d88f87f..b9e1b5571d5 100644 --- a/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts @@ -37,7 +37,7 @@ export class BuildWorkflowTestDataUseCase { environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, }) ); } diff --git a/apps/api/src/app/workflows-v2/usecases/build-workflow-test-data/workflow-test-data.command.ts b/apps/api/src/app/workflows-v2/usecases/build-workflow-test-data/workflow-test-data.command.ts new file mode 100644 index 00000000000..67ab540285e --- /dev/null +++ b/apps/api/src/app/workflows-v2/usecases/build-workflow-test-data/workflow-test-data.command.ts @@ -0,0 +1,5 @@ +import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; + +export class WorkflowTestDataCommand extends EnvironmentWithUserObjectCommand { + workflowIdOrInternalId: string; +} diff --git a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.command.ts b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.command.ts index d440a13cffa..1e4d5e1b21f 100644 --- a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.command.ts @@ -2,7 +2,7 @@ import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; import { GeneratePreviewRequestDto } from '@novu/shared'; export class GeneratePreviewCommand extends EnvironmentWithUserObjectCommand { - identifierOrInternalId: string; - stepDatabaseId: string; + workflowIdOrInternalId: string; + stepIdOrInternalId: string; generatePreviewRequestDto: GeneratePreviewRequestDto; } diff --git a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts index 6ba1be7b38b..b1e14de81f3 100644 --- a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts @@ -136,7 +136,7 @@ export class GeneratePreviewUsecase { private async findWorkflow(command: GeneratePreviewCommand) { return await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, @@ -147,8 +147,8 @@ export class GeneratePreviewUsecase { @Instrument() private async getStepData(command: GeneratePreviewCommand) { return await this.buildStepDataUsecase.execute({ - identifierOrInternalId: command.identifierOrInternalId, - stepId: command.stepDatabaseId, + workflowIdOrInternalId: command.workflowIdOrInternalId, + stepIdOrInternalId: command.stepIdOrInternalId, user: command.user, }); } diff --git a/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.command.ts b/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.command.ts index 30a21b49d6e..04c0c222aef 100644 --- a/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.command.ts @@ -4,5 +4,5 @@ import { IsDefined, IsString } from 'class-validator'; export class GetWorkflowCommand extends EnvironmentWithUserObjectCommand { @IsString() @IsDefined() - identifierOrInternalId: string; + workflowIdOrInternalId: 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 776b5d7b744..3e43fc42204 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 @@ -17,7 +17,7 @@ export class GetWorkflowUseCase { environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, }) ); diff --git a/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.command.ts b/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.command.ts index 98d9a16b940..ecb22ca3171 100644 --- a/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.command.ts @@ -1,15 +1,14 @@ import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; -import { IsArray, IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator'; -import { IdentifierOrInternalId } from '@novu/shared'; +import { IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator'; export class PatchStepCommand extends EnvironmentWithUserObjectCommand { @IsString() @IsNotEmpty() - identifierOrInternalId: IdentifierOrInternalId; + workflowIdOrInternalId: string; @IsString() @IsNotEmpty() - stepId: IdentifierOrInternalId; + stepIdOrInternalId: string; @IsString() @IsOptional() diff --git a/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.usecase.ts b/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.usecase.ts index 7020e79aaec..26b2126c0d5 100644 --- a/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/patch-step-data/patch-step.usecase.ts @@ -34,7 +34,7 @@ export class PatchStepUsecase { }); await this.persistWorkflow(updatedWorkflow, command.user); - return await this.buildStepDataUsecase.execute({ ...command }); + return await this.buildStepDataUsecase.execute(command); } private async patchFieldsOnPersistedItems(command: PatchStepCommand, persistedItems: ValidNotificationWorkflow) { @@ -76,7 +76,7 @@ export class PatchStepUsecase { private async fetchWorkflow(command: PatchStepCommand) { return await this.getWorkflowByIdsUseCase.execute({ - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, @@ -85,14 +85,14 @@ export class PatchStepUsecase { private async findStepByStepId(command: PatchStepCommand, workflow: NotificationTemplateEntity) { const currentStep = workflow.steps.find( - (stepItem) => stepItem._id === command.stepId || stepItem.stepId === command.stepId + (stepItem) => stepItem._id === command.stepIdOrInternalId || stepItem.stepId === command.stepIdOrInternalId ); if (!currentStep) { throw new BadRequestException({ message: 'No step found', - stepId: command.stepId, - workflowId: command.identifierOrInternalId, + stepIdOrInternalId: command.stepIdOrInternalId, + workflowId: command.workflowIdOrInternalId, }); } diff --git a/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.command.ts b/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.command.ts index 9d8430f8de6..c7ef8a7b4f3 100644 --- a/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.command.ts @@ -1,11 +1,10 @@ import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; -import { IdentifierOrInternalId } from '@novu/shared'; export class PatchWorkflowCommand extends EnvironmentWithUserObjectCommand { @IsString() @IsNotEmpty() - identifierOrInternalId: IdentifierOrInternalId; + workflowIdOrInternalId: string; @IsBoolean() @IsOptional() diff --git a/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.usecase.ts index c9705bd9b65..be67d748646 100644 --- a/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/patch-workflow/patch-workflow.usecase.ts @@ -26,7 +26,7 @@ export class PatchWorkflowUsecase { await this.persistWorkflow(transientWorkflow, command.user); return await this.getWorkflowUseCase.execute({ - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, user: command.user, }); } @@ -69,7 +69,7 @@ export class PatchWorkflowUsecase { private async fetchWorkflow(command: PatchWorkflowCommand): Promise { return await this.getWorkflowByIdsUseCase.execute({ - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, diff --git a/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.command.ts b/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.command.ts index 24c1bd0a8d9..86a27e41a94 100644 --- a/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.command.ts +++ b/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.command.ts @@ -1,11 +1,10 @@ import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; -import { IdentifierOrInternalId } from '@novu/shared'; import { IsDefined, IsString } from 'class-validator'; export class SyncToEnvironmentCommand extends EnvironmentWithUserObjectCommand { @IsString() @IsDefined() - identifierOrInternalId: IdentifierOrInternalId; + workflowIdOrInternalId: string; @IsString() @IsDefined() diff --git a/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts b/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts index 32e39114570..ea95a7b9c80 100644 --- a/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts @@ -51,7 +51,7 @@ export class SyncToEnvironmentUseCase { return await this.upsertWorkflowUseCase.execute( UpsertWorkflowCommand.create({ user: { ...command.user, environmentId: command.targetEnvironmentId }, - identifierOrInternalId: targetWorkflow?._id, + workflowIdOrInternalId: targetWorkflow?._id, workflowDto, }) ); @@ -76,7 +76,7 @@ export class SyncToEnvironmentUseCase { return this.getWorkflowUseCase.execute( GetWorkflowCommand.create({ user: command.user, - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, }) ); } @@ -90,7 +90,7 @@ export class SyncToEnvironmentUseCase { return await this.getWorkflowUseCase.execute( GetWorkflowCommand.create({ user: { ...command.user, environmentId: command.targetEnvironmentId }, - identifierOrInternalId: externalId, + workflowIdOrInternalId: externalId, }) ); } catch (error) { @@ -144,8 +144,8 @@ export class SyncToEnvironmentUseCase { for (const originStep of originSteps) { const idAsOptionalObject = this.prodDbIdAsOptionalObject(originStep, targetWorkflowSteps); const stepDataDto = await this.buildStepDataUsecase.execute({ - identifierOrInternalId: command.identifierOrInternalId, - stepId: originStep.stepId, + workflowIdOrInternalId: command.workflowIdOrInternalId, + stepIdOrInternalId: originStep._id || originStep.stepId, user: command.user, }); 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 185ba293be5..1213ff55b50 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,8 +1,8 @@ import { EnvironmentWithUserObjectCommand } from '@novu/application-generic'; -import { CreateWorkflowDto, IdentifierOrInternalId, UpdateWorkflowDto } from '@novu/shared'; +import { CreateWorkflowDto, UpdateWorkflowDto } from '@novu/shared'; export class UpsertWorkflowCommand extends EnvironmentWithUserObjectCommand { - identifierOrInternalId?: IdentifierOrInternalId; + workflowIdOrInternalId?: string; workflowDto: CreateWorkflowDto | UpdateWorkflowDto; } 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 096dec39cc5..85c9d9207b8 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 @@ -2,7 +2,6 @@ import { NotificationGroupRepository, NotificationStepEntity, NotificationTempla import { CreateWorkflowDto, DEFAULT_WORKFLOW_PREFERENCES, - IdentifierOrInternalId, slugify, StepCreateDto, StepDto, @@ -67,7 +66,7 @@ export class UpsertWorkflowUseCase { environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, - identifierOrInternalId: workflowId, + workflowIdOrInternalId: workflowId, }) ); } @@ -88,7 +87,7 @@ export class UpsertWorkflowUseCase { @Instrument() private async queryWorkflow(command: UpsertWorkflowCommand): Promise { - if (!command.identifierOrInternalId) { + if (!command.workflowIdOrInternalId) { return null; } @@ -97,7 +96,7 @@ export class UpsertWorkflowUseCase { environmentId: command.user.environmentId, organizationId: command.user.organizationId, userId: command.user._id, - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, }) ); } @@ -107,7 +106,7 @@ export class UpsertWorkflowUseCase { existingWorkflow: NotificationTemplateEntity | null, command: UpsertWorkflowCommand ): Promise { - if (existingWorkflow && isWorkflowUpdateDto(command.workflowDto, command.identifierOrInternalId)) { + if (existingWorkflow && isWorkflowUpdateDto(command.workflowDto, command.workflowIdOrInternalId)) { return await this.updateWorkflowGenericUsecase.execute( UpdateWorkflowCommand.create( this.convertCreateToUpdateCommand(command.workflowDto, command.user, existingWorkflow) @@ -297,9 +296,9 @@ export class UpsertWorkflowUseCase { } await this.patchStepDataUsecase.execute({ controlValues, - identifierOrInternalId: workflow._id, + workflowIdOrInternalId: workflow._id, name: step.name, - stepId: step._templateId, + stepIdOrInternalId: step._templateId, user: command.user, }); } @@ -323,7 +322,7 @@ export class UpsertWorkflowUseCase { function isWorkflowUpdateDto( workflowDto: CreateWorkflowDto | UpdateWorkflowDto, - id?: IdentifierOrInternalId + id?: string ): workflowDto is UpdateWorkflowDto { return !!id; } diff --git a/apps/api/src/app/workflows-v2/workflow.controller.ts b/apps/api/src/app/workflows-v2/workflow.controller.ts index 0fdf171dc2b..17cee824ea7 100644 --- a/apps/api/src/app/workflows-v2/workflow.controller.ts +++ b/apps/api/src/app/workflows-v2/workflow.controller.ts @@ -19,7 +19,6 @@ import { GeneratePreviewRequestDto, GeneratePreviewResponseDto, GetListQueryParams, - IdentifierOrInternalId, ListWorkflowResponse, PatchStepDataDto, PatchWorkflowDto, @@ -92,14 +91,14 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async sync( @UserSession() user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) workflowId: IdentifierOrInternalId, + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string, @Body() syncWorkflowDto: SyncWorkflowDto ): Promise { return this.syncToEnvironmentUseCase.execute( SyncToEnvironmentCommand.create({ - identifierOrInternalId: workflowId, - targetEnvironmentId: syncWorkflowDto.targetEnvironmentId, user, + workflowIdOrInternalId, + targetEnvironmentId: syncWorkflowDto.targetEnvironmentId, }) ); } @@ -108,14 +107,14 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async update( @UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) workflowId: IdentifierOrInternalId, + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string, @Body() updateWorkflowDto: UpdateWorkflowDto ): Promise { return await this.upsertWorkflowUseCase.execute( UpsertWorkflowCommand.create({ workflowDto: updateWorkflowDto, user, - identifierOrInternalId: workflowId, + workflowIdOrInternalId, }) ); } @@ -124,12 +123,12 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async getWorkflow( @UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) workflowId: IdentifierOrInternalId, + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string, @Query('environmentId') environmentId?: string ): Promise { return this.getWorkflowUseCase.execute( GetWorkflowCommand.create({ - identifierOrInternalId: workflowId, + workflowIdOrInternalId, user: { ...user, environmentId: environmentId || user.environmentId, @@ -142,11 +141,11 @@ export class WorkflowController { @HttpCode(HttpStatus.NO_CONTENT) async removeWorkflow( @UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) workflowId: IdentifierOrInternalId + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string ) { await this.deleteWorkflowUsecase.execute( DeleteWorkflowCommand.create({ - identifierOrInternalId: workflowId, + workflowIdOrInternalId, environmentId: user.environmentId, organizationId: user.organizationId, userId: user._id, @@ -176,15 +175,15 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async generatePreview( @UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) workflowId: string, - @Param('stepId', ParseSlugIdPipe) stepId: string, + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string, + @Param('stepId', ParseSlugIdPipe) stepIdOrInternalId: string, @Body() generatePreviewRequestDto: GeneratePreviewRequestDto ): Promise { return await this.generatePreviewUseCase.execute( GeneratePreviewCommand.create({ user, - identifierOrInternalId: workflowId, - stepDatabaseId: stepId, + workflowIdOrInternalId, + stepIdOrInternalId, generatePreviewRequestDto, }) ); @@ -194,11 +193,11 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async getWorkflowStepData( @UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) workflowId: IdentifierOrInternalId, - @Param('stepId', ParseSlugIdPipe) stepId: IdentifierOrInternalId + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string, + @Param('stepId', ParseSlugIdPipe) stepIdOrInternalId: string ): Promise { return await this.buildStepDataUsecase.execute( - BuildStepDataCommand.create({ user, identifierOrInternalId: workflowId, stepId }) + BuildStepDataCommand.create({ user, workflowIdOrInternalId, stepIdOrInternalId }) ); } @@ -206,12 +205,17 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async patchWorkflowStepData( @UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) identifierOrInternalId: IdentifierOrInternalId, - @Param('stepId', ParseSlugIdPipe) stepId: IdentifierOrInternalId, + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string, + @Param('stepId', ParseSlugIdPipe) stepIdOrInternalId: string, @Body() patchStepDataDto: PatchStepDataDto ): Promise { return await this.patchStepDataUsecase.execute( - PatchStepCommand.create({ user, identifierOrInternalId, stepId, ...patchStepDataDto }) + PatchStepCommand.create({ + user, + workflowIdOrInternalId, + stepIdOrInternalId, + ...patchStepDataDto, + }) ); } @@ -219,11 +223,11 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async patchWorkflow( @UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) identifierOrInternalId: IdentifierOrInternalId, + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string, @Body() patchWorkflowDto: PatchWorkflowDto ): Promise { return await this.patchWorkflowUsecase.execute( - PatchWorkflowCommand.create({ user, identifierOrInternalId, ...patchWorkflowDto }) + PatchWorkflowCommand.create({ user, workflowIdOrInternalId, ...patchWorkflowDto }) ); } @@ -231,10 +235,13 @@ export class WorkflowController { @UseGuards(UserAuthGuard) async getWorkflowTestData( @UserSession() user: UserSessionData, - @Param('workflowId', ParseSlugIdPipe) workflowId: IdentifierOrInternalId + @Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string ): Promise { return this.buildWorkflowTestDataUseCase.execute( - WorkflowTestDataCommand.create({ identifierOrInternalId: workflowId, user }) + WorkflowTestDataCommand.create({ + workflowIdOrInternalId, + user, + }) ); } } diff --git a/libs/application-generic/src/usecases/create-workflow/create-workflow.usecase.ts b/libs/application-generic/src/usecases/create-workflow/create-workflow.usecase.ts index a997a1de706..d9600915a22 100644 --- a/libs/application-generic/src/usecases/create-workflow/create-workflow.usecase.ts +++ b/libs/application-generic/src/usecases/create-workflow/create-workflow.usecase.ts @@ -407,7 +407,7 @@ export class CreateWorkflow { userId: command.userId, environmentId: command.environmentId, organizationId: command.organizationId, - identifierOrInternalId: savedWorkflow._id, + workflowIdOrInternalId: savedWorkflow._id, }), ); } diff --git a/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.command.ts b/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.command.ts index 38edd8a1a41..3664c3de136 100644 --- a/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.command.ts +++ b/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.command.ts @@ -4,5 +4,5 @@ import { EnvironmentWithUserCommand } from '../../../commands'; export class DeleteWorkflowCommand extends EnvironmentWithUserCommand { @IsString() @IsDefined() - identifierOrInternalId: string; + workflowIdOrInternalId: string; } diff --git a/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts b/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts index b45a001fdcd..f20b026db25 100644 --- a/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts +++ b/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts @@ -38,7 +38,7 @@ export class DeleteWorkflowUseCase { const workflowEntity = await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ ...command, - identifierOrInternalId: command.identifierOrInternalId, + workflowIdOrInternalId: command.workflowIdOrInternalId, }), ); diff --git a/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.command.ts b/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.command.ts index 1106bfe96ef..c43ab9789f2 100644 --- a/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.command.ts +++ b/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.command.ts @@ -4,5 +4,5 @@ import { EnvironmentWithUserCommand } from '../../../commands'; export class GetWorkflowByIdsCommand extends EnvironmentWithUserCommand { @IsString() @IsDefined() - identifierOrInternalId: string; + workflowIdOrInternalId: string; } diff --git a/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts b/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts index 2658ba189f0..d2625ded62f 100644 --- a/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts +++ b/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts @@ -67,27 +67,27 @@ export class GetWorkflowByIdsUseCase { @Instrument() private async getDbWorkflow(command: GetWorkflowByIdsCommand) { const isInternalId = NotificationTemplateRepository.isInternalId( - command.identifierOrInternalId, + command.workflowIdOrInternalId, ); let workflowEntity: NotificationTemplateEntity | null; if (isInternalId) { workflowEntity = await this.notificationTemplateRepository.findById( - command.identifierOrInternalId, + command.workflowIdOrInternalId, command.environmentId, ); } else { workflowEntity = await this.notificationTemplateRepository.findByTriggerIdentifier( command.environmentId, - command.identifierOrInternalId, + command.workflowIdOrInternalId, ); } if (!workflowEntity) { throw new NotFoundException({ message: 'Workflow cannot be found', - workflowId: command.identifierOrInternalId, + workflowId: command.workflowIdOrInternalId, }); } diff --git a/libs/application-generic/src/usecases/workflow/update-workflow/update-workflow.usecase.ts b/libs/application-generic/src/usecases/workflow/update-workflow/update-workflow.usecase.ts index 433d2bb28dd..44fbb42e4b9 100644 --- a/libs/application-generic/src/usecases/workflow/update-workflow/update-workflow.usecase.ts +++ b/libs/application-generic/src/usecases/workflow/update-workflow/update-workflow.usecase.ts @@ -96,7 +96,7 @@ export class UpdateWorkflow { const existingTemplate = await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ - identifierOrInternalId: command.id, + workflowIdOrInternalId: command.id, environmentId: command.environmentId, organizationId: command.organizationId, userId: command.userId, @@ -327,7 +327,7 @@ export class UpdateWorkflow { userId: command.userId, environmentId: command.environmentId, organizationId: command.organizationId, - identifierOrInternalId: command.id, + workflowIdOrInternalId: command.id, }), ); diff --git a/packages/shared/src/dto/workflows/workflow-response.dto.ts b/packages/shared/src/dto/workflows/workflow-response.dto.ts index fef7af36fac..a530411a99d 100644 --- a/packages/shared/src/dto/workflows/workflow-response.dto.ts +++ b/packages/shared/src/dto/workflows/workflow-response.dto.ts @@ -26,8 +26,6 @@ export interface ContentIssue extends Issue {} // eslint-disable-next-line @typescript-eslint/naming-convention export interface StepIssue extends Issue {} -export type IdentifierOrInternalId = string; - export type PatchStepDataDto = { name?: string; controlValues?: Record; From 336e752eae69a50669cfd83c3f9299e146f01376 Mon Sep 17 00:00:00 2001 From: George Djabarov <39195835+djabarovgeorge@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:53:25 +0200 Subject: [PATCH 11/18] fix(api): next build (#7217) --- .../usecases/generate-preview/generate-preview.usecase.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts index b1e14de81f3..f4d37001f44 100644 --- a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts @@ -76,8 +76,8 @@ export class GeneratePreviewUsecase { this.logger.error( { err: error, - workflowIdOrInternalId: command.identifierOrInternalId, - stepIdOrInternalId: command.stepDatabaseId, + workflowIdOrInternalId: command.workflowIdOrInternalId, + stepIdOrInternalId: command.stepIdOrInternalId, }, `Unexpected error while generating preview`, LOG_CONTEXT From 7ff24f9d3e0803789f6e5a3b4648ebee4d41c124 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Thu, 5 Dec 2024 11:18:37 +0200 Subject: [PATCH 12/18] fix(dashboard): minor onboarding updates (#7210) --- .../src/components/auth/inbox-playground.tsx | 2 +- .../src/components/auth/questionnaire-form.tsx | 16 +++++++++++++++- apps/dashboard/src/main.tsx | 8 ++++---- apps/dashboard/src/pages/usecase-select-page.tsx | 7 ++++++- apps/dashboard/src/routes/onboarding.tsx | 12 +++++++++--- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/src/components/auth/inbox-playground.tsx b/apps/dashboard/src/components/auth/inbox-playground.tsx index 7a7bfde5549..f8ba8b719b9 100644 --- a/apps/dashboard/src/components/auth/inbox-playground.tsx +++ b/apps/dashboard/src/components/auth/inbox-playground.tsx @@ -109,7 +109,7 @@ export function InboxPlayground() { * This workflow will be used by the inbox preview examples */ const initializeDemoWorkflow = async () => { - const workflow = data?.workflows.find((workflow) => workflow.workflowId === ONBOARDING_DEMO_WORKFLOW_ID); + const workflow = data?.workflows.find((workflow) => workflow.workflowId?.includes(ONBOARDING_DEMO_WORKFLOW_ID)); if (!workflow) { await createDemoWorkflow(); } diff --git a/apps/dashboard/src/components/auth/questionnaire-form.tsx b/apps/dashboard/src/components/auth/questionnaire-form.tsx index e65fb341a22..88773957a98 100644 --- a/apps/dashboard/src/components/auth/questionnaire-form.tsx +++ b/apps/dashboard/src/components/auth/questionnaire-form.tsx @@ -14,6 +14,8 @@ import { TelemetryEvent } from '../../utils/telemetry'; import { useNavigate } from 'react-router-dom'; import { ROUTES } from '../../utils/routes'; import { useMutation } from '@tanstack/react-query'; +import { useOrganization, useUser } from '@clerk/clerk-react'; +import { useFetchEnvironments } from '../../context/environment/hooks'; interface QuestionnaireFormData { jobTitle: JobTitleEnum; @@ -31,9 +33,12 @@ interface SubmitQuestionnaireData { } export function QuestionnaireForm() { + const { organization } = useOrganization(); + useFetchEnvironments({ organizationId: organization?.id }); + const { control, watch, handleSubmit } = useForm(); const submitQuestionnaireMutation = useSubmitQuestionnaire(); - + const { user } = useUser(); const selectedJobTitle = watch('jobTitle'); const selectedOrgType = watch('organizationType'); const companySize = watch('companySize'); @@ -58,6 +63,15 @@ export function QuestionnaireForm() { pageName: 'Create Organization Form', hubspotContext: hubspotContext || '', }); + + if (!user?.unsafeMetadata?.newDashboardOptInStatus) { + await user?.update({ + unsafeMetadata: { + newDashboardOptInStatus: 'opted_in', + }, + }); + await user?.reload(); + } }; return ( diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx index 9ae8ec6a49c..227d6883488 100644 --- a/apps/dashboard/src/main.tsx +++ b/apps/dashboard/src/main.tsx @@ -50,16 +50,16 @@ const router = createBrowserRouter([ path: ROUTES.SIGNUP_ORGANIZATION_LIST, element: , }, - { - path: ROUTES.SIGNUP_QUESTIONNAIRE, - element: , - }, ], }, { path: '/onboarding', element: , children: [ + { + path: ROUTES.SIGNUP_QUESTIONNAIRE, + element: , + }, { path: ROUTES.USECASE_SELECT, element: , diff --git a/apps/dashboard/src/pages/usecase-select-page.tsx b/apps/dashboard/src/pages/usecase-select-page.tsx index 58aa77a94f3..4096dc84ad7 100644 --- a/apps/dashboard/src/pages/usecase-select-page.tsx +++ b/apps/dashboard/src/pages/usecase-select-page.tsx @@ -16,6 +16,7 @@ import { useMutation } from '@tanstack/react-query'; import * as Sentry from '@sentry/react'; import { useOrganization } from '@clerk/clerk-react'; import { AnimatedPage } from '@/components/onboarding/animated-page'; +import { Helmet } from 'react-helmet-async'; const containerVariants = { hidden: { opacity: 0 }, @@ -46,7 +47,6 @@ export function UsecaseSelectPage() { }, [track]); useEffect(() => { - console.log('organization', organization?.publicMetadata); if (organization?.publicMetadata?.useCases) { setSelectedUseCases(organization.publicMetadata.useCases as ChannelTypeEnum[]); } @@ -101,6 +101,11 @@ export function UsecaseSelectPage() { return ( <> + + {channelOptions.map((option) => ( + + ))} + { return ( - - - + + + + + + + ); }; From 884e68ddc8acae812b35d5a773336d5e88846ee7 Mon Sep 17 00:00:00 2001 From: GalTidhar <39020298+tatarco@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:27:37 +0100 Subject: [PATCH 13/18] feat(api): Nv 4939 e2e testing happy path events (#7208) --- .cspell.json | 1 + .source | 2 +- apps/api/package.json | 2 +- .../events/dtos/trigger-event-request.dto.ts | 65 +- .../events/dtos/trigger-event-response.dto.ts | 18 +- .../dtos/trigger-event-to-all-request.dto.ts | 8 +- .../src/app/events/e2e/cancel-event.e2e.ts | 58 +- .../src/app/events/e2e/trigger-event.e2e.ts | 2267 +++++++---------- apps/api/src/app/events/events.controller.ts | 11 +- .../parse-event-request.command.ts | 14 +- .../dtos/create-integration-request.dto.ts | 15 +- .../dtos/integration-response.dto.ts | 72 +- .../create-integration.usecase.ts | 14 +- .../dtos/create-subscriber-request.dto.ts | 116 +- .../usecases/add-job/add-job.command.ts | 4 +- .../subscriber-job-bound.command.ts | 6 +- .../src/dtos/process-subscriber-job.dto.ts | 4 +- .../src/dtos/workflow-job.dto.ts | 4 +- .../create-notification-jobs.command.ts | 6 +- .../trigger-event/trigger-event.command.ts | 10 +- .../integration/integration.entity.ts | 6 +- .../notification-template.entity.ts | 9 +- .../notification/notification.entity.ts | 4 +- .../shared/src/dto/controls/controls.dto.ts | 6 - packages/shared/src/dto/controls/index.ts | 1 - packages/shared/src/dto/index.ts | 2 +- .../src/dto/stateless-control-values/index.ts | 1 + .../stateless-controls.ts | 10 + .../notification-template.interface.ts | 7 +- pnpm-lock.yaml | 92 +- 30 files changed, 1297 insertions(+), 1538 deletions(-) delete mode 100644 packages/shared/src/dto/controls/controls.dto.ts delete mode 100644 packages/shared/src/dto/controls/index.ts create mode 100644 packages/shared/src/dto/stateless-control-values/index.ts create mode 100644 packages/shared/src/dto/stateless-control-values/stateless-controls.ts diff --git a/.cspell.json b/.cspell.json index 2eb0f457291..937ba30e2f9 100644 --- a/.cspell.json +++ b/.cspell.json @@ -5,6 +5,7 @@ "words": [ "ABNF", "addrs", + "subscriberpayloaddto", "adresses", "sdkerror", "africas", diff --git a/.source b/.source index 61941c5bd9f..e37e9d3f03e 160000 --- a/.source +++ b/.source @@ -1 +1 @@ -Subproject commit 61941c5bd9f09f22a68620f2a51e36bcdc0f3abe +Subproject commit e37e9d3f03e2574565e00f8ed52c4ea11bfd37aa diff --git a/apps/api/package.json b/apps/api/package.json index e71ab926c85..75f94112639 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -59,7 +59,7 @@ "@sentry/tracing": "^7.40.0", "@types/newrelic": "^9.14.0", "@upstash/ratelimit": "^0.4.4", - "@novu/api": "^0.0.1-alpha.39", + "@novu/api": "^0.0.1-alpha.56", "axios": "^1.6.8", "liquidjs": "^10.13.1", "bcrypt": "^5.0.0", diff --git a/apps/api/src/app/events/dtos/trigger-event-request.dto.ts b/apps/api/src/app/events/dtos/trigger-event-request.dto.ts index 4ea6fecd5b9..f282a26f350 100644 --- a/apps/api/src/app/events/dtos/trigger-event-request.dto.ts +++ b/apps/api/src/app/events/dtos/trigger-event-request.dto.ts @@ -12,7 +12,6 @@ import { import { Type } from 'class-transformer'; import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; import { - ControlsDto, TriggerRecipients, TriggerRecipientsTypeEnum, TriggerRecipientSubscriber, @@ -21,6 +20,26 @@ import { import { CreateSubscriberRequestDto } from '../../subscribers/dtos'; import { UpdateTenantRequestDto } from '../../tenant/dtos'; +export class WorkflowToStepControlValuesDto { + /** + * A mapping of step IDs to their corresponding data. + * Built for stateless triggering by the local studio, those values will not be persisted outside of the job scope + * First key is step id, second is controlId, value is the control value + * @type {Record} + * @optional + */ + @ApiProperty({ + description: 'A mapping of step IDs to their corresponding data.', + type: 'object', + additionalProperties: { + type: 'object', + additionalProperties: true, // Allows any additional properties + }, + required: false, // Indicates that this property is optional + }) + steps?: Record>; +} + export class SubscriberPayloadDto extends CreateSubscriberRequestDto {} export class TenantPayloadDto extends UpdateTenantRequestDto {} @@ -32,9 +51,7 @@ export class TopicPayloadDto { type: TriggerRecipientsTypeEnum; } -@ApiExtraModels(SubscriberPayloadDto) -@ApiExtraModels(TenantPayloadDto) -@ApiExtraModels(TopicPayloadDto) +@ApiExtraModels(SubscriberPayloadDto, TenantPayloadDto, TopicPayloadDto) export class TriggerEventRequestDto { @ApiProperty({ description: @@ -46,10 +63,12 @@ export class TriggerEventRequestDto { name: string; @ApiProperty({ - description: - // eslint-disable-next-line max-len - `The payload object is used to pass additional custom information that could be used to render the workflow, or perform routing rules based on it. + description: `The payload object is used to pass additional custom information that could be + used to render the workflow, or perform routing rules based on it. This data will also be available when fetching the notifications feed from the API to display certain parts of the UI.`, + type: 'object', + required: false, + additionalProperties: true, example: { comment_id: 'string', post: { @@ -57,16 +76,14 @@ export class TriggerEventRequestDto { }, }, }) - @ApiProperty({ - type: 'object', - description: 'An optional payload object that can contain any properties', - required: false, - additionalProperties: true, - }) @IsObject() @IsOptional() payload?: Record; + @ApiPropertyOptional({ + description: 'A URL to bridge for additional processing.', + example: 'https://example.com/bridge', + }) @IsString() @IsOptional() bridgeUrl?: string; @@ -80,6 +97,12 @@ export class TriggerEventRequestDto { }, }, }, + type: 'object', + additionalProperties: { + type: 'object', + additionalProperties: true, // Allows any additional properties + }, + required: false, // Indicates that this property is optional }) @IsObject() @IsOptional() @@ -107,8 +130,8 @@ export class TriggerEventRequestDto { @IsDefined() to: TriggerRecipients; - @ApiProperty({ - description: 'A unique identifier for this transaction, we will generated a UUID if not provided.', + @ApiPropertyOptional({ + description: 'A unique identifier for this transaction, we will generate a UUID if not provided.', }) @IsString() @IsOptional() @@ -116,8 +139,7 @@ export class TriggerEventRequestDto { @ApiProperty({ description: `It is used to display the Avatar of the provided actor's subscriber id or actor object. - If a new actor object is provided, we will create a new subscriber in our system - `, + If a new actor object is provided, we will create a new subscriber in our system`, oneOf: [ { type: 'string', description: 'Unique identifier of a subscriber in your systems' }, { $ref: getSchemaPath(SubscriberPayloadDto) }, @@ -131,8 +153,7 @@ export class TriggerEventRequestDto { @ApiProperty({ description: `It is used to specify a tenant context during trigger event. - Existing tenants will be updated with the provided details. - `, + Existing tenants will be updated with the provided details.`, oneOf: [ { type: 'string', description: 'Unique identifier of a tenant in your system' }, { $ref: getSchemaPath(TenantPayloadDto) }, @@ -144,7 +165,11 @@ export class TriggerEventRequestDto { @Type(() => TenantPayloadDto) tenant?: TriggerTenantContext; - controls?: ControlsDto; + @ApiPropertyOptional({ + description: 'Additional control configurations.', + type: WorkflowToStepControlValuesDto, + }) + controls?: WorkflowToStepControlValuesDto; } export class BulkTriggerEventDto { diff --git a/apps/api/src/app/events/dtos/trigger-event-response.dto.ts b/apps/api/src/app/events/dtos/trigger-event-response.dto.ts index 6041e0a83ec..977eb8d04f7 100644 --- a/apps/api/src/app/events/dtos/trigger-event-response.dto.ts +++ b/apps/api/src/app/events/dtos/trigger-event-response.dto.ts @@ -1,30 +1,38 @@ -import { IsBoolean, IsDefined, IsString } from 'class-validator'; +import { IsBoolean, IsDefined, IsEnum, IsOptional, IsString } from 'class-validator'; import { TriggerEventStatusEnum } from '@novu/shared'; import { ApiProperty } from '@nestjs/swagger'; export class TriggerEventResponseDto { @ApiProperty({ - description: 'If trigger was acknowledged or not', + description: 'Indicates whether the trigger was acknowledged or not', + type: Boolean, }) @IsBoolean() @IsDefined() acknowledged: boolean; @ApiProperty({ - description: 'Status for trigger', + description: 'Status of the trigger', enum: TriggerEventStatusEnum, }) @IsDefined() + @IsEnum(TriggerEventStatusEnum) status: TriggerEventStatusEnum; @ApiProperty({ - description: 'In case of an error, this field will contain the error message', + description: 'In case of an error, this field will contain the error message(s)', + type: [String], // Specify that this is an array of strings + required: false, // Not required since it's optional }) + @IsOptional() error?: string[]; @ApiProperty({ - description: 'Transaction id for trigger', + description: 'The returned transaction ID of the trigger', + type: String, // Specify that this is a string + required: false, // Not required since it's optional }) + @IsOptional() @IsString() transactionId?: string; } diff --git a/apps/api/src/app/events/dtos/trigger-event-to-all-request.dto.ts b/apps/api/src/app/events/dtos/trigger-event-to-all-request.dto.ts index 685ca04f7fa..99c47c4575b 100644 --- a/apps/api/src/app/events/dtos/trigger-event-to-all-request.dto.ts +++ b/apps/api/src/app/events/dtos/trigger-event-to-all-request.dto.ts @@ -15,14 +15,18 @@ export class TriggerEventToAllRequestDto { name: string; @ApiProperty({ - description: `The payload object is used to pass additional custom information that could be used to render the template, or perform routing rules based on it. - This data will also be available when fetching the notifications feed from the API to display certain parts of the UI.`, example: { comment_id: 'string', post: { text: 'string', }, }, + type: 'object', + description: `The payload object is used to pass additional information that + could be used to render the template, or perform routing rules based on it. + For In-App channel, payload data are also available in `, + required: true, + additionalProperties: true, }) @IsObject() payload: Record; diff --git a/apps/api/src/app/events/e2e/cancel-event.e2e.ts b/apps/api/src/app/events/e2e/cancel-event.e2e.ts index 7b2a457c627..a2f378a52f1 100644 --- a/apps/api/src/app/events/e2e/cancel-event.e2e.ts +++ b/apps/api/src/app/events/e2e/cancel-event.e2e.ts @@ -1,14 +1,16 @@ import axios from 'axios'; import { expect } from 'chai'; import { + JobRepository, + JobStatusEnum, MessageRepository, NotificationTemplateEntity, SubscriberEntity, - JobRepository, - JobStatusEnum, } from '@novu/dal'; -import { StepTypeEnum, DigestTypeEnum, DigestUnitEnum, DelayTypeEnum } from '@novu/shared'; -import { UserSession, SubscribersService } from '@novu/testing'; +import { DelayTypeEnum, DigestTypeEnum, DigestUnitEnum, StepTypeEnum } from '@novu/shared'; +import { SubscribersService, UserSession } from '@novu/testing'; +import { Novu } from '@novu/api'; +import { initNovuClassSdk } from '../../shared/helpers/e2e/sdk/e2e-sdk.helper'; const axiosInstance = axios.create(); @@ -18,25 +20,24 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( let subscriber: SubscriberEntity; let subscriberService: SubscribersService; const jobRepository = new JobRepository(); - + let novuClient: Novu; + + async function cancelEvent(transactionId: string | undefined) { + if (!transactionId) { + throw new Error('Missing transactionId'); + } + await novuClient.cancel(transactionId); + } const triggerEvent = async (payload, transactionId?: string, overrides = {}, to = [subscriber.subscriberId]) => { return ( - await axiosInstance.post( - `${session.serverUrl}/v1/events/trigger`, - { - transactionId, - name: template.triggers[0].identifier, - to, - payload, - overrides, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ) - ).data.data; + await novuClient.trigger({ + transactionId, + name: template.triggers[0].identifier, + to, + payload, + overrides, + }) + ).result; }; beforeEach(async () => { @@ -45,6 +46,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( template = await session.createTemplate(); subscriberService = new SubscribersService(session.organization._id, session.environment._id); subscriber = await subscriberService.createSubscriber(); + novuClient = initNovuClassSdk(session); }); it('should be able to cancel digest', async function () { @@ -218,7 +220,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( customVar: 'trigger_3_data', }); - await session.testAgent.delete(`/v1/events/trigger/${trigger2.transactionId}`).send({}); + cancelEvent(trigger2.transactionId); await session.awaitRunningJobs(template?._id, false, 0); @@ -298,7 +300,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( // Wait for trigger2 to be merged to trigger1 await session.awaitRunningJobs(template?._id, false, 1); - await session.testAgent.delete(`/v1/events/trigger/${trigger1.transactionId}`).send({}); + cancelEvent(trigger1.transactionId); const trigger3 = await triggerEvent({ customVar: 'trigger_3_data', @@ -393,7 +395,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( // Wait for trigger2 to be merged to trigger1 const mainDigest = trigger1.transactionId; await session.awaitRunningJobs(template?._id, false, 1); - await session.testAgent.delete(`/v1/events/trigger/${mainDigest}`).send({}); + cancelEvent(mainDigest); const trigger3 = await triggerEvent({ customVar: 'trigger_3_data', @@ -402,7 +404,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( // Wait for trigger3 to be merged to trigger2 const followerDigest = trigger2.transactionId; await session.awaitRunningJobs(template?._id, false, 1); - await session.testAgent.delete(`/v1/events/trigger/${followerDigest}`).send({}); + cancelEvent(followerDigest); const trigger4 = await triggerEvent({ customVar: 'trigger_4_data', @@ -501,7 +503,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( // Wait for trigger2 to be merged to trigger1 const mainDigest = trigger1.transactionId; await session.awaitRunningJobs(template?._id, false, 1); - await session.testAgent.delete(`/v1/events/trigger/${mainDigest}`).send({}); + cancelEvent(mainDigest); const trigger3 = await triggerEvent({ customVar: 'trigger_3_data', @@ -510,7 +512,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( // Wait for trigger3 to be merged to trigger2 const followerDigest = trigger2.transactionId; await session.awaitRunningJobs(template?._id, false, 1); - await session.testAgent.delete(`/v1/events/trigger/${followerDigest}`).send({}); + cancelEvent(followerDigest); const trigger4 = await triggerEvent({ customVar: 'trigger_4_data', @@ -518,7 +520,7 @@ describe('Cancel event - /v1/events/trigger/:transactionId (DELETE)', function ( // Wait for trigger4 to be merged to trigger3 await session.awaitRunningJobs(template?._id, false, 1); - await session.testAgent.delete(`/v1/events/trigger/${trigger4.transactionId}`).send({}); + cancelEvent(trigger4.transactionId); await session.awaitRunningJobs(template?._id, false, 0); diff --git a/apps/api/src/app/events/e2e/trigger-event.e2e.ts b/apps/api/src/app/events/e2e/trigger-event.e2e.ts index 5c382fe2dd2..a65c6ca9561 100644 --- a/apps/api/src/app/events/e2e/trigger-event.e2e.ts +++ b/apps/api/src/app/events/e2e/trigger-event.e2e.ts @@ -1,7 +1,5 @@ import { expect } from 'chai'; -import axios, { AxiosResponse } from 'axios'; import { v4 as uuid } from 'uuid'; -import { differenceInMilliseconds, subDays } from 'date-fns'; import { EnvironmentRepository, ExecutionDetailsRepository, @@ -30,7 +28,6 @@ import { FilterPartTypeEnum, IEmailBlock, InAppProviderIdEnum, - ISubscribersDefine, PreviousStepTypeEnum, SmsProviderIdEnum, StepTypeEnum, @@ -39,18 +36,18 @@ import { } from '@novu/shared'; import { EmailEventStatusEnum } from '@novu/stateless'; import { DetailEnum } from '@novu/application-generic'; +import { Novu } from '@novu/api'; +import { SubscriberPayloadDto } from '@novu/api/src/models/components/subscriberpayloaddto'; +import { CreateIntegrationRequestDto, TriggerEventResponseDto } from '@novu/api/models/components'; +import { initNovuClassSdk } from '../../shared/helpers/e2e/sdk/e2e-sdk.helper'; import { createTenant } from '../../tenant/e2e/create-tenant.e2e'; -const axiosInstance = axios.create(); - -const eventTriggerPath = '/v1/events/trigger'; - const promiseTimeout = (ms: number): Promise => new Promise((resolve) => { setTimeout(resolve, ms); }); -describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { +describe(`Trigger event - /v1/events/trigger (POST)`, function () { let session: UserSession; let template: NotificationTemplateEntity; let subscriber: SubscriberEntity; @@ -65,8 +62,9 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { const executionDetailsRepository = new ExecutionDetailsRepository(); const environmentRepository = new EnvironmentRepository(); const tenantRepository = new TenantRepository(); + let novuClient: Novu; - describe(`Trigger Event - ${eventTriggerPath} (POST)`, function () { + describe(`Trigger Event - /v1/events/trigger (POST)`, function () { beforeEach(async () => { session = new UserSession(); await session.initialize(); @@ -77,6 +75,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { organizationId: session.organization._id, environmentId: session.environment._id, }); + novuClient = initNovuClassSdk(session); }); it('should filter delay step', async function () { @@ -123,21 +122,13 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - customVar: 'Testing of User Name', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + customVar: 'Testing of User Name', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -195,21 +186,13 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - customVar: 'Testing of User Name', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + customVar: 'Testing of User Name', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -275,21 +258,13 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - customVar: 'Testing of User Name', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + customVar: 'Testing of User Name', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -403,22 +378,14 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - customVar: 'Testing of User Name', - digest_type: '2', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + customVar: 'Testing of User Name', + digest_type: '2', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -485,22 +452,14 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - customVar: 'Testing of User Name', - exclude: false, - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + customVar: 'Testing of User Name', + exclude: false, }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -556,36 +515,20 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - exclude: false, - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + exclude: false, }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - exclude: false, - }, + }); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + exclude: false, }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -642,34 +585,18 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - exclude: false, - }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: {}, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + exclude: false, }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: {}, + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -737,22 +664,14 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - customVar: 'Testing of User Name', - exclude: false, - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + customVar: 'Testing of User Name', + exclude: false, }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template?._id, true, 0); @@ -795,7 +714,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { await createTenant({ session, identifier: 'test', name: 'test' }); - await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test'); + await sendTrigger(template, subscriber.subscriberId, {}, {}, 'test'); await session.awaitRunningJobs(template._id); @@ -839,7 +758,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { await createTenant({ session, identifier: 'test3', name: 'test3' }); await createTenant({ session, identifier: 'test2', name: 'test2' }); - await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test3'); + await sendTrigger(template, subscriber.subscriberId, {}, {}, 'test3'); await session.awaitRunningJobs(template._id); @@ -856,7 +775,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(firstMessage?.providerId).to.equal(payload.providerId); - await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test2'); + await sendTrigger(template, subscriber.subscriberId, {}, {}, 'test2'); await session.awaitRunningJobs(template._id); @@ -892,49 +811,33 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { template = await createTemplate(session, ChannelTypeEnum.EMAIL); - const result = await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test1'); + const result = await sendTrigger(template, subscriber.subscriberId, {}, {}, 'test1'); - expect(result.data.data.status).to.equal('no_tenant_found'); + expect(result.status).to.equal('no_tenant_found'); }); it('should trigger an event successfully', async function () { - const response = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: { - firstName: 'Testing of User Name', - urlVariable: '/test/url/path', - }, + const response = await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + firstName: 'Testing of User Name', + urlVariable: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - const { data: body } = response; + const body = response.result; - expect(body.data).to.be.ok; - expect(body.data.status).to.equal('processed'); - expect(body.data.acknowledged).to.equal(true); + expect(body).to.be.ok; + expect(body.status).to.equal('processed'); + expect(body.acknowledged).to.equal(true); }); it('should store jobs & message provider id successfully', async function () { - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + }); await session.awaitRunningJobs(template._id); @@ -965,7 +868,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { it('should create a subscriber based on event', async function () { const subscriberId = SubscriberRepository.createObjectId(); - const payload: ISubscribersDefine = { + const payload: SubscriberPayloadDto = { subscriberId, firstName: 'Test Name', lastName: 'Last of name', @@ -973,26 +876,18 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { locale: 'en', data: { custom1: 'custom value1', custom2: 'custom value2' }, }; - const { data: body } = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { - ...payload, - }, - payload: { - urlVar: '/test/url/path', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [payload], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); - const createdSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, subscriberId); + const envId = session.environment._id; + console.log(`created sub envId:${envId} subscriberId: ${subscriberId}`); + const createdSubscriber = await subscriberRepository.findBySubscriberId(envId, subscriberId); expect(createdSubscriber?.subscriberId).to.equal(subscriberId); expect(createdSubscriber?.firstName).to.equal(payload.firstName); @@ -1012,23 +907,17 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { locale: 'en', }; - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [ + { ...payload, }, - payload: { - urlVar: '/test/url/path', - }, + ], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); const createdSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, subscriberId); @@ -1039,24 +928,18 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(createdSubscriber?.email).to.equal(payload.email); expect(createdSubscriber?.locale).to.equal(payload.locale); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [ + { ...payload, email: 'hello@world.com', }, - payload: { - urlVar: '/test/url/path', - }, + ], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); @@ -1072,11 +955,10 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { describe('Subscriber channels', function () { it('should set a new subscriber with channels array', async function () { const subscriberId = SubscriberRepository.createObjectId(); - const payload: ISubscribersDefine = { + const payload: SubscriberPayloadDto = { subscriberId, firstName: 'Test Name', lastName: 'Last of name', - email: undefined, locale: 'en', channels: [ { @@ -1089,37 +971,40 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }; - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { - ...payload, - }, - payload: { - urlVar: '/test/url/path', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [payload], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); const createdSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, subscriberId); expect(createdSubscriber?.channels?.length).to.equal(1); + if (createdSubscriber?.channels?.length !== 1) { + throw new Error('need to have 1 channel'); + } expect(createdSubscriber?.channels[0]?.providerId).to.equal(ChatProviderIdEnum.Slack); - expect(createdSubscriber?.channels[0]?.credentials?.webhookUrl).to.equal('https://slack.com/webhook/test'); - expect(createdSubscriber?.channels[0]?.credentials?.deviceTokens.length).to.equal(2); + const credentials = createdSubscriber?.channels[0]?.credentials; + expect(credentials).to.be.ok; + if (!credentials) { + throw new Error('must have credentials'); + } + expect(credentials.webhookUrl).to.equal('https://slack.com/webhook/test'); + const { deviceTokens } = credentials; + expect(deviceTokens).to.be.ok; + if (!deviceTokens) { + throw new Error(''); + } + expect(deviceTokens?.length).to.equal(2); }); it('should update a subscribers channels array', async function () { const subscriberId = SubscriberRepository.createObjectId(); - const payload: ISubscribersDefine = { + const payload: SubscriberPayloadDto = { subscriberId, firstName: 'Test Name', lastName: 'Last of name', @@ -1135,23 +1020,17 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }; - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [ + { ...payload, }, - payload: { - urlVar: '/test/url/path', - }, + ], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); const createdSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, subscriberId); @@ -1159,11 +1038,10 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(createdSubscriber?.subscriberId).to.equal(subscriberId); expect(createdSubscriber?.channels?.length).to.equal(1); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [ + { ...payload, channels: [ { @@ -1174,22 +1052,20 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { }, ], }, - payload: { - urlVar: '/test/url/path', - }, + ], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); const updatedSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, subscriberId); expect(updatedSubscriber?.channels?.length).to.equal(1); + if (!updatedSubscriber?.channels?.length) { + throw new Error('Channels must be an array'); + } expect(updatedSubscriber?.channels[0]?.providerId).to.equal(ChatProviderIdEnum.Slack); expect(updatedSubscriber?.channels[0]?.credentials?.webhookUrl).to.equal('https://slack.com/webhook/test2'); }); @@ -1205,23 +1081,17 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { locale: 'en', }; - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [ + { ...payload, }, - payload: { - urlVar: '/test/url/path', - }, + ], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); const createdSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, subscriberId); @@ -1232,24 +1102,18 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(createdSubscriber?.email).to.equal(payload.email); expect(createdSubscriber?.locale).to.equal(payload.locale); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [ + { ...payload, email: undefined, }, - payload: { - urlVar: '/test/url/path', - }, + ], + payload: { + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(); @@ -1266,27 +1130,19 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { const subscriberId = SubscriberRepository.createObjectId(); const transactionId = SubscriberRepository.createObjectId(); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - transactionId, - to: [ - { subscriberId: subscriber.subscriberId, email: 'gg@ff.com' }, - { subscriberId, email: 'gg@ff.com' }, - ], - payload: { - email: 'new-test-email@gmail.com', - firstName: 'Testing of User Name', - urlVar: '/test/url/path', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + transactionId, + to: [ + { subscriberId: subscriber.subscriberId, email: 'gg@ff.com' }, + { subscriberId, email: 'gg@ff.com' }, + ], + payload: { + email: 'new-test-email@gmail.com', + firstName: 'Testing of User Name', + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); let completedCount = 0; do { @@ -1311,7 +1167,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ); const createdSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, subscriberId); - const messages2 = await messageRepository.findBySubscriberChannel( + await messageRepository.findBySubscriberChannel( session.environment._id, createdSubscriber?._id as string, ChannelTypeEnum.EMAIL @@ -1322,34 +1178,28 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { }); it('should generate message and notification based on event', async function () { - const { data: body } = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: { + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [ + { subscriberId: subscriber.subscriberId, }, - payload: { - firstName: 'Testing of User Name', - urlVar: '/test/url/path', - attachments: [ - { - name: 'text1.txt', - file: 'hello world!', - }, - { - name: 'text2.txt', - file: Buffer.from('hello world!', 'utf-8'), - }, - ], - }, + ], + payload: { + firstName: 'Testing of User Name', + urlVar: '/test/url/path', + attachments: [ + { + name: 'text1.txt', + file: 'hello world!', + }, + { + name: 'text2.txt', + file: Buffer.from('hello world!', 'utf-8'), + }, + ], }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template._id); @@ -1395,29 +1245,23 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { it('should correctly set expiration date (TTL) for notification and messages', async function () { const templateName = template.triggers[0].identifier; - const { data: body } = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: templateName, - to: { + const response = await novuClient.trigger({ + name: templateName, + to: [ + { subscriberId: subscriber.subscriberId, }, - payload: { - firstName: 'Testing of User Name', - urlVar: '/test/url/path', - }, + ], + payload: { + firstName: 'Testing of User Name', + urlVar: '/test/url/path', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); - - expect(body.data).to.have.all.keys('acknowledged', 'status', 'transactionId'); - expect(body.data.acknowledged).to.equal(true); - expect(body.data.status).to.equal('processed'); - expect(body.data.transaction).to.be.a.string; + }); + const body = response.result; + expect(body).to.have.all.keys('acknowledged', 'status', 'transactionId'); + expect(body.acknowledged).to.equal(true); + expect(body.status).to.equal('processed'); + expect(body.transactionId).to.be.a.string; await session.awaitRunningJobs(template._id); @@ -1431,8 +1275,6 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(notifications.length).to.equal(1); - const notification = notifications[0]; - const messages = await messageRepository.findBySubscriberChannel( session.environment._id, subscriber._id, @@ -1466,21 +1308,13 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - const { data: body } = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [subscriber.subscriberId], - payload: { - customVar: 'Testing of User Name', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + customVar: 'Testing of User Name', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template._id); @@ -1505,21 +1339,13 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - const { data: body } = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: [{ subscriberId: subscriber.subscriberId }, { subscriberId, phone: '+972541111111' }], - payload: { - organizationName: 'Testing of Organization Name', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [{ subscriberId: subscriber.subscriberId }, { subscriberId, phone: '+972541111111' }], + payload: { + organizationName: 'Testing of Organization Name', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template._id); @@ -1551,22 +1377,14 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { }, ], }); - const { data: body } = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: { - phone: '+972541111111', - firstName: 'Testing of User Name', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + phone: '+972541111111', + firstName: 'Testing of User Name', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template._id); @@ -1586,7 +1404,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { template = await createTemplate(session, channelType); - await sendTrigger(session, template, newSubscriberIdInAppNotification); + await sendTrigger(template, newSubscriberIdInAppNotification); await session.awaitRunningJobs(template._id); @@ -1610,7 +1428,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { template = await createTemplate(session, channelType); - await sendTrigger(session, template, newSubscriberIdInAppNotification); + await sendTrigger(template, newSubscriberIdInAppNotification); await session.awaitRunningJobs(template._id); @@ -1650,7 +1468,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await sendTrigger(session, template, newSubscriberIdInAppNotification, { + await sendTrigger(template, newSubscriberIdInAppNotification, { nested: { subject: 'a subject nested', }, @@ -1696,7 +1514,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await sendTrigger(session, template, newSubscriberId, {}, {}, '', actorSubscriber.subscriberId); + await sendTrigger(template, newSubscriberId, {}, {}, '', actorSubscriber.subscriberId); await session.awaitRunningJobs(template._id); @@ -1746,7 +1564,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await sendTrigger(session, template, newSubscriberIdInAppNotification, { + await sendTrigger(template, newSubscriberIdInAppNotification, { nested: { subject: 'a subject nested', }, @@ -1825,7 +1643,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await sendTrigger(session, template, newSubscriberIdInAppNotification, { + await sendTrigger(template, newSubscriberIdInAppNotification, { nested: { subject: 'a subject nested', }, @@ -1862,7 +1680,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await sendTrigger(session, template, newSubscriberIdInAppNotification, { + await sendTrigger(template, newSubscriberIdInAppNotification, { nested: { subject: 'a subject nested', }, @@ -1897,7 +1715,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { } = await session.testAgent.post('/v1/integrations').send(payload); await session.testAgent.post(`/v1/integrations/${data._id}/set-primary`).send({}); - await sendTrigger(session, template, newSubscriberIdInAppNotification, { + await sendTrigger(template, newSubscriberIdInAppNotification, { nested: { subject: 'a subject nested', }, @@ -1942,10 +1760,10 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { }); let response = await session.testAgent - .post(eventTriggerPath) + .post('/v1/events/trigger') .send({ name: template.triggers[0].identifier, - to: subscriber.subscriberId, + to: [subscriber.subscriberId], payload: {}, }) .expect(400); @@ -1955,10 +1773,10 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ); response = await session.testAgent - .post(eventTriggerPath) + .post('/v1/events/trigger') .send({ name: template.triggers[0].identifier, - to: subscriber.subscriberId, + to: [subscriber.subscriberId], payload: { myUser: { lastName: true, @@ -1974,10 +1792,10 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ); response = await session.testAgent - .post(eventTriggerPath) + .post('/v1/events/trigger') .send({ name: template.triggers[0].identifier, - to: subscriber.subscriberId, + to: [subscriber.subscriberId], payload: { myUser: { lastName: '', @@ -2024,7 +1842,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { }); await session.testAgent - .post(eventTriggerPath) + .post('/v1/events/trigger') .send({ name: template.triggers[0].identifier, to: newSubscriberIdInAppNotification, @@ -2054,10 +1872,10 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { it('should throw an error when workflow identifier provided is not in the database', async () => { const response = await session.testAgent - .post(eventTriggerPath) + .post('/v1/events/trigger') .send({ name: 'non-existent-template-identifier', - to: subscriber.subscriberId, + to: [subscriber.subscriberId], payload: { myUser: { lastName: 'Test', @@ -2078,20 +1896,17 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { steps: [], }); - const response = await session.testAgent - .post(eventTriggerPath) - .send({ - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: { - myUser: { - lastName: 'Test', - }, + const response = await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + myUser: { + lastName: 'Test', }, - }) - .expect(201); + }, + }); - const { status, acknowledged } = response.body.data; + const { status, acknowledged } = response.result; expect(status).to.equal('no_workflow_steps_defined'); expect(acknowledged).to.equal(true); }); @@ -2114,18 +1929,15 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await session.testAgent - .post(eventTriggerPath) - .send({ - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: { - myUser: { - lastName: 'Test', - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + myUser: { + lastName: 'Test', }, - }) - .expect(201); + }, + }); }); it('should broadcast trigger to all subscribers', async () => { @@ -2153,20 +1965,12 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}/broadcast`, - { - name: template.triggers[0].identifier, - payload: { - organizationName: 'Umbrella Corp', - }, + await novuClient.triggerBroadcast({ + name: template.triggers[0].identifier, + payload: { + organizationName: 'Umbrella Corp', }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template._id); const messages = await messageRepository.find({ _environmentId: session.environment._id, @@ -2251,23 +2055,15 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: { - firstName: 'Testing of User Name', - urlVariable: '/test/url/path', - run: true, - }, + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + firstName: 'Testing of User Name', + urlVariable: '/test/url/path', + run: true, }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); await session.awaitRunningJobs(template._id); @@ -2324,19 +2120,11 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { * ); */ - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: {}, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: {}, + }); await session.awaitRunningJobs(template._id); @@ -2356,19 +2144,11 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { * ); */ - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: {}, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: {}, + }); await session.awaitRunningJobs(template._id); @@ -2420,19 +2200,11 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { // const axiosPostStub = sinon.stub(axios, 'post').throws(new Error('Users remote error')); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: {}, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: {}, + }); await session.awaitRunningJobs(template._id); @@ -2495,19 +2267,11 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { * }); */ - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: {}, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: {}, + }); await session.awaitRunningJobs(template._id); @@ -2535,19 +2299,11 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { * ); */ - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: {}, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: {}, + }); await session.awaitRunningJobs(template._id); @@ -2633,20 +2389,12 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { ], }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: templateWithVariants.triggers[0].identifier, - to: subscriber.subscriberId, - payload: {}, - tenant: { identifier: tenant.identifier }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await novuClient.trigger({ + name: templateWithVariants.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: {}, + tenant: { identifier: tenant.identifier }, + }); await session.awaitRunningJobs(templateWithVariants._id); @@ -2658,965 +2406,813 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(messages.length).to.equal(1); expect(messages[0].subject).to.equal('Better Variant subject'); }); - }); + describe('filters logic', () => { + beforeEach(async () => { + subscriberService = new SubscribersService(session.organization._id, session.environment._id); + subscriber = await subscriberService.createSubscriber(); + }); - describe('filters logic', () => { - beforeEach(async () => { - session = new UserSession(); - await session.initialize(); - subscriberService = new SubscribersService(session.organization._id, session.environment._id); - subscriber = await subscriberService.createSubscriber(); - }); + it('should filter a message with variables', async function () { + template = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.EMAIL, + subject: 'Password reset', + content: [ + { + type: EmailBlockTypeEnum.TEXT, + content: 'This are the text contents of the template for {{firstName}}', + }, + { + type: EmailBlockTypeEnum.BUTTON, + content: 'SIGN UP', + url: 'https://url-of-app.com/{{urlVariable}}', + }, + ], + filters: [ + { + isNegated: false, + type: 'GROUP', + value: FieldLogicalOperatorEnum.AND, + children: [ + { + field: 'run', + value: '{{payload.var}}', + operator: FieldOperatorEnum.EQUAL, + on: FilterPartTypeEnum.PAYLOAD, + }, + ], + }, + ], + }, + { + type: StepTypeEnum.EMAIL, + subject: 'Password reset', + content: [ + { + type: EmailBlockTypeEnum.TEXT, + content: 'This are the text contents of the template for {{firstName}}', + }, + ], + filters: [ + { + isNegated: false, + type: 'GROUP', + value: FieldLogicalOperatorEnum.AND, + children: [ + { + field: 'subscriberId', + value: subscriber.subscriberId, + operator: FieldOperatorEnum.NOT_EQUAL, + on: FilterPartTypeEnum.SUBSCRIBER, + }, + ], + }, + ], + }, + ], + }); - it('should filter a message with variables', async function () { - template = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.EMAIL, - subject: 'Password reset', - content: [ - { - type: EmailBlockTypeEnum.TEXT, - content: 'This are the text contents of the template for {{firstName}}', - }, - { - type: EmailBlockTypeEnum.BUTTON, - content: 'SIGN UP', - url: 'https://url-of-app.com/{{urlVariable}}', - }, - ], - filters: [ - { - isNegated: false, - type: 'GROUP', - value: FieldLogicalOperatorEnum.AND, - children: [ - { - field: 'run', - value: '{{payload.var}}', - operator: FieldOperatorEnum.EQUAL, - on: FilterPartTypeEnum.PAYLOAD, - }, - ], - }, - ], + await novuClient.trigger({ + name: template.triggers[0].identifier, + to: [subscriber.subscriberId], + payload: { + firstName: 'Testing of User Name', + urlVariable: '/test/url/path', + run: true, + var: true, }, - { - type: StepTypeEnum.EMAIL, - subject: 'Password reset', - content: [ - { - type: EmailBlockTypeEnum.TEXT, - content: 'This are the text contents of the template for {{firstName}}', - }, - ], - filters: [ - { - isNegated: false, - type: 'GROUP', - value: FieldLogicalOperatorEnum.AND, - children: [ - { - field: 'subscriberId', - value: subscriber.subscriberId, - operator: FieldOperatorEnum.NOT_EQUAL, - on: FilterPartTypeEnum.SUBSCRIBER, - }, - ], - }, - ], - }, - ], - }); + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, - to: subscriber.subscriberId, - payload: { - firstName: 'Testing of User Name', - urlVariable: '/test/url/path', - run: true, - var: true, - }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + await session.awaitRunningJobs(template._id); - await session.awaitRunningJobs(template._id); + const messages = await messageRepository.count({ + _environmentId: session.environment._id, + _templateId: template._id, + }); - const messages = await messageRepository.count({ - _environmentId: session.environment._id, - _templateId: template._id, + expect(messages).to.equal(1); }); - expect(messages).to.equal(1); - }); - - it('should filter a message with value that includes variables and strings', async function () { - const actorSubscriber = await subscriberService.createSubscriber({ - firstName: 'Actor', - }); + it('should filter a message with value that includes variables and strings', async function () { + const actorSubscriber = await subscriberService.createSubscriber({ + firstName: 'Actor', + }); - template = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.EMAIL, - subject: 'Password reset', - content: [ - { - type: EmailBlockTypeEnum.TEXT, - content: 'This are the text contents of the template for {{firstName}}', - }, - ], - filters: [ - { - isNegated: false, - type: 'GROUP', - value: FieldLogicalOperatorEnum.AND, - children: [ - { - field: 'name', - value: 'Test {{actor.firstName}}', - operator: FieldOperatorEnum.EQUAL, - on: FilterPartTypeEnum.PAYLOAD, - }, - ], - }, - ], - }, - ], - }); + template = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.EMAIL, + subject: 'Password reset', + content: [ + { + type: EmailBlockTypeEnum.TEXT, + content: 'This are the text contents of the template for {{firstName}}', + }, + ], + filters: [ + { + isNegated: false, + type: 'GROUP', + value: FieldLogicalOperatorEnum.AND, + children: [ + { + field: 'name', + value: 'Test {{actor.firstName}}', + operator: FieldOperatorEnum.EQUAL, + on: FilterPartTypeEnum.PAYLOAD, + }, + ], + }, + ], + }, + ], + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + await novuClient.trigger({ name: template.triggers[0].identifier, - to: subscriber.subscriberId, + to: [subscriber.subscriberId], payload: { firstName: 'Testing of User Name', urlVariable: '/test/url/path', name: 'Test Actor', }, actor: actorSubscriber.subscriberId, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); - - await session.awaitRunningJobs(template._id); + }); - const messages = await messageRepository.count({ - _environmentId: session.environment._id, - _templateId: template._id, - }); + await session.awaitRunningJobs(template._id); - expect(messages).to.equal(1); - }); + const messages = await messageRepository.count({ + _environmentId: session.environment._id, + _templateId: template._id, + }); - it('should filter by tenant variables data', async function () { - const tenant = await tenantRepository.create({ - _organizationId: session.organization._id, - _environmentId: session.environment._id, - identifier: 'one_123', - name: 'The one and only tenant', - data: { value1: 'Best fighter', value2: 'Ever', count: 4 }, + expect(messages).to.equal(1); }); - const templateWithVariants = await session.createTemplate({ - name: 'test email template', - description: 'This is a test description', - steps: [ - { - name: 'Message Name', - subject: 'Test email subject', - preheader: 'Test email preheader', - content: [{ type: EmailBlockTypeEnum.TEXT, content: 'This is a sample text block' }], - type: StepTypeEnum.EMAIL, - filters: [ - { - isNegated: false, - type: 'GROUP', - value: FieldLogicalOperatorEnum.AND, - children: [ - { - on: FilterPartTypeEnum.TENANT, - field: 'data.count', - value: '{{payload.count}}', - operator: FieldOperatorEnum.LARGER, - }, - ], - }, - ], - }, - ], - }); + it('should filter by tenant variables data', async function () { + const tenant = await tenantRepository.create({ + _organizationId: session.organization._id, + _environmentId: session.environment._id, + identifier: 'one_123', + name: 'The one and only tenant', + data: { value1: 'Best fighter', value2: 'Ever', count: 4 }, + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + const templateWithVariants = await session.createTemplate({ + name: 'test email template', + description: 'This is a test description', + steps: [ + { + name: 'Message Name', + subject: 'Test email subject', + preheader: 'Test email preheader', + content: [{ type: EmailBlockTypeEnum.TEXT, content: 'This is a sample text block' }], + type: StepTypeEnum.EMAIL, + filters: [ + { + isNegated: false, + type: 'GROUP', + value: FieldLogicalOperatorEnum.AND, + children: [ + { + on: FilterPartTypeEnum.TENANT, + field: 'data.count', + value: '{{payload.count}}', + operator: FieldOperatorEnum.LARGER, + }, + ], + }, + ], + }, + ], + }); + + await novuClient.trigger({ name: templateWithVariants.triggers[0].identifier, - to: subscriber.subscriberId, + to: [subscriber.subscriberId], payload: { count: 5 }, tenant: { identifier: tenant.identifier }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - await session.awaitRunningJobs(templateWithVariants._id); + await session.awaitRunningJobs(templateWithVariants._id); - let messages = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: templateWithVariants._id, - }); + let messages = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: templateWithVariants._id, + }); - expect(messages.length).to.equal(0); + expect(messages.length).to.equal(0); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + await novuClient.trigger({ name: templateWithVariants.triggers[0].identifier, - to: subscriber.subscriberId, + to: [subscriber.subscriberId], payload: { count: 1 }, tenant: { identifier: tenant.identifier }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); - await session.awaitRunningJobs(templateWithVariants._id); - - messages = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: templateWithVariants._id, - }); + }); + await session.awaitRunningJobs(templateWithVariants._id); - expect(messages.length).to.equal(1); - }); - it('should trigger message with override integration identifier', async function () { - const newSubscriberId = SubscriberRepository.createObjectId(); - const channelType = ChannelTypeEnum.EMAIL; + messages = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: templateWithVariants._id, + }); - template = await createTemplate(session, channelType); + expect(messages.length).to.equal(1); + }); + it('should trigger message with override integration identifier', async function () { + const newSubscriberId = SubscriberRepository.createObjectId(); + const channelType = ChannelTypeEnum.EMAIL; - await sendTrigger(session, template, newSubscriberId); + template = await createTemplate(session, channelType); - await session.awaitRunningJobs(template._id); + await sendTrigger(template, newSubscriberId); - const createdSubscriber = await subscriberRepository.findBySubscriberId(session.environment._id, newSubscriberId); + await session.awaitRunningJobs(template._id); - let messages = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: createdSubscriber?._id, - channel: channelType, - }); + const createdSubscriber = await subscriberRepository.findBySubscriberId( + session.environment._id, + newSubscriberId + ); - expect(messages.length).to.be.equal(1); - expect(messages[0].providerId).to.be.equal(EmailProviderIdEnum.SendGrid); + let messages = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: createdSubscriber?._id, + channel: channelType, + }); - const prodEnv = await environmentRepository.findOne({ - name: 'Production', - _organizationId: session.organization._id, - }); + expect(messages.length).to.be.equal(1); + expect(messages[0].providerId).to.be.equal(EmailProviderIdEnum.SendGrid); - const payload = { - providerId: EmailProviderIdEnum.Mailgun, - channel: 'email', - credentials: { apiKey: '123', secretKey: 'abc' }, - _environmentId: prodEnv?._id, - active: true, - check: false, - }; + const prodEnv = await environmentRepository.findOne({ + name: 'Production', + _organizationId: session.organization._id, + }); - const { - body: { data: newIntegration }, - } = await session.testAgent.post('/v1/integrations').send(payload); + const payload: CreateIntegrationRequestDto = { + providerId: EmailProviderIdEnum.Mailgun, + channel: 'email', + credentials: { apiKey: '123', secretKey: 'abc' }, + environmentId: prodEnv?._id, + active: true, + check: false, + }; - await sendTrigger( - session, - template, - newSubscriberId, - {}, - { email: { integrationIdentifier: newIntegration.identifier } } - ); + const { result } = await novuClient.integrations.create(payload); + await sendTrigger(template, newSubscriberId, {}, { email: { integrationIdentifier: result.identifier } }); - await session.awaitRunningJobs(template._id); + await session.awaitRunningJobs(template._id); - messages = await messageRepository.find( - { - _environmentId: session.environment._id, - _subscriberId: createdSubscriber?._id, - channel: channelType, - }, - '', - { sort: { createdAt: -1 } } - ); + messages = await messageRepository.find( + { + _environmentId: session.environment._id, + _subscriberId: createdSubscriber?._id, + channel: channelType, + }, + '', + { sort: { createdAt: -1 } } + ); - expect(messages.length).to.be.equal(2); - expect(messages[0].providerId).to.be.equal(EmailProviderIdEnum.Mailgun); - }); + expect(messages.length).to.be.equal(2); + expect(messages[0].providerId).to.be.equal(EmailProviderIdEnum.Mailgun); + }); - describe('in-app avatar', () => { - it('should send the message with choosed system avatar', async () => { - const firstStepUuid = uuid(); - template = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.IN_APP, - content: 'Hello world!', - uuid: firstStepUuid, - actor: { - type: ActorTypeEnum.SYSTEM_ICON, - data: SystemAvatarIconEnum.WARNING, + describe('in-app avatar', () => { + it('should send the message with choosed system avatar', async () => { + const firstStepUuid = uuid(); + template = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.IN_APP, + content: 'Hello world!', + uuid: firstStepUuid, + actor: { + type: ActorTypeEnum.SYSTEM_ICON, + data: SystemAvatarIconEnum.WARNING, + }, }, - }, - ], - }); + ], + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + await novuClient.trigger({ name: template.triggers[0].identifier, to: [subscriber.subscriberId], payload: {}, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - await session.awaitRunningJobs(template?._id, true, 1); + await session.awaitRunningJobs(template?._id, true, 1); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: subscriber._id, - channel: StepTypeEnum.IN_APP, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: subscriber._id, + channel: StepTypeEnum.IN_APP, + }); - expect(messages.length).to.equal(1); - expect(messages[0].actor).to.be.ok; - expect(messages[0].actor?.type).to.eq(ActorTypeEnum.SYSTEM_ICON); - expect(messages[0].actor?.data).to.eq(SystemAvatarIconEnum.WARNING); - }); + expect(messages.length).to.equal(1); + expect(messages[0].actor).to.be.ok; + expect(messages[0].actor?.type).to.eq(ActorTypeEnum.SYSTEM_ICON); + expect(messages[0].actor?.data).to.eq(SystemAvatarIconEnum.WARNING); + }); - it('should send the message with custom system avatar url', async () => { - const firstStepUuid = uuid(); - const avatarUrl = 'https://gravatar.com/avatar/5246ec47a6a90ef2bcd29f0ef7d2faa6?s=400&d=robohash&r=x'; + it('should send the message with custom system avatar url', async () => { + const firstStepUuid = uuid(); + const avatarUrl = 'https://gravatar.com/avatar/5246ec47a6a90ef2bcd29f0ef7d2faa6?s=400&d=robohash&r=x'; - template = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.IN_APP, - content: 'Hello world!', - uuid: firstStepUuid, - actor: { - type: ActorTypeEnum.SYSTEM_CUSTOM, - data: avatarUrl, + template = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.IN_APP, + content: 'Hello world!', + uuid: firstStepUuid, + actor: { + type: ActorTypeEnum.SYSTEM_CUSTOM, + data: avatarUrl, + }, }, - }, - ], - }); + ], + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + await novuClient.trigger({ name: template.triggers[0].identifier, to: [subscriber.subscriberId], payload: {}, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - await session.awaitRunningJobs(template?._id, true, 1); + await session.awaitRunningJobs(template?._id, true, 1); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: subscriber._id, - channel: StepTypeEnum.IN_APP, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: subscriber._id, + channel: StepTypeEnum.IN_APP, + }); - expect(messages.length).to.equal(1); - expect(messages[0].actor).to.be.ok; - expect(messages[0].actor?.type).to.eq(ActorTypeEnum.SYSTEM_CUSTOM); - expect(messages[0].actor?.data).to.eq(avatarUrl); - }); + expect(messages.length).to.equal(1); + expect(messages[0].actor).to.be.ok; + expect(messages[0].actor?.type).to.eq(ActorTypeEnum.SYSTEM_CUSTOM); + expect(messages[0].actor?.data).to.eq(avatarUrl); + }); - it('should send the message with the actor avatar', async () => { - const firstStepUuid = uuid(); - const avatarUrl = 'https://gravatar.com/avatar/5246ec47a6a90ef2bcd29f0ef7d2faa6?s=400&d=robohash&r=x'; + it('should send the message with the actor avatar', async () => { + const firstStepUuid = uuid(); + const avatarUrl = 'https://gravatar.com/avatar/5246ec47a6a90ef2bcd29f0ef7d2faa6?s=400&d=robohash&r=x'; - const actor = await subscriberService.createSubscriber({ avatar: avatarUrl }); + const actor = await subscriberService.createSubscriber({ avatar: avatarUrl }); - template = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.IN_APP, - content: 'Hello world!', - uuid: firstStepUuid, - actor: { - type: ActorTypeEnum.USER, - data: null, + template = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.IN_APP, + content: 'Hello world!', + uuid: firstStepUuid, + actor: { + type: ActorTypeEnum.USER, + data: null, + }, }, - }, - ], - }); + ], + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + await novuClient.trigger({ name: template.triggers[0].identifier, to: [subscriber.subscriberId], - payload: {}, - actor: actor.subscriberId, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + payload: {}, + actor: actor.subscriberId, + }); - await session.awaitRunningJobs(template?._id, true, 1); + await session.awaitRunningJobs(template?._id, true, 1); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: subscriber._id, - channel: StepTypeEnum.IN_APP, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: subscriber._id, + channel: StepTypeEnum.IN_APP, + }); - expect(messages.length).to.equal(1); - expect(messages[0].actor).to.be.ok; - expect(messages[0].actor?.type).to.eq(ActorTypeEnum.USER); - expect(messages[0].actor?.data).to.eq(null); - expect(messages[0]._actorId).to.eq(actor._id); + expect(messages.length).to.equal(1); + expect(messages[0].actor).to.be.ok; + expect(messages[0].actor?.type).to.eq(ActorTypeEnum.USER); + expect(messages[0].actor?.data).to.eq(null); + expect(messages[0]._actorId).to.eq(actor._id); + }); }); - }); - describe('seen/read filter', () => { - it('should filter in app seen/read step', async function () { - const firstStepUuid = uuid(); - template = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.IN_APP, - content: 'Not Delayed {{customVar}}' as string, - uuid: firstStepUuid, - }, - { - type: StepTypeEnum.DELAY, - content: '', - metadata: { - unit: DigestUnitEnum.SECONDS, - amount: 2, - type: DelayTypeEnum.REGULAR, + describe('seen/read filter', () => { + it('should filter in app seen/read step', async function () { + const firstStepUuid = uuid(); + template = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.IN_APP, + content: 'Not Delayed {{customVar}}' as string, + uuid: firstStepUuid, }, - }, - { - type: StepTypeEnum.IN_APP, - content: 'Hello world {{customVar}}' as string, - filters: [ - { - isNegated: false, - type: 'GROUP', - value: FieldLogicalOperatorEnum.AND, - children: [ - { - on: FilterPartTypeEnum.PREVIOUS_STEP, - stepType: PreviousStepTypeEnum.READ, - step: firstStepUuid, - }, - ], + { + type: StepTypeEnum.DELAY, + content: '', + metadata: { + unit: DigestUnitEnum.SECONDS, + amount: 2, + type: DelayTypeEnum.REGULAR, }, - ], - }, - ], - }); + }, + { + type: StepTypeEnum.IN_APP, + content: 'Hello world {{customVar}}' as string, + filters: [ + { + isNegated: false, + type: 'GROUP', + value: FieldLogicalOperatorEnum.AND, + children: [ + { + on: FilterPartTypeEnum.PREVIOUS_STEP, + stepType: PreviousStepTypeEnum.READ, + step: firstStepUuid, + }, + ], + }, + ], + }, + ], + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + await novuClient.trigger({ name: template.triggers[0].identifier, to: [subscriber.subscriberId], payload: { customVar: 'Testing of User Name', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - await session.awaitRunningJobs(template?._id, true, 1); + await session.awaitRunningJobs(template?._id, true, 1); - const delayedJob = await jobRepository.findOne({ - _environmentId: session.environment._id, - _templateId: template._id, - type: StepTypeEnum.DELAY, - }); + const delayedJob = await jobRepository.findOne({ + _environmentId: session.environment._id, + _templateId: template._id, + type: StepTypeEnum.DELAY, + }); - if (!delayedJob) { - throw new Error(); - } + if (!delayedJob) { + throw new Error(); + } - expect(delayedJob.status).to.equal(JobStatusEnum.DELAYED); + expect(delayedJob.status).to.equal(JobStatusEnum.DELAYED); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: subscriber._id, - channel: StepTypeEnum.IN_APP, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: subscriber._id, + channel: StepTypeEnum.IN_APP, + }); - expect(messages.length).to.equal(1); + expect(messages.length).to.equal(1); - await session.awaitRunningJobs(template?._id, true, 0); + await session.awaitRunningJobs(template?._id, true, 0); - const messagesAfter = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: subscriber._id, - channel: StepTypeEnum.IN_APP, - }); + const messagesAfter = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: subscriber._id, + channel: StepTypeEnum.IN_APP, + }); - expect(messagesAfter.length).to.equal(1); - }); + expect(messagesAfter.length).to.equal(1); + }); - it('should filter email seen/read step', async function () { - const firstStepUuid = uuid(); - template = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.EMAIL, - name: 'Message Name', - subject: 'Test email subject', - content: [{ type: EmailBlockTypeEnum.TEXT, content: 'This is a sample text block' }], - uuid: firstStepUuid, - }, - { - type: StepTypeEnum.DELAY, - content: '', - metadata: { - unit: DigestUnitEnum.SECONDS, - amount: 2, - type: DelayTypeEnum.REGULAR, + it('should filter email seen/read step', async function () { + const firstStepUuid = uuid(); + template = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.EMAIL, + name: 'Message Name', + subject: 'Test email subject', + content: [{ type: EmailBlockTypeEnum.TEXT, content: 'This is a sample text block' }], + uuid: firstStepUuid, }, - }, - { - type: StepTypeEnum.EMAIL, - name: 'Message Name', - subject: 'Test email subject', - content: [{ type: EmailBlockTypeEnum.TEXT, content: 'This is a sample text block' }], - filters: [ - { - isNegated: false, - type: 'GROUP', - value: FieldLogicalOperatorEnum.AND, - children: [ - { - on: FilterPartTypeEnum.PREVIOUS_STEP, - stepType: PreviousStepTypeEnum.READ, - step: firstStepUuid, - }, - ], + { + type: StepTypeEnum.DELAY, + content: '', + metadata: { + unit: DigestUnitEnum.SECONDS, + amount: 2, + type: DelayTypeEnum.REGULAR, }, - ], - }, - ], - }); + }, + { + type: StepTypeEnum.EMAIL, + name: 'Message Name', + subject: 'Test email subject', + content: [{ type: EmailBlockTypeEnum.TEXT, content: 'This is a sample text block' }], + filters: [ + { + isNegated: false, + type: 'GROUP', + value: FieldLogicalOperatorEnum.AND, + children: [ + { + on: FilterPartTypeEnum.PREVIOUS_STEP, + stepType: PreviousStepTypeEnum.READ, + step: firstStepUuid, + }, + ], + }, + ], + }, + ], + }); - await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + await novuClient.trigger({ name: template.triggers[0].identifier, to: [subscriber.subscriberId], payload: { customVar: 'Testing of User Name', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - await session.awaitRunningJobs(template?._id, true, 1); + await session.awaitRunningJobs(template?._id, true, 1); - const delayedJob = await jobRepository.findOne({ - _environmentId: session.environment._id, - _templateId: template._id, - type: StepTypeEnum.DELAY, - }); + const delayedJob = await jobRepository.findOne({ + _environmentId: session.environment._id, + _templateId: template._id, + type: StepTypeEnum.DELAY, + }); - expect(delayedJob!.status).to.equal(JobStatusEnum.DELAYED); + expect(delayedJob!.status).to.equal(JobStatusEnum.DELAYED); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: subscriber._id, - channel: StepTypeEnum.EMAIL, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: subscriber._id, + channel: StepTypeEnum.EMAIL, + }); - expect(messages.length).to.equal(1); + expect(messages.length).to.equal(1); - await executionDetailsRepository.create({ - _jobId: delayedJob!._parentId, - _messageId: messages[0]._id, - _environmentId: session.environment._id, - _organizationId: session.organization._id, - webhookStatus: EmailEventStatusEnum.OPENED, - }); + await executionDetailsRepository.create({ + _jobId: delayedJob!._parentId, + _messageId: messages[0]._id, + _environmentId: session.environment._id, + _organizationId: session.organization._id, + webhookStatus: EmailEventStatusEnum.OPENED, + }); - await session.awaitRunningJobs(template?._id, true, 0); + await session.awaitRunningJobs(template?._id, true, 0); - const messagesAfter = await messageRepository.find({ - _environmentId: session.environment._id, - _subscriberId: subscriber._id, - channel: StepTypeEnum.EMAIL, - }); + const messagesAfter = await messageRepository.find({ + _environmentId: session.environment._id, + _subscriberId: subscriber._id, + channel: StepTypeEnum.EMAIL, + }); - expect(messagesAfter.length).to.equal(1); + expect(messagesAfter.length).to.equal(1); + }); }); - }); - - describe('workflow override', () => { - beforeEach(async () => { - session = new UserSession(); - await session.initialize(); - workflowOverrideService = new WorkflowOverrideService({ - organizationId: session.organization._id, - environmentId: session.environment._id, + describe('workflow override', () => { + beforeEach(async () => { + workflowOverrideService = new WorkflowOverrideService({ + organizationId: session.organization._id, + environmentId: session.environment._id, + }); }); - }); - it('should override - active false', async function () { - const subscriberOverride = SubscriberRepository.createObjectId(); + it('should override - active false', async function () { + const subscriberOverride = SubscriberRepository.createObjectId(); - // Create active workflow - const workflow = await createTemplate(session, ChannelTypeEnum.IN_APP); + // Create active workflow + const workflow = await createTemplate(session, ChannelTypeEnum.IN_APP); - // Create workflow override with active false - const { tenant } = await workflowOverrideService.createWorkflowOverride({ - workflowId: workflow._id, - active: false, - }); + // Create workflow override with active false + const { tenant } = await workflowOverrideService.createWorkflowOverride({ + workflowId: workflow._id, + active: false, + }); - if (!tenant) { - throw new Error('Tenant not found'); - } + if (!tenant) { + throw new Error('Tenant not found'); + } - const triggerResponse = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + const triggerResponse = await novuClient.trigger({ name: workflow.triggers[0].identifier, - to: subscriberOverride, + to: [subscriberOverride], tenant: tenant.identifier, payload: { firstName: 'Testing of User Name', urlVariable: '/test/url/path', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - expect(triggerResponse.status).to.equal(201); - expect(triggerResponse.data.data.status).to.equal('trigger_not_active'); + expect(triggerResponse.result.status).to.equal('trigger_not_active'); - await session.awaitRunningJobs(); + await session.awaitRunningJobs(); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: workflow._id, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: workflow._id, + }); - expect(messages.length).to.equal(0); + expect(messages.length).to.equal(0); - // Disable workflow - should not take effect, test for anomalies - await notificationTemplateRepository.update( - { _id: workflow._id, _environmentId: session.environment._id }, - { $set: { active: false } } - ); + // Disable workflow - should not take effect, test for anomalies + await notificationTemplateRepository.update( + { _id: workflow._id, _environmentId: session.environment._id }, + { $set: { active: false } } + ); - const triggerResponse2 = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + const triggerResponse2 = await novuClient.trigger({ name: workflow.triggers[0].identifier, - to: subscriberOverride, + to: [subscriberOverride], tenant: tenant.identifier, payload: { firstName: 'Testing of User Name', urlVariable: '/test/url/path', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - expect(triggerResponse2.status).to.equal(201); - expect(triggerResponse2.data.data.status).to.equal('trigger_not_active'); + expect(triggerResponse2.result.status).to.equal('trigger_not_active'); - await session.awaitRunningJobs(); + await session.awaitRunningJobs(); - const messages2 = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: workflow._id, - }); + const messages2 = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: workflow._id, + }); - expect(messages2.length).to.equal(0); - }); + expect(messages2.length).to.equal(0); + }); - /* - * TODO: we need to add support for Tenants in V2 Preferences - * This test is skipped for now as the tenant-level active flag is not taken into account for V2 Preferences - */ - it.skip('should override - active true', async function () { - const subscriberOverride = SubscriberRepository.createObjectId(); + /* + * TODO: we need to add support for Tenants in V2 Preferences + * This test is skipped for now as the tenant-level active flag is not taken into account for V2 Preferences + */ + it.skip('should override - active true', async function () { + const subscriberOverride = SubscriberRepository.createObjectId(); - // Create active workflow - const workflow = await createTemplate(session, ChannelTypeEnum.IN_APP); + // Create active workflow + const workflow = await createTemplate(session, ChannelTypeEnum.IN_APP); - // Create active workflow override - const { tenant } = await workflowOverrideService.createWorkflowOverride({ - workflowId: workflow._id, - active: true, - }); + // Create active workflow override + const { tenant } = await workflowOverrideService.createWorkflowOverride({ + workflowId: workflow._id, + active: true, + }); - if (!tenant) { - throw new Error('Tenant not found'); - } + if (!tenant) { + throw new Error('Tenant not found'); + } - const triggerResponse = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + const triggerResponse = await novuClient.trigger({ name: workflow.triggers[0].identifier, - to: subscriberOverride, + to: [subscriberOverride], tenant: tenant.identifier, payload: { firstName: 'Testing of User Name', urlVariable: '/test/url/path', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - expect(triggerResponse.status).to.equal(201); - expect(triggerResponse.data.data.status).to.equal('processed'); + expect(triggerResponse.result.status).to.equal('processed'); - await session.awaitRunningJobs(); + await session.awaitRunningJobs(); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: workflow._id, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: workflow._id, + }); - expect(messages.length).to.equal(1); + expect(messages.length).to.equal(1); - // Disable workflow - should not take effect as override is active - await notificationTemplateRepository.update( - { _id: workflow._id, _environmentId: session.environment._id }, - { $set: { active: false } } - ); + // Disable workflow - should not take effect as override is active + await notificationTemplateRepository.update( + { _id: workflow._id, _environmentId: session.environment._id }, + { $set: { active: false } } + ); - const triggerResponse2 = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + const triggerResponse2 = await novuClient.trigger({ name: workflow.triggers[0].identifier, - to: subscriberOverride, + to: [subscriberOverride], tenant: tenant.identifier, payload: { firstName: 'Testing of User Name', urlVariable: '/test/url/path', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - expect(triggerResponse2.status).to.equal(201); - expect(triggerResponse2.data.data.status).to.equal('processed'); + expect(triggerResponse2.result.status).to.equal('processed'); - await session.awaitRunningJobs(); + await session.awaitRunningJobs(); - const messages2 = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: workflow._id, - }); + const messages2 = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: workflow._id, + }); - expect(messages2.length).to.equal(2); - }); + expect(messages2.length).to.equal(2); + }); - /* - * TODO: we need to add support for Tenants in V2 Preferences - * This test is skipped for now as the tenant-level active flag is not taken into account for V2 Preferences - */ - it.skip('should override - preference - should disable in app channel', async function () { - const subscriberOverride = SubscriberRepository.createObjectId(); + /* + * TODO: we need to add support for Tenants in V2 Preferences + * This test is skipped for now as the tenant-level active flag is not taken into account for V2 Preferences + */ + it.skip('should override - preference - should disable in app channel', async function () { + const subscriberOverride = SubscriberRepository.createObjectId(); - // Create a workflow with in app channel enabled - const workflow = await createTemplate(session, ChannelTypeEnum.IN_APP); + // Create a workflow with in app channel enabled + const workflow = await createTemplate(session, ChannelTypeEnum.IN_APP); - // Create a workflow with in app channel disabled - const { tenant } = await workflowOverrideService.createWorkflowOverride({ - workflowId: workflow._id, - active: true, - preferenceSettings: { in_app: false }, - }); + // Create a workflow with in app channel disabled + const { tenant } = await workflowOverrideService.createWorkflowOverride({ + workflowId: workflow._id, + active: true, + preferenceSettings: { in_app: false }, + }); - if (!tenant) { - throw new Error('Tenant not found'); - } - const triggerResponse = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + if (!tenant) { + throw new Error('Tenant not found'); + } + const triggerResponse = await novuClient.trigger({ name: workflow.triggers[0].identifier, - to: subscriberOverride, + to: [subscriberOverride], tenant: tenant.identifier, payload: { firstName: 'Testing of User Name', urlVariable: '/test/url/path', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - expect(triggerResponse.status).to.equal(201); - expect(triggerResponse.data.data.status).to.equal('processed'); + expect(triggerResponse.result.status).to.equal('processed'); - await session.awaitRunningJobs(); + await session.awaitRunningJobs(); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: workflow._id, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: workflow._id, + }); - expect(messages.length).to.equal(0); - }); + expect(messages.length).to.equal(0); + }); - /* - * TODO: we need to add support for Tenants in V2 Preferences - * This test is skipped for now as the tenant-level active flag is not taken into account for V2 Preferences - */ - it.skip('should override - preference - should enable in app channel', async function () { - const subscriberOverride = SubscriberRepository.createObjectId(); + /* + * TODO: we need to add support for Tenants in V2 Preferences + * This test is skipped for now as the tenant-level active flag is not taken into account for V2 Preferences + */ + it.skip('should override - preference - should enable in app channel', async function () { + const subscriberOverride = SubscriberRepository.createObjectId(); - // Create a workflow with in-app channel disabled - const workflow = await session.createTemplate({ - steps: [ - { - type: StepTypeEnum.IN_APP, - content: 'Hello' as string, - }, - ], - preferenceSettingsOverride: { in_app: false }, - }); + // Create a workflow with in-app channel disabled + const workflow = await session.createTemplate({ + steps: [ + { + type: StepTypeEnum.IN_APP, + content: 'Hello' as string, + }, + ], + preferenceSettingsOverride: { in_app: false }, + }); - // Create workflow override with in app channel enabled - const { tenant } = await workflowOverrideService.createWorkflowOverride({ - workflowId: workflow._id, - active: true, - preferenceSettings: { in_app: true }, - }); + // Create workflow override with in app channel enabled + const { tenant } = await workflowOverrideService.createWorkflowOverride({ + workflowId: workflow._id, + active: true, + preferenceSettings: { in_app: true }, + }); - if (!tenant) { - throw new Error('Tenant not found'); - } + if (!tenant) { + throw new Error('Tenant not found'); + } - const triggerResponse = await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { + const triggerResponse = await novuClient.trigger({ name: workflow.triggers[0].identifier, - to: subscriberOverride, + to: [subscriberOverride], tenant: tenant.identifier, payload: { firstName: 'Testing of User Name', urlVariable: '/test/url/path', }, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, - }, - } - ); + }); - expect(triggerResponse.status).to.equal(201); - expect(triggerResponse.data.data.status).to.equal('processed'); + expect(triggerResponse.result.status).to.equal(201); + expect(triggerResponse.result.status).to.equal('processed'); - await session.awaitRunningJobs(); + await session.awaitRunningJobs(); - const messages = await messageRepository.find({ - _environmentId: session.environment._id, - _templateId: workflow._id, - }); + const messages = await messageRepository.find({ + _environmentId: session.environment._id, + _templateId: workflow._id, + }); - expect(messages.length).to.equal(1); + expect(messages.length).to.equal(1); + }); }); }); }); -}); - -async function createTemplate(session, channelType) { - return await session.createTemplate({ - steps: [ - { - type: channelType, - content: 'Hello {{subscriber.lastName}}, Welcome to {{organizationName}}' as string, - }, - ], - }); -} -export async function sendTrigger( - session, - template, - newSubscriberIdInAppNotification: string, - payload: Record = {}, - overrides: Record = {}, - tenant?: string, - actor?: string -): Promise { - return await axiosInstance.post( - `${session.serverUrl}${eventTriggerPath}`, - { - name: template.triggers[0].identifier, + async function sendTrigger( + templateInner: NotificationTemplateEntity, + newSubscriberIdInAppNotification: string, + payload: Record = {}, + overrides: Record> = {}, + tenant?: string, + actor?: string + ): Promise { + const request = { + name: templateInner.triggers[0].identifier, to: [{ subscriberId: newSubscriberIdInAppNotification, lastName: 'Smith', email: 'test@email.novu' }], payload: { organizationName: 'Umbrella Corp', @@ -3626,11 +3222,20 @@ export async function sendTrigger( overrides, tenant, actor, - }, - { - headers: { - authorization: `ApiKey ${session.apiKey}`, + }; + console.log('request111', JSON.stringify(request, null, 2)); + + return (await novuClient.trigger(request)).result; + } +}); + +async function createTemplate(session, channelType) { + return await session.createTemplate({ + steps: [ + { + type: channelType, + content: 'Hello {{subscriber.lastName}}, Welcome to {{organizationName}}' as string, }, - } - ); + ], + }); } diff --git a/apps/api/src/app/events/events.controller.ts b/apps/api/src/app/events/events.controller.ts index 36cca3a8068..3437769e161 100644 --- a/apps/api/src/app/events/events.controller.ts +++ b/apps/api/src/app/events/events.controller.ts @@ -26,7 +26,12 @@ import { SendTestEmail, SendTestEmailCommand } from './usecases/send-test-email' import { UserSession } from '../shared/framework/user.decorator'; import { ExternalApiAccessible } from '../auth/framework/external-api.decorator'; -import { ApiCommonResponses, ApiOkResponse, ApiResponse } from '../shared/framework/response.decorator'; +import { + ApiCommonResponses, + ApiCreatedResponse, + ApiOkResponse, + ApiResponse, +} from '../shared/framework/response.decorator'; import { DataBooleanDto } from '../shared/dtos/data-wrapper-dto'; import { ThrottlerCategory, ThrottlerCost } from '../rate-limiting/guards'; import { UserAuthentication } from '../shared/framework/swagger/api.key.security'; @@ -132,6 +137,10 @@ export class EventsController { description: `Trigger a broadcast event to all existing subscribers, could be used to send announcements, etc. In the future could be used to trigger events to a subset of subscribers based on defined filters.`, }) + @ApiCreatedResponse({ + description: 'Broadcast request has been registered successfully ', + type: TriggerEventResponseDto, // Specify the response type + }) async broadcastEventToAll( @UserSession() user: UserSessionData, @Body() body: TriggerEventToAllRequestDto diff --git a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts index ecc3aa68730..f876a5298e6 100644 --- a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts +++ b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts @@ -1,7 +1,7 @@ -import { IsDefined, IsString, IsOptional, ValidateNested, ValidateIf, IsEnum, IsObject } from 'class-validator'; +import { IsDefined, IsEnum, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator'; import { AddressingTypeEnum, - ControlsDto, + StatelessControls, TriggerRecipients, TriggerRecipientSubscriber, TriggerRequestCategoryEnum, @@ -42,8 +42,14 @@ export class ParseEventRequestBaseCommand extends EnvironmentWithUserCommand { @IsString() @IsOptional() bridgeUrl?: string; - - controls?: ControlsDto; + /** + * A mapping of step IDs to their corresponding data. + * Built for stateless triggering by the local studio, those values will not be persisted outside the job scope + * First key is step id, second is controlId, value is the control value + * @type {Record} + * @optional + */ + controls?: StatelessControls; } export class ParseEventRequestMulticastCommand extends ParseEventRequestBaseCommand { diff --git a/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts b/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts index 324735cb258..4af1276904d 100644 --- a/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts +++ b/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts @@ -16,28 +16,29 @@ import { CredentialsDto } from './credentials.dto'; import { StepFilter } from '../../shared/dtos/step-filter'; export class CreateIntegrationRequestDto implements ICreateIntegrationBodyDto { - @ApiPropertyOptional({ type: String }) + @ApiPropertyOptional({ type: String, description: 'The name of the integration' }) @IsOptional() @IsString() name?: string; - @ApiPropertyOptional({ type: String }) + @ApiPropertyOptional({ type: String, description: 'The unique identifier for the integration' }) @IsOptional() @IsString() identifier?: string; - @ApiPropertyOptional({ type: String }) + @ApiPropertyOptional({ type: String, description: 'The ID of the associated environment', format: 'uuid' }) @IsOptional() @IsMongoId() _environmentId?: string; - @ApiProperty({ type: String }) + @ApiProperty({ type: String, description: 'The provider ID for the integration' }) @IsDefined() @IsString() providerId: string; @ApiProperty({ enum: ChannelTypeEnum, + description: 'The channel type for the integration', }) @IsDefined() @IsEnum(ChannelTypeEnum) @@ -45,6 +46,7 @@ export class CreateIntegrationRequestDto implements ICreateIntegrationBodyDto { @ApiPropertyOptional({ type: CredentialsDto, + description: 'The credentials for the integration', }) @IsOptional() @Type(() => CredentialsDto) @@ -53,19 +55,20 @@ export class CreateIntegrationRequestDto implements ICreateIntegrationBodyDto { @ApiPropertyOptional({ type: Boolean, - description: 'If the integration is active the validation on the credentials field will run', + description: 'If the integration is active, the validation on the credentials field will run', }) @IsOptional() @IsBoolean() active?: boolean; - @ApiPropertyOptional({ type: Boolean }) + @ApiPropertyOptional({ type: Boolean, description: 'Flag to check the integration status' }) @IsOptional() @IsBoolean() check?: boolean; @ApiPropertyOptional({ type: [StepFilter], + description: 'Conditions for the integration', }) @IsArray() @IsOptional() diff --git a/apps/api/src/app/integrations/dtos/integration-response.dto.ts b/apps/api/src/app/integrations/dtos/integration-response.dto.ts index 5c3e0fe95c1..b200d45388d 100644 --- a/apps/api/src/app/integrations/dtos/integration-response.dto.ts +++ b/apps/api/src/app/integrations/dtos/integration-response.dto.ts @@ -4,50 +4,94 @@ import { StepFilter } from '../../shared/dtos/step-filter'; import { CredentialsDto } from './credentials.dto'; export class IntegrationResponseDto { - @ApiPropertyOptional() + @ApiPropertyOptional({ + description: 'The unique identifier of the integration record in the database. This is automatically generated.', + type: String, + }) _id?: string; - @ApiProperty() + @ApiProperty({ + description: + 'The unique identifier for the environment associated with this integration. This links to the Environment collection.', + type: String, + }) _environmentId: string; - @ApiProperty() + @ApiProperty({ + description: + 'The unique identifier for the organization that owns this integration. This links to the Organization collection.', + type: String, + }) _organizationId: string; - @ApiProperty({ type: String }) + @ApiProperty({ + description: 'The name of the integration, which is used to identify it in the user interface.', + type: String, + }) name: string; - @ApiProperty({ type: String }) + @ApiProperty({ + description: 'A unique string identifier for the integration, often used for API calls or internal references.', + type: String, + }) identifier: string; - @ApiProperty() + @ApiProperty({ + description: 'The identifier for the provider of the integration (e.g., "mailgun", "twilio").', + type: String, + }) providerId: string; @ApiProperty({ + description: + 'The channel type for the integration, which defines how the integration communicates (e.g., email, SMS).', enum: ChannelTypeEnum, }) channel: ChannelTypeEnum; @ApiProperty({ - type: CredentialsDto, + description: + 'The credentials required for the integration to function, including API keys and other sensitive information.', + type: () => CredentialsDto, }) credentials: CredentialsDto; - @ApiProperty() + @ApiProperty({ + description: + 'Indicates whether the integration is currently active. An active integration will process events and messages.', + type: Boolean, + }) active: boolean; - @ApiProperty() + @ApiProperty({ + description: 'Indicates whether the integration has been marked as deleted (soft delete).', + type: Boolean, + }) deleted: boolean; - @ApiProperty() - deletedAt: string; + @ApiPropertyOptional({ + description: + 'The timestamp indicating when the integration was deleted. This is set when the integration is soft deleted.', + type: String, + }) + deletedAt?: string; - @ApiProperty() - deletedBy: string; + @ApiPropertyOptional({ + description: 'The identifier of the user who performed the deletion of this integration. Useful for audit trails.', + type: String, + }) + deletedBy?: string; - @ApiProperty() + @ApiProperty({ + description: + 'Indicates whether this integration is marked as primary. A primary integration is often the default choice for processing.', + type: Boolean, + }) primary: boolean; @ApiPropertyOptional({ + description: + 'An array of conditions associated with the integration that may influence its behavior or processing logic.', type: [StepFilter], }) conditions?: StepFilter[]; diff --git a/apps/api/src/app/integrations/usecases/create-integration/create-integration.usecase.ts b/apps/api/src/app/integrations/usecases/create-integration/create-integration.usecase.ts index 964b31c4fbc..f2bb1aa3e20 100644 --- a/apps/api/src/app/integrations/usecases/create-integration/create-integration.usecase.ts +++ b/apps/api/src/app/integrations/usecases/create-integration/create-integration.usecase.ts @@ -1,22 +1,22 @@ import { BadRequestException, ConflictException, Inject, Injectable } from '@nestjs/common'; import shortid from 'shortid'; -import { IntegrationEntity, IntegrationRepository, DalException, IntegrationQuery } from '@novu/dal'; +import { DalException, IntegrationEntity, IntegrationQuery, IntegrationRepository } from '@novu/dal'; import { + CHANNELS_WITH_PRIMARY, ChannelTypeEnum, EmailProviderIdEnum, - providers, - SmsProviderIdEnum, InAppProviderIdEnum, - CHANNELS_WITH_PRIMARY, + providers, slugify, + SmsProviderIdEnum, } from '@novu/shared'; import { AnalyticsService, - encryptCredentials, + areNovuEmailCredentialsSet, + areNovuSmsCredentialsSet, buildIntegrationKey, + encryptCredentials, InvalidateCacheService, - areNovuSmsCredentialsSet, - areNovuEmailCredentialsSet, } from '@novu/application-generic'; import { CreateIntegrationCommand } from './create-integration.command'; diff --git a/apps/api/src/app/subscribers/dtos/create-subscriber-request.dto.ts b/apps/api/src/app/subscribers/dtos/create-subscriber-request.dto.ts index 7d6ed104cf8..f5e5547c368 100644 --- a/apps/api/src/app/subscribers/dtos/create-subscriber-request.dto.ts +++ b/apps/api/src/app/subscribers/dtos/create-subscriber-request.dto.ts @@ -5,21 +5,55 @@ import { IsArray, IsDefined, IsEmail, - IsEnum, IsLocale, + IsObject, IsOptional, IsString, ValidateNested, } from 'class-validator'; -import { - ChatProviderIdEnum, - IChannelCredentials, - ISubscriberChannel, - PushProviderIdEnum, - SubscriberCustomData, -} from '@novu/shared'; +import { ChatProviderIdEnum, IChannelCredentials, PushProviderIdEnum, SubscriberCustomData } from '@novu/shared'; import { Type } from 'class-transformer'; -import { SubscriberPayloadDto, TopicPayloadDto } from '../../events/dtos'; + +export class ChannelCredentialsDto implements IChannelCredentials { + @ApiPropertyOptional({ + description: 'The URL for the webhook associated with the channel.', + type: String, + }) + @IsOptional() + @IsString() + webhookUrl?: string; + + @ApiPropertyOptional({ + description: 'An array of device tokens for push notifications.', + type: [String], + }) + @IsOptional() + @IsArray() + deviceTokens?: string[]; +} + +export class SubscriberChannelDto { + @ApiProperty({ + description: 'The ID of the chat or push provider.', + enum: [...Object.values(ChatProviderIdEnum), ...Object.values(PushProviderIdEnum)], + }) + providerId: ChatProviderIdEnum | PushProviderIdEnum; + + @ApiPropertyOptional({ + description: 'An optional identifier for the integration.', + type: String, + }) + @IsOptional() + integrationIdentifier?: string; + + @ApiProperty({ + description: 'Credentials for the channel.', + type: ChannelCredentialsDto, + }) + @ValidateNested() + @Type(() => ChannelCredentialsDto) + credentials: ChannelCredentialsDto; +} export class CreateSubscriberRequestDto { @ApiProperty({ @@ -30,68 +64,84 @@ export class CreateSubscriberRequestDto { @IsDefined() subscriberId: string; - @ApiPropertyOptional() + @ApiPropertyOptional({ + description: 'The email address of the subscriber.', + }) @IsEmail() @IsOptional() email?: string; - @ApiPropertyOptional() + @ApiPropertyOptional({ + description: 'The first name of the subscriber.', + }) @IsString() @IsOptional() firstName?: string; - @ApiPropertyOptional() + @ApiPropertyOptional({ + description: 'The last name of the subscriber.', + }) @IsString() @IsOptional() lastName?: string; - @ApiPropertyOptional() + @ApiPropertyOptional({ + description: 'The phone number of the subscriber.', + }) @IsString() @IsOptional() phone?: string; @ApiPropertyOptional({ - description: 'An http url to the profile image of your subscriber', + description: 'An HTTP URL to the profile image of your subscriber.', }) @IsString() @IsOptional() avatar?: string; - @ApiPropertyOptional() + @ApiPropertyOptional({ + description: 'The locale of the subscriber.', + }) @IsLocale() @IsOptional() locale?: string; - @ApiPropertyOptional() + @ApiProperty({ + type: 'object', + description: 'An optional payload object that can contain any properties.', + required: false, + additionalProperties: { + oneOf: [ + { type: 'string' }, + { type: 'array', items: { type: 'string' } }, + { type: 'boolean' }, + { type: 'number' }, + ], + }, + }) @IsOptional() + @IsObject() data?: SubscriberCustomData; - @ApiPropertyOptional() + @ApiPropertyOptional({ + type: [SubscriberChannelDto], + description: 'An optional array of subscriber channels.', + }) @IsOptional() @IsArray() + @ValidateNested({ each: true }) + @Type(() => SubscriberChannelDto) channels?: SubscriberChannelDto[]; } -export class SubscriberChannelDto { - providerId: ChatProviderIdEnum | PushProviderIdEnum; - - @ApiPropertyOptional() - integrationIdentifier?: string; - - credentials: ChannelCredentialsDto; -} - -export class ChannelCredentialsDto implements IChannelCredentials { - webhookUrl?: string; - deviceTokens?: string[]; -} - export class BulkSubscriberCreateDto { - @ApiProperty() + @ApiProperty({ + description: 'An array of subscribers to be created in bulk.', + }) @IsArray() @ArrayNotEmpty() @ArrayMaxSize(500) - @ValidateNested() + @ValidateNested({ each: true }) @Type(() => CreateSubscriberRequestDto) subscribers: CreateSubscriberRequestDto[]; } diff --git a/apps/worker/src/app/workflow/usecases/add-job/add-job.command.ts b/apps/worker/src/app/workflow/usecases/add-job/add-job.command.ts index b81555b6187..3bae70a3e5e 100644 --- a/apps/worker/src/app/workflow/usecases/add-job/add-job.command.ts +++ b/apps/worker/src/app/workflow/usecases/add-job/add-job.command.ts @@ -1,7 +1,7 @@ import { IsDefined } from 'class-validator'; import { JobEntity } from '@novu/dal'; import { EnvironmentWithUserCommand } from '@novu/application-generic'; -import { ControlsDto } from '@novu/shared'; +import { StatelessControls } from '@novu/shared'; export class AddJobCommand extends EnvironmentWithUserCommand { @IsDefined() @@ -10,5 +10,5 @@ export class AddJobCommand extends EnvironmentWithUserCommand { @IsDefined() job: JobEntity; - controls?: ControlsDto; + controls?: StatelessControls; } diff --git a/apps/worker/src/app/workflow/usecases/subscriber-job-bound/subscriber-job-bound.command.ts b/apps/worker/src/app/workflow/usecases/subscriber-job-bound/subscriber-job-bound.command.ts index 354621f64bc..9673b2d9d76 100644 --- a/apps/worker/src/app/workflow/usecases/subscriber-job-bound/subscriber-job-bound.command.ts +++ b/apps/worker/src/app/workflow/usecases/subscriber-job-bound/subscriber-job-bound.command.ts @@ -1,9 +1,9 @@ -import { IsDefined, IsString, IsOptional, ValidateNested, IsMongoId, IsEnum } from 'class-validator'; +import { IsDefined, IsEnum, IsMongoId, IsOptional, IsString, ValidateNested } from 'class-validator'; import { - ControlsDto, ISubscribersDefine, ITenantDefine, + StatelessControls, SubscriberSourceEnum, TriggerRequestCategoryEnum, } from '@novu/shared'; @@ -50,7 +50,7 @@ export class SubscriberJobBoundCommand extends EnvironmentWithUserCommand { bridge?: { url: string; workflow: DiscoverWorkflowOutput }; - controls?: ControlsDto; + controls?: StatelessControls; @IsDefined() @IsString() diff --git a/libs/application-generic/src/dtos/process-subscriber-job.dto.ts b/libs/application-generic/src/dtos/process-subscriber-job.dto.ts index f873b44f2f5..08be3bc1268 100644 --- a/libs/application-generic/src/dtos/process-subscriber-job.dto.ts +++ b/libs/application-generic/src/dtos/process-subscriber-job.dto.ts @@ -1,7 +1,7 @@ import { - ControlsDto, ISubscribersDefine, ITenantDefine, + StatelessControls, SubscriberSourceEnum, TriggerRequestCategoryEnum, } from '@novu/shared'; @@ -29,7 +29,7 @@ export interface IProcessSubscriberDataDto { _subscriberSource: SubscriberSourceEnum; requestCategory?: TriggerRequestCategoryEnum; bridge?: { url: string; workflow: DiscoverWorkflowOutput }; - controls?: ControlsDto; + controls?: StatelessControls; } export interface IProcessSubscriberJobDto extends IJobParams { diff --git a/libs/application-generic/src/dtos/workflow-job.dto.ts b/libs/application-generic/src/dtos/workflow-job.dto.ts index 64e6fe2dbdc..4c58d3f2327 100644 --- a/libs/application-generic/src/dtos/workflow-job.dto.ts +++ b/libs/application-generic/src/dtos/workflow-job.dto.ts @@ -1,6 +1,6 @@ import { AddressingTypeEnum, - ControlsDto, + StatelessControls, TriggerRecipientsPayload, TriggerRecipientSubscriber, TriggerRequestCategoryEnum, @@ -36,7 +36,7 @@ export type IWorkflowDataDto = { requestCategory?: TriggerRequestCategoryEnum; bridgeUrl?: string; bridgeWorkflow?: DiscoverWorkflowOutput; - controls?: ControlsDto; + controls?: StatelessControls; } & Addressing; export interface IWorkflowJobDto extends IJobParams { diff --git a/libs/application-generic/src/usecases/create-notification-jobs/create-notification-jobs.command.ts b/libs/application-generic/src/usecases/create-notification-jobs/create-notification-jobs.command.ts index 7e8520c96b6..238a2d888db 100644 --- a/libs/application-generic/src/usecases/create-notification-jobs/create-notification-jobs.command.ts +++ b/libs/application-generic/src/usecases/create-notification-jobs/create-notification-jobs.command.ts @@ -1,12 +1,12 @@ -import { IsDefined, IsString, IsOptional } from 'class-validator'; +import { IsDefined, IsOptional, IsString } from 'class-validator'; // TODO: We shouldn't be importing from DAL here. Needs big refactor throughout monorepo. import { NotificationTemplateEntity, SubscriberEntity } from '@novu/dal'; import { ChannelTypeEnum, - ControlsDto, ISubscribersDefine, ITenantDefine, ProvidersIdEnum, + StatelessControls, WorkflowPreferences, } from '@novu/shared'; @@ -47,7 +47,7 @@ export class CreateNotificationJobsCommand extends EnvironmentWithUserCommand { bridgeUrl?: string; - controls?: ControlsDto; + controls?: StatelessControls; preferences?: WorkflowPreferences; } diff --git a/libs/application-generic/src/usecases/trigger-event/trigger-event.command.ts b/libs/application-generic/src/usecases/trigger-event/trigger-event.command.ts index c82c15c76f0..1bc61847439 100644 --- a/libs/application-generic/src/usecases/trigger-event/trigger-event.command.ts +++ b/libs/application-generic/src/usecases/trigger-event/trigger-event.command.ts @@ -1,15 +1,15 @@ import { IsDefined, - IsString, + IsEnum, IsOptional, - ValidateNested, + IsString, ValidateIf, - IsEnum, + ValidateNested, } from 'class-validator'; import { AddressingTypeEnum, - ControlsDto, + StatelessControls, TriggerRecipientsPayload, TriggerRecipientSubscriber, TriggerRequestCategoryEnum, @@ -55,7 +55,7 @@ export class TriggerEventBaseCommand extends EnvironmentWithUserCommand { @IsOptional() bridgeWorkflow?: DiscoverWorkflowOutput; - controls?: ControlsDto; + controls?: StatelessControls; } export class TriggerEventMulticastCommand extends TriggerEventBaseCommand { diff --git a/libs/dal/src/repositories/integration/integration.entity.ts b/libs/dal/src/repositories/integration/integration.entity.ts index f9ef0439067..2dd15c2c818 100644 --- a/libs/dal/src/repositories/integration/integration.entity.ts +++ b/libs/dal/src/repositories/integration/integration.entity.ts @@ -1,4 +1,4 @@ -import { BuilderFieldType, BuilderGroupValues, ChannelTypeEnum, FilterParts, ICredentials } from '@novu/shared'; +import { ChannelTypeEnum, ICredentials } from '@novu/shared'; import type { EnvironmentId } from '../environment'; import type { OrganizationId } from '../organization'; @@ -30,9 +30,9 @@ export class IntegrationEntity { deleted: boolean; - deletedAt: string; + deletedAt?: string; - deletedBy: string; + deletedBy?: string; conditions?: StepFilter[]; diff --git a/libs/dal/src/repositories/notification-template/notification-template.entity.ts b/libs/dal/src/repositories/notification-template/notification-template.entity.ts index 820018d0999..eb95c0ed9ac 100644 --- a/libs/dal/src/repositories/notification-template/notification-template.entity.ts +++ b/libs/dal/src/repositories/notification-template/notification-template.entity.ts @@ -4,7 +4,6 @@ import { BuilderGroupValues, ContentIssue, ControlSchemas, - ControlsDto, CustomDataType, FilterParts, IMessageFilter, @@ -142,11 +141,11 @@ export class StepVariantEntity implements IStepVariant { shouldStopOnFail?: boolean; bridgeUrl?: string; - /** - * @deprecated This property is deprecated and will be removed in future versions. - * Use `fullName` instead. + /* + * controlVariables exists + * only on none production environment in order to provide stateless control variables on fly */ - controlVariables?: ControlsDto; + controlVariables?: Record; /** * @deprecated This property is deprecated and will be removed in future versions. * Use IMessageTemplate.controls diff --git a/libs/dal/src/repositories/notification/notification.entity.ts b/libs/dal/src/repositories/notification/notification.entity.ts index aa6d9e78e8f..6fa8697cfc9 100644 --- a/libs/dal/src/repositories/notification/notification.entity.ts +++ b/libs/dal/src/repositories/notification/notification.entity.ts @@ -1,4 +1,4 @@ -import { ControlsDto, ISubscribersDefine, StepTypeEnum } from '@novu/shared'; +import { ISubscribersDefine, StatelessControls, StepTypeEnum } from '@novu/shared'; import { NotificationTemplateEntity } from '../notification-template'; import type { OrganizationId } from '../organization'; @@ -37,7 +37,7 @@ export class NotificationEntity { createdAt?: string; updatedAt?: string; tags?: string[]; - controls?: ControlsDto; + controls?: StatelessControls; } export type NotificationDBModel = ChangePropsValueType< diff --git a/packages/shared/src/dto/controls/controls.dto.ts b/packages/shared/src/dto/controls/controls.dto.ts deleted file mode 100644 index 6e151e51b44..00000000000 --- a/packages/shared/src/dto/controls/controls.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type ControlsDto = { - steps?: StepControl; -}; -type StepControl = Record; -type stepId = string; -type Data = Record; diff --git a/packages/shared/src/dto/controls/index.ts b/packages/shared/src/dto/controls/index.ts deleted file mode 100644 index ea4825a6c32..00000000000 --- a/packages/shared/src/dto/controls/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './controls.dto'; diff --git a/packages/shared/src/dto/index.ts b/packages/shared/src/dto/index.ts index 78c02ec3498..184548a3ddc 100644 --- a/packages/shared/src/dto/index.ts +++ b/packages/shared/src/dto/index.ts @@ -13,4 +13,4 @@ export * from './workflow-override'; export * from './widget'; export * from './session'; export * from './subscription'; -export * from './controls'; +export * from './stateless-control-values'; diff --git a/packages/shared/src/dto/stateless-control-values/index.ts b/packages/shared/src/dto/stateless-control-values/index.ts new file mode 100644 index 00000000000..b3210d03998 --- /dev/null +++ b/packages/shared/src/dto/stateless-control-values/index.ts @@ -0,0 +1 @@ +export * from './stateless-controls'; diff --git a/packages/shared/src/dto/stateless-control-values/stateless-controls.ts b/packages/shared/src/dto/stateless-control-values/stateless-controls.ts new file mode 100644 index 00000000000..0c03c4cb22c --- /dev/null +++ b/packages/shared/src/dto/stateless-control-values/stateless-controls.ts @@ -0,0 +1,10 @@ +export class StatelessControls { + /** + * A mapping of step IDs to their corresponding data. + * Built for stateless triggering by the local studio, those values will not be persisted outside of the job scope + * First key is step id, second is controlId, value is the control value + * @type {Record} + * @optional + */ + steps?: Record>; +} diff --git a/packages/shared/src/entities/notification-template/notification-template.interface.ts b/packages/shared/src/entities/notification-template/notification-template.interface.ts index a93f006c693..26e1362f335 100644 --- a/packages/shared/src/entities/notification-template/notification-template.interface.ts +++ b/packages/shared/src/entities/notification-template/notification-template.interface.ts @@ -1,10 +1,9 @@ import type { BuilderFieldType, BuilderGroupValues, CustomDataType, FilterParts, WorkflowTypeEnum } from '../../types'; -import type { ControlsDto } from '../../dto/controls'; -import type { JSONSchemaDto } from '../../dto/workflows'; +import { JSONSchemaDto } from '../../dto/workflows'; import type { ContentIssue, StepIssue } from '../../dto/workflows/workflow-response.dto'; import { ControlSchemas, IMessageTemplate } from '../message-template'; import { INotificationGroup } from '../notification-group'; -import { INotificationTrigger, INotificationBridgeTrigger } from '../notification-trigger'; +import { INotificationBridgeTrigger, INotificationTrigger } from '../notification-trigger'; import { IPreferenceChannels } from '../subscriber-preference'; import { IWorkflowStepMetadata } from '../step'; @@ -77,7 +76,7 @@ export interface IStepVariant { * controlVariables exists * only on none production environment in order to provide stateless control variables on fly */ - controlVariables?: ControlsDto; + controlVariables?: Record; bridgeUrl?: string; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7421d217e9..10365ee8600 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -393,8 +393,8 @@ importers: specifier: 6.2.1 version: 6.2.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2) '@novu/api': - specifier: ^0.0.1-alpha.39 - version: 0.0.1-alpha.39(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) + specifier: ^0.0.1-alpha.56 + version: 0.0.1-alpha.56(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) '@novu/application-generic': specifier: workspace:* version: link:../../libs/application-generic @@ -3692,7 +3692,7 @@ importers: version: 5.3.0 compression-webpack-plugin: specifier: ^10.0.0 - version: 10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)) + version: 10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) concurrently: specifier: ^5.3.0 version: 5.3.0 @@ -3701,13 +3701,13 @@ importers: version: 7.0.4(postcss@8.4.38) esbuild-plugin-compress: specifier: ^1.0.1 - version: 1.0.1(esbuild@0.21.5) + version: 1.0.1(esbuild@0.23.1) esbuild-plugin-inline-import: specifier: ^1.0.4 version: 1.0.4 esbuild-plugin-solid: specifier: ^0.6.0 - version: 0.6.0(esbuild@0.21.5)(solid-js@1.8.17) + version: 0.6.0(esbuild@0.23.1)(solid-js@1.8.17) http-server: specifier: ^0.13.0 version: 0.13.0 @@ -3740,28 +3740,28 @@ importers: version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))) terser-webpack-plugin: specifier: ^5.3.9 - version: 5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)) + version: 5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) tiny-glob: specifier: ^0.2.9 version: 0.2.9 ts-jest: specifier: ^29.0.3 - version: 29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.21.5)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2) ts-loader: specifier: ~9.4.0 - version: 9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)) + version: 9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) tsup: specifier: ^8.1.0 version: 8.1.0(@microsoft/api-extractor@7.47.7(@types/node@20.16.5))(@swc/core@1.7.26(@swc/helpers@0.5.12))(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))(typescript@5.6.2) tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.21.5)(solid-js@1.8.17)(tsup@8.1.0(@microsoft/api-extractor@7.47.7(@types/node@20.16.5))(@swc/core@1.7.26(@swc/helpers@0.5.12))(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))(typescript@5.6.2)) + version: 2.2.0(esbuild@0.23.1)(solid-js@1.8.17)(tsup@8.1.0(@microsoft/api-extractor@7.47.7(@types/node@20.16.5))(@swc/core@1.7.26(@swc/helpers@0.5.12))(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))(typescript@5.6.2)) typescript: specifier: 5.6.2 version: 5.6.2 webpack: specifier: ^5.74.0 - version: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + version: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-bundle-analyzer: specifier: ^4.9.0 version: 4.10.1 @@ -3920,7 +3920,7 @@ importers: version: 0.0.0 ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.21.5)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2) typedoc: specifier: ^0.24.0 version: 0.24.6(typescript@5.6.2) @@ -10588,8 +10588,8 @@ packages: '@nothing-but/utils@0.12.1': resolution: {integrity: sha512-1qZU1Q5El0IjE7JT/ucvJNzdr2hL3W8Rm27xNf1p6gb3Nw8pGnZmxp6/GEW9h+I1k1cICxXNq25hBwknTQ7yhg==} - '@novu/api@0.0.1-alpha.39': - resolution: {integrity: sha512-yXWWdQv2657dyETzSdx/klbq7eJdAguDSFSXt+Bum29uKQvOOFe/a5dR3spqF7Xd2Nt1dGNqf8cSeEBTBdFUuQ==} + '@novu/api@0.0.1-alpha.56': + resolution: {integrity: sha512-21wnQ1uQ5PUpdK7V5nrXAjZEIXIKVm1sV846l6UYhYXRkQO5JJBg2eeFbcwc8kAUmbKLPuMGKCimyaRMt9RiSw==} peerDependencies: react: ^18 || ^19 react-dom: ^18 || ^19 @@ -46501,7 +46501,7 @@ snapshots: '@nothing-but/utils@0.12.1': {} - '@novu/api@0.0.1-alpha.39(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8)': + '@novu/api@0.0.1-alpha.56(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8)': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -58645,9 +58645,9 @@ snapshots: '@webcontainer/api@1.2.0': {} - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4))': dependencies: - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0) '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': @@ -58655,9 +58655,9 @@ snapshots: webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4))': dependencies: - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0) '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': @@ -58665,9 +58665,9 @@ snapshots: webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4))': dependencies: - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0) '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': @@ -61450,11 +61450,11 @@ snapshots: dependencies: mime-db: 1.52.0 - compression-webpack-plugin@10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)): + compression-webpack-plugin@10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): dependencies: schema-utils: 4.0.0 serialize-javascript: 6.0.1 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) compression-webpack-plugin@10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): dependencies: @@ -63466,31 +63466,31 @@ snapshots: esbuild-plugin-alias@0.2.1: {} - esbuild-plugin-compress@1.0.1(esbuild@0.21.5): + esbuild-plugin-compress@1.0.1(esbuild@0.23.1): dependencies: chalk: 4.1.2 - esbuild: 0.21.5 + esbuild: 0.23.1 fs-extra: 10.1.0 micromatch: 4.0.5 esbuild-plugin-inline-import@1.0.4: {} - esbuild-plugin-solid@0.5.0(esbuild@0.21.5)(solid-js@1.8.17): + esbuild-plugin-solid@0.5.0(esbuild@0.23.1)(solid-js@1.8.17): dependencies: '@babel/core': 7.25.2 '@babel/preset-typescript': 7.23.2(@babel/core@7.25.2) babel-preset-solid: 1.8.17(@babel/core@7.25.2) - esbuild: 0.21.5 + esbuild: 0.23.1 solid-js: 1.8.17 transitivePeerDependencies: - supports-color - esbuild-plugin-solid@0.6.0(esbuild@0.21.5)(solid-js@1.8.17): + esbuild-plugin-solid@0.6.0(esbuild@0.23.1)(solid-js@1.8.17): dependencies: '@babel/core': 7.24.4 '@babel/preset-typescript': 7.23.2(@babel/core@7.24.4) babel-preset-solid: 1.8.17(@babel/core@7.24.4) - esbuild: 0.21.5 + esbuild: 0.23.1 solid-js: 1.8.17 transitivePeerDependencies: - supports-color @@ -78951,17 +78951,17 @@ snapshots: optionalDependencies: '@swc/core': 1.3.107(@swc/helpers@0.5.12) - terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.6 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.7.26(@swc/helpers@0.5.12) - esbuild: 0.21.5 + esbuild: 0.23.1 terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): dependencies: @@ -79019,17 +79019,17 @@ snapshots: optionalDependencies: '@swc/core': 1.3.107(@swc/helpers@0.5.12) - terser-webpack-plugin@5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.22.0 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.7.26(@swc/helpers@0.5.12) - esbuild: 0.21.5 + esbuild: 0.23.1 terser@5.16.9: dependencies: @@ -79493,7 +79493,7 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) - ts-jest@29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.21.5)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -79509,7 +79509,7 @@ snapshots: '@babel/core': 7.25.2 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) - esbuild: 0.21.5 + esbuild: 0.23.1 ts-jest@29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2): dependencies: @@ -79537,14 +79537,14 @@ snapshots: typescript: 5.6.2 webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.12)) - ts-loader@9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)): + ts-loader@9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 micromatch: 4.0.8 semver: 7.6.3 typescript: 5.6.2 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) ts-loader@9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): dependencies: @@ -79871,9 +79871,9 @@ snapshots: tslib@2.7.0: {} - tsup-preset-solid@2.2.0(esbuild@0.21.5)(solid-js@1.8.17)(tsup@8.1.0(@microsoft/api-extractor@7.47.7(@types/node@20.16.5))(@swc/core@1.7.26(@swc/helpers@0.5.12))(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))(typescript@5.6.2)): + tsup-preset-solid@2.2.0(esbuild@0.23.1)(solid-js@1.8.17)(tsup@8.1.0(@microsoft/api-extractor@7.47.7(@types/node@20.16.5))(@swc/core@1.7.26(@swc/helpers@0.5.12))(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))(typescript@5.6.2)): dependencies: - esbuild-plugin-solid: 0.5.0(esbuild@0.21.5)(solid-js@1.8.17) + esbuild-plugin-solid: 0.5.0(esbuild@0.23.1)(solid-js@1.8.17) tsup: 8.1.0(@microsoft/api-extractor@7.47.7(@types/node@20.16.5))(@swc/core@1.7.26(@swc/helpers@0.5.12))(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))(typescript@5.6.2) transitivePeerDependencies: - esbuild @@ -81607,9 +81607,9 @@ snapshots: webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -81618,7 +81618,7 @@ snapshots: import-local: 3.1.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-merge: 5.9.0 optionalDependencies: webpack-bundle-analyzer: 4.10.1 @@ -81940,7 +81940,7 @@ snapshots: - esbuild - uglify-js - webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4): + webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.4 '@types/estree': 0.0.51 @@ -81963,7 +81963,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.21.5)(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: From 3218466b089586f0d06335ac953c209aa29c905a Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Thu, 5 Dec 2024 12:34:55 +0200 Subject: [PATCH 14/18] feat(dashboard): Clerk based settings page (#7202) --- .../src/components/primitives/tabs.tsx | 14 +- .../side-navigation/side-navigation.tsx | 4 +- .../dashboard/src/components/user-profile.tsx | 2 + apps/dashboard/src/main.tsx | 17 +++ apps/dashboard/src/pages/index.ts | 1 + apps/dashboard/src/pages/settings.tsx | 143 ++++++++++++++++++ apps/dashboard/src/utils/routes.ts | 4 + 7 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 apps/dashboard/src/pages/settings.tsx diff --git a/apps/dashboard/src/components/primitives/tabs.tsx b/apps/dashboard/src/components/primitives/tabs.tsx index a68e15e3d87..c87746d7635 100644 --- a/apps/dashboard/src/components/primitives/tabs.tsx +++ b/apps/dashboard/src/components/primitives/tabs.tsx @@ -4,23 +4,29 @@ import * as TabsPrimitive from '@radix-ui/react-tabs'; import { cn } from '@/utils/ui'; import { cva, VariantProps } from 'class-variance-authority'; -const tabsListVariants = cva('inline-flex items-center', { +const tabsListVariants = cva('inline-flex', { variants: { variant: { - default: 'h-9 justify-center rounded-[10px] bg-neutral-alpha-100 p-1 text-muted-foreground', + default: 'h-9 rounded-[10px] bg-neutral-alpha-100 p-1 text-muted-foreground', regular: 'border-neutral-alpha-200 w-full justify-start gap-6 border-b border-t px-3.5', }, + align: { + center: 'justify-center', + start: 'justify-start', + end: 'justify-end', + }, }, defaultVariants: { variant: 'default', + align: 'center', }, }); type TabsListProps = React.ComponentPropsWithoutRef & VariantProps; const TabsList = React.forwardRef, TabsListProps>( - ({ className, variant, ...props }, ref) => ( - + ({ className, variant, align, ...props }, ref) => ( + ) ); TabsList.displayName = TabsPrimitive.List.displayName; diff --git a/apps/dashboard/src/components/side-navigation/side-navigation.tsx b/apps/dashboard/src/components/side-navigation/side-navigation.tsx index 2a039f99622..cbb66cfd302 100644 --- a/apps/dashboard/src/components/side-navigation/side-navigation.tsx +++ b/apps/dashboard/src/components/side-navigation/side-navigation.tsx @@ -76,7 +76,7 @@ export const SideNavigation = () => { - + Settings @@ -87,7 +87,7 @@ export const SideNavigation = () => { - + Invite teammates diff --git a/apps/dashboard/src/components/user-profile.tsx b/apps/dashboard/src/components/user-profile.tsx index 39b0a2b28ed..a7dd7b3f99b 100644 --- a/apps/dashboard/src/components/user-profile.tsx +++ b/apps/dashboard/src/components/user-profile.tsx @@ -1,12 +1,14 @@ import { UserButton } from '@clerk/clerk-react'; import { useNewDashboardOptIn } from '@/hooks/use-new-dashboard-opt-in'; import { RiSignpostFill } from 'react-icons/ri'; +import { ROUTES } from '../utils/routes'; export function UserProfile() { const { optOut } = useNewDashboardOptIn(); return ( , + }, + { + path: ROUTES.SETTINGS_ACCOUNT, + element: , + }, + { + path: ROUTES.SETTINGS_ORGANIZATION, + element: , + }, + { + path: ROUTES.SETTINGS_TEAM, + element: , + }, { path: '*', element: , diff --git a/apps/dashboard/src/pages/index.ts b/apps/dashboard/src/pages/index.ts index c9184036d71..b1cc5c6461f 100644 --- a/apps/dashboard/src/pages/index.ts +++ b/apps/dashboard/src/pages/index.ts @@ -4,4 +4,5 @@ export * from './sign-up'; export * from './organization-list'; export * from './questionnaire-page'; export * from './usecase-select-page'; +export * from './settings'; export * from './welcome-page'; diff --git a/apps/dashboard/src/pages/settings.tsx b/apps/dashboard/src/pages/settings.tsx new file mode 100644 index 00000000000..26352557bb9 --- /dev/null +++ b/apps/dashboard/src/pages/settings.tsx @@ -0,0 +1,143 @@ +import { Card } from '@/components/primitives/card'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/primitives/tabs'; +import { OrganizationProfile, UserProfile } from '@clerk/clerk-react'; +import { DashboardLayout } from '../components/dashboard-layout'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { ROUTES } from '@/utils/routes'; +import { Appearance } from '@clerk/types'; +import { motion } from 'motion/react'; + +const FADE_ANIMATION = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, + transition: { duration: 0.15 }, +} as const; + +const clerkComponentAppearance: Appearance = { + variables: { + colorPrimary: 'rgba(82, 88, 102, 0.95)', + colorText: 'rgba(82, 88, 102, 0.95)', + }, + elements: { + navbar: { display: 'none' }, + navbarMobileMenuRow: { display: 'none !important' }, + rootBox: { + width: '100%', + height: '100%', + }, + cardBox: { + display: 'block', + width: '100%', + height: '100%', + boxShadow: 'none', + }, + + pageScrollBox: { + padding: '0 !important', + }, + header: { + display: 'none', + }, + profileSection: { + borderTop: 'none', + borderBottom: '1px solid #e0e0e0', + }, + page: { + padding: '0 5px', + }, + }, +}; + +export function SettingsPage() { + const navigate = useNavigate(); + const location = useLocation(); + + const currentTab = + location.pathname === ROUTES.SETTINGS ? 'account' : location.pathname.split('/settings/')[1] || 'account'; + + const handleTabChange = (value: string) => { + switch (value) { + case 'account': + navigate(ROUTES.SETTINGS_ACCOUNT); + break; + case 'organization': + navigate(ROUTES.SETTINGS_ORGANIZATION); + break; + case 'team': + navigate(ROUTES.SETTINGS_TEAM); + break; + } + }; + + return ( + Settings}> + + + + Account + + + Organization + + + Team + + + +
+ + + + + + + + +

Security

+ + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ ); +} diff --git a/apps/dashboard/src/utils/routes.ts b/apps/dashboard/src/utils/routes.ts index 340a1e204e2..594318292f9 100644 --- a/apps/dashboard/src/utils/routes.ts +++ b/apps/dashboard/src/utils/routes.ts @@ -9,6 +9,10 @@ export const ROUTES = { INBOX_EMBED_SUCCESS: '/onboarding/inbox/success', ROOT: '/', ENV: '/env', + SETTINGS: '/settings', + SETTINGS_ACCOUNT: '/settings/account', + SETTINGS_ORGANIZATION: '/settings/organization', + SETTINGS_TEAM: '/settings/team', WORKFLOWS: '/env/:environmentSlug/workflows', EDIT_WORKFLOW: '/env/:environmentSlug/workflows/:workflowSlug', TEST_WORKFLOW: '/env/:environmentSlug/workflows/:workflowSlug/test', From 5680bd671cd0e2840fd3b3a39bd1d1e89d4cd89e Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Thu, 5 Dec 2024 13:05:18 +0200 Subject: [PATCH 15/18] feat(dashboard): api keys page (#7204) --- .source | 2 +- .../auth/services/passport/jwt.strategy.ts | 24 +++- apps/dashboard/src/api/environments.ts | 8 +- .../src/components/create-workflow-button.tsx | 4 +- .../src/components/shared/external-link.tsx | 21 ++++ .../side-navigation/side-navigation.tsx | 2 +- .../configure-workflow-form.tsx | 4 +- apps/dashboard/src/hooks/use-api-keys.ts | 17 +++ .../hooks/{use-tags-query.ts => use-tags.ts} | 2 +- apps/dashboard/src/main.tsx | 5 + apps/dashboard/src/pages/api-keys.tsx | 115 ++++++++++++++++++ apps/dashboard/src/pages/index.ts | 1 + apps/dashboard/src/utils/query-keys.ts | 1 + apps/dashboard/src/utils/routes.ts | 1 + 14 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 apps/dashboard/src/components/shared/external-link.tsx create mode 100644 apps/dashboard/src/hooks/use-api-keys.ts rename apps/dashboard/src/hooks/{use-tags-query.ts => use-tags.ts} (93%) create mode 100644 apps/dashboard/src/pages/api-keys.tsx diff --git a/.source b/.source index e37e9d3f03e..3a84f648a85 160000 --- a/.source +++ b/.source @@ -1 +1 @@ -Subproject commit e37e9d3f03e2574565e00f8ed52c4ea11bfd37aa +Subproject commit 3a84f648a85341317a96f56cb596edf84d13b039 diff --git a/apps/api/src/app/auth/services/passport/jwt.strategy.ts b/apps/api/src/app/auth/services/passport/jwt.strategy.ts index 8af770b41c4..1124da1f62c 100644 --- a/apps/api/src/app/auth/services/passport/jwt.strategy.ts +++ b/apps/api/src/app/auth/services/passport/jwt.strategy.ts @@ -29,17 +29,33 @@ export class JwtStrategy extends PassportStrategy(Strategy) { throw new UnauthorizedException(); } - await this.resolveEnvironmentId(req, session); + const environmentId = this.resolveEnvironmentId(req, session); + + // eslint-disable-next-line no-param-reassign + session.environmentId = environmentId; + + if (session.environmentId) { + const environment = await this.environmentRepository.findOne( + { + _id: session.environmentId, + _organizationId: session.organizationId, + }, + '_id' + ); + + if (!environment) { + throw new UnauthorizedException('Cannot find environment', JSON.stringify({ session })); + } + } return session; } @Instrument() - async resolveEnvironmentId(req: http.IncomingMessage, session: UserSessionData) { + resolveEnvironmentId(req: http.IncomingMessage, session: UserSessionData) { const environmentIdFromHeader = (req.headers[HttpRequestHeaderKeysEnum.NOVU_ENVIRONMENT_ID.toLowerCase()] as string) || ''; - // eslint-disable-next-line no-param-reassign - session.environmentId = environmentIdFromHeader; + return environmentIdFromHeader; } } diff --git a/apps/dashboard/src/api/environments.ts b/apps/dashboard/src/api/environments.ts index ca1016ec650..451ee8f4f46 100644 --- a/apps/dashboard/src/api/environments.ts +++ b/apps/dashboard/src/api/environments.ts @@ -1,4 +1,4 @@ -import type { IEnvironment } from '@novu/shared'; +import type { IApiKey, IEnvironment } from '@novu/shared'; import { get, put } from './api.client'; export async function getEnvironments() { @@ -10,3 +10,9 @@ export async function getEnvironments() { export async function updateBridgeUrl(payload: { url: string | undefined }, environmentId: string) { return put(`/environments/${environmentId}`, { bridge: payload }); } + +export async function getApiKeys(): Promise<{ data: IApiKey[] }> { + const data = await get<{ data: IApiKey[] }>(`/environments/api-keys`); + + return data; +} diff --git a/apps/dashboard/src/components/create-workflow-button.tsx b/apps/dashboard/src/components/create-workflow-button.tsx index 5ca61fe9681..a94c81637d4 100644 --- a/apps/dashboard/src/components/create-workflow-button.tsx +++ b/apps/dashboard/src/components/create-workflow-button.tsx @@ -24,7 +24,7 @@ import { import { TagInput } from '@/components/primitives/tag-input'; import { Textarea } from '@/components/primitives/textarea'; import { useEnvironment } from '@/context/environment/hooks'; -import { useTagsQuery } from '@/hooks/use-tags-query'; +import { useTags } from '@/hooks/use-tags'; import { QueryKeys } from '@/utils/query-keys'; import { buildRoute, ROUTES } from '@/utils/routes'; import { AUTOCOMPLETE_PASSWORD_MANAGERS_OFF } from '@/utils/constants'; @@ -56,7 +56,7 @@ export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { ); }, }); - const tagsQuery = useTagsQuery(); + const tagsQuery = useTags(); const form = useForm>({ resolver: zodResolver(workflowSchema), diff --git a/apps/dashboard/src/components/shared/external-link.tsx b/apps/dashboard/src/components/shared/external-link.tsx new file mode 100644 index 00000000000..798b8d6e01d --- /dev/null +++ b/apps/dashboard/src/components/shared/external-link.tsx @@ -0,0 +1,21 @@ +import { RiExternalLinkLine } from 'react-icons/ri'; +import { cn } from '@/utils/ui'; + +interface ExternalLinkProps extends React.AnchorHTMLAttributes { + children: React.ReactNode; + iconClassName?: string; +} + +export function ExternalLink({ children, className, iconClassName, ...props }: ExternalLinkProps) { + return ( + + {children} + + ); +} diff --git a/apps/dashboard/src/components/side-navigation/side-navigation.tsx b/apps/dashboard/src/components/side-navigation/side-navigation.tsx index cbb66cfd302..2795bc5800f 100644 --- a/apps/dashboard/src/components/side-navigation/side-navigation.tsx +++ b/apps/dashboard/src/components/side-navigation/side-navigation.tsx @@ -70,7 +70,7 @@ export const SideNavigation = () => { Integration Store
- + API Keys diff --git a/apps/dashboard/src/components/workflow-editor/configure-workflow-form.tsx b/apps/dashboard/src/components/workflow-editor/configure-workflow-form.tsx index d8409e3a55f..c8b99a8f4c6 100644 --- a/apps/dashboard/src/components/workflow-editor/configure-workflow-form.tsx +++ b/apps/dashboard/src/components/workflow-editor/configure-workflow-form.tsx @@ -5,7 +5,7 @@ import { z } from 'zod'; import { PAUSE_MODAL_TITLE, PauseModalDescription } from '@/components/pause-workflow-dialog'; import { SidebarContent, SidebarHeader } from '@/components/side-navigation/sidebar'; -import { useTagsQuery } from '@/hooks/use-tags-query'; +import { useTags } from '@/hooks/use-tags'; import { cn } from '@/utils/ui'; import { zodResolver } from '@hookform/resolvers/zod'; import { UpdateWorkflowDto, WorkflowOriginEnum, WorkflowResponseDto } from '@novu/shared'; @@ -31,7 +31,7 @@ export const ConfigureWorkflowForm = (props: ConfigureWorkflowFormProps) => { const { workflow, update } = props; const isReadOnly = workflow.origin === WorkflowOriginEnum.EXTERNAL; const [isPauseModalOpen, setIsPauseModalOpen] = useState(false); - const tagsQuery = useTagsQuery(); + const tagsQuery = useTags(); const form = useForm>({ defaultValues: { diff --git a/apps/dashboard/src/hooks/use-api-keys.ts b/apps/dashboard/src/hooks/use-api-keys.ts new file mode 100644 index 00000000000..f948ea18a4b --- /dev/null +++ b/apps/dashboard/src/hooks/use-api-keys.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query'; +import { QueryKeys } from '@/utils/query-keys'; +import { useEnvironment } from '@/context/environment/hooks'; +import { IApiKey } from '@novu/shared'; +import { getApiKeys } from '../api/environments'; + +export const useApiKeys = () => { + const { currentEnvironment } = useEnvironment(); + + const query = useQuery<{ data: IApiKey[] }>({ + queryKey: [QueryKeys.getApiKeys, currentEnvironment?._id], + queryFn: async () => await getApiKeys(), + enabled: !!currentEnvironment?._id, + }); + + return query; +}; diff --git a/apps/dashboard/src/hooks/use-tags-query.ts b/apps/dashboard/src/hooks/use-tags.ts similarity index 93% rename from apps/dashboard/src/hooks/use-tags-query.ts rename to apps/dashboard/src/hooks/use-tags.ts index 9fe17943cfe..d299c8a2726 100644 --- a/apps/dashboard/src/hooks/use-tags-query.ts +++ b/apps/dashboard/src/hooks/use-tags.ts @@ -3,7 +3,7 @@ import { QueryKeys } from '@/utils/query-keys'; import { useEnvironment } from '@/context/environment/hooks'; import { getV2 } from '@/api/api.client'; -export const useTagsQuery = () => { +export const useTags = () => { const { currentEnvironment } = useEnvironment(); const query = useQuery<{ data: { name: string }[] }>({ queryKey: [QueryKeys.fetchTags, currentEnvironment?._id], diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx index 08ab5a80d13..1bd44d27a5c 100644 --- a/apps/dashboard/src/main.tsx +++ b/apps/dashboard/src/main.tsx @@ -11,6 +11,7 @@ import { OrganizationListPage, QuestionnairePage, UsecaseSelectPage, + ApiKeysPage, WelcomePage, SettingsPage, } from '@/pages'; @@ -94,6 +95,10 @@ const router = createBrowserRouter([ path: ROUTES.WORKFLOWS, element: , }, + { + path: ROUTES.API_KEYS, + element: , + }, { path: ROUTES.EDIT_WORKFLOW, element: , diff --git a/apps/dashboard/src/pages/api-keys.tsx b/apps/dashboard/src/pages/api-keys.tsx new file mode 100644 index 00000000000..6f9bec33ef7 --- /dev/null +++ b/apps/dashboard/src/pages/api-keys.tsx @@ -0,0 +1,115 @@ +import { useState } from 'react'; +import { RiKey2Line, RiEyeLine, RiEyeOffLine } from 'react-icons/ri'; +import { useEnvironment } from '@/context/environment/hooks'; +import { CopyButton } from '@/components/primitives/copy-button'; +import { Card, CardContent } from '@/components/primitives/card'; +import { Button } from '@/components/primitives/button'; +import { Input, InputField } from '@/components/primitives/input'; +import { Form } from '@/components/primitives/form/form'; +import { useForm } from 'react-hook-form'; +import { DashboardLayout } from '../components/dashboard-layout'; +import { PageMeta } from '@/components/page-meta'; +import { useApiKeys } from '../hooks/use-api-keys'; +import { ExternalLink } from '@/components/shared/external-link'; + +interface ApiKeysFormData { + apiKey: string; + environmentId: string; + identifier: string; +} + +export function ApiKeysPage() { + const apiKeysQuery = useApiKeys(); + const { currentEnvironment } = useEnvironment(); + const [showApiKey, setShowApiKey] = useState(false); + const apiKeys = apiKeysQuery.data?.data; + + const form = useForm({ + values: { + apiKey: apiKeys?.[0]?.key ?? '', + environmentId: currentEnvironment?._id ?? '', + identifier: currentEnvironment?.identifier ?? '', + }, + }); + + if (!currentEnvironment) { + return null; + } + + const toggleApiKeyVisibility = () => { + setShowApiKey(!showApiKey); + }; + + const maskApiKey = (key: string) => { + return `${'•'.repeat(28)} ${key.slice(-4)}`; + }; + + return ( + <> + + API Keys}> +
+
+
+ + +
+
+ +
+ + + + + + +
+

+ Use this key to authenticate your API requests. Keep it secure and never share it publicly. +

+
+ +
+ +
+ + + + +
+

+ The public application identifier used for the Inbox component +

+
+
+
+
+
+
+
+ +

Environment Keys

+

Copy and manage your public and private keys

+ + + Read about our SDKs + +
+
+
+
+
+ + ); +} diff --git a/apps/dashboard/src/pages/index.ts b/apps/dashboard/src/pages/index.ts index b1cc5c6461f..9dd7c0f91fa 100644 --- a/apps/dashboard/src/pages/index.ts +++ b/apps/dashboard/src/pages/index.ts @@ -4,5 +4,6 @@ export * from './sign-up'; export * from './organization-list'; export * from './questionnaire-page'; export * from './usecase-select-page'; +export * from './api-keys'; export * from './settings'; export * from './welcome-page'; diff --git a/apps/dashboard/src/utils/query-keys.ts b/apps/dashboard/src/utils/query-keys.ts index 3af473e3792..c6d0884d2b4 100644 --- a/apps/dashboard/src/utils/query-keys.ts +++ b/apps/dashboard/src/utils/query-keys.ts @@ -6,5 +6,6 @@ export const QueryKeys = Object.freeze({ fetchWorkflowTestData: 'fetchWorkflowTestData', fetchWorkflows: 'fetchWorkflows', fetchTags: 'fetchTags', + getApiKeys: 'getApiKeys', fetchIntegrations: 'fetchIntegrations', }); diff --git a/apps/dashboard/src/utils/routes.ts b/apps/dashboard/src/utils/routes.ts index 594318292f9..9997a888f30 100644 --- a/apps/dashboard/src/utils/routes.ts +++ b/apps/dashboard/src/utils/routes.ts @@ -19,6 +19,7 @@ export const ROUTES = { WELCOME: '/env/:environmentSlug/welcome', EDIT_STEP: 'steps/:stepSlug', EDIT_STEP_TEMPLATE: 'steps/:stepSlug/edit', + API_KEYS: '/env/:environmentSlug/api-keys', }; export const buildRoute = (route: string, params: Record) => { From 2bc56b111b75ba4ec90d082e0e4f373cc21428ce Mon Sep 17 00:00:00 2001 From: George Desipris <73396808+desiprisg@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:13:23 +0200 Subject: [PATCH 16/18] feat(dashboard): Implement email step editor & mini preview (#7129) --- apps/dashboard/package.json | 4 +- .../src/components/primitives/editor.tsx | 1 + .../workflow-editor/add-step-menu.tsx | 16 +- .../workflow-editor/steps/component-utils.tsx | 8 + .../steps/configure-step-form.tsx | 32 +- ...ta.tsx => configure-step-template-cta.tsx} | 12 +- .../steps/configure-step-template-form.tsx | 3 +- .../steps/configure-step-template.tsx | 13 +- .../email/configure-email-step-preview.tsx | 79 ++ .../steps/email/email-editor-preview.tsx | 106 ++ .../steps/email/email-editor.tsx | 26 + .../steps/email/email-preview.tsx | 24 + .../steps/email/email-subject.tsx | 45 + .../steps/email/email-tabs-section.tsx | 14 + .../steps/email/email-tabs.tsx | 66 + .../workflow-editor/steps/email/maily.tsx | 51 + .../in-app/configure-in-app-step-preview.tsx | 13 +- package.json | 3 +- packages/shared/src/types/feature-flags.ts | 1 + pnpm-lock.yaml | 1167 +++++++++++++---- 20 files changed, 1429 insertions(+), 255 deletions(-) rename apps/dashboard/src/components/workflow-editor/steps/{in-app/configure-in-app-step-template-cta.tsx => configure-step-template-cta.tsx} (79%) create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/configure-email-step-preview.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/email-editor-preview.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/email-editor.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/email-preview.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/email-subject.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/email-tabs-section.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/email/maily.tsx diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index f7d41360738..4fa5d95fb7c 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -25,6 +25,7 @@ "@codemirror/autocomplete": "^6.18.3", "@hookform/resolvers": "^3.9.0", "@lezer/highlight": "^1.2.1", + "@maily-to/core": "^0.0.16", "@novu/framework": "workspace:*", "@novu/js": "workspace:*", "@novu/react": "workspace:*", @@ -65,13 +66,13 @@ "cmdk": "1.0.0", "date-fns": "^4.1.0", "flat": "^6.0.1", - "motion": "^11.12.0", "js-cookie": "^3.0.5", "launchdarkly-react-client-sdk": "^3.3.2", "lodash.debounce": "^4.0.8", "lodash.merge": "^4.6.2", "lucide-react": "^0.439.0", "mixpanel-browser": "^2.52.0", + "motion": "^11.12.0", "next-themes": "^0.3.0", "react": "^18.3.1", "react-colorful": "^5.6.1", @@ -94,6 +95,7 @@ "@hookform/devtools": "^4.3.0", "@playwright/test": "^1.44.0", "@sentry/vite-plugin": "^2.22.6", + "@tiptap/core": "^2.10.3", "@types/lodash.debounce": "^4.0.9", "@types/lodash.merge": "^4.6.6", "@types/mixpanel-browser": "^2.49.0", diff --git a/apps/dashboard/src/components/primitives/editor.tsx b/apps/dashboard/src/components/primitives/editor.tsx index 5fb4ef26033..2cc8f3b2fc3 100644 --- a/apps/dashboard/src/components/primitives/editor.tsx +++ b/apps/dashboard/src/components/primitives/editor.tsx @@ -10,6 +10,7 @@ const editorVariants = cva('h-full w-full flex-1 [&_.cm-focused]:outline-none', variants: { size: { default: 'text-xs [&_.cm-editor]:py-1', + lg: 'text-base [&_.cm-editor]:py-1', }, }, defaultVariants: { diff --git a/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx b/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx index 8c10b1d75e9..9daf743bb08 100644 --- a/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx +++ b/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx @@ -8,6 +8,8 @@ import { Badge } from '../primitives/badge'; import { cn } from '@/utils/ui'; import { StepTypeEnum } from '@/utils/enums'; import { STEP_TYPE_TO_COLOR } from '@/utils/color'; +import { useFeatureFlag } from '@/hooks/use-feature-flag'; +import { FeatureFlagsKeysEnum } from '@novu/shared'; const MenuGroup = ({ children }: { children: ReactNode }) => { return
{children}
; @@ -73,6 +75,7 @@ export const AddStepMenu = ({ onMenuItemClick: (stepType: StepTypeEnum) => void; }) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const areNewStepsEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_ND_DELAY_DIGEST_EMAIL_ENABLED); const handleMenuItemClick = (stepType: StepTypeEnum) => { onMenuItemClick(stepType); @@ -104,7 +107,18 @@ export const AddStepMenu = ({ Channels - Email + { + if (!areNewStepsEnabled) { + return; + } + handleMenuItemClick(StepTypeEnum.EMAIL); + }} + > + Email + { switch (component) { @@ -23,6 +25,12 @@ export const getComponentByType = ({ component }: { component?: UiComponentEnum case UiComponentEnum.URL_TEXT_BOX: { return ; } + case UiComponentEnum.MAILY: { + return ; + } + case UiComponentEnum.TEXT_INLINE_LABEL: { + return ; + } default: { return null; } diff --git a/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx index 90fa58e293f..81fa658de92 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-step-form.tsx @@ -1,5 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { + FeatureFlagsKeysEnum, IEnvironment, StepDataDto, StepTypeEnum, @@ -8,7 +9,7 @@ import { WorkflowResponseDto, } from '@novu/shared'; import { motion } from 'motion/react'; -import { useMemo, useState } from 'react'; +import { HTMLAttributes, ReactNode, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { RiArrowLeftSLine, RiArrowRightSLine, RiCloseFill, RiDeleteBin2Line, RiPencilRuler2Fill } from 'react-icons/ri'; import { Link, useNavigate } from 'react-router-dom'; @@ -29,14 +30,20 @@ import { getFirstControlsErrorMessage, updateStepInWorkflow, } from '@/components/workflow-editor/step-utils'; -import { ConfigureInAppStepTemplateCta } from '@/components/workflow-editor/steps/in-app/configure-in-app-step-template-cta'; import { SdkBanner } from '@/components/workflow-editor/steps/sdk-banner'; import { buildRoute, ROUTES } from '@/utils/routes'; import { EXCLUDED_EDITOR_TYPES } from '@/utils/constants'; import { STEP_NAME_BY_TYPE } from './step-provider'; import { useFormAutosave } from '@/hooks/use-form-autosave'; - -const SUPPORTED_STEP_TYPES = [StepTypeEnum.IN_APP]; +import { ConfigureStepTemplateCta } from '@/components/workflow-editor/steps/configure-step-template-cta'; +import { ConfigureInAppStepPreview } from '@/components/workflow-editor/steps/in-app/configure-in-app-step-preview'; +import { useFeatureFlag } from '@/hooks/use-feature-flag'; +import { ConfigureEmailStepPreview } from '@/components/workflow-editor/steps/email/configure-email-step-preview'; + +const stepTypeToPreview: Record) => ReactNode) | undefined> = { + [StepTypeEnum.IN_APP]: ConfigureInAppStepPreview, + [StepTypeEnum.EMAIL]: ConfigureEmailStepPreview, +}; type ConfigureStepFormProps = { workflow: WorkflowResponseDto; @@ -50,6 +57,13 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => { const { step, workflow, update, updateStepCache, environment } = props; const navigate = useNavigate(); const isCodeCreatedWorkflow = workflow.origin === WorkflowOriginEnum.EXTERNAL; + const areNewStepsEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_ND_DELAY_DIGEST_EMAIL_ENABLED); + + const supportedStepTypes = [StepTypeEnum.IN_APP]; + if (areNewStepsEnabled) { + supportedStepTypes.push(StepTypeEnum.EMAIL); + } + const Preview = stepTypeToPreview[step.type] || (() => null); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -84,7 +98,7 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => { [step] ); - const isDashboardStepThatSupportsEditor = !isCodeCreatedWorkflow && SUPPORTED_STEP_TYPES.includes(step.type); + const isDashboardStepThatSupportsEditor = !isCodeCreatedWorkflow && supportedStepTypes.includes(step.type); const isCodeStepThatSupportsEditor = isCodeCreatedWorkflow && !EXCLUDED_EDITOR_TYPES.includes(step.type); const isStepSupportsEditor = isDashboardStepThatSupportsEditor || isCodeStepThatSupportsEditor; @@ -184,9 +198,13 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => { )} - {step.type === StepTypeEnum.IN_APP && } + {supportedStepTypes.includes(step.type) && ( + + + + )} - {!isCodeCreatedWorkflow && !SUPPORTED_STEP_TYPES.includes(step.type) && ( + {!isCodeCreatedWorkflow && !supportedStepTypes.includes(step.type) && ( <> diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/configure-in-app-step-template-cta.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-cta.tsx similarity index 79% rename from apps/dashboard/src/components/workflow-editor/steps/in-app/configure-in-app-step-template-cta.tsx rename to apps/dashboard/src/components/workflow-editor/steps/configure-step-template-cta.tsx index c528fa8e945..a637e6a23e5 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app/configure-in-app-step-template-cta.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-cta.tsx @@ -1,17 +1,17 @@ import { Button } from '@/components/primitives/button'; import { Separator } from '@/components/primitives/separator'; import { SidebarContent } from '@/components/side-navigation/sidebar'; -import { ConfigureInAppStepPreview } from '@/components/workflow-editor/steps/in-app/configure-in-app-step-preview'; import { StepDataDto } from '@novu/shared'; +import { PropsWithChildren } from 'react'; import { RiArrowRightUpLine } from 'react-icons/ri'; import { Link } from 'react-router-dom'; -type ConfigureInAppStepTemplateCtaProps = { +type ConfigureStepTemplateCtaProps = PropsWithChildren & { step: StepDataDto; issue?: string; }; -export const ConfigureInAppStepTemplateCta = (props: ConfigureInAppStepTemplateCtaProps) => { - const { step, issue } = props; +export const ConfigureStepTemplateCta = (props: ConfigureStepTemplateCtaProps) => { + const { step, children, issue } = props; if (issue) { return ( @@ -44,9 +44,7 @@ export const ConfigureInAppStepTemplateCta = (props: ConfigureInAppStepTemplateC return ( <> - - - + {children} ); diff --git a/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx index 59898b51468..1f709e2500d 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template-form.tsx @@ -17,9 +17,10 @@ import { OtherStepTabs } from './other-steps-tabs'; import { Form } from '@/components/primitives/form/form'; import { useFormAutosave } from '@/hooks/use-form-autosave'; import { SaveFormContext } from '@/components/workflow-editor/steps/save-form-context'; +import { EmailTabs } from '@/components/workflow-editor/steps/email/email-tabs'; const STEP_TYPE_TO_EDITOR: Record React.JSX.Element | null> = { - [StepTypeEnum.EMAIL]: OtherStepTabs, + [StepTypeEnum.EMAIL]: EmailTabs, [StepTypeEnum.CHAT]: OtherStepTabs, [StepTypeEnum.IN_APP]: InAppTabs, [StepTypeEnum.SMS]: OtherStepTabs, diff --git a/apps/dashboard/src/components/workflow-editor/steps/configure-step-template.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template.tsx index c7137455e1b..6722813c176 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/configure-step-template.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-step-template.tsx @@ -16,8 +16,14 @@ import { PageMeta } from '@/components/page-meta'; import { useWorkflow } from '@/components/workflow-editor/workflow-provider'; import { useStep } from '@/components/workflow-editor/steps/step-provider'; import { getEncodedId, STEP_DIVIDER } from '@/utils/step'; +import { StepTypeEnum } from '@novu/shared'; +import { cn } from '@/utils/ui'; const transitionSetting = { ease: [0.29, 0.83, 0.57, 0.99], duration: 0.4 }; +const stepTypeToClassname: Record = { + [StepTypeEnum.IN_APP]: 'sm:max-w-[600px]', + [StepTypeEnum.EMAIL]: 'sm:max-w-[800px]', +}; export const ConfigureStepTemplate = () => { const { stepSlug = '' } = useParams<{ @@ -75,9 +81,10 @@ export const ConfigureStepTemplate = () => { x: '100%', }} transition={transitionSetting} - className={ - 'bg-background fixed inset-y-0 right-0 z-50 flex h-full w-3/4 flex-col border-l shadow-lg outline-none sm:max-w-[600px]' - } + className={cn( + 'bg-background fixed inset-y-0 right-0 z-50 flex h-full w-3/4 flex-col border-l shadow-lg outline-none sm:max-w-[600px]', + stepTypeToClassname[step.type] + )} > diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/configure-email-step-preview.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/configure-email-step-preview.tsx new file mode 100644 index 00000000000..fd37ab86276 --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/configure-email-step-preview.tsx @@ -0,0 +1,79 @@ +import * as Sentry from '@sentry/react'; +import { HTMLAttributes, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; + +import { useStep } from '@/components/workflow-editor/steps/step-provider'; +import { usePreviewStep } from '@/hooks'; +import { EmailPreviewHeader } from '@/components/workflow-editor/steps/email/email-preview'; +import { Separator } from '@/components/primitives/separator'; +import { Skeleton } from '@/components/primitives/skeleton'; +import { ChannelTypeEnum } from '@novu/shared'; +import { cn } from '@/utils/ui'; + +type MiniEmailPreviewProps = HTMLAttributes; +const MiniEmailPreview = (props: MiniEmailPreviewProps) => { + const { className, children, ...rest } = props; + return ( +
+
+ + +
{children}
+
+
+ ); +}; + +type ConfigureEmailStepPreviewProps = HTMLAttributes; +export function ConfigureEmailStepPreview(props: ConfigureEmailStepPreviewProps) { + const { + previewStep, + data: previewData, + isPending: isPreviewPending, + } = usePreviewStep({ + onError: (error) => { + Sentry.captureException(error); + }, + }); + const { step, isPending } = useStep(); + + const { workflowSlug, stepSlug } = useParams<{ + workflowSlug: string; + stepSlug: string; + }>(); + + useEffect(() => { + if (!workflowSlug || !stepSlug || !step || isPending) return; + + previewStep({ + workflowSlug, + stepSlug, + data: { controlValues: step.controls.values, previewPayload: {} }, + }); + }, [workflowSlug, stepSlug, previewStep, step, isPending]); + + if (isPreviewPending) { + return ( + + + + + ); + } + + if (previewData?.result?.type !== ChannelTypeEnum.EMAIL) { + return No preview available; + } + + return ( + +
{previewData.result.preview.subject}
+
+ ); +} diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/email-editor-preview.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/email-editor-preview.tsx new file mode 100644 index 00000000000..9049dd86532 --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/email-editor-preview.tsx @@ -0,0 +1,106 @@ +import { CSSProperties, useEffect, useRef, useState } from 'react'; +import { type StepDataDto, type WorkflowResponseDto } from '@novu/shared'; + +import { Notification5Fill } from '@/components/icons'; +import { Code2 } from '@/components/icons/code-2'; +import { Button } from '@/components/primitives/button'; +import { Editor } from '@/components/primitives/editor'; +import { loadLanguage } from '@uiw/codemirror-extensions-langs'; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/primitives/accordion'; +import { useEditorPreview } from '../use-editor-preview'; +import { EmailTabsPreviewSection } from '@/components/workflow-editor/steps/email/email-tabs-section'; + +const getInitialAccordionValue = (value: string) => { + try { + return Object.keys(JSON.parse(value)).length > 0 ? 'payload' : undefined; + } catch (e) { + return undefined; + } +}; + +type EmailEditorPreviewProps = { + workflow: WorkflowResponseDto; + step: StepDataDto; + formValues: Record; +}; + +export const EmailEditorPreview = ({ workflow, step, formValues }: EmailEditorPreviewProps) => { + const workflowSlug = workflow.workflowId; + const stepSlug = step.stepId; + const { editorValue, setEditorValue, previewStep } = useEditorPreview({ + workflowSlug, + stepSlug, + controlValues: formValues, + }); + const [accordionValue, setAccordionValue] = useState(getInitialAccordionValue(editorValue)); + const [payloadError, setPayloadError] = useState(''); + const [height, setHeight] = useState(0); + const contentRef = useRef(null); + + useEffect(() => { + setAccordionValue(getInitialAccordionValue(editorValue)); + }, [editorValue]); + + useEffect(() => { + const timeout = setTimeout(() => { + if (contentRef.current) { + const rect = contentRef.current.getBoundingClientRect(); + setHeight(rect.height); + } + }, 0); + + return () => clearTimeout(timeout); + }, [editorValue]); + + return ( + +
+
+ + In-app template editor +
+ {/* Email preview goes here */} + + + +
+ + Configure preview +
+
+ + + {payloadError &&

{payloadError}

} + +
+
+
+
+
+ ); +}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/email-editor.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/email-editor.tsx new file mode 100644 index 00000000000..26cdd62d433 --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/email-editor.tsx @@ -0,0 +1,26 @@ +import { Separator } from '@/components/primitives/separator'; +import { getComponentByType } from '@/components/workflow-editor/steps/component-utils'; +import { EmailPreviewHeader } from '@/components/workflow-editor/steps/email/email-preview'; +import { EmailTabsEditSection } from '@/components/workflow-editor/steps/email/email-tabs-section'; +import { type UiSchema } from '@novu/shared'; + +const subjectKey = 'subject'; +const emailEditorKey = 'emailEditor'; + +type EmailEditorProps = { uiSchema?: UiSchema }; +export const EmailEditor = (props: EmailEditorProps) => { + const { uiSchema } = props; + const { [emailEditorKey]: emailEditor, [subjectKey]: subject } = uiSchema?.properties ?? {}; + + return ( + <> + + {subject && getComponentByType({ component: subject.component })} + + + + {emailEditor && getComponentByType({ component: emailEditor.component })} + + + ); +}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/email-preview.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/email-preview.tsx new file mode 100644 index 00000000000..db36b47816e --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/email-preview.tsx @@ -0,0 +1,24 @@ +import { Avatar, AvatarImage } from '@/components/primitives/avatar'; +import { cn } from '@/utils/ui'; +import { HTMLAttributes } from 'react'; +import { RiArrowDownSFill } from 'react-icons/ri'; + +type EmailPreviewHeaderProps = HTMLAttributes; +export const EmailPreviewHeader = (props: EmailPreviewHeaderProps) => { + const { className, ...rest } = props; + return ( +
+ + + +
+
+ Acme Inc. {``} +
+
+ to me +
+
+
+ ); +}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/email-subject.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/email-subject.tsx new file mode 100644 index 00000000000..24f58b41c12 --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/email-subject.tsx @@ -0,0 +1,45 @@ +import { EditorView } from '@uiw/react-codemirror'; +import { useMemo } from 'react'; +import { useFormContext } from 'react-hook-form'; + +import { Editor } from '@/components/primitives/editor'; +import { FormControl, FormField, FormItem, FormMessage } from '@/components/primitives/form/form'; +import { completions } from '@/utils/liquid-autocomplete'; +import { parseStepVariablesToLiquidVariables } from '@/utils/parseStepVariablesToLiquidVariables'; +import { capitalize } from '@/utils/string'; +import { autocompletion } from '@codemirror/autocomplete'; +import { useStep } from '@/components/workflow-editor/steps/step-provider'; + +const subjectKey = 'subject'; + +export const EmailSubject = () => { + const { control } = useFormContext(); + const { step } = useStep(); + const variables = useMemo(() => (step ? parseStepVariablesToLiquidVariables(step.variables) : []), [step]); + + return ( + ( + <> + + + field.onChange(val)} + /> + + + + + )} + /> + ); +}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs-section.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs-section.tsx new file mode 100644 index 00000000000..6267bba58ce --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs-section.tsx @@ -0,0 +1,14 @@ +import { cn } from '@/utils/ui'; +import { HTMLAttributes } from 'react'; + +type EmailTabsSectionProps = HTMLAttributes; +export const EmailTabsEditSection = (props: EmailTabsSectionProps) => { + const { className, ...rest } = props; + return
; +}; + +type EmailTabsPreviewSectionProps = HTMLAttributes; +export const EmailTabsPreviewSection = (props: EmailTabsPreviewSectionProps) => { + const { className, ...rest } = props; + return
; +}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx new file mode 100644 index 00000000000..03469a303ce --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx @@ -0,0 +1,66 @@ +import { Cross2Icon } from '@radix-ui/react-icons'; +import { useFormContext } from 'react-hook-form'; +import { RiEdit2Line, RiPencilRuler2Line } from 'react-icons/ri'; +import { useNavigate } from 'react-router-dom'; + +import { Notification5Fill } from '@/components/icons'; +import { Button } from '@/components/primitives/button'; +import { Separator } from '@/components/primitives/separator'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/primitives/tabs'; +import { StepEditorProps } from '@/components/workflow-editor/steps/configure-step-template-form'; +import { EmailEditor } from '@/components/workflow-editor/steps/email/email-editor'; +import { EmailEditorPreview } from '@/components/workflow-editor/steps/email/email-editor-preview'; +import { CustomStepControls } from '../controls/custom-step-controls'; + +const tabsContentClassName = 'h-full w-full overflow-y-auto'; + +export const EmailTabs = (props: StepEditorProps) => { + const { workflow, step } = props; + const { dataSchema, uiSchema } = step.controls; + const form = useFormContext(); + const navigate = useNavigate(); + + return ( + +
+
+ + Configure Template +
+ + + + Editor + + + + Preview + + + + +
+ + + + + + + + + +
+ ); +}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/maily.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/maily.tsx new file mode 100644 index 00000000000..27cec8faa75 --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/email/maily.tsx @@ -0,0 +1,51 @@ +import { FormControl, FormField, FormMessage } from '@/components/primitives/form/form'; +import { useStep } from '@/components/workflow-editor/steps/step-provider'; +import { parseStepVariablesToLiquidVariables } from '@/utils/parseStepVariablesToLiquidVariables'; +import { cn } from '@/utils/ui'; +import { Editor } from '@maily-to/core'; +import type { Editor as TiptapEditor } from '@tiptap/core'; +import { HTMLAttributes, useMemo, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; + +const bodyKey = 'emailEditor'; + +type MailyProps = HTMLAttributes; +export const Maily = (props: MailyProps) => { + const { className, ...rest } = props; + const { step } = useStep(); + const variables = useMemo(() => (step ? parseStepVariablesToLiquidVariables(step.variables) : []), [step]); + const [_, setEditor] = useState(); + const { control } = useFormContext(); + + return ( + { + return ( + <> +
+ + ({ name: v.label, required: false }))} + contentJson={field.value ? JSON.parse(field.value) : undefined} + onCreate={setEditor} + onUpdate={(editor) => { + setEditor(editor); + field.onChange(JSON.stringify(editor.getJSON())); + }} + /> + +
+ + + ); + }} + /> + ); +}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/configure-in-app-step-preview.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/configure-in-app-step-preview.tsx index 125b9a1361c..d121a93f0e3 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app/configure-in-app-step-preview.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/configure-in-app-step-preview.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { HTMLAttributes, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import * as Sentry from '@sentry/react'; import { ChannelTypeEnum } from '@novu/shared'; @@ -15,7 +15,8 @@ import { } from '@/components/workflow-editor/in-app-preview'; import { useStep } from '@/components/workflow-editor/steps/step-provider'; -export function ConfigureInAppStepPreview() { +type ConfigureInAppStepPreviewProps = HTMLAttributes; +export const ConfigureInAppStepPreview = (props: ConfigureInAppStepPreviewProps) => { const { previewStep, data: previewData, @@ -45,7 +46,7 @@ export function ConfigureInAppStepPreview() { const previewResult = previewData?.result; if (isPreviewPending || previewData === undefined) { return ( - + @@ -60,7 +61,7 @@ export function ConfigureInAppStepPreview() { if (previewResult?.type === undefined || previewResult?.type !== ChannelTypeEnum.IN_APP) { return ( - + @@ -74,7 +75,7 @@ export function ConfigureInAppStepPreview() { const preview = previewResult.preview; return ( - + @@ -85,4 +86,4 @@ export function ConfigureInAppStepPreview() { ); -} +}; diff --git a/package.json b/package.json index 0956d2d880c..20983d0793c 100644 --- a/package.json +++ b/package.json @@ -240,7 +240,8 @@ "xml2js@<0.5.0": "^0.5.0", "@types/mocha": "^10.0.8", "rollup@>=4.0.0 <4.22.4": "^4.22.4", - "@nestjs/common@>=10.0.0 <11.0.0": "10.4.1" + "@nestjs/common@>=10.0.0 <11.0.0": "10.4.1", + "prosemirror-model": "1.22.3" } } } diff --git a/packages/shared/src/types/feature-flags.ts b/packages/shared/src/types/feature-flags.ts index 8d204dc948f..0ce67b9b144 100644 --- a/packages/shared/src/types/feature-flags.ts +++ b/packages/shared/src/types/feature-flags.ts @@ -44,4 +44,5 @@ export enum FeatureFlagsKeysEnum { IS_USAGE_ALERTS_ENABLED = 'IS_USAGE_ALERTS_ENABLED', IS_NEW_DASHBOARD_ENABLED = 'IS_NEW_DASHBOARD_ENABLED', IS_NEW_DASHBOARD_GETTING_STARTED_ENABLED = 'IS_NEW_DASHBOARD_GETTING_STARTED_ENABLED', + IS_ND_DELAY_DIGEST_EMAIL_ENABLED = 'IS_ND_DELAY_DIGEST_EMAIL_ENABLED', } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10365ee8600..b20e7bb906b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,7 @@ overrides: '@types/mocha': ^10.0.8 rollup@>=4.0.0 <4.22.4: ^4.22.4 '@nestjs/common@>=10.0.0 <11.0.0': 10.4.1 + prosemirror-model: 1.22.3 importers: @@ -130,10 +131,10 @@ importers: version: 4.13.0(typescript@5.6.2) eslint-config-airbnb-base: specifier: ^15.0.0 - version: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1))(eslint@8.57.1) + version: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.1) eslint-config-airbnb-typescript: specifier: ^18.0.0 - version: 18.0.0(@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1))(eslint@8.57.1) + version: 18.0.0(@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@8.57.1) eslint-config-auto: specifier: ^0.9.0 version: 0.9.0(typescript@5.6.2) @@ -235,7 +236,7 @@ importers: version: 12.1.1(eslint@8.57.1) eslint-plugin-sonarjs: specifier: ^2.0.1 - version: 2.0.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@8.57.1) + version: 2.0.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1) eslint-plugin-spellcheck: specifier: 0.0.20 version: 0.0.20(eslint@8.57.1) @@ -385,13 +386,13 @@ importers: version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@nestjs/swagger': specifier: 7.4.0 - version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: 10.2.3 - version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/throttler': specifier: 6.2.1 - version: 6.2.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2) + version: 6.2.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(reflect-metadata@0.2.2) '@novu/api': specifier: ^0.0.1-alpha.56 version: 0.0.1-alpha.56(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) @@ -430,7 +431,7 @@ importers: version: 7.114.0 '@sentry/nestjs': specifier: ^8.33.1 - version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@sentry/node': specifier: ^8.33.1 version: 8.33.1 @@ -508,7 +509,7 @@ importers: version: 3.3.6 nest-raven: specifier: 10.1.0 - version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) newrelic: specifier: ^9.15.0 version: 9.15.0 @@ -597,7 +598,7 @@ importers: version: 10.1.4(chokidar@3.6.0)(typescript@5.6.2) '@nestjs/testing': specifier: 10.4.1 - version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-express@10.4.1) '@stoplight/spectral-cli': specifier: ^6.11.0 version: 6.11.0(encoding@0.1.13) @@ -673,6 +674,9 @@ importers: '@lezer/highlight': specifier: ^1.2.1 version: 1.2.1 + '@maily-to/core': + specifier: ^0.0.16 + version: 0.0.16(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@22.7.4)(typescript@5.6.2))(y-prosemirror@1.2.15(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0)(y-protocols@1.0.6(yjs@13.6.20))(yjs@13.6.20)) '@novu/framework': specifier: workspace:* version: link:../../packages/framework @@ -878,6 +882,9 @@ importers: '@sentry/vite-plugin': specifier: ^2.22.6 version: 2.22.6(encoding@0.1.13) + '@tiptap/core': + specifier: ^2.10.3 + version: 2.10.3(@tiptap/pm@2.10.3) '@types/lodash.debounce': specifier: ^4.0.9 version: 4.0.9 @@ -970,7 +977,7 @@ importers: version: 7.114.0 '@sentry/nestjs': specifier: ^8.33.1 - version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@sentry/node': specifier: ^8.33.1 version: 8.33.1 @@ -1467,7 +1474,7 @@ importers: version: 7.4.2 '@storybook/preset-create-react-app': specifier: ^7.4.2 - version: 7.4.2(ucmnrhmq4kewpo24xrp57f5r6y) + version: 7.4.2(@babel/core@7.22.11)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(react-refresh@0.11.0)(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1))(type-fest@2.19.0)(typescript@5.6.2)(webpack-dev-server@4.11.1(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)))(webpack-hot-middleware@2.26.1)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)) '@storybook/react': specifier: ^7.4.2 version: 7.4.2(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) @@ -1503,13 +1510,13 @@ importers: version: 4.1.0(less@4.1.3)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)) react-app-rewired: specifier: ^2.2.1 - version: 2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)) + version: 2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)) react-error-overlay: specifier: 6.0.11 version: 6.0.11 react-scripts: specifier: ^5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) sinon: specifier: 9.2.4 version: 9.2.4 @@ -1545,7 +1552,7 @@ importers: version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@nestjs/terminus': specifier: 10.2.3 - version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@novu/application-generic': specifier: workspace:* version: link:../../libs/application-generic @@ -1569,7 +1576,7 @@ importers: version: 7.114.0 '@sentry/nestjs': specifier: ^8.33.1 - version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@sentry/node': specifier: ^8.33.1 version: 8.33.1 @@ -1602,7 +1609,7 @@ importers: version: 4.17.21 nest-raven: specifier: 10.1.0 - version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) newrelic: specifier: ^9.15.0 version: 9.15.0 @@ -1624,7 +1631,7 @@ importers: version: 10.1.4(chokidar@3.6.0)(typescript@5.6.2) '@nestjs/testing': specifier: 10.4.1 - version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-express@10.4.1) '@types/chai': specifier: ^4.3.4 version: 4.3.4 @@ -1841,10 +1848,10 @@ importers: version: 4.1.0(less@4.1.3)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))) react-app-rewired: specifier: ^2.2.1 - version: 2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)) + version: 2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)) react-scripts: specifier: ^5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) typescript: specifier: 5.6.2 version: 5.6.2 @@ -1871,13 +1878,13 @@ importers: version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@nestjs/schedule': specifier: ^4.1.1 - version: 4.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 4.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@nestjs/swagger': specifier: 7.4.0 - version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: 10.2.3 - version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@novu/application-generic': specifier: workspace:* version: link:../../libs/application-generic @@ -1904,7 +1911,7 @@ importers: version: 7.114.0 '@sentry/nestjs': specifier: ^8.33.1 - version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@sentry/node': specifier: ^8.33.1 version: 8.33.1 @@ -1961,7 +1968,7 @@ importers: version: 4.17.21 nest-raven: specifier: 10.1.0 - version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) newrelic: specifier: ^9.15.0 version: 9.15.0 @@ -2005,7 +2012,7 @@ importers: version: 10.1.4(chokidar@3.6.0)(typescript@5.6.2) '@nestjs/testing': specifier: 10.4.1 - version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-express@10.4.1) '@types/bcrypt': specifier: ^3.0.0 version: 3.0.1 @@ -2080,13 +2087,13 @@ importers: version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.4.1)(rxjs@7.8.1) '@nestjs/serve-static': specifier: 4.0.2 - version: 4.0.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(express@4.21.0) + version: 4.0.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(express@4.21.0) '@nestjs/swagger': specifier: 7.4.0 - version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: 10.2.3 - version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/websockets': specifier: 10.4.1 version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-socket.io@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -2110,7 +2117,7 @@ importers: version: 7.114.0 '@sentry/nestjs': specifier: ^8.33.1 - version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@sentry/node': specifier: ^8.33.1 version: 8.33.1 @@ -2152,7 +2159,7 @@ importers: version: 4.17.21 nest-raven: specifier: 10.1.0 - version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1) newrelic: specifier: ^9.15.0 version: 9.15.0 @@ -2180,7 +2187,7 @@ importers: version: 10.1.4(chokidar@3.6.0)(typescript@5.6.2) '@nestjs/testing': specifier: 10.4.1 - version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-express@10.4.1) '@types/chai': specifier: ^4.2.11 version: 4.3.4 @@ -2252,7 +2259,7 @@ importers: version: 10.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/swagger': specifier: 7.4.0 - version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@novu/application-generic': specifier: workspace:* version: link:../../../libs/application-generic @@ -2334,10 +2341,10 @@ importers: version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@nestjs/swagger': specifier: 7.4.0 - version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/throttler': specifier: 6.2.1 - version: 6.2.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2) + version: 6.2.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(reflect-metadata@0.2.2) '@novu/application-generic': specifier: workspace:* version: link:../../../libs/application-generic @@ -2496,7 +2503,7 @@ importers: version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@nestjs/swagger': specifier: 7.4.0 - version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@novu/application-generic': specifier: workspace:* version: link:../../../libs/application-generic @@ -2593,13 +2600,13 @@ importers: version: 10.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/swagger': specifier: 7.4.0 - version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: 10.2.3 - version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.575.0))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.575.0))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/testing': specifier: 10.4.1 - version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-express@10.4.1) '@novu/dal': specifier: workspace:* version: link:../dal @@ -2722,7 +2729,7 @@ importers: version: 3.3.7 nestjs-otel: specifier: 6.1.1 - version: 6.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 6.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) nestjs-pino: specifier: 4.1.0 version: 4.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(pino-http@8.3.3) @@ -3233,7 +3240,7 @@ importers: version: 7.12.1(react@18.3.1) '@mantine/tiptap': specifier: ^7.12.1 - version: 7.12.1(@mantine/core@7.12.1(@mantine/hooks@7.12.1(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.12.1(react@18.3.1))(@tiptap/extension-link@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6))(@tiptap/react@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 7.12.1(@mantine/core@7.12.1(@mantine/hooks@7.12.1(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.12.1(react@18.3.1))(@tiptap/extension-link@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6))(@tiptap/react@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rjsf/core': specifier: ^5.20.0 version: 5.20.0(@rjsf/utils@5.20.0(react@18.3.1))(react@18.3.1) @@ -3248,28 +3255,28 @@ importers: version: 8.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tiptap/extension-document': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) + version: 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6)) '@tiptap/extension-history': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + version: 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) '@tiptap/extension-mention': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(@tiptap/suggestion@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)) + version: 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(@tiptap/suggestion@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)) '@tiptap/extension-paragraph': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) + version: 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6)) '@tiptap/extension-text': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) + version: 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6)) '@tiptap/pm': specifier: ^2.6.6 version: 2.6.6 '@tiptap/react': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tiptap/suggestion': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + version: 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) react-icons: specifier: ^5.0.1 version: 5.0.1(react@18.3.1) @@ -3692,7 +3699,7 @@ importers: version: 5.3.0 compression-webpack-plugin: specifier: ^10.0.0 - version: 10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) + version: 10.0.0(webpack@5.78.0) concurrently: specifier: ^5.3.0 version: 5.3.0 @@ -3740,7 +3747,7 @@ importers: version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))) terser-webpack-plugin: specifier: ^5.3.9 - version: 5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) + version: 5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0) tiny-glob: specifier: ^0.2.9 version: 0.2.9 @@ -3749,7 +3756,7 @@ importers: version: 29.1.2(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2) ts-loader: specifier: ~9.4.0 - version: 9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) + version: 9.4.4(typescript@5.6.2)(webpack@5.78.0) tsup: specifier: ^8.1.0 version: 8.1.0(@microsoft/api-extractor@7.47.7(@types/node@20.16.5))(@swc/core@1.7.26(@swc/helpers@0.5.12))(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2))(typescript@5.6.2) @@ -4011,7 +4018,7 @@ importers: version: 7.4.2(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@storybook/react-webpack5': specifier: ^7.4.2 - version: 7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack-hot-middleware@2.26.1) + version: 7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4)(webpack-hot-middleware@2.26.1) '@testing-library/dom': specifier: ^9.3.0 version: 9.3.0 @@ -4044,10 +4051,10 @@ importers: version: 8.8.2 babel-loader: specifier: ^8.2.4 - version: 8.3.0(@babel/core@7.25.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + version: 8.3.0(@babel/core@7.25.2)(webpack@5.78.0) compression-webpack-plugin: specifier: ^10.0.0 - version: 10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + version: 10.0.0(webpack@5.78.0) jest: specifier: ^29.3.1 version: 29.5.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)) @@ -4071,19 +4078,19 @@ importers: version: 7.4.2(encoding@0.1.13) terser-webpack-plugin: specifier: ^5.3.9 - version: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + version: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0) ts-jest: specifier: ^29.0.3 version: 29.1.0(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.5.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.16.5)(typescript@5.6.2)))(typescript@5.6.2) ts-loader: specifier: ~9.4.0 - version: 9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + version: 9.4.4(typescript@5.6.2)(webpack@5.78.0) typescript: specifier: 5.6.2 version: 5.6.2 url-loader: specifier: ^4.1.1 - version: 4.1.1(file-loader@6.2.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + version: 4.1.1(file-loader@6.2.0(webpack@5.78.0))(webpack@5.78.0) webpack: specifier: ^5.74.0 version: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) @@ -4557,7 +4564,7 @@ importers: version: 10.1.4(chokidar@3.6.0)(typescript@5.6.2) '@nestjs/testing': specifier: 10.4.1 - version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-express@10.4.1) '@swc/core': specifier: ^1.7.26 version: 1.7.26(@swc/helpers@0.5.12) @@ -9865,6 +9872,12 @@ packages: resolution: {integrity: sha512-SaNFseFPSDQlOYM9JTyYY6wauMu6qJ8eExo+jssFyb20ZaVvxKX1eTb3Gm5aW/4aWuxn6nofU+02sCk51//wdw==} engines: {node: '>=10.0.0'} + '@maily-to/core@0.0.16': + resolution: {integrity: sha512-kzZX3KfLfzM8OoCvHDVf9QdMoftSIO9kGbr43ke0t0xeLxPYZBHt5ivQZ4L0S/GW1bE1obm+rBWRmunLW6GuBA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.3.1 + '@maily-to/render@0.0.12': resolution: {integrity: sha512-IpLb5g6JJ7aBuZrVyUmUcOfRj1rc/DrDQS0uk8200xXD2Wr3V+mschMGUeJghW0IMEbk/Q14jbLTNfvTg3G42w==} engines: {node: '>=18.0.0'} @@ -12507,6 +12520,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.2': + resolution: {integrity: sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.0.1': resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -12622,6 +12648,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.2': + resolution: {integrity: sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popover@1.1.1': resolution: {integrity: sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g==} peerDependencies: @@ -12635,6 +12674,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.2': + resolution: {integrity: sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.1.2': resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==} peerDependencies: @@ -13465,6 +13517,9 @@ packages: '@remirror/core-constants@2.0.2': resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@remix-run/router@1.19.2': resolution: {integrity: sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==} engines: {node: '>=14.0.0'} @@ -16438,10 +16493,26 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tiptap/core@2.6.6': - resolution: {integrity: sha512-VO5qTsjt6rwworkuo0s5AqYMfDA0ZwiTiH6FHKFSu2G/6sS7HKcc/LjPq+5Legzps4QYdBDl3W28wGsGuS1GdQ==} + '@tiptap/core@2.10.3': + resolution: {integrity: sha512-wAG/0/UsLeZLmshWb6rtWNXKJftcmnned91/HLccHVQAuQZ1UWH+wXeQKu/mtodxEO7JcU2mVPR9mLGQkK0McQ==} peerDependencies: - '@tiptap/pm': ^2.6.6 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-blockquote@2.10.3': + resolution: {integrity: sha512-u9Mq4r8KzoeGVT8ms6FQDIMN95dTh3TYcT7fZpwcVM96mIl2Oyt+Bk66mL8z4zuFptfRI57Cu9QdnHEeILd//w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bold@2.10.3': + resolution: {integrity: sha512-xnF1tS2BsORenr11qyybW120gHaeHKiKq+ZOP14cGA0MsriKvWDnaCSocXP/xMEYHy7+2uUhJ0MsKkHVj4bPzQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bubble-menu@2.10.3': + resolution: {integrity: sha512-e9a4yMjQezuKy0rtyyzxbV2IAE1bm1PY3yoZEFrcaY0o47g1CMUn2Hwe+9As2HdntEjQpWR7NO1mZeKxHlBPYA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 '@tiptap/extension-bubble-menu@2.6.6': resolution: {integrity: sha512-IkfmlZq67aaegym5sBddBc/xXWCArxn5WJEl1oxKEayjQhybKSaqI7tk0lOx/x7fa5Ml1WlGpCFh+KKXbQTG0g==} @@ -16449,28 +16520,123 @@ packages: '@tiptap/core': ^2.6.6 '@tiptap/pm': ^2.6.6 + '@tiptap/extension-bullet-list@2.10.3': + resolution: {integrity: sha512-PTkwJOVlHi4RR4Wrs044tKMceweXwNmWA6EoQ93hPUVtQcwQL990Es5Izp+i88twTPLuGD9dH+o9QDyH9SkWdA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-code-block@2.10.3': + resolution: {integrity: sha512-yiDVNg22fYkzsFk5kBlDSHcjwVJgajvO/M5fDXA+Hfxwo2oNcG6aJyyHXFe+UaXTVjdkPej0J6kcMKrTMCiFug==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code@2.10.3': + resolution: {integrity: sha512-JyLbfyY3cPctq9sVdpcRWTcoUOoq3/MnGE1eP6eBNyMTHyBPcM9TPhOkgj+xkD1zW/884jfelB+wa70RT/AMxQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-color@2.10.3': + resolution: {integrity: sha512-FC2hPMSQ4w9UmO9kJCAdoU7gHpDbJ6MeJAmikB9EPp16dbGwFLrZm9TZ/4pv74fGfVm0lv720316ALOEgPEDjQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-text-style': ^2.7.0 + + '@tiptap/extension-document@2.10.3': + resolution: {integrity: sha512-6i8+xbS2zB6t8iFzli1O/QB01MmwyI5Hqiiv4m5lOxqavmJwLss2sRhoMC2hB3CyFg5UmeODy/f/RnI6q5Vixg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-document@2.6.6': resolution: {integrity: sha512-6qlH5VWzLHHRVeeciRC6C4ZHpMsAGPNG16EF53z0GeMSaaFD/zU3B239QlmqXmLsAl8bpf8Bn93N0t2ABUvScw==} peerDependencies: '@tiptap/core': ^2.6.6 + '@tiptap/extension-dropcursor@2.10.3': + resolution: {integrity: sha512-wzWf82ixWzZQr0hxcf/A0ul8NNxgy1N63O+c56st6OomoLuKUJWOXF+cs9O7V+/5rZKWdbdYYoRB5QLvnDBAlQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-floating-menu@2.10.3': + resolution: {integrity: sha512-Prg8rYLxeyzHxfzVu1mDkkUWMnD9ZN3y370O/1qy55e+XKVw9jFkTSuz0y0+OhMJG6bulYpDUMtb+N3+2xOWlQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@tiptap/extension-floating-menu@2.6.6': resolution: {integrity: sha512-lPkESOfAUxgmXRiNqUU23WSyja5FUfSWjsW4hqe+BKNjsUt1OuFMEtYJtNc+MCGhhtPfFvM3Jg6g9jd6g5XsLQ==} peerDependencies: '@tiptap/core': ^2.6.6 '@tiptap/pm': ^2.6.6 + '@tiptap/extension-focus@2.10.3': + resolution: {integrity: sha512-/zJHmngINGaJ3uHxOVXi2hmDTh5kuJKy0E5pLMokcaAICmyr1u+7Zq6ltKS2Hwmo4AQ2Dl6Hcr1IIMEHXo6Y8g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.10.3': + resolution: {integrity: sha512-FskZi2DqDSTH1WkgLF2OLy0xU7qj3AgHsKhVsryeAtld4jAK5EsonneWgaipbz0e/MxuIvc1oyacfZKABpLaNg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-hard-break@2.10.3': + resolution: {integrity: sha512-2rFlimUKAgKDwT6nqAMtPBjkrknQY8S7oBNyIcDOUGyFkvbDUl3Jd0PiC929S5F3XStJRppnMqhpNDAlWmvBLA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.10.3': + resolution: {integrity: sha512-AlxXXPCWIvw8hQUDFRskasj32iMNB8Sb19VgyFWqwvntGs2/UffNu8VdsVqxD2HpZ0g5rLYCYtSW4wigs9R3og==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.10.3': + resolution: {integrity: sha512-HaSiMdx9Im9Pb9qGlVud7W8bweRDRMez33Uzs5a2x0n1RWkelfH7TwYs41Y3wus8Ujs7kw6qh7jyhvPpQBKaSA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@tiptap/extension-history@2.6.6': resolution: {integrity: sha512-tPTzAmPGqMX5Bd5H8lzRpmsaMvB9DvI5Dy2za/VQuFtxgXmDiFVgHRkRXIuluSkPTuANu84XBOQ0cBijqY8x4w==} peerDependencies: '@tiptap/core': ^2.6.6 '@tiptap/pm': ^2.6.6 - '@tiptap/extension-link@2.6.6': - resolution: {integrity: sha512-NJSR5Yf/dI3do0+Mr6e6nkbxRQcqbL7NOPxo5Xw8VaKs2Oe8PX+c7hyqN3GZgn6uEbZdbVi1xjAniUokouwpFg==} + '@tiptap/extension-horizontal-rule@2.10.3': + resolution: {integrity: sha512-1a2IWhD00tgUNg/91RLnBvfENL7DLCui5L245+smcaLu+OXOOEpoBHawx59/M4hEpsjqvRRM79TzO9YXfopsPw==} peerDependencies: - '@tiptap/core': ^2.6.6 - '@tiptap/pm': ^2.6.6 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-image@2.10.3': + resolution: {integrity: sha512-YIjAF5CwDkMe28OQ5pvnmdRgbJ9JcGMIHY1kyqNunSf2iwphK+6SWz9UEIkDFiT7AsRZySqxFSq93iK1XyTifw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-italic@2.10.3': + resolution: {integrity: sha512-wAiO6ZxoHx2H90phnKttLWGPjPZXrfKxhOCsqYrK8BpRByhr48godOFRuGwYnKaiwoVjpxc63t+kDJDWvqmgMw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-link@2.10.3': + resolution: {integrity: sha512-8esKlkZBzEiNcpt7I8Cd6l1mWmCc/66pPbUq9LfnIniDXE3U+ahBf4m3TJltYFBGbiiTR/xqMtJyVHOpuLDtAw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-list-item@2.10.3': + resolution: {integrity: sha512-9sok81gvZfSta2K1Dwrq5/HSz1jk4zHBpFqCx0oydzodGslx6X1bNxdca+eXJpXZmQIWALK7zEr4X8kg3WZsgw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-mention@2.10.3': + resolution: {integrity: sha512-h0+BrTS2HdjMfsuy6zkFIqmVGYL8w3jIG0gYaDHjWwwe/Lf2BDgOu3bZWcSr/3bKiJIwwzpOJrXssqta4TZ0yQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@tiptap/suggestion': ^2.7.0 '@tiptap/extension-mention@2.6.6': resolution: {integrity: sha512-fghNe4ZQRiZ7i3+sSrZx87zPZjaCwVtxn56/5UinoBUP/ZpCGwGtI+ErKhCBVyLW1fKyd0MmlihK/IGIeCBw1A==} @@ -16479,19 +16645,71 @@ packages: '@tiptap/pm': ^2.6.6 '@tiptap/suggestion': ^2.6.6 + '@tiptap/extension-ordered-list@2.10.3': + resolution: {integrity: sha512-/SFuEDnbJxy3jvi72LeyiPHWkV+uFc0LUHTUHSh20vwyy+tLrzncJfXohGbTIv5YxYhzExQYZDRD4VbSghKdlw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.10.3': + resolution: {integrity: sha512-sNkTX/iN+YoleDiTJsrWSBw9D7c4vsYwnW5y/G5ydfuJMIRQMF78pWSIWZFDRNOMkgK5UHkhu9anrbCFYgBfaA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-paragraph@2.6.6': resolution: {integrity: sha512-fD/onCr16UQWx+/xEmuFC2MccZZ7J5u4YaENh8LMnAnBXf78iwU7CAcmuc9rfAEO3qiLoYGXgLKiHlh2ZfD4wA==} peerDependencies: '@tiptap/core': ^2.6.6 + '@tiptap/extension-placeholder@2.10.3': + resolution: {integrity: sha512-0OkwnDLguZgoiJM85cfnOySuMmPUF7qqw7DHQ+c3zwTAYnvzpvqrvpupc+2Zi9GfC1sDgr+Ajrp8imBHa6PHfA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-strike@2.10.3': + resolution: {integrity: sha512-jYoPy6F6njYp3txF3u23bgdRy/S5ATcWDO9LPZLHSeikwQfJ47nqb+EUNo5M8jIOgFBTn4MEbhuZ6OGyhnxopA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-align@2.10.3': + resolution: {integrity: sha512-g75sNl73gtgjP3XIcl06kvv1qw3c0rGEUD848rUU1bvlBpU3IxjkcQLgYvHmv3vpuUp9cKUkA2wa7Sv6R3fjvw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.10.3': + resolution: {integrity: sha512-TalYIdlF7vBA4afFhmido7AORdBbu3sV+HCByda0FiNbM6cjng3Nr9oxHOCVJy+ChqrcgF4m54zDfLmamdyu5Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.10.3': + resolution: {integrity: sha512-7p9XiRprsRZm8y9jvF/sS929FCELJ5N9FQnbzikOiyGNUx5mdI+exVZlfvBr9xOD5s7fBLg6jj9Vs0fXPNRkPg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-text@2.6.6': resolution: {integrity: sha512-e84uILnRzNzcwK1DVQNpXVmBG1Cq3BJipTOIDl1LHifOok7MBjhI/X+/NR0bd3N2t6gmDTWi63+4GuJ5EeDmsg==} peerDependencies: '@tiptap/core': ^2.6.6 + '@tiptap/extension-underline@2.10.3': + resolution: {integrity: sha512-VeGs0jeNiTnXddHHJEgOc/sKljZiyTEgSSuqMmsBACrr9aGFXbLTgKTvNjkZ9WzSnu7LwgJuBrwEhg8yYixUyQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.10.3': + resolution: {integrity: sha512-771p53aU0KFvujvKpngvq2uAxThlEsjYaXcVVmwrhf0vxSSg+psKQEvqvWvHv/3BwkPVCGwmEKNVJZjaXFKu4g==} + '@tiptap/pm@2.6.6': resolution: {integrity: sha512-56FGLPn3fwwUlIbLs+BO21bYfyqP9fKyZQbQyY0zWwA/AG2kOwoXaRn7FOVbjP6CylyWpFJnpRRmgn694QKHEg==} + '@tiptap/react@2.10.3': + resolution: {integrity: sha512-5GBL3arWai8WZuCl1MMA7bT5aWwqDi5AOQhX+hovKjwHvttpKDogRoUBL5k6Eds/eQMBMGTpsfmZlGNiFxSv1g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tiptap/react@2.6.6': resolution: {integrity: sha512-AUmdb/J1O/vCO2b8LL68ctcZr9a3931BwX4fUUZ1kCrCA5lTj2xz0rjeAtpxEdzLnR+Z7q96vB7vf7bPYOUAew==} peerDependencies: @@ -16500,6 +16718,15 @@ packages: react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 + '@tiptap/starter-kit@2.10.3': + resolution: {integrity: sha512-oq8xdVIMqohSs91ofHSr7i5dCp2F56Lb9aYIAI25lZmwNwQJL2geGOYjMSfL0IC4cQHPylIuSKYCg7vRFdZmAA==} + + '@tiptap/suggestion@2.10.3': + resolution: {integrity: sha512-ReEwiPQoDTXn3RuWnj9D7Aod9dbNQz0QAoLRftWUTdbj3O2ohbvTNX6tlcfS+7x48Q+fAALiJGpp5BtctODlsA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@tiptap/suggestion@2.6.6': resolution: {integrity: sha512-jogG0QgGit9UtTznVnhQfNImZfQM89NR0is20yRQzC0HmD8B8f3jmGrotG63Why2oKbeoe3CpM5/5eDE/paqCA==} peerDependencies: @@ -17012,6 +17239,9 @@ packages: '@types/linkify-it@3.0.2': resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + '@types/lodash.debounce@4.0.9': resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} @@ -17033,6 +17263,9 @@ packages: '@types/markdown-it@12.2.3': resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@3.0.11': resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} @@ -17045,6 +17278,9 @@ packages: '@types/mdurl@1.0.2': resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/mdx@2.0.7': resolution: {integrity: sha512-BG4tyr+4amr3WsSEmHn/fXPqaCba/AYZ7dsaQTiavihQunHSIxk+uAtqsjvicNpyHN6cm+B9RVrUOtW9VzIKHw==} @@ -21489,6 +21725,13 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + echo-drag-handle-plugin@0.0.2: + resolution: {integrity: sha512-gVBrpqAXXUU3Y/E33BjA1yxQ9n5jBS2OCoJ8QzEVArGBz4S7dqT4v3mx5w1cnKx88er3JBeb0ors8bnoLK3Bgw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + y-prosemirror: ^1.2.1 + edge-runtime@2.4.4: resolution: {integrity: sha512-uq1YdIxkMDsBYLdSSp/w62PciCL46ic4m1Z/2G6N8RcAPI8p35O8u6hJQT83j28Dnt4U5iyvmwFMYouHMK51uA==} engines: {node: '>=14'} @@ -24794,6 +25037,9 @@ packages: isomorphic-unfetch@3.1.0: resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} @@ -25663,6 +25909,11 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lib0@0.2.99: + resolution: {integrity: sha512-vwztYuUf1uf/1zQxfzRfO5yzfNKhTtgOByCruuiQQxWQXnPb8Itaube5ylofcV0oM0aKal9Mv+S1s1Ky0UYP1w==} + engines: {node: '>=16'} + hasBin: true + libnpmaccess@6.0.4: resolution: {integrity: sha512-qZ3wcfIyUoW0+qSFkMBovcTrSGJ3ZeyvpR7d5N9pEYv/kXs8sHP2wiqEIXBKLFrZlmM0kR0RJD7mtfLngtlLag==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -25869,6 +26120,10 @@ packages: resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + limiter@1.1.5: resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} @@ -26215,6 +26470,11 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + lucide-react@0.453.0: + resolution: {integrity: sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -29039,6 +29299,12 @@ packages: peerDependencies: postcss: ^8.4.31 + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.31 + postcss-nesting@10.2.0: resolution: {integrity: sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==} engines: {node: ^12 || ^14 || >=16} @@ -29664,6 +29930,9 @@ packages: prosemirror-commands@1.6.0: resolution: {integrity: sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==} + prosemirror-commands@1.6.2: + resolution: {integrity: sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==} + prosemirror-dropcursor@1.8.1: resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==} @@ -29682,6 +29951,9 @@ packages: prosemirror-markdown@1.13.0: resolution: {integrity: sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g==} + prosemirror-markdown@1.13.1: + resolution: {integrity: sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==} + prosemirror-menu@1.2.4: resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==} @@ -29700,19 +29972,35 @@ packages: prosemirror-tables@1.4.0: resolution: {integrity: sha512-fxryZZkQG12fSCNuZDrYx6Xvo2rLYZTbKLRd8rglOPgNJGMKIS8uvTt6gGC38m7UCu/ENnXIP9pEz5uDaPc+cA==} + prosemirror-tables@1.6.1: + resolution: {integrity: sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==} + prosemirror-trailing-node@2.0.9: resolution: {integrity: sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==} peerDependencies: - prosemirror-model: ^1.22.1 + prosemirror-model: 1.22.3 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: 1.22.3 prosemirror-state: ^1.4.2 prosemirror-view: ^1.33.8 prosemirror-transform@1.10.0: resolution: {integrity: sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==} + prosemirror-transform@1.10.2: + resolution: {integrity: sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==} + prosemirror-view@1.33.10: resolution: {integrity: sha512-wsKg9JeQkWlkXG8DDcloI/tbB9r3CysziubigoC8wTuE6zobN/9cl8bGRk1J1XjkUp7rxGBziOSxrhoILL84hg==} + prosemirror-view@1.37.0: + resolution: {integrity: sha512-z2nkKI1sJzyi7T47Ji/ewBPuIma1RNvQCCYVdV+MqWBV7o4Sa1n94UJCJJ1aQRF/xRkFfyqLGlGFWitIcCOtbg==} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -32483,6 +32771,9 @@ packages: tailwind-merge@2.4.0: resolution: {integrity: sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==} + tailwind-merge@2.5.5: + resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==} + tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: @@ -32493,6 +32784,11 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + tailwindcss@3.4.16: + resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==} + engines: {node: '>=14.0.0'} + hasBin: true + tailwindcss@3.4.4: resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} engines: {node: '>=14.0.0'} @@ -34843,6 +35139,22 @@ packages: resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==} deprecated: This package is now deprecated. Move to @xterm/xterm instead. + y-prosemirror@1.2.15: + resolution: {integrity: sha512-XDdrytq2M5bIy3qusQvfRclLu2eWZYPA+BbGWAb9FFWEhOB5FCrnzez2vsA+gvAd0FJTAcr89mjJ5g45r0j7TQ==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + prosemirror-model: 1.22.3 + prosemirror-state: ^1.2.3 + prosemirror-view: ^1.9.10 + y-protocols: ^1.0.1 + yjs: ^13.5.38 + + y-protocols@1.0.6: + resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.0.0 + y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -34917,6 +35229,10 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yjs@13.6.20: + resolution: {integrity: sha512-Z2YZI+SYqK7XdWlloI3lhMiKnCdFCVC4PchpdO+mCYwtiTwncjUbnRK9R1JmkNfdmHyDXuWN3ibJAt0wsqTbLQ==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -35387,8 +35703,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -35589,8 +35905,8 @@ snapshots: '@aws-crypto/sha1-browser': 3.0.0 '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-bucket-endpoint': 3.575.0 @@ -35816,11 +36132,11 @@ snapshots: - aws-crt optional: true - '@aws-sdk/client-sso-oidc@3.575.0': + '@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -35859,6 +36175,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.7.0 transitivePeerDependencies: + - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)': @@ -36243,11 +36560,11 @@ snapshots: - aws-crt optional: true - '@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': + '@aws-sdk/client-sts@3.575.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -36286,7 +36603,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.7.0 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/client-sts@3.637.0': @@ -36516,7 +36832,7 @@ snapshots: '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/credential-provider-env': 3.575.0 '@aws-sdk/credential-provider-process': 3.575.0 '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) @@ -36827,7 +37143,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/types': 3.575.0 '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 @@ -37348,7 +37664,7 @@ snapshots: '@aws-sdk/token-providers@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/types': 3.575.0 '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 @@ -37357,7 +37673,7 @@ snapshots: '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 @@ -45664,6 +45980,49 @@ snapshots: transitivePeerDependencies: - debug + '@maily-to/core@0.0.16(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@22.7.4)(typescript@5.6.2))(y-prosemirror@1.2.15(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0)(y-protocols@1.0.6(yjs@13.6.20))(yjs@13.6.20))': + dependencies: + '@radix-ui/react-dropdown-menu': 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-tooltip': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/extension-color': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/extension-text-style@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))) + '@tiptap/extension-document': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-dropcursor': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-focus': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-heading': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-horizontal-rule': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-image': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-link': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-list-item': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-mention': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)(@tiptap/suggestion@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)) + '@tiptap/extension-paragraph': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-placeholder': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-text-align': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-text-style': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-underline': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/pm': 2.10.3 + '@tiptap/react': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tiptap/starter-kit': 2.10.3 + '@tiptap/suggestion': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + clsx: 2.1.1 + echo-drag-handle-plugin: 0.0.2(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)(y-prosemirror@1.2.15(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0)(y-protocols@1.0.6(yjs@13.6.20))(yjs@13.6.20)) + fast-deep-equal: 3.1.3 + lucide-react: 0.453.0(react@18.3.1) + react: 18.3.1 + react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tailwind-merge: 2.5.5 + tailwindcss: 3.4.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@22.7.4)(typescript@5.6.2)) + tippy.js: 6.3.7 + uuid: 10.0.0 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - react-dom + - ts-node + - y-prosemirror + '@maily-to/render@0.0.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@react-email/components': 0.0.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -45798,12 +46157,12 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@mantine/tiptap@7.12.1(@mantine/core@7.12.1(@mantine/hooks@7.12.1(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.12.1(react@18.3.1))(@tiptap/extension-link@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6))(@tiptap/react@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mantine/tiptap@7.12.1(@mantine/core@7.12.1(@mantine/hooks@7.12.1(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.12.1(react@18.3.1))(@tiptap/extension-link@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6))(@tiptap/react@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@mantine/core': 7.12.1(@mantine/hooks@7.12.1(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/hooks': 7.12.1(react@18.3.1) - '@tiptap/extension-link': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) - '@tiptap/react': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tiptap/extension-link': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + '@tiptap/react': 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -46154,7 +46513,7 @@ snapshots: transitivePeerDependencies: - encoding - '@nestjs/graphql@12.0.9(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)': + '@nestjs/graphql@12.0.9(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)': dependencies: '@graphql-tools/merge': 9.0.0(graphql@16.9.0) '@graphql-tools/schema': 10.0.0(graphql@16.9.0) @@ -46234,7 +46593,7 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@4.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))': + '@nestjs/schedule@4.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -46263,7 +46622,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/serve-static@4.0.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(express@4.21.0)': + '@nestjs/serve-static@4.0.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(express@4.21.0)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -46271,7 +46630,7 @@ snapshots: optionalDependencies: express: 4.21.0 - '@nestjs/swagger@7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': + '@nestjs/swagger@7.4.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.15.0 '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -46286,7 +46645,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.1 - '@nestjs/terminus@10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.575.0))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/terminus@10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.575.0))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -46300,7 +46659,7 @@ snapshots: '@nestjs/axios': 3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1) mongoose: 8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.575.0))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1) - '@nestjs/terminus@10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/terminus@10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -46314,7 +46673,7 @@ snapshots: '@nestjs/axios': 3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.6.8)(rxjs@7.8.1) mongoose: 8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1) - '@nestjs/terminus@10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/terminus@10.2.3(@grpc/grpc-js@1.11.1)(@grpc/proto-loader@0.7.13)(@nestjs/axios@3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1))(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(mongoose@8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -46328,7 +46687,7 @@ snapshots: '@nestjs/axios': 3.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1) mongoose: 8.6.0(@aws-sdk/credential-providers@3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.7.1) - '@nestjs/testing@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1))': + '@nestjs/testing@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@nestjs/platform-express@10.4.1)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -46336,7 +46695,7 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) - '@nestjs/throttler@6.2.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)': + '@nestjs/throttler@6.2.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(reflect-metadata@0.2.2)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -48945,7 +49304,7 @@ snapshots: webpack-dev-server: 4.11.1(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))) webpack-hot-middleware: 2.26.1 - '@pmmmwh/react-refresh-webpack-plugin@0.5.10(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.10(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.78.0)': dependencies: ansi-html-community: 0.0.8 common-path-prefix: 3.0.0 @@ -48959,7 +49318,7 @@ snapshots: source-map: 0.7.4 webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) optionalDependencies: - '@types/webpack': 5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)) + '@types/webpack': 5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) type-fest: 2.19.0 webpack-hot-middleware: 2.26.1 @@ -49610,6 +49969,21 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-dropdown-menu@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.3)(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 @@ -49718,6 +50092,32 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-menu@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) + aria-hidden: 1.2.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.3)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-popover@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -49741,6 +50141,29 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-popover@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + aria-hidden: 1.2.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.3)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-popper@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 @@ -50582,6 +51005,8 @@ snapshots: '@remirror/core-constants@2.0.2': {} + '@remirror/core-constants@3.0.0': {} + '@remix-run/router@1.19.2': {} '@replit/codemirror-lang-csharp@6.2.0(@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3)(@lezer/common@1.2.3))(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.3)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2)': @@ -51278,7 +51703,7 @@ snapshots: '@sentry/types': 7.114.0 '@sentry/utils': 7.114.0 - '@sentry/nestjs@8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))': + '@sentry/nestjs@8.33.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)': dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -54160,7 +54585,7 @@ snapshots: - uglify-js - webpack-cli - '@storybook/builder-webpack5@7.4.2(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))': + '@storybook/builder-webpack5@7.4.2(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack-cli@5.1.4)': dependencies: '@babel/core': 7.23.2 '@storybook/addons': 7.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -54182,30 +54607,30 @@ snapshots: '@swc/core': 1.3.107(@swc/helpers@0.5.12) '@types/node': 16.11.7 '@types/semver': 7.3.13 - babel-loader: 9.1.2(@babel/core@7.23.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + babel-loader: 9.1.2(@babel/core@7.23.2)(webpack@5.78.0) babel-plugin-named-exports-order: 0.0.2 browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 constants-browserify: 1.0.0 - css-loader: 6.7.3(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + css-loader: 6.7.3(webpack@5.78.0) express: 4.21.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.6.2)(webpack@5.78.0) fs-extra: 11.2.0 - html-webpack-plugin: 5.5.3(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + html-webpack-plugin: 5.5.3(webpack@5.78.0) path-browserify: 1.0.1 process: 0.11.10 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) semver: 7.6.3 - style-loader: 3.3.2(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) - swc-loader: 0.2.3(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) - terser-webpack-plugin: 5.3.10(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + style-loader: 3.3.2(webpack@5.78.0) + swc-loader: 0.2.3(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)) - webpack-dev-middleware: 6.1.1(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack-cli@5.1.4) + webpack-dev-middleware: 6.1.1(webpack@5.78.0) webpack-hot-middleware: 2.25.3 webpack-virtual-modules: 0.5.0 optionalDependencies: @@ -54856,7 +55281,7 @@ snapshots: '@storybook/postinstall@7.4.2': {} - '@storybook/preset-create-react-app@7.4.2(ucmnrhmq4kewpo24xrp57f5r6y)': + '@storybook/preset-create-react-app@7.4.2(@babel/core@7.22.11)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(react-refresh@0.11.0)(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1))(type-fest@2.19.0)(typescript@5.6.2)(webpack-dev-server@4.11.1(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)))(webpack-hot-middleware@2.26.1)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))': dependencies: '@babel/core': 7.22.11 '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.11.1(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)))(webpack-hot-middleware@2.26.1)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)) @@ -54865,7 +55290,7 @@ snapshots: '@types/babel__core': 7.20.0 babel-plugin-react-docgen: 4.2.1 pnp-webpack-plugin: 1.7.0(typescript@5.6.2) - react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) semver: 7.5.4 transitivePeerDependencies: - '@types/webpack' @@ -54953,16 +55378,16 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack-hot-middleware@2.26.1)': + '@storybook/preset-react-webpack@7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4)(webpack-hot-middleware@2.26.1)': dependencies: '@babel/preset-flow': 7.22.15(@babel/core@7.25.2) '@babel/preset-react': 7.22.15(@babel/core@7.25.2) - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.78.0) '@storybook/core-webpack': 7.4.2(encoding@0.1.13) '@storybook/docs-tools': 7.4.2(encoding@0.1.13) '@storybook/node-logger': 7.4.2 '@storybook/react': 7.4.2(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.6.2)(webpack@5.78.0) '@types/node': 16.11.7 '@types/semver': 7.5.8 babel-plugin-add-react-displayname: 0.0.5 @@ -55076,7 +55501,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12)))': dependencies: debug: 4.3.6(supports-color@8.1.1) endent: 2.1.0 @@ -55086,11 +55511,11 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@5.6.2) tslib: 2.7.0 typescript: 5.6.2 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12)) transitivePeerDependencies: - supports-color - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12)))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.6.2)(webpack@5.78.0)': dependencies: debug: 4.3.6(supports-color@8.1.1) endent: 2.1.0 @@ -55100,7 +55525,7 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@5.6.2) tslib: 2.7.0 typescript: 5.6.2 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12)) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color @@ -55196,10 +55621,10 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/react-webpack5@7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack-hot-middleware@2.26.1)': + '@storybook/react-webpack5@7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4)(webpack-hot-middleware@2.26.1)': dependencies: - '@storybook/builder-webpack5': 7.4.2(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)) - '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack-hot-middleware@2.26.1) + '@storybook/builder-webpack5': 7.4.2(@swc/helpers@0.5.12)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack-cli@5.1.4) + '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.25.2)(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@2.19.0)(typescript@5.6.2)(webpack-cli@5.1.4)(webpack-hot-middleware@2.26.1) '@storybook/react': 7.4.2(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2) '@types/node': 16.11.7 react: 18.3.1 @@ -55914,50 +56339,207 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 - '@tiptap/core@2.6.6(@tiptap/pm@2.6.6)': + '@tiptap/core@2.10.3(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/pm': 2.10.3 + + '@tiptap/core@2.10.3(@tiptap/pm@2.6.6)': dependencies: '@tiptap/pm': 2.6.6 - '@tiptap/extension-bubble-menu@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/extension-blockquote@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-bold@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-bubble-menu@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + tippy.js: 6.3.7 + + '@tiptap/extension-bubble-menu@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) '@tiptap/pm': 2.6.6 tippy.js: 6.3.7 - '@tiptap/extension-document@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': + '@tiptap/extension-bullet-list@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-code-block@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/extension-code@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-color@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/extension-text-style@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/extension-text-style': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + + '@tiptap/extension-document@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-document@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) - '@tiptap/extension-floating-menu@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/extension-dropcursor@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/extension-floating-menu@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + tippy.js: 6.3.7 + + '@tiptap/extension-floating-menu@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) '@tiptap/pm': 2.6.6 tippy.js: 6.3.7 - '@tiptap/extension-history@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/extension-focus@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/extension-gapcursor@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/extension-hard-break@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-heading@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-history@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/extension-history@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) '@tiptap/pm': 2.6.6 - '@tiptap/extension-link@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/extension-horizontal-rule@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/extension-image@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-italic@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-link@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + linkifyjs: 4.1.3 + + '@tiptap/extension-link@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) '@tiptap/pm': 2.6.6 linkifyjs: 4.1.3 - '@tiptap/extension-mention@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(@tiptap/suggestion@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6))': + '@tiptap/extension-list-item@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-mention@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)(@tiptap/suggestion@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + '@tiptap/suggestion': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + + '@tiptap/extension-mention@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(@tiptap/suggestion@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) '@tiptap/pm': 2.6.6 - '@tiptap/suggestion': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + '@tiptap/suggestion': 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + + '@tiptap/extension-ordered-list@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-paragraph@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-paragraph@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) + + '@tiptap/extension-placeholder@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/extension-strike@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-text-align@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) - '@tiptap/extension-paragraph@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': + '@tiptap/extension-text-style@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) - '@tiptap/extension-text@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': + '@tiptap/extension-text@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/extension-text@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) + + '@tiptap/extension-underline@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + + '@tiptap/pm@2.10.3': + dependencies: + prosemirror-changeset: 2.2.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.6.2 + prosemirror-dropcursor: 1.8.1 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.4.1 + prosemirror-inputrules: 1.4.0 + prosemirror-keymap: 1.2.2 + prosemirror-markdown: 1.13.1 + prosemirror-menu: 1.2.4 + prosemirror-model: 1.22.3 + prosemirror-schema-basic: 1.2.3 + prosemirror-schema-list: 1.4.1 + prosemirror-state: 1.4.3 + prosemirror-tables: 1.6.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0) + prosemirror-transform: 1.10.2 + prosemirror-view: 1.37.0 '@tiptap/pm@2.6.6': dependencies: @@ -55980,20 +56562,61 @@ snapshots: prosemirror-transform: 1.10.0 prosemirror-view: 1.33.10 - '@tiptap/react@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tiptap/react@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/extension-bubble-menu': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) - '@tiptap/extension-floating-menu': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) - '@tiptap/pm': 2.6.6 + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/extension-bubble-menu': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-floating-menu': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) use-sync-external-store: 1.2.2(react@18.3.1) - '@tiptap/suggestion@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/react@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) + '@tiptap/extension-bubble-menu': 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + '@tiptap/extension-floating-menu': 2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + '@tiptap/pm': 2.6.6 + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.2.2(react@18.3.1) + + '@tiptap/starter-kit@2.10.3': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/extension-blockquote': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-bold': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-bullet-list': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-code': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-code-block': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-document': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-dropcursor': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-gapcursor': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-hard-break': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-heading': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-history': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-horizontal-rule': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) + '@tiptap/extension-italic': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-list-item': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-ordered-list': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-paragraph': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-strike': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-text': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/extension-text-style': 2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3)) + '@tiptap/pm': 2.10.3 + + '@tiptap/suggestion@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + + '@tiptap/suggestion@2.6.6(@tiptap/core@2.10.3(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.6.6) '@tiptap/pm': 2.6.6 '@tokenizer/token@0.3.0': {} @@ -56609,6 +57232,8 @@ snapshots: '@types/linkify-it@3.0.2': optional: true + '@types/linkify-it@5.0.0': {} + '@types/lodash.debounce@4.0.9': dependencies: '@types/lodash': 4.14.192 @@ -56634,6 +57259,11 @@ snapshots: '@types/mdurl': 1.0.2 optional: true + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@3.0.11': dependencies: '@types/unist': 2.0.6 @@ -56649,6 +57279,8 @@ snapshots: '@types/mdurl@1.0.2': optional: true + '@types/mdurl@2.0.0': {} + '@types/mdx@2.0.7': {} '@types/memcached@2.2.10': @@ -57052,7 +57684,7 @@ snapshots: - webpack-cli optional: true - '@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))': + '@types/webpack@5.28.5(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)': dependencies: '@types/node': 20.16.5 tapable: 2.2.1 @@ -58645,36 +59277,21 @@ snapshots: '@webcontainer/api@1.2.0': {} - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.78.0)': dependencies: webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0) - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': - dependencies: - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0) - - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.78.0)': dependencies: webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': - dependencies: - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0) - - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.78.0)': dependencies: webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4))': - dependencies: - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0) - '@wry/context@0.4.4': dependencies: '@types/node': 20.16.5 @@ -59848,7 +60465,7 @@ snapshots: schema-utils: 2.7.1 webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - babel-loader@8.3.0(@babel/core@7.25.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + babel-loader@8.3.0(@babel/core@7.25.2)(webpack@5.78.0): dependencies: '@babel/core': 7.25.2 find-cache-dir: 3.3.2 @@ -59871,7 +60488,7 @@ snapshots: schema-utils: 4.0.0 webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - babel-loader@9.1.2(@babel/core@7.23.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + babel-loader@9.1.2(@babel/core@7.23.2)(webpack@5.78.0): dependencies: '@babel/core': 7.23.2 find-cache-dir: 3.3.2 @@ -61450,18 +62067,12 @@ snapshots: dependencies: mime-db: 1.52.0 - compression-webpack-plugin@10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): + compression-webpack-plugin@10.0.0(webpack@5.78.0): dependencies: schema-utils: 4.0.0 serialize-javascript: 6.0.1 webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) - compression-webpack-plugin@10.0.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): - dependencies: - schema-utils: 4.0.0 - serialize-javascript: 6.0.1 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) - compression@1.7.4: dependencies: accepts: 1.3.8 @@ -62165,7 +62776,7 @@ snapshots: semver: 7.6.3 webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - css-loader@6.7.3(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + css-loader@6.7.3(webpack@5.78.0): dependencies: icss-utils: 5.1.0(postcss@8.4.47) postcss: 8.4.47 @@ -63089,6 +63700,13 @@ snapshots: dependencies: safe-buffer: 5.2.1 + echo-drag-handle-plugin@0.0.2(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3)(y-prosemirror@1.2.15(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0)(y-protocols@1.0.6(yjs@13.6.20))(yjs@13.6.20)): + dependencies: + '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) + '@tiptap/pm': 2.10.3 + tippy.js: 6.3.7 + y-prosemirror: 1.2.15(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0)(y-protocols@1.0.6(yjs@13.6.20))(yjs@13.6.20) + edge-runtime@2.4.4: dependencies: '@edge-runtime/format': 2.1.0 @@ -63746,7 +64364,7 @@ snapshots: - supports-color - typescript - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.1): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 @@ -63755,12 +64373,12 @@ snapshots: object.entries: 1.1.8 semver: 6.3.1 - eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@8.57.1): dependencies: '@typescript-eslint/eslint-plugin': 8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/parser': 8.3.0(eslint@8.57.1)(typescript@5.6.2) eslint: 8.57.1 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1))(eslint@8.57.1) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.1) transitivePeerDependencies: - eslint-plugin-import @@ -63783,7 +64401,7 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2): + eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2): dependencies: '@babel/core': 7.21.4 '@babel/eslint-parser': 7.25.1(@babel/core@7.21.4)(eslint@9.9.1(jiti@1.21.6)) @@ -63794,7 +64412,7 @@ snapshots: confusing-browser-globals: 1.0.11 eslint: 9.9.1(jiti@1.21.6) eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(eslint@9.9.1(jiti@1.21.6)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2) eslint-plugin-jsx-a11y: 6.9.0(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-react: 7.35.0(eslint@9.9.1(jiti@1.21.6)) @@ -63810,7 +64428,7 @@ snapshots: - jest - supports-color - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2): + eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2): dependencies: '@babel/core': 7.21.4 '@babel/eslint-parser': 7.25.1(@babel/core@7.21.4)(eslint@9.9.1(jiti@1.21.6)) @@ -63821,7 +64439,7 @@ snapshots: confusing-browser-globals: 1.0.11 eslint: 9.9.1(jiti@1.21.6) eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint@9.9.1(jiti@1.21.6)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2) eslint-plugin-jsx-a11y: 6.9.0(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-react: 7.35.0(eslint@9.9.1(jiti@1.21.6)) @@ -63863,7 +64481,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6)): + eslint-module-utils@2.8.2(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6)): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -63874,7 +64492,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -63952,7 +64570,7 @@ snapshots: dependencies: htmlparser2: 9.1.0 - eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6)): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6)): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -63962,7 +64580,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.9.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6)) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@5.62.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -63989,7 +64607,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -64223,7 +64841,7 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-sonarjs@2.0.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@8.57.1): + eslint-plugin-sonarjs@2.0.1(@typescript-eslint/parser@8.3.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-webpack@0.13.8)(eslint@8.57.1): dependencies: '@babel/core': 7.24.3 '@babel/eslint-parser': 7.24.1(@babel/core@7.24.3)(eslint@8.57.1) @@ -65063,7 +65681,7 @@ snapshots: schema-utils: 3.3.0 webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - file-loader@6.2.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + file-loader@6.2.0(webpack@5.78.0): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 @@ -65387,7 +66005,7 @@ snapshots: typescript: 5.6.2 webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.6.2)(webpack@5.78.0): dependencies: '@babel/code-frame': 7.24.7 chalk: 4.1.2 @@ -66636,7 +67254,7 @@ snapshots: tapable: 2.2.1 webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - html-webpack-plugin@5.5.3(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + html-webpack-plugin@5.5.3(webpack@5.78.0): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -67553,6 +68171,8 @@ snapshots: transitivePeerDependencies: - encoding + isomorphic.js@0.2.5: {} + isstream@0.1.2: {} istanbul-lib-coverage@3.2.0: {} @@ -69602,6 +70222,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lib0@0.2.99: + dependencies: + isomorphic.js: 0.2.5 + libnpmaccess@6.0.4: dependencies: aproba: 2.0.0 @@ -69763,6 +70387,8 @@ snapshots: lilconfig@3.1.2: {} + lilconfig@3.1.3: {} + limiter@1.1.5: {} lines-and-columns@1.2.4: {} @@ -70137,6 +70763,10 @@ snapshots: dependencies: react: 18.3.1 + lucide-react@0.453.0(react@18.3.1): + dependencies: + react: 18.3.1 + lunr@2.3.9: {} luxon@3.3.0: {} @@ -71914,13 +72544,13 @@ snapshots: neo-async@2.6.2: {} - nest-raven@10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1): + nest-raven@10.1.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(@sentry/node@8.33.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2)(rxjs@7.8.1): dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@sentry/node': 8.33.1 rxjs: 7.8.1 optionalDependencies: - '@nestjs/graphql': 12.0.9(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2) + '@nestjs/graphql': 12.0.9(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.9.0)(reflect-metadata@0.2.2) transitivePeerDependencies: - '@apollo/subgraph' - '@nestjs/core' @@ -71934,7 +72564,7 @@ snapshots: nested-error-stacks@2.0.1: {} - nestjs-otel@6.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)): + nestjs-otel@6.1.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1): dependencies: '@nestjs/common': 10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(@nestjs/websockets@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -74161,6 +74791,11 @@ snapshots: postcss: 8.4.47 postcss-selector-parser: 6.1.2 + postcss-nested@6.2.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + postcss-nesting@10.2.0(postcss@8.4.47): dependencies: '@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.1.2) @@ -74862,6 +75497,12 @@ snapshots: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.0 + prosemirror-commands@1.6.2: + dependencies: + prosemirror-model: 1.22.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.2 + prosemirror-dropcursor@1.8.1: dependencies: prosemirror-state: 1.4.3 @@ -74897,6 +75538,12 @@ snapshots: markdown-it: 14.1.0 prosemirror-model: 1.22.3 + prosemirror-markdown@1.13.1: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + prosemirror-model: 1.22.3 + prosemirror-menu@1.2.4: dependencies: crelt: 1.0.6 @@ -74932,6 +75579,14 @@ snapshots: prosemirror-transform: 1.10.0 prosemirror-view: 1.33.10 + prosemirror-tables@1.6.1: + dependencies: + prosemirror-keymap: 1.2.2 + prosemirror-model: 1.22.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.2 + prosemirror-view: 1.37.0 + prosemirror-trailing-node@2.0.9(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.33.10): dependencies: '@remirror/core-constants': 2.0.2 @@ -74940,16 +75595,34 @@ snapshots: prosemirror-state: 1.4.3 prosemirror-view: 1.33.10 + prosemirror-trailing-node@3.0.0(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.22.3 + prosemirror-state: 1.4.3 + prosemirror-view: 1.37.0 + prosemirror-transform@1.10.0: dependencies: prosemirror-model: 1.22.3 + prosemirror-transform@1.10.2: + dependencies: + prosemirror-model: 1.22.3 + prosemirror-view@1.33.10: dependencies: prosemirror-model: 1.22.3 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.0 + prosemirror-view@1.37.0: + dependencies: + prosemirror-model: 1.22.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.2 + proto-list@1.2.4: {} proto3-json-serializer@1.1.1: @@ -75551,14 +76224,14 @@ snapshots: regenerator-runtime: 0.13.11 whatwg-fetch: 3.6.2 - react-app-rewired@2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)): + react-app-rewired@2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)): dependencies: - react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) semver: 5.7.2 - react-app-rewired@2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)): + react-app-rewired@2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1)): dependencies: - react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) semver: 5.7.2 react-chartjs-2@4.3.1(chart.js@3.9.1)(react@18.3.1): @@ -75988,7 +76661,7 @@ snapshots: transitivePeerDependencies: - supports-color - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): + react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(esbuild@0.18.20)(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): dependencies: '@babel/core': 7.21.4 '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.11.1(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)))(webpack-hot-middleware@2.26.1)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)) @@ -76006,7 +76679,7 @@ snapshots: dotenv: 10.0.0 dotenv-expand: 5.1.0 eslint: 9.9.1(jiti@1.21.6) - eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2) + eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.11))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.22.11))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2) eslint-webpack-plugin: 3.2.0(eslint@9.9.1(jiti@1.21.6))(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)) file-loader: 6.2.0(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(esbuild@0.18.20)) fs-extra: 10.1.0 @@ -76074,7 +76747,7 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): + react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/babel__core@7.20.5)(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(react@18.3.1)(sass@1.77.8)(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): dependencies: '@babel/core': 7.21.4 '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(@types/webpack@5.28.5(@swc/core@1.3.107(@swc/helpers@0.5.12)))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.11.1(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(webpack-hot-middleware@2.26.1)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))) @@ -76092,7 +76765,7 @@ snapshots: dotenv: 10.0.0 dotenv-expand: 5.1.0 eslint: 9.9.1(jiti@1.21.6) - eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-webpack@0.13.8(eslint-plugin-import@2.29.1)(webpack@5.94.0(@swc/core@1.3.107(@swc/helpers@0.5.12))))(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2) + eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-webpack@0.13.8)(eslint@9.9.1(jiti@1.21.6))(jest@27.5.1(ts-node@10.9.1(@swc/core@1.3.107(@swc/helpers@0.5.12))(@types/node@18.16.9)(typescript@5.6.2)))(typescript@5.6.2) eslint-webpack-plugin: 3.2.0(eslint@9.9.1(jiti@1.21.6))(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))) file-loader: 6.2.0(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))) fs-extra: 10.1.0 @@ -78326,7 +78999,7 @@ snapshots: dependencies: webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - style-loader@3.3.2(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + style-loader@3.3.2(webpack@5.78.0): dependencies: webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) @@ -78628,7 +79301,7 @@ snapshots: '@swc/core': 1.3.107(@swc/helpers@0.5.12) webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - swc-loader@0.2.3(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + swc-loader@0.2.3(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0): dependencies: '@swc/core': 1.3.107(@swc/helpers@0.5.12) webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) @@ -78670,6 +79343,8 @@ snapshots: tailwind-merge@2.4.0: {} + tailwind-merge@2.5.5: {} + tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@22.7.4)(typescript@5.6.2))): dependencies: tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@22.7.4)(typescript@5.6.2)) @@ -78736,6 +79411,33 @@ snapshots: transitivePeerDependencies: - ts-node + tailwindcss@3.4.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@22.7.4)(typescript@5.6.2)): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@22.7.4)(typescript@5.6.2)) + postcss-nested: 6.2.0(postcss@8.4.47) + postcss-selector-parser: 6.1.2 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.12))(@types/node@20.14.10)(typescript@5.6.2)): dependencies: '@alloc/quick-lru': 5.2.0 @@ -78929,7 +79631,7 @@ snapshots: optionalDependencies: '@swc/core': 1.3.107(@swc/helpers@0.5.12) - terser-webpack-plugin@5.3.10(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.10(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -78951,7 +79653,7 @@ snapshots: optionalDependencies: '@swc/core': 1.3.107(@swc/helpers@0.5.12) - terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -78963,25 +79665,25 @@ snapshots: '@swc/core': 1.7.26(@swc/helpers@0.5.12) esbuild: 0.23.1 - terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.6 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12)) optionalDependencies: '@swc/core': 1.7.26(@swc/helpers@0.5.12) - terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))): + terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.6 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12)) + webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.7.26(@swc/helpers@0.5.12) @@ -79019,7 +79721,7 @@ snapshots: optionalDependencies: '@swc/core': 1.3.107(@swc/helpers@0.5.12) - terser-webpack-plugin@5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.9(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -79537,7 +80239,7 @@ snapshots: typescript: 5.6.2 webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.12)) - ts-loader@9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)): + ts-loader@9.4.4(typescript@5.6.2)(webpack@5.78.0): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 @@ -79546,15 +80248,6 @@ snapshots: typescript: 5.6.2 webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4) - ts-loader@9.4.4(typescript@5.6.2)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): - dependencies: - chalk: 4.1.2 - enhanced-resolve: 5.17.1 - micromatch: 4.0.8 - semver: 7.6.3 - typescript: 5.6.2 - webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) - ts-loader@9.4.4(typescript@5.6.2)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.12))): dependencies: chalk: 4.1.2 @@ -80508,14 +81201,14 @@ snapshots: url-join@5.0.0: {} - url-loader@4.1.1(file-loader@6.2.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.78.0))(webpack@5.78.0): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 webpack: 5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4) optionalDependencies: - file-loader: 6.2.0(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + file-loader: 6.2.0(webpack@5.78.0) url-loader@4.1.1(file-loader@6.2.0(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.12))))(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.12))): dependencies: @@ -81607,9 +82300,9 @@ snapshots: webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.1)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.78.0) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.78.0) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.78.0) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -81626,9 +82319,9 @@ snapshots: webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.78.0) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.78.0) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.78.0) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -81680,7 +82373,7 @@ snapshots: optionalDependencies: webpack: 5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12)) - webpack-dev-middleware@6.1.1(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)): + webpack-dev-middleware@6.1.1(webpack@5.78.0): dependencies: colorette: 2.0.19 memfs: 3.5.0 @@ -81876,7 +82569,7 @@ snapshots: - esbuild - uglify-js - webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.78.0)): + webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.4 '@types/estree': 0.0.51 @@ -81899,7 +82592,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.3.107(@swc/helpers@0.5.12))(webpack@5.78.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: @@ -81963,7 +82656,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(esbuild@0.23.1)(webpack@5.78.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: @@ -81996,7 +82689,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.12))(webpack@5.78.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: @@ -82546,6 +83239,20 @@ snapshots: xterm@5.3.0: {} + y-prosemirror@1.2.15(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0)(y-protocols@1.0.6(yjs@13.6.20))(yjs@13.6.20): + dependencies: + lib0: 0.2.99 + prosemirror-model: 1.22.3 + prosemirror-state: 1.4.3 + prosemirror-view: 1.37.0 + y-protocols: 1.0.6(yjs@13.6.20) + yjs: 13.6.20 + + y-protocols@1.0.6(yjs@13.6.20): + dependencies: + lib0: 0.2.99 + yjs: 13.6.20 + y18n@4.0.3: {} y18n@5.0.8: {} @@ -82647,6 +83354,10 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 + yjs@13.6.20: + dependencies: + lib0: 0.2.99 + yn@3.1.1: {} yocto-queue@0.1.0: {} From 47fb34cf15a0a66d12813a05f21f0507bb1fee07 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Thu, 5 Dec 2024 14:41:21 +0200 Subject: [PATCH 17/18] fix(dashboard): default tabs alignment --- apps/dashboard/src/components/primitives/tabs.tsx | 2 +- apps/dashboard/src/pages/settings.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/components/primitives/tabs.tsx b/apps/dashboard/src/components/primitives/tabs.tsx index c87746d7635..712abb0a1c5 100644 --- a/apps/dashboard/src/components/primitives/tabs.tsx +++ b/apps/dashboard/src/components/primitives/tabs.tsx @@ -18,7 +18,7 @@ const tabsListVariants = cva('inline-flex', { }, defaultVariants: { variant: 'default', - align: 'center', + align: 'start', }, }); diff --git a/apps/dashboard/src/pages/settings.tsx b/apps/dashboard/src/pages/settings.tsx index 26352557bb9..23b7e4a1bd3 100644 --- a/apps/dashboard/src/pages/settings.tsx +++ b/apps/dashboard/src/pages/settings.tsx @@ -72,7 +72,7 @@ export function SettingsPage() { return ( Settings}> - + Date: Thu, 5 Dec 2024 14:45:28 +0200 Subject: [PATCH 18/18] fix: missing prop --- apps/dashboard/src/pages/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/pages/settings.tsx b/apps/dashboard/src/pages/settings.tsx index 23b7e4a1bd3..26352557bb9 100644 --- a/apps/dashboard/src/pages/settings.tsx +++ b/apps/dashboard/src/pages/settings.tsx @@ -72,7 +72,7 @@ export function SettingsPage() { return ( Settings}> - +