diff --git a/apps/app/languine.lock b/apps/app/languine.lock index 692e9e463e..5b9b66c732 100644 --- a/apps/app/languine.lock +++ b/apps/app/languine.lock @@ -9,6 +9,10 @@ files: languages.fr: ad225f707802ba118c22987186dd38e8 languages.no: da550ca06bcacbd30b7c6ed32c864c70 languages.pt: 30e32c7c4cf434e9c75e60c14c442541 + common.notifications.inbox: 3882d32c66e7e768145ecd8f104b0c08 + common.notifications.archive: e727b00944f81e1d0a95c12886ac4641 + common.notifications.archive_all: 449e2fbab2a05e2f9f3cc1717f58eee0 + common.notifications.no_notifications: bb76b111a28211312083c68b2efabae7 common.actions.save: c9cc8cce247e49bae79f15173ce97354 common.actions.edit: 7dce122004969d56ae2e0245cb754d35 common.actions.delete: f2a6c498fb90ee345d997f888fce3b18 diff --git a/apps/app/package.json b/apps/app/package.json index fc0b1a0b86..129ead548a 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -19,6 +19,7 @@ "@nangohq/frontend": "^0.48.4", "@nangohq/node": "^0.48.4", "@novu/headless": "^2.0.4", + "@novu/react": "^2.6.5", "@prisma/instrumentation": "^6.3.0", "@tanstack/react-query": "^5.66.0", "@tanstack/react-table": "^8.20.6", @@ -86,6 +87,7 @@ }, "devDependencies": { "@bubba/db": "workspace:*", + "@bubba/notifications": "workspace:*", "@trigger.dev/build": "3.3.13", "@types/node": "^22.13.0", "@types/react": "^19.0.8", diff --git a/apps/app/src/components/header.tsx b/apps/app/src/components/header.tsx index dba9fe73ee..66d085c55a 100644 --- a/apps/app/src/components/header.tsx +++ b/apps/app/src/components/header.tsx @@ -3,11 +3,14 @@ import { getI18n } from "@/locales/server"; import { Button } from "@bubba/ui/button"; import { Icons } from "@bubba/ui/icons"; import { Skeleton } from "@bubba/ui/skeleton"; +import { Inbox } from "@novu/react"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { Suspense } from "react"; import { AssistantButton } from "./assistant/button"; import { FeedbackForm } from "./feedback-form"; import { MobileMenu } from "./mobile-menu"; +import { NotificationCenter } from "./notification-center"; export async function Header() { const t = await getI18n(); @@ -38,6 +41,9 @@ export async function Header() { + + + }> diff --git a/apps/app/src/components/notification-center.tsx b/apps/app/src/components/notification-center.tsx new file mode 100644 index 0000000000..2a264153c3 --- /dev/null +++ b/apps/app/src/components/notification-center.tsx @@ -0,0 +1,230 @@ +"use client"; + +import { useNotifications } from "@/hooks/use-notifications"; +import { useI18n } from "@/locales/client"; +import { Button } from "@bubba/ui/button"; +import { Icons } from "@bubba/ui/icons"; +import { Popover, PopoverContent, PopoverTrigger } from "@bubba/ui/popover"; +import { ScrollArea } from "@bubba/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@bubba/ui/tabs"; +import { formatDistanceToNow } from "date-fns"; +import Link from "next/link"; +import { useEffect, useState } from "react"; + +function EmptyState({ description }: { description: string }) { + return ( +
+
+ +
+

{description}

+
+ ); +} + +function NotificationItem({ + id, + setOpen, + description, + createdAt, + recordId, + from, + to, + markMessageAsRead, + type, +}: { + id: string; + setOpen: (open: boolean) => void; + description: string; + createdAt: string; + recordId: string; + from: string; + to: string; + markMessageAsRead: (id: string) => void; + type: string; +}) { + switch (type) { + case "inapp_task_reminder": + return ( +
+ setOpen(false)} + href={`/tasks/${recordId}`} + > +
+
+ +
+
+
+

{description}

+ + {formatDistanceToNow(new Date(createdAt))} ago + +
+ + {markMessageAsRead && ( +
+ +
+ )} +
+ ); + default: + return null; + } +} + +export function NotificationCenter() { + const t = useI18n(); + + const [isOpen, setOpen] = useState(false); + const { + hasUnseenNotifications, + notifications, + markMessageAsRead, + markAllMessagesAsSeen, + markAllMessagesAsRead, + } = useNotifications(); + + const unreadNotifications = notifications.filter( + (notification) => !notification.read, + ); + + const archivedNotifications = notifications.filter( + (notification) => notification.read, + ); + + useEffect(() => { + if (isOpen && hasUnseenNotifications) { + markAllMessagesAsSeen(); + } + }, [hasUnseenNotifications, isOpen]); + + return ( + + + + + + + + + {t("common.notifications.inbox")} + + + {t("common.notifications.archive")} + + + + + + + + + {!unreadNotifications.length && ( + + )} + + {unreadNotifications.length > 0 && ( + +
+ {unreadNotifications.map((notification) => { + return ( + + ); + })} +
+
+ )} + + {unreadNotifications.length > 0 && ( +
+ +
+ )} +
+ + + {!archivedNotifications.length && ( + + )} + + {archivedNotifications.length > 0 && ( + +
+ {archivedNotifications.map((notification) => { + return ( + + ); + })} +
+
+ )} +
+
+
+
+ ); +} diff --git a/apps/app/src/env.mjs b/apps/app/src/env.mjs index 4ff1d6de13..7273fdb6fb 100644 --- a/apps/app/src/env.mjs +++ b/apps/app/src/env.mjs @@ -26,6 +26,7 @@ export const env = createEnv({ NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(), NEXT_PUBLIC_POSTHOG_HOST: z.string().optional(), NEXT_PUBLIC_VERCEL_URL: z.string().optional(), + NEXT_PUBLIC_NOVU_IDENTIFIER: z.string().optional(), }, runtimeEnv: { @@ -49,6 +50,7 @@ export const env = createEnv({ VERCEL_TEAM_ID: process.env.VERCEL_TEAM_ID, VERCEL_PROJECT_ID: process.env.VERCEL_PROJECT_ID, NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL, + NEXT_PUBLIC_NOVU_IDENTIFIER: process.env.NEXT_PUBLIC_NOVU_IDENTIFIER, }, skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION, diff --git a/apps/app/src/hooks/use-notifications.ts b/apps/app/src/hooks/use-notifications.ts new file mode 100644 index 0000000000..b40ab78931 --- /dev/null +++ b/apps/app/src/hooks/use-notifications.ts @@ -0,0 +1,164 @@ +import { env } from "@/env.mjs"; +import { HeadlessService, type IMessage } from "@novu/headless"; +import { useSession } from "next-auth/react"; +import { useCallback, useEffect, useRef, useState } from "react"; + +interface Notification { + id: string; + read: boolean; + seen: boolean; + createdAt: string; + payload: { + description: string; + recordId: string; + type: string; + from: string; + to: string; + } +} + +export function useNotifications() { + const [isLoading, setLoading] = useState(true); + const [notifications, setNotifications] = useState([]); + const [subscriberId, setSubscriberId] = useState(); + const headlessServiceRef = useRef(null); + const { data: session } = useSession(); + + const markAllMessagesAsRead = () => { + const headlessService = headlessServiceRef.current; + + if (headlessService) { + setNotifications((prevNotifications) => + prevNotifications.map((notification) => { + return { + ...notification, + read: true, + }; + }), + ); + + headlessService.markAllMessagesAsRead({ + listener: () => { }, + onError: () => { }, + }); + } + }; + + const markMessageAsRead = (messageId: string) => { + const headlessService = headlessServiceRef.current; + + if (headlessService) { + setNotifications((prevNotifications) => + prevNotifications.map((notification) => { + if (notification.id === messageId) { + return { + ...notification, + read: true, + }; + } + + return notification; + }), + ); + + headlessService.markNotificationsAsRead({ + messageId: [messageId], + listener: (result) => { }, + onError: (error) => { }, + }); + } + }; + + const fetchNotifications = useCallback(() => { + const headlessService = headlessServiceRef.current; + + if (headlessService) { + headlessService.fetchNotifications({ + listener: () => { }, + onSuccess: (response) => { + setLoading(false); + setNotifications( + response.data.map((msg: IMessage) => ({ + id: msg._id, + read: msg.read, + seen: msg.seen, + createdAt: msg.createdAt, + payload: { + description: msg.payload.description as string, + recordId: msg.payload.recordId as string, + type: msg.payload.type as string, + from: msg.payload.from as string, + to: msg.payload.to as string, + } + })) + ); + }, + }); + } + }, []); + + const markAllMessagesAsSeen = () => { + const headlessService = headlessServiceRef.current; + + if (headlessService) { + setNotifications((prevNotifications) => + prevNotifications.map((notification) => ({ + ...notification, + seen: true, + })), + ); + headlessService.markAllMessagesAsSeen({ + listener: () => { }, + onError: () => { }, + }); + } + }; + + useEffect(() => { + if (session?.user) { + setSubscriberId(`${session.user.organizationId}_${session.user.id}`); + } + }, [session]); + + useEffect(() => { + const headlessService = headlessServiceRef.current; + + if (headlessService) { + headlessService.listenNotificationReceive({ + listener: () => { + fetchNotifications(); + } + }); + } + }, [headlessServiceRef.current]); + + useEffect(() => { + if (subscriberId && !headlessServiceRef.current) { + const headlessService = new HeadlessService({ + applicationIdentifier: env.NEXT_PUBLIC_NOVU_IDENTIFIER!, + subscriberId, + }); + + headlessService.initializeSession({ + listener: () => { }, + onSuccess: () => { + console.log('Novu session initialized successfully'); + headlessServiceRef.current = headlessService; + fetchNotifications(); + }, + onError: (error) => { + console.error('Failed to initialize Novu session:', error); + }, + }); + } + }, [fetchNotifications, subscriberId]); + + return { + isLoading, + markAllMessagesAsRead, + markMessageAsRead, + markAllMessagesAsSeen, + hasUnseenNotifications: notifications.some((notification) => !notification.seen), + notifications, + }; +} diff --git a/apps/app/src/jobs/tasks/organization/new-organization.ts b/apps/app/src/jobs/tasks/organization/new-organization.ts index 5886f5d94b..e52c79a7af 100644 --- a/apps/app/src/jobs/tasks/organization/new-organization.ts +++ b/apps/app/src/jobs/tasks/organization/new-organization.ts @@ -1,13 +1,11 @@ -import { soc2Seed } from "@/actions/soc2-seed"; import { logger, schemaTask } from "@trigger.dev/sdk/v3"; import { z } from "zod"; -import createDefaultPoliciesTask from "./create-default-policies"; export const newOrganizationTask = schemaTask({ id: "new-organization", schema: z.object({ - organizationId: z.string().uuid(), - userId: z.string().uuid(), + organizationId: z.string(), + userId: z.string() }), run: async ({ organizationId, userId }) => { logger.info("New organization task started", { diff --git a/apps/app/src/locales/en.ts b/apps/app/src/locales/en.ts index 487862eae3..fc7ff06208 100644 --- a/apps/app/src/locales/en.ts +++ b/apps/app/src/locales/en.ts @@ -12,6 +12,12 @@ export default { pt: "Portuguese", }, common: { + notifications: { + inbox: "Inbox", + archive: "Archive", + archive_all: "Archive all", + no_notifications: "No new notifications", + }, actions: { save: "Save", edit: "Edit", diff --git a/apps/app/src/locales/es.ts b/apps/app/src/locales/es.ts index a52f3dca21..681a41230c 100644 --- a/apps/app/src/locales/es.ts +++ b/apps/app/src/locales/es.ts @@ -136,6 +136,12 @@ export default { uploading_files: "Subiendo {target}...", remove_file: "Eliminar archivo" } + }, + notifications: { + inbox: "Bandeja de entrada", + archive: "Archivo", + archive_all: "Archivar todo", + no_notifications: "No hay nuevas notificaciones" } }, header: { diff --git a/apps/app/src/locales/fr.ts b/apps/app/src/locales/fr.ts index 0b9ffbc967..41a6add479 100644 --- a/apps/app/src/locales/fr.ts +++ b/apps/app/src/locales/fr.ts @@ -136,6 +136,12 @@ export default { uploading_files: "Téléchargement de {target}...", remove_file: "Supprimer le fichier" } + }, + notifications: { + inbox: "Boîte de réception", + archive: "Archive", + archive_all: "Archiver tout", + no_notifications: "Aucune nouvelle notification" } }, header: { diff --git a/apps/app/src/locales/no.ts b/apps/app/src/locales/no.ts index 43f0677812..c781c98700 100644 --- a/apps/app/src/locales/no.ts +++ b/apps/app/src/locales/no.ts @@ -136,6 +136,12 @@ export default { uploading_files: "Laster opp {target}...", remove_file: "Fjern fil" } + }, + notifications: { + inbox: "Innboks", + archive: "Arkiv", + archive_all: "Arkiver alt", + no_notifications: "Ingen nye varsler" } }, header: { diff --git a/apps/app/src/locales/pt.ts b/apps/app/src/locales/pt.ts index ffc617ec94..a2bd0dcb51 100644 --- a/apps/app/src/locales/pt.ts +++ b/apps/app/src/locales/pt.ts @@ -136,6 +136,12 @@ export default { uploading_files: "Carregando {target}...", remove_file: "Remover arquivo" } + }, + notifications: { + inbox: "Caixa de entrada", + archive: "Arquivo", + archive_all: "Arquivar tudo", + no_notifications: "Sem novas notificações" } }, header: { diff --git a/bun.lockb b/bun.lockb index 17d59db256..338ee6ab69 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/notifications/package.json b/packages/notifications/package.json new file mode 100644 index 0000000000..909d7d8e3f --- /dev/null +++ b/packages/notifications/package.json @@ -0,0 +1,20 @@ +{ + "name": "@bubba/notifications", + "version": "1.0.0", + "private": true, + "main": "src/index.ts", + "type": "module", + "scripts": { + "clean": "rm -rf .turbo node_modules", + "lint": "biome check .", + "format": "biome format --write .", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@novu/node": "^2.0.1", + "nanoid": "^5.0.7" + }, + "devDependencies": { + "typescript": "^5.6.3" + } +} diff --git a/packages/notifications/src/index.ts b/packages/notifications/src/index.ts new file mode 100644 index 0000000000..5a54b3c5c5 --- /dev/null +++ b/packages/notifications/src/index.ts @@ -0,0 +1,113 @@ +import { Novu } from "@novu/node"; +import ky from "ky"; +import { nanoid } from "nanoid"; + +const novu = new Novu(process.env.NOVU_API_KEY!); +const novu_api = "https://api.novu.co/v1"; + +export enum TriggerEvents { + TaskReminderInApp = "task-reminder-inapp", +} + +export enum NotificationTypes { + Task = "task", +} + +type TriggerUser = { + subscriberId: string; + email: string; + fullName: string; + image: string; + organizationId: string; +} + +type TriggerPayload = { + name: TriggerEvents; + payload: any; + user: TriggerUser; + replyTo?: string; + tenant?: string; // organization_id + user_id +} + +type GetSubscriberPreferencesParams = { + organizationId: string; + subscriberId: string; +} + +type UpdateSubscriberPreferencesParams = { + subscriberId: string; + organizationId: string; + templateId: string; + type: string; + enabled: boolean; +} + +export async function trigger(data: TriggerPayload) { + try { + const subscriberId = `${data.user.organizationId}_${data.user.subscriberId}`; + + await novu.trigger(data.name, { + to: { + ...data.user, + subscriberId + }, + payload: data.payload, + tenant: data.tenant, + overrides: { + email: { + replyTo: data.replyTo, + headers: { + "X-Entity-Ref-ID": nanoid(), + } + } + } + }); + } catch (error: any) { + console.error('Novu trigger error:', { + event: data.name, + error: error.response?.data || error.message, + status: error.response?.status + }); + + throw error; + } +} + +export async function getSubscriberPreferences({ organizationId, subscriberId }: GetSubscriberPreferencesParams) { + try { + const response = await ky.get(`${novu_api}/subscribers/${organizationId}_${subscriberId}/preferences?includeInactiveChannels=false`, { + method: "GET", + headers: { + Authorization: `ApiKey ${process.env.NOVU_API_KEY!}`, + }, + }) + + return response.json(); + } catch (error) { + console.log(error); + return null; + } +} + +export async function updateSubscriberPreferences({ subscriberId, organizationId, templateId, type, enabled }: UpdateSubscriberPreferencesParams) { + try { + const response = await ky.patch(`${novu_api}/subscribers/${organizationId}_${subscriberId}/preferences/${templateId}`, { + method: "PATCH", + headers: { + Authorization: `ApiKey ${process.env.NOVU_API_KEY!}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + channel: { + type, + enabled, + }, + }), + }) + + return response.json(); + } catch (error) { + console.log(error); + return null; + } +} \ No newline at end of file diff --git a/packages/notifications/tsconfig.json b/packages/notifications/tsconfig.json new file mode 100644 index 0000000000..3a0a99d5c6 --- /dev/null +++ b/packages/notifications/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@bubba/tsconfig/base.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/turbo.json b/turbo.json index b22e7c5c43..8c0c5b8ba8 100644 --- a/turbo.json +++ b/turbo.json @@ -31,7 +31,8 @@ "VERCEL_AUTH_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID", - "NEXT_PUBLIC_VERCEL_URL" + "NEXT_PUBLIC_VERCEL_URL", + "NOVU_API_KEY" ], "inputs": [ "$TURBO_DEFAULT$", diff --git a/yarn.lock b/yarn.lock index 4b9479bf64..d92df5a4c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6 +1,6 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 -# bun ./bun.lockb --hash: E61F2EF946E5A25E-7e221dd7b0c731c7-903B7615989286AD-79b4c36c31dd44c7 +# bun ./bun.lockb --hash: E3588BF47FA0E20B-abcd9cbbd126aa0d-23D6C38DF3A8DD19-ad81e27570f355aa "@ai-sdk/anthropic@^1.1.6": @@ -316,7 +316,7 @@ dependencies: "@babel/types" "^7.26.5" -"@babel/runtime@^7.13.10", "@babel/runtime@^7.20.13", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.20.13", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.26.0" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz" integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== @@ -479,6 +479,15 @@ "@upstash/redis" "^1.34.2" server-only "^0.0.1" +"@bubba/notifications@packages/notifications", "@bubba/notifications@workspace:*": + version "workspace:packages/notifications" + resolved "workspace:packages/notifications" + devDependencies: + typescript "^5.6.3" + dependencies: + "@novu/node" "^2.0.1" + nanoid "^5.0.7" + "@bubba/tsconfig@packages/tsconfig", "@bubba/tsconfig@workspace:*": version "workspace:packages/tsconfig" resolved "workspace:packages/tsconfig" @@ -832,7 +841,7 @@ dependencies: "@floating-ui/utils" "^0.2.9" -"@floating-ui/dom@^1.0.0": +"@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.5", "@floating-ui/dom@^1.6.7": version "1.6.13" resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz" integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w== @@ -1153,6 +1162,59 @@ jju "^1.4.0" js-yaml "^4.1.0" +"@motionone/animation@^10.18.0": + version "10.18.0" + resolved "https://registry.npmjs.org/@motionone/animation/-/animation-10.18.0.tgz" + integrity sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw== + dependencies: + "@motionone/easing" "^10.18.0" + "@motionone/types" "^10.17.1" + "@motionone/utils" "^10.18.0" + tslib "^2.3.1" + +"@motionone/dom@^10.17.0": + version "10.18.0" + resolved "https://registry.npmjs.org/@motionone/dom/-/dom-10.18.0.tgz" + integrity sha512-bKLP7E0eyO4B2UaHBBN55tnppwRnaE3KFfh3Ps9HhnAkar3Cb69kUCJY9as8LrccVYKgHA+JY5dOQqJLOPhF5A== + dependencies: + "@motionone/animation" "^10.18.0" + "@motionone/generators" "^10.18.0" + "@motionone/types" "^10.17.1" + "@motionone/utils" "^10.18.0" + hey-listen "^1.0.8" + tslib "^2.3.1" + +"@motionone/easing@^10.18.0": + version "10.18.0" + resolved "https://registry.npmjs.org/@motionone/easing/-/easing-10.18.0.tgz" + integrity sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg== + dependencies: + "@motionone/utils" "^10.18.0" + tslib "^2.3.1" + +"@motionone/generators@^10.18.0": + version "10.18.0" + resolved "https://registry.npmjs.org/@motionone/generators/-/generators-10.18.0.tgz" + integrity sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg== + dependencies: + "@motionone/types" "^10.17.1" + "@motionone/utils" "^10.18.0" + tslib "^2.3.1" + +"@motionone/types@^10.17.1": + version "10.17.1" + resolved "https://registry.npmjs.org/@motionone/types/-/types-10.17.1.tgz" + integrity sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A== + +"@motionone/utils@^10.17.0", "@motionone/utils@^10.18.0": + version "10.18.0" + resolved "https://registry.npmjs.org/@motionone/utils/-/utils-10.18.0.tgz" + integrity sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw== + dependencies: + "@motionone/types" "^10.17.1" + hey-listen "^1.0.8" + tslib "^2.3.1" + "@mui/core-downloads-tracker@^6.4.0": version "6.4.0" resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.0.tgz" @@ -1434,6 +1496,34 @@ socket.io-client "4.7.2" "@tanstack/query-core" "^4.15.1" +"@novu/js@2.6.5": + version "2.6.5" + resolved "https://registry.npmjs.org/@novu/js/-/js-2.6.5.tgz" + integrity sha512-lh9InJdzyU+nCX9c0P00iHR4Q/R85KhmOdBSW/JLkKdH8755Kc3KO8c9cpvOhGUYhUsVuJcok8wdzj0s/09rMw== + dependencies: + "@floating-ui/dom" "^1.6.7" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + mitt "^3.0.1" + socket.io-client "4.7.2" + solid-floating-ui "^0.3.1" + solid-js "^1.8.11" + solid-motionone "^1.0.1" + tailwind-merge "^2.4.0" + +"@novu/node@^2.0.1": + version "2.6.5" + resolved "https://registry.npmjs.org/@novu/node/-/node-2.6.5.tgz" + integrity sha512-5GQI6EkjsLNbzUOXIRGbeFNsY1+bzI8sVhoeBCJfnJ9r/elFNRSMYsgVp5dCXtzLBrp7cjMfYNdLFhp1uUNaig== + dependencies: + axios "^1.6.8" + axios-retry "^3.8.0" + handlebars "^4.7.7" + lodash.get "^4.4.2" + lodash.merge "^4.6.2" + uuid "^9.0.1" + "@novu/shared" "2.6.5" + "@novu/ntfr-client@^0.0.4": version "0.0.4" resolved "https://registry.npmjs.org/@novu/ntfr-client/-/ntfr-client-0.0.4.tgz" @@ -1446,11 +1536,23 @@ partysocket "^0.0.17" proxy-agent "^6.3.0" +"@novu/react@^2.6.5": + version "2.6.5" + resolved "https://registry.npmjs.org/@novu/react/-/react-2.6.5.tgz" + integrity sha512-LJNeUnEAV6morGvVjv1iaQIov+Q32irESfBmHHI1SjDbVOHD+CPHCkZZ9R3Yb/9IRIUA6PTGb8vWOs2oPaHKSQ== + dependencies: + "@novu/js" "2.6.5" + "@novu/shared@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@novu/shared/-/shared-2.1.5.tgz" integrity sha512-bAnZkvljegpf2GdjeYS9lbNp49K9a72OKfDXfYt3tSYB5DaTxj7RdGRaYW1IXDI7UTNEEg7G2XcbC3F7TadrmA== +"@novu/shared@2.6.5": + version "2.6.5" + resolved "https://registry.npmjs.org/@novu/shared/-/shared-2.6.5.tgz" + integrity sha512-nKSuZ6oIBGezvEP7ICsBT1jtwzL/Y1mzZxpYjmzLiZUXVV35GGk83NqNwjImNjDYAz1c4wVpDb8uV+nMy4e0Tw== + "@one-ini/wasm@0.1.1": version "0.1.1" resolved "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz" @@ -2912,6 +3014,30 @@ resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== +"@solid-primitives/props@^3.1.11": + version "3.2.0" + resolved "https://registry.npmjs.org/@solid-primitives/props/-/props-3.2.0.tgz" + integrity sha512-vEg5yERdXftJz2+A6B0IMYTrPL9SE2DPmpURV/nZyqQ+PXziF02V4b4SDr6JX3jNJxBlY6c17LqwYEw+bIfGRg== + dependencies: + "@solid-primitives/utils" "^6.3.0" + +"@solid-primitives/refs@^1.0.8": + version "1.1.0" + resolved "https://registry.npmjs.org/@solid-primitives/refs/-/refs-1.1.0.tgz" + integrity sha512-QJ3bTSQOlPdHBP2m6llrT13FvVzAwZfx41lTN8lQrRwwcZoWb7kfCAjhaohPnwkAsQ6nJpLjtGfT5GOyuCA4tA== + dependencies: + "@solid-primitives/utils" "^6.3.0" + +"@solid-primitives/transition-group@^1.0.5": + version "1.1.0" + resolved "https://registry.npmjs.org/@solid-primitives/transition-group/-/transition-group-1.1.0.tgz" + integrity sha512-pL1sEPCHuC4V+Yh+SQsKSPuGDYrZbLJYSkk3AB4TZrWhptEJUS0IHoi7BAynYcMiULbvMMVKFbeFHqINZq0+ig== + +"@solid-primitives/utils@^6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.3.0.tgz" + integrity sha512-e7hTlJ1Ywh2+g/Qug+n4L1mpfxsikoIS4/sHE2EK9WatQt8UJqop/vE6bsLnXlU1xuhb/jo94Ah5Y27rd4wP7A== + "@standard-schema/spec@1.0.0-beta.3": version "1.0.0-beta.3" resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0-beta.3.tgz" @@ -4505,6 +4631,14 @@ axios@^1.6.8, axios@^1.7.4, axios@^1.7.8, axios@^1.7.9: proxy-from-env "^1.1.0" follow-redirects "^1.15.6" +axios-retry@^3.8.0: + version "3.9.1" + resolved "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz" + integrity sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w== + dependencies: + "@babel/runtime" "^7.15.4" + is-retry-allowed "^2.2.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz" @@ -4977,6 +5111,7 @@ commander@11.1.0: resolved "workspace:apps/app" devDependencies: "@bubba/db" "workspace:*" + "@bubba/notifications" "workspace:*" "@trigger.dev/build" "3.3.13" "@types/node" "^22.13.0" "@types/react" "^19.0.8" @@ -4994,6 +5129,7 @@ commander@11.1.0: "@nangohq/frontend" "^0.48.4" "@nangohq/node" "^0.48.4" "@novu/headless" "^2.0.4" + "@novu/react" "^2.6.5" "@prisma/instrumentation" "^6.3.0" "@tanstack/react-query" "^5.66.0" "@tanstack/react-table" "^8.20.6" @@ -5232,7 +5368,7 @@ cssstyle@^4.2.1: "@asamuzakjp/css-color" "^2.8.2" rrweb-cssom "^0.8.0" -csstype@^3.0.2, csstype@^3.1.3: +csstype@^3.0.2, csstype@^3.1.0, csstype@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -6636,6 +6772,18 @@ gray-matter@^4.0.3: section-matter "^1.0.0" strip-bom-string "^1.0.0" +handlebars@^4.7.7: + version "4.7.8" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" @@ -6725,6 +6873,11 @@ hast-util-whitespace@^3.0.0: dependencies: "@types/hast" "^3.0.0" +hey-listen@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz" + integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q== + highlight.js@^11, highlight.js@^11.11.1, highlight.js@~11.11.0: version "11.11.1" resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz" @@ -7180,6 +7333,11 @@ is-regex@^1.2.1: has-tostringtag "^1.0.2" hasown "^2.0.2" +is-retry-allowed@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz" + integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg== + is-set@^2.0.2, is-set@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz" @@ -7651,6 +7809,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz" @@ -8354,7 +8517,7 @@ minimatch@^9.0.1, minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -8450,6 +8613,11 @@ nanoid@^3.3.4, nanoid@^3.3.6, nanoid@^3.3.7, nanoid@^3.3.8: resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +nanoid@^5.0.7: + version "5.0.9" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz" + integrity sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q== + negotiator@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" @@ -8460,6 +8628,11 @@ negotiator@^1.0.0: resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + netmask@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" @@ -9690,7 +9863,7 @@ react@18.2.0, react@18.x, "react@^16.8 || ^17.0 || ^18.0", react@^18.0.0, react@ dependencies: loose-envify "^1.1.0" -react@*, react@19.0.0, "react@>= 16.8 || 18.0.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", "react@>= 18.0.0", "react@>= 18.2.0", react@>=16, react@>=16.6.0, react@>=16.8, "react@>=16.8.0 || ^17.0.0 || ^18", react@>=16.8.1, react@>=18, "react@>=18.2.0 || ^19.0.0-0", "react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc", "react@^17.0.0 || ^18.0.0 || ^19.0.0", "react@^17.0.2 || ^18.0.0 || ^19.0.0", "react@^18 || ^19", "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0 || ^19.0", "react@^18.0 || ^19.0 || ^19.0.0-rc", "react@^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react@^18.2.0 || ^19.0.0-0", react@^19.0.0: +react@*, react@19.0.0, "react@>= 16.8 || 18.0.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", "react@>= 18.0.0", "react@>= 18.2.0", react@>=16, react@>=16.6.0, react@>=16.8, "react@>=16.8.0 || ^17.0.0 || ^18", react@>=16.8.1, react@>=17, react@>=18, "react@>=18.2.0 || ^19.0.0-0", "react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc", "react@^17.0.0 || ^18.0.0 || ^19.0.0", "react@^17.0.2 || ^18.0.0 || ^19.0.0", "react@^18 || ^19", "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0 || ^19.0", "react@^18.0 || ^19.0 || ^19.0.0-rc", "react@^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react@^18.2.0 || ^19.0.0-0", react@^19.0.0: version "19.0.0" resolved "https://registry.npmjs.org/react/-/react-19.0.0.tgz" integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ== @@ -10362,6 +10535,16 @@ send@0.19.0: on-finished "2.4.1" range-parser "~1.2.1" +seroval@^1.0, seroval@^1.1.0: + version "1.2.1" + resolved "https://registry.npmjs.org/seroval/-/seroval-1.2.1.tgz" + integrity sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw== + +seroval-plugins@^1.1.0: + version "1.2.1" + resolved "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.2.1.tgz" + integrity sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw== + serve-static@1.16.2: version "1.16.2" resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz" @@ -10611,12 +10794,38 @@ socks-proxy-agent@^8.0.5: debug "^4.3.4" socks "^2.8.3" +solid-floating-ui@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/solid-floating-ui/-/solid-floating-ui-0.3.1.tgz" + integrity sha512-o/QmGsWPS2Z3KidAxP0nDvN7alI7Kqy0kU+wd85Fz+au5SYcnYm7I6Fk3M60Za35azsPX0U+5fEtqfOuk6Ao0Q== + +solid-js@^1.6.12, solid-js@^1.8, solid-js@^1.8.11: + version "1.9.4" + resolved "https://registry.npmjs.org/solid-js/-/solid-js-1.9.4.tgz" + integrity sha512-ipQl8FJ31bFUoBNScDQTG3BjN6+9Rg+Q+f10bUbnO6EOTTf5NGerJeHc7wyu5I4RMHEl/WwZwUmy/PTRgxxZ8g== + dependencies: + csstype "^3.1.0" + seroval "^1.1.0" + seroval-plugins "^1.1.0" + +solid-motionone@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/solid-motionone/-/solid-motionone-1.0.3.tgz" + integrity sha512-C1XGpbKz7klrYwYPI75X0TJdIIJWMslLPUIgnu9a0jXqN6TWA65+EByBkhVarZeBfNjVg3+uOrIRFewqZkjuFA== + dependencies: + "@motionone/dom" "^10.17.0" + "@motionone/utils" "^10.17.0" + "@solid-primitives/props" "^3.1.11" + "@solid-primitives/refs" "^1.0.8" + "@solid-primitives/transition-group" "^1.0.5" + csstype "^3.1.3" + sonner@^1.7.4: version "1.7.4" resolved "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz" integrity sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw== -source-map@~0.6.1: +source-map@^0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -10941,7 +11150,7 @@ tailwind-merge@2.5.3: resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.3.tgz" integrity sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw== -tailwind-merge@^2.6.0: +tailwind-merge@^2.4.0, tailwind-merge@^2.6.0: version "2.6.0" resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz" integrity sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA== @@ -11193,7 +11402,7 @@ tsconfck@3.1.3: resolved "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.3.tgz" integrity sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ== -tslib@2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.7.0, tslib@^2.8.0: +tslib@2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.7.0, tslib@^2.8.0: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -11359,6 +11568,11 @@ ufo@^1.5.4: resolved "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz" integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ== +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + ulid@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz" @@ -11556,7 +11770,7 @@ uuid@^3.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^9.0.0: +uuid@^9.0.0, uuid@^9.0.1: version "9.0.1" resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== @@ -11829,6 +12043,11 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2 || 3 || 4" +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wordwrapjs@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz"