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..d20d9368d 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 = () => ( ( + +) + +export const CMSCollectionIcon = () => ( + + ) diff --git a/plugins/hubspot/src/pages/canvas/CMS.tsx b/plugins/hubspot/src/pages/canvas/CMS.tsx new file mode 100644 index 000000000..578cb7b2b --- /dev/null +++ b/plugins/hubspot/src/pages/canvas/CMS.tsx @@ -0,0 +1,126 @@ +import { framer, useIsAllowedTo } from "framer-plugin" +import { useEffect, useState } from "react" +import { Button } from "../../components/Button" +import { CenteredSpinner } from "../../components/CenteredSpinner" +import { CMSCollectionIcon } from "../../components/Icons" +import { ScrollFadeContainer } from "../../components/ScrollFadeContainer" + +export default function CMSPage() { + const [collections, setCollections] = useState<{ id: string; name: string }[]>([]) + 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 name = await findAvailableCollectionName("HubSpot") + const collection = await framer.createManagedCollection(name) + 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) + 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 => ( +
void handleCollectionClick(collection.id)} + > + + {collection.name} +
+ ))} +
+ ) : ( +
+

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

+
+ )} + +
+
+ +
+
+ ) +} + +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 +} 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 f261d270e..90ab46583 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4573,7 +4573,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: @@ -4899,7 +4899,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"