Skip to content

Commit

Permalink
Embedded Portal Flow (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaelyn authored Apr 5, 2024
1 parent 13a4b6b commit e12576f
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 28 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
21 changes: 17 additions & 4 deletions app/(authenticated)/embedded-portal/[spaceId]/page.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 (
<div>
<VisitSpaceForm spaceId={spaceId} />
</div>
<EmbeddedPortal
flatfileSpaceId={flatfileSpace.id}
flatfileSpaceAccessToken={flatfileSpace.accessToken as string}
/>
);
}
86 changes: 86 additions & 0 deletions app/(authenticated)/embedded-portal/embedded-portal.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="space-y-6">
<HeaderContent item={EMBEDDED_PORTAL_ITEM} steps={steps} />
<div className="text-white">
<p className="text-2xl mb-8 md:max-w-lg">
Your embedded Flatfile space is configured and ready for import. 🎉
</p>
<div className="flex flex-col md:flex-row justify-between lg:justify-start lg:space-x-12 space-y-12 md:space-y-0">
<div className="md:max-w-md">
<p className="font-semibold mb-4">Launch Flatfile</p>
<p>Launch Flatfile via the &quot;Import&quot; button below.</p>
<p>
Use the Sidebar in the embedded application to guide you through
the import process!
</p>
<div className="mt-8">
<div className="content">
<div>
<Button className="contrast" onClick={onOpenSpace}>
{showSpace ? "Close Portal" : "Import Data"}
{showSpace ? (
<ArrowsPointingInIcon className="w-4 h-4 ml-2" />
) : (
<ArrowsPointingOutIcon className="w-4 h-4 ml-2" />
)}
</Button>
{showSpace && <Space />}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
35 changes: 27 additions & 8 deletions app/(authenticated)/embedded-portal/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<CreateSpaceForm
workflowType={WorkflowType.Embed}
spaceName={"Embedded Portal"}
/>
</div>
<SetupSpace
workflowType={WorkflowType.Embed}
storageKey={EMBEDDED_PORTAL_STORAGE_KEY}
item={EMBEDDED_PORTAL_ITEM}
/>
);
}
14 changes: 12 additions & 2 deletions app/(authenticated)/project-onboarding/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -18,5 +22,11 @@ export default async function Page() {
redirect(`/project-onboarding/${space.id}`);
}

return <SetupSpace />;
return (
<SetupSpace
workflowType={WorkflowType.ProjectOnboarding}
storageKey={PROJECT_ONBOARDING_STORAGE_KEY}
item={PROJECT_ONBOARDING_ITEM}
/>
);
}
2 changes: 1 addition & 1 deletion components/shared/create-space-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default function CreateSpaceForm({
)}

<Button disabled={isPending} type="submit">
{isPending ? "Creating..." : "Create Space"}
{isPending ? "Setting Up Flatfile..." : "Setup Flatfile"}
</Button>
</form>
</Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Step[]>(PROJECT_ONBOARDING_INITIAL_STEPS);
export default function SetupSpace({
workflowType,
storageKey,
item,
}: {
workflowType: WorkflowType;
storageKey: string;
item: WorkflowItem;
}) {
const [steps, setSteps] = useState<Step[]>(
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" },
Expand All @@ -28,13 +39,13 @@ export default function SetupSpace() {

return (
<div className="space-y-6">
<HeaderContent item={PROJECT_ONBOARDING_ITEM} steps={steps} />
<HeaderContent item={item} steps={steps} />

{steps[0].status === "current" && (
<DownloadSampleData
fileName={SAMPLE_DATA_FILENAME}
onClick={() => {
localStorage.setItem(STORAGE_KEY, "true");
localStorage.setItem(storageKey, "true");

setSteps([
{ ...steps[0], status: "complete" },
Expand All @@ -54,10 +65,7 @@ export default function SetupSpace() {
invite you to it. 👇
</p>

<CreateSpaceForm
workflowType={WorkflowType.ProjectOnboarding}
spaceName={"Project Onboarding"}
/>
<CreateSpaceForm workflowType={workflowType} spaceName={item.name} />

<p className="text-xs block text-gray-400">
To download the sample data again,{" "}
Expand Down
19 changes: 19 additions & 0 deletions lib/workflow-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +39,7 @@ export type WorkflowItem = NavItem & {
color: string;
highlightColor: string;
description: string;
steps?: Step[];
};

export const HOME_ITEM: NavItem = {
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions preflight.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ VARS = [
"NEXT_PUBLIC_FLATFILE_ENVIRONMENT_ID",
"NEXT_PUBLIC_APP_ID",
"LISTENER_AUTH_TOKEN",
"NEXT_PUBLIC_FLATFILE_NAMESPACE",
];

function preflight() {
Expand Down

0 comments on commit e12576f

Please sign in to comment.