Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded Portal Flow #19

Merged
merged 12 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
});

Comment on lines +14 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for asynchronous operations to prevent unhandled promise rejections and improve user experience in case of errors.

+  }).catch(error => {
+    console.error("Failed to fetch space data:", error);
+    redirect("/error");
+  });
+
+  if (!space) {
+    redirect("/embedded-portal");
+  } else {
+    const flatfileSpace = await FlatfileService.getSpace({
+      flatfileSpaceId: space.flatfileSpaceId,
+    }).catch(error => {
+      console.error("Failed to fetch Flatfile space data:", error);
+      redirect("/error");
+    });
+
+    if (!flatfileSpace) {
+      redirect("/error");
+    }
+  }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const space = await SpaceService.getSpace({
id: spaceId,
});
if (!space) {
redirect("/embedded-portal");
}
const flatfileSpace = await FlatfileService.getSpace({
flatfileSpaceId: space.flatfileSpaceId,
});
const space = await SpaceService.getSpace({
id: spaceId,
}).catch(error => {
console.error("Failed to fetch space data:", error);
redirect("/error");
});
if (!space) {
redirect("/embedded-portal");
} else {
const flatfileSpace = await FlatfileService.getSpace({
flatfileSpaceId: space.flatfileSpaceId,
}).catch(error => {
console.error("Failed to fetch Flatfile space data:", error);
redirect("/error");
});
if (!flatfileSpace) {
redirect("/error");
}
}

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();
};
Comment on lines +46 to +49
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the onOpenSpace function to gracefully manage any exceptions that might occur during the operation.

  const onOpenSpace = async () => {
    try {
      setShowSpace(!showSpace);
      OpenEmbed();
    } catch (error) {
      console.error("Error opening Flatfile space:", error);
      // Handle error appropriately
    }
  };

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const onOpenSpace = async () => {
setShowSpace(!showSpace);
OpenEmbed();
};
const onOpenSpace = async () => {
try {
setShowSpace(!showSpace);
OpenEmbed();
} catch (error) {
console.error("Error opening Flatfile space:", error);
// Handle error appropriately
}
};


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,
});
Comment on lines +16 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for the asynchronous operation to improve robustness and user experience in case of errors.

  const space = await SpaceService.getSpaceForWorkflow({
    userId: session.user.id,
    workflowType: WorkflowType.Embed,
  }).catch(error => {
    console.error("Failed to fetch space data:", error);
    // Redirect to an error page or show an error message
    redirect("/error");
  });

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const space = await SpaceService.getSpaceForWorkflow({
userId: session.user.id,
workflowType: WorkflowType.Embed,
});
const space = await SpaceService.getSpaceForWorkflow({
userId: session.user.id,
workflowType: WorkflowType.Embed,
}).catch(error => {
console.error("Failed to fetch space data:", error);
// Redirect to an error page or show an error message
redirect("/error");
});


if (space) {
redirect(`/embedded-portal/${space.id}`);
}
Comment on lines +16 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider wrapping the space fetching and redirection logic in a try-catch block to handle potential errors more gracefully.

+ try {
  const space = await SpaceService.getSpaceForWorkflow({
    userId: session.user.id,
    workflowType: WorkflowType.Embed,
  });

  if (space) {
    redirect(`/embedded-portal/${space.id}`);
  }
+ } catch (error) {
+   console.error("Error fetching space or redirecting:", error);
+   // Redirect to an error page or show an error message
+   redirect("/error");
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const space = await SpaceService.getSpaceForWorkflow({
userId: session.user.id,
workflowType: WorkflowType.Embed,
});
if (space) {
redirect(`/embedded-portal/${space.id}`);
}
try {
const space = await SpaceService.getSpaceForWorkflow({
userId: session.user.id,
workflowType: WorkflowType.Embed,
});
if (space) {
redirect(`/embedded-portal/${space.id}`);
}
} catch (error) {
console.error("Error fetching space or redirecting:", error);
// Redirect to an error page or show an error message
redirect("/error");
}


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"}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding an aria-live attribute to the button or surrounding element to improve accessibility by announcing the button state change to screen readers.

+ <div aria-live="polite">
    <Button disabled={isPending} type="submit">
      {isPending ? "Setting Up Flatfile..." : "Setup Flatfile"}
    </Button>
+ </div>

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
{isPending ? "Setting Up Flatfile..." : "Setup Flatfile"}
<div aria-live="polite">
<Button disabled={isPending} type="submit">
{isPending ? "Setting Up Flatfile..." : "Setup Flatfile"}
</Button>
</div>

</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
);
gaelyn marked this conversation as resolved.
Show resolved Hide resolved

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",
gaelyn marked this conversation as resolved.
Show resolved Hide resolved
];

function preflight() {
Expand Down
Loading