diff --git a/.env.example b/.env.example index 3dd4887..4a68b68 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,8 @@ DATABASE_URL="postgresql://username:password@localhost:5432/products_show_develo NEXTAUTH_SECRET="hey" FLATFILE_API_KEY='sk_...' FLATFILE_ENVIRONMENT_ID='us_env_...' -FLATFILE_NAMESPACE='space:plmproject' +FLATFILE_NAMESPACE='plmproject' +NEXT_PUBLIC_FLATFILE_NAMESPACE='plmproject' NEXT_PUBLIC_FLATFILE_PUBLISHABLE_KEY='pk_...' NEXT_PUBLIC_FLATFILE_ENVIRONMENT_ID='us_env_...' NEXT_PUBLIC_APP_ID='products-show' diff --git a/app/(authenticated)/embedded-portal/[spaceId]/page.tsx b/app/(authenticated)/embedded-portal/[spaceId]/page.tsx index 5957c36..3344246 100644 --- a/app/(authenticated)/embedded-portal/[spaceId]/page.tsx +++ b/app/(authenticated)/embedded-portal/[spaceId]/page.tsx @@ -1,4 +1,7 @@ -import VisitSpaceForm from "@/components/shared/visit-space-form"; +import EmbeddedPortal from "@/app/(authenticated)/embedded-portal/embedded-portal"; +import { FlatfileService } from "@/lib/services/flatfile"; +import { SpaceService } from "@/lib/services/space"; +import { redirect } from "next/navigation"; export default async function Page({ params, @@ -8,10 +11,20 @@ export default async function Page({ }; }) { const spaceId = params.spaceId; + const space = await SpaceService.getSpace({ + id: spaceId, + }); + if (!space) { + redirect("/embedded-portal"); + } + const flatfileSpace = await FlatfileService.getSpace({ + flatfileSpaceId: space.flatfileSpaceId, + }); return ( -
- -
+ ); } diff --git a/app/(authenticated)/embedded-portal/embedded-portal.tsx b/app/(authenticated)/embedded-portal/embedded-portal.tsx new file mode 100644 index 0000000..a07e307 --- /dev/null +++ b/app/(authenticated)/embedded-portal/embedded-portal.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { useState } from "react"; +import { ISpace, initializeFlatfile } from "@flatfile/react"; +import { Button } from "@/components/ui/button"; +import { + ArrowsPointingInIcon, + ArrowsPointingOutIcon, +} from "@heroicons/react/24/outline"; +import HeaderContent from "@/app/(authenticated)/project-onboarding/header-content"; +import { + EMBEDDED_PORTAL_ITEM, + EMBEDDED_PORTAL_INITIAL_STEPS, +} from "@/lib/workflow-constants"; +import { Step } from "@/components/shared/step-list"; + +export default function EmbeddedPortal({ + flatfileSpaceId, + flatfileSpaceAccessToken, +}: { + flatfileSpaceId: string; + flatfileSpaceAccessToken: string; +}) { + const [showSpace, setShowSpace] = useState(false); + const steps: Step[] = [ + { ...EMBEDDED_PORTAL_INITIAL_STEPS[0], status: "complete" }, + { ...EMBEDDED_PORTAL_INITIAL_STEPS[1], status: "current" }, + ]; + + const spaceProps: ISpace = { + space: { + id: flatfileSpaceId, + accessToken: flatfileSpaceAccessToken, + }, + namespace: process.env.NEXT_PUBLIC_FLATFILE_NAMESPACE as string, + environmentId: process.env.NEXT_PUBLIC_FLATFILE_ENVIRONMENT_ID as string, + }; + const { Space, OpenEmbed } = initializeFlatfile({ + ...spaceProps, + closeSpace: { + operation: "contacts:submit", + onClose: () => setShowSpace(false), + }, + }); + + const onOpenSpace = async () => { + setShowSpace(!showSpace); + OpenEmbed(); + }; + + return ( +
+ +
+

+ Your embedded Flatfile space is configured and ready for import. 🎉 +

+
+
+

Launch Flatfile

+

Launch Flatfile via the "Import" button below.

+

+ Use the Sidebar in the embedded application to guide you through + the import process! +

+
+
+
+ + {showSpace && } +
+
+
+
+
+
+
+ ); +} diff --git a/app/(authenticated)/embedded-portal/page.tsx b/app/(authenticated)/embedded-portal/page.tsx index 43ac807..bbe452d 100644 --- a/app/(authenticated)/embedded-portal/page.tsx +++ b/app/(authenticated)/embedded-portal/page.tsx @@ -1,13 +1,32 @@ import { WorkflowType } from "@/lib/workflow-type"; -import CreateSpaceForm from "@/components/shared/create-space-form"; +import { SpaceService } from "@/lib/services/space"; +import invariant from "ts-invariant"; +import { getServerSession } from "@/lib/get-server-session"; +import { redirect } from "next/navigation"; +import SetupSpace from "@/components/shared/setup-space"; +import { + EMBEDDED_PORTAL_ITEM, + EMBEDDED_PORTAL_STORAGE_KEY, +} from "@/lib/workflow-constants"; + +export default async function Page() { + const session = await getServerSession(); + invariant(session?.user, "User must be logged in"); + + const space = await SpaceService.getSpaceForWorkflow({ + userId: session.user.id, + workflowType: WorkflowType.Embed, + }); + + if (space) { + redirect(`/embedded-portal/${space.id}`); + } -export default function Page() { return ( -
- -
+ ); } diff --git a/app/(authenticated)/project-onboarding/page.tsx b/app/(authenticated)/project-onboarding/page.tsx index 12d4be0..116e10c 100644 --- a/app/(authenticated)/project-onboarding/page.tsx +++ b/app/(authenticated)/project-onboarding/page.tsx @@ -2,8 +2,12 @@ import { WorkflowType } from "@/lib/workflow-type"; import { SpaceService } from "@/lib/services/space"; import invariant from "ts-invariant"; import { getServerSession } from "@/lib/get-server-session"; -import SetupSpace from "@/app/(authenticated)/project-onboarding/setup-space"; import { redirect } from "next/navigation"; +import SetupSpace from "@/components/shared/setup-space"; +import { + PROJECT_ONBOARDING_ITEM, + PROJECT_ONBOARDING_STORAGE_KEY, +} from "@/lib/workflow-constants"; export default async function Page() { const session = await getServerSession(); @@ -18,5 +22,11 @@ export default async function Page() { redirect(`/project-onboarding/${space.id}`); } - return ; + return ( + + ); } diff --git a/components/shared/create-space-form.tsx b/components/shared/create-space-form.tsx index f166d10..660dbc9 100644 --- a/components/shared/create-space-form.tsx +++ b/components/shared/create-space-form.tsx @@ -91,7 +91,7 @@ export default function CreateSpaceForm({ )} diff --git a/app/(authenticated)/project-onboarding/setup-space.tsx b/components/shared/setup-space.tsx similarity index 75% rename from app/(authenticated)/project-onboarding/setup-space.tsx rename to components/shared/setup-space.tsx index 453368a..07ac764 100644 --- a/app/(authenticated)/project-onboarding/setup-space.tsx +++ b/components/shared/setup-space.tsx @@ -6,19 +6,30 @@ import DownloadSampleData from "@/components/shared/download-sample-data"; import { Step } from "@/components/shared/step-list"; import { PROJECT_ONBOARDING_INITIAL_STEPS, - PROJECT_ONBOARDING_ITEM, SAMPLE_DATA_FILENAME, + WorkflowItem, } from "@/lib/workflow-constants"; import { WorkflowType } from "@/lib/workflow-type"; import { useEffect, useState } from "react"; -const STORAGE_KEY = `${process.env.NEXT_PUBLIC_APP_ID}-project-onboarding-downloaded`; - -export default function SetupSpace() { - const [steps, setSteps] = useState(PROJECT_ONBOARDING_INITIAL_STEPS); +export default function SetupSpace({ + workflowType, + storageKey, + item, +}: { + workflowType: WorkflowType; + storageKey: string; + item: WorkflowItem; +}) { + const [steps, setSteps] = useState( + item.steps || PROJECT_ONBOARDING_INITIAL_STEPS + ); useEffect(() => { - if (localStorage.getItem(STORAGE_KEY) === "true" && steps[0].status === "current") { + if ( + localStorage.getItem(storageKey) === "true" && + steps[0].status === "current" + ) { setSteps([ { ...steps[0], status: "complete" }, { ...steps[1], status: "current" }, @@ -28,13 +39,13 @@ export default function SetupSpace() { return (
- + {steps[0].status === "current" && ( { - localStorage.setItem(STORAGE_KEY, "true"); + localStorage.setItem(storageKey, "true"); setSteps([ { ...steps[0], status: "complete" }, @@ -54,10 +65,7 @@ export default function SetupSpace() { invite you to it. 👇

- +

To download the sample data again,{" "} diff --git a/lib/workflow-constants.ts b/lib/workflow-constants.ts index b1fac08..7171b81 100644 --- a/lib/workflow-constants.ts +++ b/lib/workflow-constants.ts @@ -14,6 +14,20 @@ export const PROJECT_ONBOARDING_INITIAL_STEPS: Step[] = [ }, ]; +export const EMBEDDED_PORTAL_INITIAL_STEPS: Step[] = [ + { + name: "Download Sample Data", + status: "current", + }, + { + name: "Setup Flatfile", + status: "upcoming", + }, +]; + +export const PROJECT_ONBOARDING_STORAGE_KEY = `${process.env.NEXT_PUBLIC_APP_ID}-project-onboarding-downloaded`; +export const EMBEDDED_PORTAL_STORAGE_KEY = `${process.env.NEXT_PUBLIC_APP_ID}-embedded-portal-downloaded`; + type NavItem = { name: string; href: string; @@ -25,6 +39,7 @@ export type WorkflowItem = NavItem & { color: string; highlightColor: string; description: string; + steps?: Step[]; }; export const HOME_ITEM: NavItem = { @@ -48,6 +63,7 @@ export const WORKFLOW_ITEMS: { highlightColor: "hover:border-project-onboarding", description: "Flatfile enables multiple team members to collaborate over the course of a project in real-time, validating, transforming, and loading data into HCM.Show while ensuring everyone is on the same page.", + steps: PROJECT_ONBOARDING_INITIAL_STEPS, }, [WorkflowType.Embed]: { slug: "embedded-portal", @@ -61,6 +77,7 @@ export const WORKFLOW_ITEMS: { highlightColor: "hover:border-embedded-portal", description: "Flatfile's deeply configurable import experience is available right inside HCM Show. See how Flatfile simplifies the data onboarding process, eliminating the need for manual data mapping and significantly reducing errors.", + steps: EMBEDDED_PORTAL_INITIAL_STEPS, }, [WorkflowType.FileFeed]: { slug: "file-feed", @@ -92,6 +109,8 @@ export const WORKFLOW_ITEMS: { export const PROJECT_ONBOARDING_ITEM = WORKFLOW_ITEMS[WorkflowType.ProjectOnboarding]; +export const EMBEDDED_PORTAL_ITEM = WORKFLOW_ITEMS[WorkflowType.Embed]; + export const RESOURCE_ITEMS: NavItem[] = [ { name: "Suppliers", diff --git a/preflight.js b/preflight.js index c6e4f13..a170423 100644 --- a/preflight.js +++ b/preflight.js @@ -8,6 +8,7 @@ VARS = [ "NEXT_PUBLIC_FLATFILE_ENVIRONMENT_ID", "NEXT_PUBLIC_APP_ID", "LISTENER_AUTH_TOKEN", + "NEXT_PUBLIC_FLATFILE_NAMESPACE", ]; function preflight() {