-
- {activeProject?.name.charAt(0).toUpperCase() || 'P'}
+ {/* 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