From 6ac9d31947508fdeeac5b9ca4fc332cf12d10acb Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:31:57 -0500 Subject: [PATCH 1/8] Add CMS page to Hubspot plugin --- plugins/hubspot/package.json | 2 +- plugins/hubspot/src/App.tsx | 9 ++ plugins/hubspot/src/components/Icons.tsx | 9 -- plugins/hubspot/src/pages/canvas/CMS.tsx | 102 +++++++++++++++++++++ plugins/hubspot/src/pages/canvas/Menu.tsx | 10 +- plugins/hubspot/src/pages/canvas/index.tsx | 1 + yarn.lock | 4 +- 7 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 plugins/hubspot/src/pages/canvas/CMS.tsx diff --git a/plugins/hubspot/package.json b/plugins/hubspot/package.json index 69a5edada..49b388ca3 100644 --- a/plugins/hubspot/package.json +++ b/plugins/hubspot/package.json @@ -15,7 +15,7 @@ "dependencies": { "@tanstack/react-query": "^5.87.4", "classnames": "^2.5.1", - "framer-plugin": "^3.6.0", + "framer-plugin": "^3.10.2", "motion": "^12.23.12", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/plugins/hubspot/src/App.tsx b/plugins/hubspot/src/App.tsx index ebdf2d65e..271b3485c 100644 --- a/plugins/hubspot/src/App.tsx +++ b/plugins/hubspot/src/App.tsx @@ -9,6 +9,7 @@ import { AccountPage, CanvasMenuPage, ChatPage, + CMSPage, FormsInstallationPage, FormsPage, LearnMoreTrackingPage, @@ -83,6 +84,14 @@ const routes: Route[] = [ }, ], }, + { + path: "/cms", + element: CMSPage, + title: "CMS", + size: { + height: 345, + }, + }, ], }, { diff --git a/plugins/hubspot/src/components/Icons.tsx b/plugins/hubspot/src/components/Icons.tsx index 6f1db2ec2..a85674331 100644 --- a/plugins/hubspot/src/components/Icons.tsx +++ b/plugins/hubspot/src/components/Icons.tsx @@ -49,15 +49,6 @@ export const MessageIcon = () => ( ) -export const LightningIcon = () => ( - - - -) - export const CaretLeftIcon = () => ( ([]) + const [isLoading, setIsLoading] = useState(true) + const [isCreating, setIsCreating] = useState(false) + const isAllowedToCreateCollection = useIsAllowedTo("createManagedCollection") + + useEffect(() => { + const loadCollections = async () => { + try { + const allCollections = await framer.getCollections() + const thisPluginCollections = allCollections + .filter(collection => collection.managedBy === "thisPlugin") + .map(collection => ({ + id: collection.id, + name: collection.name, + })) + setCollections(thisPluginCollections) + } catch (error) { + console.error("Failed to load collections:", error) + framer.notify("Failed to load collections", { variant: "error" }) + } finally { + setIsLoading(false) + } + } + + void loadCollections() + }, []) + + const handleCollectionClick = async (collectionId: string) => { + try { + await framer.navigateTo(collectionId) + } catch (error) { + console.error("Failed to navigate to collection:", error) + framer.notify("Failed to open collection", { variant: "error" }) + } + } + + const handleCreateCollection = async () => { + if (!isAllowedToCreateCollection) { + framer.notify("You are not allowed to create collections", { variant: "error" }) + return + } + + try { + setIsCreating(true) + const collection = await framer.createManagedCollection("HubSpot") + framer.notify("Created a collection. Click Sync to sync data from HubSpot.") + await framer.navigateTo(collection.id) + } catch (error) { + console.error("Failed to create collection:", error) + framer.notify(`Failed to create collection: ${error instanceof Error ? error.message : "Unknown error"}`, { + variant: "error", + }) + } finally { + setIsCreating(false) + } + } + + if (isLoading) return + + return ( +
+ {collections.length > 0 ? ( + + {collections.map(collection => ( + + ))} + + ) : ( +
+

+ No HubSpot collections yet. Create one to get started. +

+
+ )} + +
+
+ +
+
+ ) +} diff --git a/plugins/hubspot/src/pages/canvas/Menu.tsx b/plugins/hubspot/src/pages/canvas/Menu.tsx index 5cda72f7b..e9391e63a 100644 --- a/plugins/hubspot/src/pages/canvas/Menu.tsx +++ b/plugins/hubspot/src/pages/canvas/Menu.tsx @@ -1,9 +1,8 @@ import cx from "classnames" -import { framer } from "framer-plugin" import { useEffect } from "react" import { useLocation } from "wouter" import { useAccountQuery, useFormsQuery, useInboxesQuery, useMeetingsQuery, useUserQuery } from "../../api" -import { ChartIcon, FormsIcon, LightningIcon, MeetingsIcon, MessageIcon, PersonIcon } from "../../components/Icons" +import { ChartIcon, DatabaseIcon, FormsIcon, MeetingsIcon, MessageIcon, PersonIcon } from "../../components/Icons" import { Logo } from "../../components/Logo" const queryHooks = { @@ -76,12 +75,7 @@ export default function MenuPage() { } /> } /> } className="gap-[7px]" /> - } - onClick={() => framer.notify("The events feature will be out soon", { variant: "info" })} - /> + } /> } /> diff --git a/plugins/hubspot/src/pages/canvas/index.tsx b/plugins/hubspot/src/pages/canvas/index.tsx index 8bf6165f8..76cfa892c 100644 --- a/plugins/hubspot/src/pages/canvas/index.tsx +++ b/plugins/hubspot/src/pages/canvas/index.tsx @@ -1,5 +1,6 @@ export { default as AccountPage } from "./Account" export { default as ChatPage } from "./Chat" +export { default as CMSPage } from "./CMS" export { default as FormsPage } from "./forms" export { default as FormsInstallationPage } from "./forms/installation" export { default as MeetingsPage } from "./Meetings" diff --git a/yarn.lock b/yarn.lock index 6a6bef9b8..a85ef3a7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4574,7 +4574,7 @@ __metadata: languageName: node linkType: hard -"framer-plugin@npm:3.10.2": +"framer-plugin@npm:3.10.2, framer-plugin@npm:^3.10.2": version: 3.10.2 resolution: "framer-plugin@npm:3.10.2" peerDependencies: @@ -4900,7 +4900,7 @@ __metadata: "@types/react": "npm:^18.3.24" "@types/react-dom": "npm:^18.3.7" classnames: "npm:^2.5.1" - framer-plugin: "npm:^3.6.0" + framer-plugin: "npm:^3.10.2" motion: "npm:^12.23.12" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" From 585e783ed010c547645a2de209d1142fd4aca4b5 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:37:29 -0500 Subject: [PATCH 2/8] Fix duplicate names --- plugins/hubspot/src/pages/canvas/CMS.tsx | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/plugins/hubspot/src/pages/canvas/CMS.tsx b/plugins/hubspot/src/pages/canvas/CMS.tsx index f3230c686..9cb1df016 100644 --- a/plugins/hubspot/src/pages/canvas/CMS.tsx +++ b/plugins/hubspot/src/pages/canvas/CMS.tsx @@ -49,7 +49,8 @@ export default function CMSPage() { try { setIsCreating(true) - const collection = await framer.createManagedCollection("HubSpot") + const name = await findAvailableCollectionName("HubSpot") + const collection = await framer.createManagedCollection(name) framer.notify("Created a collection. Click Sync to sync data from HubSpot.") await framer.navigateTo(collection.id) } catch (error) { @@ -93,6 +94,7 @@ export default function CMSPage() { onClick={() => void handleCreateCollection()} isLoading={isCreating} disabled={!isAllowedToCreateCollection} + title={isAllowedToCreateCollection ? undefined : "Insufficient permissions"} > Create New Collection @@ -100,3 +102,23 @@ export default function CMSPage() { ) } + +async function findAvailableCollectionName(baseName: string): Promise { + const collections = await framer.getCollections() + const existingNames = new Set(collections.map(c => c.name)) + + // Check if base name is available + if (!existingNames.has(baseName)) { + return baseName + } + + // Try numbered variants: "HubSpot 2", "HubSpot 3", etc. + let counter = 2 + let candidateName = `${baseName} ${counter}` + while (existingNames.has(candidateName)) { + counter++ + candidateName = `${baseName} ${counter}` + } + + return candidateName +} From 25f08a818513c2074c0dd8ff296a5d709427849b Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:43:08 -0500 Subject: [PATCH 3/8] Improve buttons --- plugins/hubspot/src/pages/canvas/CMS.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/hubspot/src/pages/canvas/CMS.tsx b/plugins/hubspot/src/pages/canvas/CMS.tsx index 9cb1df016..d7b6b7404 100644 --- a/plugins/hubspot/src/pages/canvas/CMS.tsx +++ b/plugins/hubspot/src/pages/canvas/CMS.tsx @@ -51,7 +51,7 @@ export default function CMSPage() { setIsCreating(true) const name = await findAvailableCollectionName("HubSpot") const collection = await framer.createManagedCollection(name) - framer.notify("Created a collection. Click Sync to sync data from HubSpot.") + framer.notify("Created a new collection. Click Sync to sync data from HubSpot.") await framer.navigateTo(collection.id) } catch (error) { console.error("Failed to create collection:", error) @@ -68,15 +68,15 @@ export default function CMSPage() { return (
{collections.length > 0 ? ( - + {collections.map(collection => ( - + {collection.name} +
))} ) : ( From 63fb10b235ad9ef85f7e91e356d49759586e1539 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:49:52 -0500 Subject: [PATCH 4/8] Add collection icon --- plugins/hubspot/src/components/Icons.tsx | 9 +++++++++ plugins/hubspot/src/pages/canvas/CMS.tsx | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/hubspot/src/components/Icons.tsx b/plugins/hubspot/src/components/Icons.tsx index a85674331..55b81f38f 100644 --- a/plugins/hubspot/src/components/Icons.tsx +++ b/plugins/hubspot/src/components/Icons.tsx @@ -72,6 +72,15 @@ export const DatabaseIcon = () => ( ) +export const CMSCollectionIcon = () => ( + + + +) + export const IconChevron = ({ className }: { className?: string }) => ( (
void handleCollectionClick(collection.id)} > + {collection.name}
))} From cd1019af72745901aed545636ee7db0391daeea1 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:00:35 -0500 Subject: [PATCH 5/8] Update icon color --- plugins/hubspot/src/components/Icons.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/hubspot/src/components/Icons.tsx b/plugins/hubspot/src/components/Icons.tsx index 55b81f38f..d20d9368d 100644 --- a/plugins/hubspot/src/components/Icons.tsx +++ b/plugins/hubspot/src/components/Icons.tsx @@ -67,7 +67,7 @@ export const DatabaseIcon = () => ( ) From f65a3f4626e016e01e69678ce8e2a96951f4d7c5 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:58:46 -0500 Subject: [PATCH 6/8] Update padding --- plugins/hubspot/src/pages/canvas/CMS.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/hubspot/src/pages/canvas/CMS.tsx b/plugins/hubspot/src/pages/canvas/CMS.tsx index 578cb7b2b..cd7986707 100644 --- a/plugins/hubspot/src/pages/canvas/CMS.tsx +++ b/plugins/hubspot/src/pages/canvas/CMS.tsx @@ -73,7 +73,7 @@ export default function CMSPage() { {collections.map(collection => (
void handleCollectionClick(collection.id)} > From 849b30923a2ab37b78754bcf9fd78de3c06fdaa8 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:06:20 -0500 Subject: [PATCH 7/8] Update collections array, add context menu --- plugins/hubspot/src/pages/canvas/CMS.tsx | 69 +++++++++++++++--------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/plugins/hubspot/src/pages/canvas/CMS.tsx b/plugins/hubspot/src/pages/canvas/CMS.tsx index cd7986707..0ea7eed3f 100644 --- a/plugins/hubspot/src/pages/canvas/CMS.tsx +++ b/plugins/hubspot/src/pages/canvas/CMS.tsx @@ -1,4 +1,4 @@ -import { framer, useIsAllowedTo } from "framer-plugin" +import { type Collection, framer, useIsAllowedTo } from "framer-plugin" import { useEffect, useState } from "react" import { Button } from "../../components/Button" import { CenteredSpinner } from "../../components/CenteredSpinner" @@ -6,42 +6,57 @@ import { CMSCollectionIcon } from "../../components/Icons" import { ScrollFadeContainer } from "../../components/ScrollFadeContainer" export default function CMSPage() { - const [collections, setCollections] = useState<{ id: string; name: string }[]>([]) + const [collections, setCollections] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isCreating, setIsCreating] = useState(false) const isAllowedToCreateCollection = useIsAllowedTo("createManagedCollection") - useEffect(() => { - const loadCollections = async () => { - try { - const allCollections = await framer.getCollections() - const thisPluginCollections = allCollections - .filter(collection => collection.managedBy === "thisPlugin") - .map(collection => ({ - id: collection.id, - name: collection.name, - })) - setCollections(thisPluginCollections) - } catch (error) { - console.error("Failed to load collections:", error) - framer.notify("Failed to load collections", { variant: "error" }) - } finally { - setIsLoading(false) - } + const loadCollections = async () => { + try { + const allCollections = await framer.getCollections() + const thisPluginCollections = allCollections.filter(collection => collection.managedBy === "thisPlugin") + setCollections(thisPluginCollections) + } catch (error) { + console.error("Failed to load collections:", error) + framer.notify("Failed to load collections", { variant: "error" }) + } finally { + setIsLoading(false) } + } + useEffect(() => { void loadCollections() }, []) - const handleCollectionClick = async (collectionId: string) => { + const handleCollectionClick = (collectionId: string) => { try { - await framer.navigateTo(collectionId) + void framer.navigateTo(collectionId) } catch (error) { console.error("Failed to navigate to collection:", error) framer.notify("Failed to open collection", { variant: "error" }) } } + const handleCollectionContextMenu = (e: React.MouseEvent, collectionId: string) => { + e.preventDefault() + e.stopPropagation() + + void framer.showContextMenu( + [ + { + label: "Open Collection", + onAction: () => void handleCollectionClick(collectionId), + }, + ], + { + location: { + x: e.clientX, + y: e.clientY, + }, + } + ) + } + const handleCreateCollection = async () => { if (!isAllowedToCreateCollection) { framer.notify("You are not allowed to create collections", { variant: "error" }) @@ -53,6 +68,7 @@ export default function CMSPage() { const name = await findAvailableCollectionName("HubSpot") const collection = await framer.createManagedCollection(name) framer.notify("Created a new collection. Click Sync to sync data from HubSpot.") + void loadCollections() await framer.navigateTo(collection.id) } catch (error) { console.error("Failed to create collection:", error) @@ -73,11 +89,16 @@ export default function CMSPage() { {collections.map(collection => (
void handleCollectionClick(collection.id)} + className="h-[30px] text-secondary hover:text-primary cursor-pointer px-[15px] flex flex-row items-center hover:bg-tertiary rounded-lg gap-3 select-none" + onClick={() => { + handleCollectionClick(collection.id) + }} + onContextMenu={e => { + handleCollectionContextMenu(e, collection.id) + }} > - {collection.name} + {collection.name}
))} From 306ed0f0833361bae7e33f8cb6f4e0f2b3acaa04 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:14:41 -0500 Subject: [PATCH 8/8] Refresh array --- plugins/hubspot/src/pages/canvas/CMS.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/hubspot/src/pages/canvas/CMS.tsx b/plugins/hubspot/src/pages/canvas/CMS.tsx index 0ea7eed3f..a972716d2 100644 --- a/plugins/hubspot/src/pages/canvas/CMS.tsx +++ b/plugins/hubspot/src/pages/canvas/CMS.tsx @@ -14,8 +14,8 @@ export default function CMSPage() { const loadCollections = async () => { try { const allCollections = await framer.getCollections() - const thisPluginCollections = allCollections.filter(collection => collection.managedBy === "thisPlugin") - setCollections(thisPluginCollections) + const hubSpotCollections = allCollections.filter(collection => collection.managedBy === "thisPlugin") + setCollections(hubSpotCollections) } catch (error) { console.error("Failed to load collections:", error) framer.notify("Failed to load collections", { variant: "error" }) @@ -25,7 +25,16 @@ export default function CMSPage() { } useEffect(() => { + const handleWindowFocus = () => { + void loadCollections() + } + + window.addEventListener("focus", handleWindowFocus) void loadCollections() + + return () => { + window.removeEventListener("focus", handleWindowFocus) + } }, []) const handleCollectionClick = (collectionId: string) => { @@ -45,7 +54,9 @@ export default function CMSPage() { [ { label: "Open Collection", - onAction: () => void handleCollectionClick(collectionId), + onAction: () => { + handleCollectionClick(collectionId) + }, }, ], {