From 5b84df4f00b94d20f6478b1f87830f2381e93e47 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Mon, 17 Feb 2025 23:57:56 +0200 Subject: [PATCH 01/34] fix(shared): Remove used feature flags --- packages/shared/src/types/feature-flags.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/shared/src/types/feature-flags.ts b/packages/shared/src/types/feature-flags.ts index 10316a0d653d..e8ff8c912a4b 100644 --- a/packages/shared/src/types/feature-flags.ts +++ b/packages/shared/src/types/feature-flags.ts @@ -35,11 +35,9 @@ export enum FeatureFlagsKeysEnum { IS_EVENT_QUOTA_LIMITING_ENABLED = 'IS_EVENT_QUOTA_LIMITING_ENABLED', IS_HUBSPOT_ONBOARDING_ENABLED = 'IS_HUBSPOT_ONBOARDING_ENABLED', IS_INTEGRATION_INVALIDATION_DISABLED = 'IS_INTEGRATION_INVALIDATION_DISABLED', - IS_MIXPANEL_RECORDING_ENABLED = 'IS_MIXPANEL_RECORDING_ENABLED', IS_NEW_MESSAGES_API_RESPONSE_ENABLED = 'IS_NEW_MESSAGES_API_RESPONSE_ENABLED', IS_PLAYGROUND_ONBOARDING_ENABLED = 'IS_PLAYGROUND_ONBOARDING_ENABLED', IS_QUOTA_LIMITING_ENABLED = 'IS_QUOTA_LIMITING_ENABLED', - IS_TEAM_MEMBER_INVITE_NUDGE_ENABLED = 'IS_TEAM_MEMBER_INVITE_NUDGE_ENABLED', IS_TEMPLATE_STORE_ENABLED = 'IS_TEMPLATE_STORE_ENABLED', IS_USAGE_ALERTS_ENABLED = 'IS_USAGE_ALERTS_ENABLED', IS_USE_MERGED_DIGEST_ID_ENABLED = 'IS_USE_MERGED_DIGEST_ID_ENABLED', From d83e662b841b5c95ee80c5aa69c0d4b180a78471 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Fri, 21 Feb 2025 16:52:56 +0100 Subject: [PATCH 02/34] chore(api): Remove unused env vars --- apps/api/src/.env.development | 1 - apps/api/src/.env.production | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/api/src/.env.development b/apps/api/src/.env.development index 4e82600e495e..4dde1434d166 100644 --- a/apps/api/src/.env.development +++ b/apps/api/src/.env.development @@ -7,7 +7,6 @@ GLOBAL_CONTEXT_PATH= API_CONTEXT_PATH= DISABLE_USER_REGISTRATION=false -NEST_STARTER_MAIL=support@novu.co CLIENT_SUCCESS_AUTH_REDIRECT=https://dashboard.novu-staging.co/auth/login REDIS_PORT=6379 diff --git a/apps/api/src/.env.production b/apps/api/src/.env.production index fda63ca2ce5a..3c67ab3edc78 100644 --- a/apps/api/src/.env.production +++ b/apps/api/src/.env.production @@ -7,7 +7,6 @@ API_CONTEXT_PATH= DISABLE_USER_REGISTRATION=false WIDGET_BASE_URL=https://widget.novu.co -NEST_STARTER_MAIL=support@novu.co REDIS_PORT=6379 REDIS_PREFIX= From 716d0636b1ffe5cf846c0983ceadf380a56790f8 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Fri, 21 Feb 2025 23:45:33 +0100 Subject: [PATCH 03/34] chore(dashboard): Align ESLint rules Enforce new line between two blocks. --- apps/dashboard/eslint.config.js | 8 ++++++++ apps/dashboard/src/api/api.client.ts | 3 +++ apps/dashboard/src/api/workflows.ts | 1 + .../components/activity/activity-job-item.tsx | 3 +++ .../activity/components/status-badge.tsx | 2 ++ .../src/components/auth/inbox-playground.tsx | 1 + .../conditions-editor-context.tsx | 1 + .../conditions-editor/variable-select.tsx | 3 +++ .../edit-bridge-url-button.tsx | 2 ++ .../header-navigation/header-navigation.tsx | 1 + apps/dashboard/src/components/hubspot-form.tsx | 3 +++ .../icons/add-subscriber-illustration.tsx | 1 + .../components/icons/version-control-dev.tsx | 1 + .../components/icons/version-control-prod.tsx | 1 + .../src/components/icons/workflow-cloud.tsx | 1 + apps/dashboard/src/components/inbox-button.tsx | 13 ++++++++++++- .../hooks/use-sidebar-navigation-manager.ts | 1 + .../components/integration-configuration.tsx | 1 + .../components/integrations-list.tsx | 1 + .../modals/select-primary-integration-modal.tsx | 1 + .../src/components/primitives/badge.tsx | 2 ++ .../components/primitives/button-compact.tsx | 1 + .../src/components/primitives/button-group.tsx | 1 + .../src/components/primitives/button-link.tsx | 1 + .../src/components/primitives/button.tsx | 1 + .../src/components/primitives/command.tsx | 1 + .../variable-plugin/plugin-view.ts | 3 +++ .../variable-plugin/variable-pill-widget.ts | 2 ++ .../src/components/primitives/dropdown-menu.tsx | 1 + .../primitives/form/avatar-picker.tsx | 2 +- .../form/faceted-filter/facated-form-filter.tsx | 2 ++ .../hooks/use-keyboard-navigation.ts | 3 +++ .../src/components/primitives/hint.tsx | 2 ++ .../src/components/primitives/input.tsx | 4 ++++ .../src/components/primitives/separator.tsx | 1 + .../src/components/primitives/status-badge.tsx | 2 ++ .../src/components/primitives/tag-input.tsx | 5 +++++ .../dashboard/src/components/primitives/tag.tsx | 2 ++ .../src/components/primitives/textarea.tsx | 2 ++ .../src/components/primitives/time-picker.tsx | 2 ++ .../side-navigation/side-navigation.tsx | 1 + .../src/components/side-navigation/sidebar.tsx | 3 +++ .../subscribers/create-subscriber-form.tsx | 2 ++ .../src/components/subscribers/schema.ts | 1 + .../subscribers/subscriber-drawer.tsx | 1 + .../components/subscribers/subscriber-list.tsx | 2 ++ .../subscribers/subscriber-overview-form.tsx | 2 ++ .../components/subscribers/subscriber-tabs.tsx | 2 ++ .../variable/edit-variable-popover-content.tsx | 2 ++ .../variable/hooks/use-filter-manager.ts | 1 + apps/dashboard/src/components/variable/utils.ts | 1 + .../variable/utils/process-filters.ts | 1 + .../channel-preferences-form.tsx | 3 +++ .../workflow-editor/configure-workflow-form.tsx | 1 + .../workflow-editor/in-app-preview.tsx | 12 ++++++++++++ .../src/components/workflow-editor/nodes.tsx | 3 +++ .../src/components/workflow-editor/schema.ts | 4 ++++ .../components/workflow-editor/step-utils.ts | 3 +++ .../workflow-editor/steps/component-utils.tsx | 15 +++++++++++++++ .../conditions/edit-step-conditions-form.tsx | 7 +++++++ .../steps/controls/array-field-template.tsx | 1 + .../steps/controls/custom-step-controls.tsx | 1 + .../steps/controls/text-widget.tsx | 1 + .../steps/digest/days-of-week.tsx | 1 + .../steps/digest/digest-window.tsx | 2 ++ .../steps/digest/regular-digest.tsx | 1 + .../workflow-editor/steps/digest/utils.ts | 3 +++ .../email/configure-email-step-preview.tsx | 2 ++ .../steps/email/email-editor.tsx | 1 + .../steps/email/email-preview.tsx | 6 ++++++ .../steps/email/email-tabs-section.tsx | 1 + .../email/extensions/maily-variables-list.tsx | 1 + .../email/extensions/update-attributes.tsx | 1 + .../steps/email/extensions/variable-view.tsx | 1 + .../workflow-editor/steps/email/maily.tsx | 5 +++++ .../in-app/configure-in-app-step-preview.tsx | 2 ++ .../steps/in-app/in-app-tabs-section.tsx | 1 + .../steps/in-app/inbox-preview.tsx | 1 + .../workflow-editor/steps/push/push-preview.tsx | 2 ++ .../shared/configure-preview-accordion.tsx | 1 + .../workflow-editor/steps/step-drawer.tsx | 2 ++ .../workflow-editor/steps/tabs-section.tsx | 1 + .../workflow-editor/steps/template-tabs.tsx | 1 + .../steps/use-editor-preview.tsx | 1 + .../test-workflow/test-workflow-tabs.tsx | 2 ++ .../workflow-editor/workflow-checklist.tsx | 2 ++ apps/dashboard/src/components/workflow-row.tsx | 1 + .../dashboard/src/components/workflow-steps.tsx | 1 + apps/dashboard/src/components/workflow-tags.tsx | 1 + .../src/context/auth/auth-provider.tsx | 1 + apps/dashboard/src/context/clerk-provider.tsx | 1 + apps/dashboard/src/context/segment/hooks.ts | 1 + .../src/hooks/use-activity-url-state.ts | 10 ++++++++++ apps/dashboard/src/hooks/use-before-unload.ts | 1 + apps/dashboard/src/hooks/use-boot-intercom.ts | 1 + apps/dashboard/src/hooks/use-form-autosave.ts | 4 ++++ .../dashboard/src/hooks/use-form-protection.tsx | 1 + .../dashboard/src/hooks/use-invocation-queue.ts | 1 + apps/dashboard/src/hooks/use-plain-chat.ts | 1 + apps/dashboard/src/hooks/use-sync-workflow.tsx | 1 + apps/dashboard/src/pages/api-keys.tsx | 10 ++++++++++ apps/dashboard/src/pages/workflows.tsx | 1 + apps/dashboard/src/utils/conditions.ts | 1 + .../src/utils/handleValidationIssues.ts | 1 + apps/dashboard/src/utils/liquid-autocomplete.ts | 2 ++ .../parseStepVariablesToLiquidVariables.ts | 2 ++ apps/dashboard/src/utils/polymorphic.ts | 17 +++++++++-------- apps/dashboard/src/utils/schema.ts | 11 +++++++++++ apps/dashboard/src/utils/segment.ts | 6 ++++++ apps/dashboard/src/utils/time.ts | 6 ++++++ apps/dashboard/src/utils/validation.ts | 1 + 111 files changed, 274 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/eslint.config.js b/apps/dashboard/eslint.config.js index 95bb45ab773b..fcae300a1263 100644 --- a/apps/dashboard/eslint.config.js +++ b/apps/dashboard/eslint.config.js @@ -31,6 +31,14 @@ export default tseslint.config( format: ['PascalCase', 'camelCase', 'UPPER_CASE'], }, ], + 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: false }], + 'padding-line-between-statements': [ + 'warn', + { blankLine: 'always', prev: '*', next: 'block' }, + { blankLine: 'always', prev: 'block', next: '*' }, + { blankLine: 'always', prev: '*', next: 'block-like' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], }, } ); diff --git a/apps/dashboard/src/api/api.client.ts b/apps/dashboard/src/api/api.client.ts index 0ff2c8bb5aec..246328640f9e 100644 --- a/apps/dashboard/src/api/api.client.ts +++ b/apps/dashboard/src/api/api.client.ts @@ -26,6 +26,7 @@ const request = async ( } ): Promise => { const { body, environment, headers, method = 'GET', version = 'v1', signal } = options || {}; + try { const jwt = await getToken(); const config: RequestInit = { @@ -60,9 +61,11 @@ const request = async ( if (error instanceof NovuApiError) { throw error; } + if (typeof error === 'object' && error && 'message' in error) { throw new Error(`Fetch error: ${error.message}`); } + throw new Error(`Fetch error: ${JSON.stringify(error)}`); } }; diff --git a/apps/dashboard/src/api/workflows.ts b/apps/dashboard/src/api/workflows.ts index 68310e6ade94..10e08cb38d1a 100644 --- a/apps/dashboard/src/api/workflows.ts +++ b/apps/dashboard/src/api/workflows.ts @@ -53,6 +53,7 @@ export const getWorkflows = async ({ if (orderBy) { params.append('orderBy', orderBy); } + if (orderDirection) { params.append('orderDirection', orderDirection.toUpperCase()); } diff --git a/apps/dashboard/src/components/activity/activity-job-item.tsx b/apps/dashboard/src/components/activity/activity-job-item.tsx index aa71da0bc116..37dd5f43cb66 100644 --- a/apps/dashboard/src/components/activity/activity-job-item.tsx +++ b/apps/dashboard/src/components/activity/activity-job-item.tsx @@ -123,6 +123,7 @@ function getStatusMessage(job: IActivityJob): string | React.ReactNode { (job.digest as IDigestRegularMetadata)?.unit ?? '' }`; } + if (job.status === JobStatusEnum.DELAYED) { return `Collecting Digest events for ${(job.digest as IDigestRegularMetadata)?.amount ?? 0} ${ (job.digest as IDigestRegularMetadata)?.unit ?? '' @@ -139,9 +140,11 @@ function getStatusMessage(job: IActivityJob): string | React.ReactNode { // TODO: Ensure that the API populates the delay unit and amount for all occasions const { unit, amount } = (job.digest || {}) as IDelayRegularMetadata; let msg = 'Waiting'; + if (unit && amount) { msg = `Waiting for ${amount} ${unit}`; } + return msg; } diff --git a/apps/dashboard/src/components/activity/components/status-badge.tsx b/apps/dashboard/src/components/activity/components/status-badge.tsx index 3f6bd86bcf1a..880d2854da46 100644 --- a/apps/dashboard/src/components/activity/components/status-badge.tsx +++ b/apps/dashboard/src/components/activity/components/status-badge.tsx @@ -19,6 +19,7 @@ export function ActivityStatusBadge({ jobs }: StatusBadgeProps) { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } + timeoutRef.current = setTimeout(() => { setIsOpen(true); }, 200); @@ -28,6 +29,7 @@ export function ActivityStatusBadge({ jobs }: StatusBadgeProps) { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } + timeoutRef.current = setTimeout(() => { setIsOpen(false); }, 150); diff --git a/apps/dashboard/src/components/auth/inbox-playground.tsx b/apps/dashboard/src/components/auth/inbox-playground.tsx index b5e4fa6f54a3..591bf6490551 100644 --- a/apps/dashboard/src/components/auth/inbox-playground.tsx +++ b/apps/dashboard/src/components/auth/inbox-playground.tsx @@ -107,6 +107,7 @@ export function InboxPlayground() { */ const initializeDemoWorkflow = async () => { const workflow = data?.workflows.find((workflow) => workflow.workflowId?.includes(ONBOARDING_DEMO_WORKFLOW_ID)); + if (!workflow) { await createDemoWorkflow({ environment: currentEnvironment! }); } diff --git a/apps/dashboard/src/components/conditions-editor/conditions-editor-context.tsx b/apps/dashboard/src/components/conditions-editor/conditions-editor-context.tsx index 8df1d3ef55f0..0f548c54033d 100644 --- a/apps/dashboard/src/components/conditions-editor/conditions-editor-context.tsx +++ b/apps/dashboard/src/components/conditions-editor/conditions-editor-context.tsx @@ -48,6 +48,7 @@ export function ConditionsEditorProvider({ if (isRuleGroup(rule)) { const parent = findParent(rule); + if (parent) { return parent; } diff --git a/apps/dashboard/src/components/conditions-editor/variable-select.tsx b/apps/dashboard/src/components/conditions-editor/variable-select.tsx index 8d1082375f5e..2d7d5ae9f352 100644 --- a/apps/dashboard/src/components/conditions-editor/variable-select.tsx +++ b/apps/dashboard/src/components/conditions-editor/variable-select.tsx @@ -43,12 +43,14 @@ export const VariableSelect = ({ if (!filterValue) { return options; } + return options.filter((option) => option.value?.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase())); }, [options, filterValue]); const inputRef = useRef(null); const onInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value.trim(); + if (newValue !== inputValue) { setInputValue(newValue); setFilterValue(newValue); @@ -57,6 +59,7 @@ export const VariableSelect = ({ const onInputKeyDown = (e: React.KeyboardEvent) => { setIsOpen(true); + if (e.key === 'ArrowDown') { variablesListRef.current?.next(); e.preventDefault(); diff --git a/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx b/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx index aa9c91e59d37..041ab045c39f 100644 --- a/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx +++ b/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx @@ -46,6 +46,7 @@ export const EditBridgeUrlButton = () => { const onSubmit = async ({ bridgeUrl }: z.infer) => { const { isValid } = await validateBridgeUrl({ bridgeUrl }); + if (isValid) { await updateBridgeUrl({ url: bridgeUrl, environmentId: currentEnvironment?._id ?? '' }); setBridgeUrl(bridgeUrl); @@ -59,6 +60,7 @@ export const EditBridgeUrlButton = () => { open={isPopoverOpen} onOpenChange={(newIsOpen) => { setIsPopoverOpen(newIsOpen); + if (!newIsOpen && isDirty) { reset({ bridgeUrl: envBridgeUrl }); } diff --git a/apps/dashboard/src/components/header-navigation/header-navigation.tsx b/apps/dashboard/src/components/header-navigation/header-navigation.tsx index a37ca22ae06a..6086976e0c8f 100644 --- a/apps/dashboard/src/components/header-navigation/header-navigation.tsx +++ b/apps/dashboard/src/components/header-navigation/header-navigation.tsx @@ -9,6 +9,7 @@ type HeaderNavigationProps = HTMLAttributes & { startItems?: ReactNode; hideBridgeUrl?: boolean; }; + export const HeaderNavigation = (props: HeaderNavigationProps) => { const { startItems, hideBridgeUrl = false, className, ...rest } = props; return ( diff --git a/apps/dashboard/src/components/hubspot-form.tsx b/apps/dashboard/src/components/hubspot-form.tsx index b31085a45e31..6da7fd6e758f 100644 --- a/apps/dashboard/src/components/hubspot-form.tsx +++ b/apps/dashboard/src/components/hubspot-form.tsx @@ -28,6 +28,7 @@ export function HubspotForm({ formId, properties = {}, readonlyProperties = [], const script = document.createElement('script'); script.src = '//js.hsforms.net/forms/embed/v2.js'; script.async = true; + script.onload = () => { if (window.hbspt && formContainerRef.current) { window.hbspt.forms.create({ @@ -43,6 +44,7 @@ export function HubspotForm({ formId, properties = {}, readonlyProperties = [], if (readonlyProperties.includes(key)) { acc[key] = { value, hidden: true }; } + return acc; }, {} as Record @@ -50,6 +52,7 @@ export function HubspotForm({ formId, properties = {}, readonlyProperties = [], }); } }; + document.body.appendChild(script); return () => { diff --git a/apps/dashboard/src/components/icons/add-subscriber-illustration.tsx b/apps/dashboard/src/components/icons/add-subscriber-illustration.tsx index 87734bcde545..cd4a4aca6ba5 100644 --- a/apps/dashboard/src/components/icons/add-subscriber-illustration.tsx +++ b/apps/dashboard/src/components/icons/add-subscriber-illustration.tsx @@ -1,6 +1,7 @@ import type { HTMLAttributes } from 'react'; type AddSubscriberIllustrationProps = HTMLAttributes; + export const AddSubscriberIllustration = (props: AddSubscriberIllustrationProps) => { return ( diff --git a/apps/dashboard/src/components/icons/version-control-dev.tsx b/apps/dashboard/src/components/icons/version-control-dev.tsx index 75eebae473c4..3336acf11c1a 100644 --- a/apps/dashboard/src/components/icons/version-control-dev.tsx +++ b/apps/dashboard/src/components/icons/version-control-dev.tsx @@ -1,6 +1,7 @@ import type { HTMLAttributes } from 'react'; type VersionControlDevProps = HTMLAttributes; + export const VersionControlDev = (props: VersionControlDevProps) => { return ( diff --git a/apps/dashboard/src/components/icons/version-control-prod.tsx b/apps/dashboard/src/components/icons/version-control-prod.tsx index 9eca51e05e49..8d25dab01015 100644 --- a/apps/dashboard/src/components/icons/version-control-prod.tsx +++ b/apps/dashboard/src/components/icons/version-control-prod.tsx @@ -1,6 +1,7 @@ import type { HTMLAttributes } from 'react'; type VersionControlProdProps = HTMLAttributes; + export const VersionControlProd = (props: VersionControlProdProps) => { return ( diff --git a/apps/dashboard/src/components/icons/workflow-cloud.tsx b/apps/dashboard/src/components/icons/workflow-cloud.tsx index b7de0115ff82..2e267c922a3d 100644 --- a/apps/dashboard/src/components/icons/workflow-cloud.tsx +++ b/apps/dashboard/src/components/icons/workflow-cloud.tsx @@ -1,6 +1,7 @@ import type { HTMLAttributes } from 'react'; type WorkflowCloudProps = HTMLAttributes; + export const WorkflowCloud = (props: WorkflowCloudProps) => { return ( diff --git a/apps/dashboard/src/components/inbox-button.tsx b/apps/dashboard/src/components/inbox-button.tsx index 8bd15c23efa4..76207d085bdd 100644 --- a/apps/dashboard/src/components/inbox-button.tsx +++ b/apps/dashboard/src/components/inbox-button.tsx @@ -8,6 +8,16 @@ import { useEffect, useState } from 'react'; import { HeaderButton } from './header-navigation/header-button'; import { InboxBellFilled } from './icons/inbox-bell-filled'; +declare global { + interface Window { + Clerk: { + session: { + getToken: (options: { template: string }) => Promise; + }; + }; + } +} + const InboxInner = () => { const [open, setOpen] = useState(false); const [jingle, setJingle] = useState(false); @@ -92,12 +102,13 @@ export const InboxButton = () => { * This displays a test inbox, where the user can see their test notifications appear * in real-time. */ - const appId = isTestPage ? currentEnvironment?.identifier : APP_ID; + const appId = isTestPage ? currentEnvironment?.identifier : 'aqs6CnVJ1kbL'; const localizationTestSuffix = isTestPage ? ' (Test)' : ''; return ( { const env = environments?.find((env) => env.name === value); + if (env) { setValue('environmentId', env._id); } diff --git a/apps/dashboard/src/components/integrations/components/integrations-list.tsx b/apps/dashboard/src/components/integrations/components/integrations-list.tsx index 4d5d86613918..8550c695419f 100644 --- a/apps/dashboard/src/components/integrations/components/integrations-list.tsx +++ b/apps/dashboard/src/components/integrations/components/integrations-list.tsx @@ -58,6 +58,7 @@ export function IntegrationsList({ onItemClick }: IntegrationsListProps) { return integrations?.reduce( (acc, integration) => { const channel = integration.channel; + if (!acc[channel]) { acc[channel] = []; } diff --git a/apps/dashboard/src/components/integrations/components/modals/select-primary-integration-modal.tsx b/apps/dashboard/src/components/integrations/components/modals/select-primary-integration-modal.tsx index 85ff7c5d8e46..eae68f48cb28 100644 --- a/apps/dashboard/src/components/integrations/components/modals/select-primary-integration-modal.tsx +++ b/apps/dashboard/src/components/integrations/components/modals/select-primary-integration-modal.tsx @@ -66,6 +66,7 @@ export function SelectPrimaryIntegrationModal({ if (!open) { setSelectedIntegrationId(''); } + onOpenChange(open); }} onConfirm={() => onConfirm(mode === 'select' ? selectedIntegrationId : undefined)} diff --git a/apps/dashboard/src/components/primitives/badge.tsx b/apps/dashboard/src/components/primitives/badge.tsx index 450d507a88c8..a53261856f60 100644 --- a/apps/dashboard/src/components/primitives/badge.tsx +++ b/apps/dashboard/src/components/primitives/badge.tsx @@ -444,6 +444,7 @@ function BadgeIcon({ return ; } + BadgeIcon.displayName = BADGE_ICON_NAME; type BadgeDotProps = BadgeSharedProps & Omit, 'color'>; @@ -453,6 +454,7 @@ function BadgeDot({ size, variant, color, className, ...rest }: BadgeDotProps) { return
; } + BadgeDot.displayName = BADGE_DOT_NAME; export { BadgeRoot as Badge, BadgeIcon, BadgeDot as Dot, BadgeRoot as Root }; diff --git a/apps/dashboard/src/components/primitives/button-compact.tsx b/apps/dashboard/src/components/primitives/button-compact.tsx index 7a5755c01c85..b9ae08e9759b 100644 --- a/apps/dashboard/src/components/primitives/button-compact.tsx +++ b/apps/dashboard/src/components/primitives/button-compact.tsx @@ -129,6 +129,7 @@ function CompactButtonIcon({ return ; } + CompactButtonIcon.displayName = COMPACT_BUTTON_ICON_NAME; const CompactButton = React.forwardRef< diff --git a/apps/dashboard/src/components/primitives/button-group.tsx b/apps/dashboard/src/components/primitives/button-group.tsx index 6c1071d2fd2c..26263b335ae1 100644 --- a/apps/dashboard/src/components/primitives/button-group.tsx +++ b/apps/dashboard/src/components/primitives/button-group.tsx @@ -139,6 +139,7 @@ function ButtonGroupIcon({ return ; } + ButtonGroupIcon.displayName = BUTTON_GROUP_ICON_NAME; export { ButtonGroupIcon, ButtonGroupItem, ButtonGroupRoot }; diff --git a/apps/dashboard/src/components/primitives/button-link.tsx b/apps/dashboard/src/components/primitives/button-link.tsx index fecf81020e0b..ca0c01359d74 100644 --- a/apps/dashboard/src/components/primitives/button-link.tsx +++ b/apps/dashboard/src/components/primitives/button-link.tsx @@ -127,6 +127,7 @@ function LinkButtonIcon({ return ; } + LinkButtonIcon.displayName = LINK_BUTTON_ICON_NAME; const LinkButton = React.forwardRef< diff --git a/apps/dashboard/src/components/primitives/button.tsx b/apps/dashboard/src/components/primitives/button.tsx index 566f60b06296..6d666f047d6f 100644 --- a/apps/dashboard/src/components/primitives/button.tsx +++ b/apps/dashboard/src/components/primitives/button.tsx @@ -386,6 +386,7 @@ function ButtonIcon({ return ; } + ButtonIcon.displayName = BUTTON_ICON_NAME; export { Button, ButtonIcon, ButtonRoot }; diff --git a/apps/dashboard/src/components/primitives/command.tsx b/apps/dashboard/src/components/primitives/command.tsx index 70966c08225e..268625ed0e50 100644 --- a/apps/dashboard/src/components/primitives/command.tsx +++ b/apps/dashboard/src/components/primitives/command.tsx @@ -122,6 +122,7 @@ CommandItem.displayName = CommandPrimitive.Item.displayName; const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ; }; + CommandShortcut.displayName = 'CommandShortcut'; export { diff --git a/apps/dashboard/src/components/primitives/control-input/variable-plugin/plugin-view.ts b/apps/dashboard/src/components/primitives/control-input/variable-plugin/plugin-view.ts index f84b961fcb42..77534dc06689 100644 --- a/apps/dashboard/src/components/primitives/control-input/variable-plugin/plugin-view.ts +++ b/apps/dashboard/src/components/primitives/control-input/variable-plugin/plugin-view.ts @@ -6,7 +6,9 @@ import { VariablePillWidget } from './variable-pill-widget'; export class VariablePluginView { decorations: DecorationSet; + lastCursor: number = 0; + isTypingVariable: boolean = false; constructor( @@ -40,6 +42,7 @@ export class VariablePluginView { let match: RegExpExecArray | null = null; const regex = new RegExp(VARIABLE_REGEX_STRING, 'g'); + // Iterate through all variable matches in the content and add the pills while ((match = regex.exec(content)) !== null) { const { fullLiquidExpression, name, start, end, filters } = parseVariable(match); diff --git a/apps/dashboard/src/components/primitives/control-input/variable-plugin/variable-pill-widget.ts b/apps/dashboard/src/components/primitives/control-input/variable-plugin/variable-pill-widget.ts index d63968d60dea..5a613db9f6a4 100644 --- a/apps/dashboard/src/components/primitives/control-input/variable-plugin/variable-pill-widget.ts +++ b/apps/dashboard/src/components/primitives/control-input/variable-plugin/variable-pill-widget.ts @@ -13,6 +13,7 @@ export class VariablePillWidget extends WidgetType { private onSelect?: (value: string, from: number, to: number) => void ) { super(); + this.clickHandler = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -106,6 +107,7 @@ export class VariablePillWidget extends WidgetType { span.appendChild(before); span.appendChild(content); + if (this.hasFilters) { const after = document.createElement('span'); const afterStyles = this.createAfterStyles(); diff --git a/apps/dashboard/src/components/primitives/dropdown-menu.tsx b/apps/dashboard/src/components/primitives/dropdown-menu.tsx index 098b2d06c03c..485e841efcf9 100644 --- a/apps/dashboard/src/components/primitives/dropdown-menu.tsx +++ b/apps/dashboard/src/components/primitives/dropdown-menu.tsx @@ -178,6 +178,7 @@ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ; }; + DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; export { diff --git a/apps/dashboard/src/components/primitives/form/avatar-picker.tsx b/apps/dashboard/src/components/primitives/form/avatar-picker.tsx index 176a38b3026c..4773b7c7fbe4 100644 --- a/apps/dashboard/src/components/primitives/form/avatar-picker.tsx +++ b/apps/dashboard/src/components/primitives/form/avatar-picker.tsx @@ -53,7 +53,7 @@ export const AvatarPicker = forwardRef(({ n
); } + HintRoot.displayName = HINT_ROOT_NAME; function HintIcon({ @@ -68,6 +69,7 @@ function HintIcon({ return ; } + HintIcon.displayName = HINT_ICON_NAME; export { HintRoot as Hint, HintIcon as HintIcon, HintRoot as Root }; diff --git a/apps/dashboard/src/components/primitives/input.tsx b/apps/dashboard/src/components/primitives/input.tsx index e1c508a6e2ba..85d167265f27 100644 --- a/apps/dashboard/src/components/primitives/input.tsx +++ b/apps/dashboard/src/components/primitives/input.tsx @@ -221,6 +221,7 @@ function InputWrapper({ ); } + InputWrapper.displayName = INPUT_WRAPPER_NAME; const InputEl = React.forwardRef< @@ -308,6 +309,7 @@ function InputIcon({ return ; } + InputIcon.displayName = INPUT_ICON_NAME; function InputAffix({ @@ -328,6 +330,7 @@ function InputAffix({ ); } + InputAffix.displayName = INPUT_AFFIX_NAME; function InputInlineAffix({ @@ -348,6 +351,7 @@ function InputInlineAffix({ ); } + InputInlineAffix.displayName = INPUT_INLINE_AFFIX_NAME; export { diff --git a/apps/dashboard/src/components/primitives/separator.tsx b/apps/dashboard/src/components/primitives/separator.tsx index 1408f7361934..4346ccf319bf 100644 --- a/apps/dashboard/src/components/primitives/separator.tsx +++ b/apps/dashboard/src/components/primitives/separator.tsx @@ -54,6 +54,7 @@ function Separator({ }: React.HTMLAttributes & VariantProps) { return
; } + Separator.displayName = SEPARATOR_ROOT_NAME; export { Separator }; diff --git a/apps/dashboard/src/components/primitives/status-badge.tsx b/apps/dashboard/src/components/primitives/status-badge.tsx index 63157e6ef6f7..52e4f0f2e78c 100644 --- a/apps/dashboard/src/components/primitives/status-badge.tsx +++ b/apps/dashboard/src/components/primitives/status-badge.tsx @@ -133,6 +133,7 @@ function StatusBadgeIcon({ return ; } + StatusBadgeIcon.displayName = STATUS_BADGE_ICON_NAME; function StatusBadgeDot({ @@ -145,6 +146,7 @@ function StatusBadgeDot({ return
; } + StatusBadgeDot.displayName = STATUS_BADGE_DOT_NAME; export { diff --git a/apps/dashboard/src/components/primitives/tag-input.tsx b/apps/dashboard/src/components/primitives/tag-input.tsx index 0af12f659ef3..89808004f7bc 100644 --- a/apps/dashboard/src/components/primitives/tag-input.tsx +++ b/apps/dashboard/src/components/primitives/tag-input.tsx @@ -30,11 +30,13 @@ const TagInput = forwardRef((props, ref) => { const addTag = (tag: string) => { const newTag = tag.trim(); + if (newTag === '') { return; } const newTags = [...tags, tag]; + if (new Set(newTags).size !== newTags.length) { return; } @@ -47,9 +49,11 @@ const TagInput = forwardRef((props, ref) => { const removeTag = (tag: string) => { const newTags = [...tags]; const index = newTags.indexOf(tag); + if (index !== -1) { newTags.splice(index, 1); } + onChange(newTags); setInputValue(''); }; @@ -67,6 +71,7 @@ const TagInput = forwardRef((props, ref) => { placeholder="Type a tag and press Enter" onValueChange={(value) => { setInputValue(value); + if (value) { setIsOpen(true); } diff --git a/apps/dashboard/src/components/primitives/tag.tsx b/apps/dashboard/src/components/primitives/tag.tsx index 9d31810e31b5..fc810e4aba66 100644 --- a/apps/dashboard/src/components/primitives/tag.tsx +++ b/apps/dashboard/src/components/primitives/tag.tsx @@ -121,6 +121,7 @@ function TagIcon({ return ; } + TagIcon.displayName = TAG_ICON_NAME; type TagDismissButtonProps = TagSharedProps & @@ -154,6 +155,7 @@ function TagDismissIcon({ return ; } + TagDismissIcon.displayName = TAG_DISMISS_ICON_NAME; type TagProps = { diff --git a/apps/dashboard/src/components/primitives/textarea.tsx b/apps/dashboard/src/components/primitives/textarea.tsx index 8e2910704e20..2207062c5f59 100644 --- a/apps/dashboard/src/components/primitives/textarea.tsx +++ b/apps/dashboard/src/components/primitives/textarea.tsx @@ -76,6 +76,7 @@ function ResizeHandle() {
); } + ResizeHandle.displayName = TEXTAREA_RESIZE_HANDLE_NAME; type TextareaProps = React.TextareaHTMLAttributes & @@ -172,6 +173,7 @@ function CharCounter({ ); } + CharCounter.displayName = TEXTAREA_COUNTER_NAME; export { TextareaRoot as Textarea, CharCounter as TextareaCounter }; diff --git a/apps/dashboard/src/components/primitives/time-picker.tsx b/apps/dashboard/src/components/primitives/time-picker.tsx index b3a5c12349bc..cd8c2ce483ac 100644 --- a/apps/dashboard/src/components/primitives/time-picker.tsx +++ b/apps/dashboard/src/components/primitives/time-picker.tsx @@ -73,6 +73,7 @@ const TimePickerInput = React.forwardRef e.preventDefault(); if (e.key === 'ArrowRight') onRightFocus?.(); if (e.key === 'ArrowLeft') onLeftFocus?.(); + if (['ArrowUp', 'ArrowDown'].includes(e.key)) { const step = e.key === 'ArrowUp' ? 1 : -1; const newValue = getArrowByType(calculatedValue, step, picker); @@ -80,6 +81,7 @@ const TimePickerInput = React.forwardRef const tempDate = new Date(date); setDate(setDateByType(tempDate, newValue, picker, period)); } + if (e.key >= '0' && e.key <= '9') { if (picker === '12hours') setPrevIntKey(e.key); diff --git a/apps/dashboard/src/components/side-navigation/side-navigation.tsx b/apps/dashboard/src/components/side-navigation/side-navigation.tsx index 021130653c80..c982793cf8e3 100644 --- a/apps/dashboard/src/components/side-navigation/side-navigation.tsx +++ b/apps/dashboard/src/components/side-navigation/side-navigation.tsx @@ -60,6 +60,7 @@ export const SideNavigation = () => { console.error('Error opening plain chat:', error); } }; + return (