diff --git a/apps/web/src/components/DashboardLayout.tsx b/apps/web/src/components/DashboardLayout.tsx index 94e3299..950e914 100644 --- a/apps/web/src/components/DashboardLayout.tsx +++ b/apps/web/src/components/DashboardLayout.tsx @@ -132,135 +132,135 @@ export function DashboardLayout({children}: DashboardLayoutProps) { }, [handleLogout]); // Sidebar content (reusable for both desktop and mobile) - const SidebarContent = () => ( + const SidebarContent = () => ( <> - {/* Logo */} -
- Plunk -

Plunk

-
+ {/* Logo */} +
+ Plunk +

Plunk

+
- {/* Project Switcher */} -
-
-
- - + + - {/* Project Dropdown */} - {showProjectMenu && ( -
- {availableProjects.map(project => ( - - ))} -
- - - Create project - -
- )} -
-
- - {/* Navigation */} - - - {/* Settings & User Menu */} -
- setShowMobileMenu(false)} - className={`flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg transition-colors text-neutral-700 ${ - router.pathname.startsWith('/settings') ? 'bg-neutral-100' : 'hover:bg-neutral-50 hover:text-neutral-900' - }`} - > - - Settings - - -
- - - {/* User Dropdown */} - {showUserMenu && ( -
- -
- )}
-
- - ); + + {/* Navigation */} + + + {/* Settings & User Menu */} +
+ setShowMobileMenu(false)} + className={`flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg transition-colors text-neutral-700 ${ + router.pathname.startsWith('/settings') ? 'bg-neutral-100' : 'hover:bg-neutral-50 hover:text-neutral-900' + }`} + > + + Settings + + +
+ + + {/* User Dropdown */} + {showUserMenu && ( +
+ +
+ )} +
+
+ + ); return (
diff --git a/apps/web/src/components/EmailEditor/EmailEditor.tsx b/apps/web/src/components/EmailEditor/EmailEditor.tsx index 1fc4d7a..b808887 100644 --- a/apps/web/src/components/EmailEditor/EmailEditor.tsx +++ b/apps/web/src/components/EmailEditor/EmailEditor.tsx @@ -35,7 +35,6 @@ interface EmailEditorProps { value: string; onChange: (value: string) => void; placeholder?: string; - canUploadImages?: boolean; // Optional props for preview header subject?: string; from?: string; diff --git a/apps/web/src/components/SegmentFilterBuilder.tsx b/apps/web/src/components/SegmentFilterBuilder.tsx index 3549683..8dded84 100644 --- a/apps/web/src/components/SegmentFilterBuilder.tsx +++ b/apps/web/src/components/SegmentFilterBuilder.tsx @@ -119,6 +119,44 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available const [open, setOpen] = useState(false); const [search, setSearch] = useState(''); + // Helper to get default value based on field type + const getDefaultValueForType = useCallback((type: string) => { + switch (type) { + case 'boolean': + return true; + case 'number': + return 0; + case 'date': + return ''; + default: + return ''; + } + }, []); + + // Helper to get valid operators for a field type + const getOperatorsForType = useCallback((type: string, isEvent: boolean) => { + if (isEvent) { + return EVENT_OPERATORS; + } + + if (type === 'boolean') { + return STANDARD_OPERATORS.filter(op => + ['equals', 'notEquals', 'exists', 'notExists'].includes(op.value) + ); + } + + if (type === 'number' || type === 'date') { + return STANDARD_OPERATORS.filter(op => + ['equals', 'notEquals', 'greaterThan', 'lessThan', 'greaterThanOrEqual', 'lessThanOrEqual', 'exists', 'notExists', 'within'].includes(op.value) + ); + } + + // String type - no within operator, no comparison operators + return STANDARD_OPERATORS.filter(op => + ['equals', 'notEquals', 'contains', 'notContains', 'exists', 'notExists'].includes(op.value) + ); + }, []); + const needsValue = !['exists', 'notExists', 'triggered', 'notTriggered'].includes(filter.operator); const needsUnit = ['within', 'triggeredWithin'].includes(filter.operator); @@ -208,45 +246,7 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available setOpen(false); setSearch(''); - }, [availableFields, filter.operator, fieldType, onChange]); - - // Helper to get default value based on field type - const getDefaultValueForType = useCallback((type: string) => { - switch (type) { - case 'boolean': - return true; - case 'number': - return 0; - case 'date': - return ''; - default: - return ''; - } - }, []); - - // Helper to get valid operators for a field type - const getOperatorsForType = useCallback((type: string, isEvent: boolean) => { - if (isEvent) { - return EVENT_OPERATORS; - } - - if (type === 'boolean') { - return STANDARD_OPERATORS.filter(op => - ['equals', 'notEquals', 'exists', 'notExists'].includes(op.value) - ); - } - - if (type === 'number' || type === 'date') { - return STANDARD_OPERATORS.filter(op => - ['equals', 'notEquals', 'greaterThan', 'lessThan', 'greaterThanOrEqual', 'lessThanOrEqual', 'exists', 'notExists', 'within'].includes(op.value) - ); - } - - // String type - no within operator, no comparison operators - return STANDARD_OPERATORS.filter(op => - ['equals', 'notEquals', 'contains', 'notContains', 'exists', 'notExists'].includes(op.value) - ); - }, []); + }, [availableFields, filter.operator, fieldType, onChange, getDefaultValueForType, getOperatorsForType]); // Get label for selected field const getFieldLabel = useCallback(() => { diff --git a/apps/web/src/components/WorkflowBuilder.tsx b/apps/web/src/components/WorkflowBuilder.tsx index 11c5c2f..9388b3e 100644 --- a/apps/web/src/components/WorkflowBuilder.tsx +++ b/apps/web/src/components/WorkflowBuilder.tsx @@ -334,6 +334,18 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [stepToDelete, setStepToDelete] = useState(null); + // Define handlers before they are used in useMemo + const handleEditStep = useCallback((stepId: string) => { + // This will be handled by the parent component + const event = new CustomEvent('workflow-edit-step', {detail: {stepId}}); + window.dispatchEvent(event); + }, []); + + const handleDeleteStepClick = useCallback((stepId: string) => { + setStepToDelete(stepId); + setShowDeleteDialog(true); + }, []); + // Convert workflow steps to React Flow nodes const rawNodes: Node[] = useMemo(() => { @@ -622,17 +634,6 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr [addStepContext, workflowId, onUpdate], ); - const handleEditStep = useCallback((stepId: string) => { - // This will be handled by the parent component - const event = new CustomEvent('workflow-edit-step', {detail: {stepId}}); - window.dispatchEvent(event); - }, []); - - const handleDeleteStepClick = useCallback((stepId: string) => { - setStepToDelete(stepId); - setShowDeleteDialog(true); - }, []); - // Get all steps that will be affected by deleting a step (the step itself + all downstream steps) const getAffectedSteps = useCallback( (stepId: string): typeof steps => { diff --git a/apps/web/src/pages/campaigns/[id].tsx b/apps/web/src/pages/campaigns/[id].tsx index d7f6252..5729cad 100644 --- a/apps/web/src/pages/campaigns/[id].tsx +++ b/apps/web/src/pages/campaigns/[id].tsx @@ -546,10 +546,6 @@ export default function CampaignDetailsPage() { setHasChanges(true); }} placeholder="

Welcome!

Your email content here...

" - canUploadImages={true} - subject={editedCampaign.subject} - from={editedCampaign.from} - replyTo={editedCampaign.replyTo || undefined} /> diff --git a/apps/web/src/pages/campaigns/create.tsx b/apps/web/src/pages/campaigns/create.tsx index 94f3b27..e0a1592 100644 --- a/apps/web/src/pages/campaigns/create.tsx +++ b/apps/web/src/pages/campaigns/create.tsx @@ -195,10 +195,6 @@ export default function CreateCampaignPage() { value={body} onChange={setBody} placeholder="

Welcome!

Your email content here...

" - canUploadImages={true} - subject={subject} - from={from} - replyTo={replyTo} />
diff --git a/apps/web/src/pages/templates/[id].tsx b/apps/web/src/pages/templates/[id].tsx index e86d787..c9ce879 100644 --- a/apps/web/src/pages/templates/[id].tsx +++ b/apps/web/src/pages/templates/[id].tsx @@ -270,10 +270,6 @@ export default function TemplateEditorPage() { value={editedTemplate.body || ''} onChange={body => setEditedTemplate({...editedTemplate, body})} placeholder="

Welcome!

Thanks for subscribing to our newsletter.

" - canUploadImages={true} - subject={editedTemplate.subject} - from={editedTemplate.from} - replyTo={editedTemplate.replyTo || undefined} /> diff --git a/apps/web/src/pages/templates/create.tsx b/apps/web/src/pages/templates/create.tsx index a5c4e45..fa0039a 100644 --- a/apps/web/src/pages/templates/create.tsx +++ b/apps/web/src/pages/templates/create.tsx @@ -179,10 +179,6 @@ export default function CreateTemplatePage() { value={body} onChange={setBody} placeholder="

Welcome!

Thanks for subscribing to our newsletter.

" - canUploadImages={true} - subject={subject} - from={from} - replyTo={replyTo} /> diff --git a/apps/wiki/next-env.d.ts b/apps/wiki/next-env.d.ts index c4b7818..9edff1c 100644 --- a/apps/wiki/next-env.d.ts +++ b/apps/wiki/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/packages/ui/src/components/atoms/Dialog.tsx b/packages/ui/src/components/atoms/Dialog.tsx index 8229bfa..0451c1f 100644 --- a/packages/ui/src/components/atoms/Dialog.tsx +++ b/packages/ui/src/components/atoms/Dialog.tsx @@ -36,14 +36,14 @@ const DialogContent = React.forwardRef< {children} - - + + Close