diff --git a/apps/portal/src/components/editor/BlockPalette.tsx b/apps/portal/src/components/editor/BlockPalette.tsx
deleted file mode 100644
index 8d6d5a5c..00000000
--- a/apps/portal/src/components/editor/BlockPalette.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { useMemo, useState } from "react";
-import { Button, Input, Label, TextField } from "@fluxify/components";
-import { TbX } from "react-icons/tb";
-import { BLOCK_CATALOG } from "./blockCatalog";
-
-export function BlockPalette({
- open,
- onClose,
- onAdd,
-}: {
- open: boolean;
- onClose: () => void;
- onAdd: (type: string) => void;
-}) {
- const [query, setQuery] = useState("");
-
- const groups = useMemo(() => {
- const q = query.toLowerCase();
- return BLOCK_CATALOG.map((g) => ({
- ...g,
- blocks: g.blocks.filter((b) => b.label.toLowerCase().includes(q)),
- })).filter((g) => g.blocks.length > 0);
- }, [query]);
-
- if (!open) return null;
-
- return (
-
-
- Add block
-
-
-
-
-
-
-
-
-
- {groups.map((g) => (
-
-
- {g.group}
-
- {g.blocks.map((b) => (
-
- ))}
-
- ))}
- {groups.length === 0 && (
-
No blocks match.
- )}
-
-
- );
-}
diff --git a/apps/portal/src/components/editor/GenericBlockNode.tsx b/apps/portal/src/components/editor/GenericBlockNode.tsx
deleted file mode 100644
index ef140d68..00000000
--- a/apps/portal/src/components/editor/GenericBlockNode.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Handle, Position } from "@xyflow/react";
-
-export type GenericNodeData = {
- label?: string;
- name?: string;
- targets?: string[];
- sources?: string[];
-};
-
-// ponytail: Stage 2 node — generic visual, but renders the exact handle IDs its
-// edges reference (derived in the editor route) so connections draw correctly
-// for any block type. Stage 3 swaps in per-type icons/UIs.
-export function GenericBlockNode({
- data,
- type,
- selected,
-}: {
- data?: GenericNodeData;
- type?: string;
- selected?: boolean;
-}) {
- const targets = data?.targets ?? [];
- const sources = data?.sources ?? [];
-
- return (
-
- {targets.length === 0 ? (
-
- ) : (
- targets.map((id, i) => (
-
- ))
- )}
-
-
- {type}
-
-
- {data?.label ?? data?.name ?? type ?? "Block"}
-
-
- {sources.length === 0 ? (
-
- ) : (
- sources.map((id, i) => (
-
- ))
- )}
-
- );
-}
diff --git a/apps/portal/src/components/editor/blockCatalog.ts b/apps/portal/src/components/editor/blockCatalog.ts
deleted file mode 100644
index 2a203051..00000000
--- a/apps/portal/src/components/editor/blockCatalog.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { BlockTypes } from "@/types/block";
-
-export type CatalogEntry = { type: string; label: string };
-export type CatalogGroup = { group: string; blocks: CatalogEntry[] };
-
-// Addable blocks grouped by category (mirrors the web block palette).
-export const BLOCK_CATALOG: CatalogGroup[] = [
- {
- group: "Core",
- blocks: [
- { type: BlockTypes.response, label: "Response" },
- { type: BlockTypes.errorHandler, label: "Error Handler" },
- { type: BlockTypes.setvar, label: "Set Variable" },
- { type: BlockTypes.getvar, label: "Get Variable" },
- { type: BlockTypes.transformer, label: "Transformer" },
- { type: BlockTypes.jsrunner, label: "JS Runner" },
- { type: BlockTypes.arrayops, label: "Array Operations" },
- ],
- },
- {
- group: "Flow",
- blocks: [
- { type: BlockTypes.if, label: "If" },
- { type: BlockTypes.forloop, label: "For Loop" },
- { type: BlockTypes.foreachloop, label: "Foreach Loop" },
- ],
- },
- {
- group: "Database",
- blocks: [
- { type: BlockTypes.db_getsingle, label: "Get Single Record" },
- { type: BlockTypes.db_getall, label: "Get All Records" },
- { type: BlockTypes.db_insert, label: "Insert New Record" },
- { type: BlockTypes.db_insertbulk, label: "Insert Bulk Record" },
- { type: BlockTypes.db_update, label: "Update Record(s)" },
- { type: BlockTypes.db_delete, label: "Delete Record(s)" },
- { type: BlockTypes.db_transaction, label: "Database Transaction" },
- { type: BlockTypes.db_native, label: "Native Database" },
- ],
- },
- {
- group: "HTTP",
- blocks: [
- { type: BlockTypes.httprequest, label: "Http Request" },
- { type: BlockTypes.httpgetcookie, label: "Get Cookie" },
- { type: BlockTypes.httpsetcookie, label: "Set Cookie" },
- { type: BlockTypes.httpgetheader, label: "Get Header" },
- { type: BlockTypes.httpsetheader, label: "Set Header" },
- { type: BlockTypes.httpgetparam, label: "Get Param" },
- { type: BlockTypes.httpgetrequestbody, label: "Get Request Body" },
- ],
- },
- {
- group: "Logging",
- blocks: [
- { type: BlockTypes.consolelog, label: "Console" },
- { type: BlockTypes.cloudLogs, label: "Cloud Log store" },
- ],
- },
-];
diff --git a/apps/portal/src/components/editor/blocks/BaseBlock.tsx b/apps/portal/src/components/editor/blocks/BaseBlock.tsx
deleted file mode 100644
index 8bf1a43d..00000000
--- a/apps/portal/src/components/editor/blocks/BaseBlock.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { ReactNode } from "react";
-import { cn } from "@fluxify/components";
-
-type Props = {
- blockId: string;
- blockName: string;
- icon?: ReactNode;
- children?: ReactNode; // handles
- selected?: boolean;
- labelPlacement?: "top" | "bottom" | "left" | "right";
- topLeftRounded?: boolean;
- topRightRounded?: boolean;
- bottomLeftRounded?: boolean;
- bottomRightRounded?: boolean;
- color?: string;
-};
-
-// HeroUI/Tailwind port of the web BaseBlock: compact icon card with a label and
-// per-corner rounding (entrypoint = rounded top, response = rounded bottom).
-export function BaseBlock({
- blockName,
- icon,
- children,
- selected,
- labelPlacement = "top",
- topLeftRounded,
- topRightRounded,
- bottomLeftRounded,
- bottomRightRounded,
- color,
-}: Props) {
- return (
-
- {icon}
- {children}
-
- {blockName}
-
-
- );
-}
diff --git a/apps/portal/src/components/editor/blocks/BlockHandle.tsx b/apps/portal/src/components/editor/blocks/BlockHandle.tsx
deleted file mode 100644
index 66379d43..00000000
--- a/apps/portal/src/components/editor/blocks/BlockHandle.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Handle, Position, useNodeConnections } from "@xyflow/react";
-import type React from "react";
-
-type Props = {
- style?: React.CSSProperties;
- position?: Position;
- blockId: string;
- type: "source" | "target";
- handleVariant?: string;
- color?: string;
-};
-
-// Ported verbatim from web: handle id is `${blockId}-${variant ?? type}`, which
-// is exactly what the saved edges reference.
-export function BlockHandle(props: Props) {
- const handleId = `${props.blockId}-${props.handleVariant ?? props.type}`;
- const isVertical =
- props.position === Position.Top || props.position === Position.Bottom;
- const isTarget = props.type === "target";
- const width = isVertical ? "15px" : "6px";
- const height = isVertical ? "6px" : "15px";
- const connection = useNodeConnections({ handleId, handleType: props.type });
-
- return (
-
- );
-}
diff --git a/apps/portal/src/components/editor/blocks/nodes.tsx b/apps/portal/src/components/editor/blocks/nodes.tsx
deleted file mode 100644
index 749ae075..00000000
--- a/apps/portal/src/components/editor/blocks/nodes.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import type { ReactNode } from "react";
-import { type NodeProps, Position } from "@xyflow/react";
-import { TbWorldCode, TbDoorExit, TbInfinity, TbTransform, TbCodeVariablePlus, TbMatrix, TbCookie, TbDatabaseX, TbDatabaseSearch, TbDatabasePlus, TbDatabaseEdit, TbDatabaseImport, TbTerminal2, TbCloud } from "react-icons/tb";
-import { MdOutlineReportGmailerrorred, MdHttp, MdDataObject } from "react-icons/md";
-import { FaMapSigns, FaHeading } from "react-icons/fa";
-import { IoLogoJavascript } from "react-icons/io";
-import { VscSymbolParameter } from "react-icons/vsc";
-import { LuDatabaseZap } from "react-icons/lu";
-import { BlockTypes } from "@/types/block";
-import { BaseBlock } from "./BaseBlock";
-import { BlockHandle } from "./BlockHandle";
-
-const GREEN = "#40c057";
-const VIOLET = "#7c5cff";
-const RED = "#e5484d";
-
-type Label = "top" | "bottom" | "left" | "right";
-type HandleCfg = {
- type: "source" | "target";
- position: Position;
- variant?: string;
- color?: string;
-};
-type Shape = {
- topLeftRounded?: boolean;
- topRightRounded?: boolean;
- bottomLeftRounded?: boolean;
- bottomRightRounded?: boolean;
-};
-
-// Standard block: one target on top, one source on bottom.
-const STD: HandleCfg[] = [
- { type: "target", position: Position.Top },
- { type: "source", position: Position.Bottom },
-];
-
-function node(
- name: string,
- icon: ReactNode,
- label: Label,
- handles: HandleCfg[] = STD,
- shape: Shape = {},
-) {
- return function Node(props: NodeProps) {
- return (
-
- {handles.map((h) => (
-
- ))}
-
- );
- };
-}
-
-const sz = 18;
-
-export const blocksList: Record
ReactNode> = {
- [BlockTypes.entrypoint]: node("Entrypoint", , "top", [{ type: "source", position: Position.Bottom }], { topLeftRounded: true, topRightRounded: true }),
- [BlockTypes.response]: node("Response", , "bottom", [{ type: "target", position: Position.Top }], { bottomLeftRounded: true, bottomRightRounded: true }),
- [BlockTypes.errorHandler]: node("Error Handler", , "top", [{ type: "source", position: Position.Bottom, color: RED }]),
- [BlockTypes.if]: node("If", , "bottom", [
- { type: "target", position: Position.Top },
- { type: "source", position: Position.Left, variant: "success", color: GREEN },
- { type: "source", position: Position.Right, variant: "failure", color: RED },
- ]),
- [BlockTypes.forloop]: node("For", , "left", [
- { type: "target", position: Position.Top },
- { type: "source", position: Position.Bottom },
- { type: "source", position: Position.Right, variant: "executor", color: GREEN },
- ]),
- [BlockTypes.foreachloop]: node("Foreach", , "left", [
- { type: "target", position: Position.Top },
- { type: "source", position: Position.Bottom },
- { type: "source", position: Position.Right, variant: "executor", color: VIOLET },
- ]),
- [BlockTypes.transformer]: node("Transformer", , "left"),
- [BlockTypes.jsrunner]: node("JS Runner", , "left"),
- [BlockTypes.setvar]: node("Set Variable", , "left"),
- [BlockTypes.getvar]: node("Get Variable", , "left"),
- [BlockTypes.arrayops]: node("Array Operations", , "left"),
- [BlockTypes.httprequest]: node("Http Request", , "left"),
- [BlockTypes.httpgetcookie]: node("Get Cookie", , "left"),
- [BlockTypes.httpsetcookie]: node("Set Cookie", , "left"),
- [BlockTypes.httpgetheader]: node("Get Header", , "left"),
- [BlockTypes.httpsetheader]: node("Set Header", , "left"),
- [BlockTypes.httpgetparam]: node("Get Param", , "left"),
- [BlockTypes.httpgetrequestbody]: node("Get Request Body", , "left"),
- [BlockTypes.db_getsingle]: node("Get Single Record", , "left"),
- [BlockTypes.db_getall]: node("Get All Records", , "left"),
- [BlockTypes.db_insert]: node("Insert New Record", , "left"),
- [BlockTypes.db_insertbulk]: node("Insert Bulk Record", , "left"),
- [BlockTypes.db_update]: node("Update Record(s)", , "left"),
- [BlockTypes.db_delete]: node("Delete Record(s)", , "left"),
- [BlockTypes.db_native]: node("Native Database", , "left"),
- [BlockTypes.db_transaction]: node("Database Transaction", , "left", [
- { type: "target", position: Position.Top },
- { type: "source", position: Position.Bottom },
- { type: "source", position: Position.Right, variant: "executor", color: GREEN },
- ]),
- [BlockTypes.consolelog]: node("Console", , "left"),
- [BlockTypes.cloudLogs]: node("Cloud Log store", , "left"),
-};
diff --git a/apps/portal/src/routes/_authed/$projectId.tsx b/apps/portal/src/routes/_authed/$projectId.tsx
index 48a63e8c..9410b125 100644
--- a/apps/portal/src/routes/_authed/$projectId.tsx
+++ b/apps/portal/src/routes/_authed/$projectId.tsx
@@ -23,6 +23,11 @@ import { authClient } from "@/lib/auth";
import { useAuthStore } from "@/store/auth";
import { createRouteHead } from "@/lib/seo";
+// BASE_URL, not a hardcoded path: files in public/ are served from the bundle
+// root, so the literal "/_/admin/ui/public/..." resolved to nothing and the SPA
+// fallback answered with index.html — an HTML body where an image was expected.
+const logo = `${import.meta.env.BASE_URL}icons/logo.webp`;
+
const NAV = [
{ key: "ai", label: "Fluxify AI", to: "/$projectId/ai", icon: TbSparkles },
{ key: "routes", label: "Routes", to: "/$projectId/routes", icon: TbStack2 },
@@ -79,7 +84,7 @@ function ProjectLayout() {
{/* Logo */}
-

+
FLUXIFY
diff --git a/apps/portal/src/routes/_authed/$projectId/routes.tsx b/apps/portal/src/routes/_authed/$projectId/routes.tsx
index f5a9367f..19b512f3 100644
--- a/apps/portal/src/routes/_authed/$projectId/routes.tsx
+++ b/apps/portal/src/routes/_authed/$projectId/routes.tsx
@@ -94,7 +94,7 @@ function RoutesPage() {
-
+