diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 473ee931..5f477a63 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -367,15 +367,17 @@ model forms {
/// This model contains row level security and requires additional setup for migrations. Visit https://pris.ly/d/row-level-security for more info.
model members {
- id String @id @db.Uuid
- first_name String
- last_name String
- grad_year Int @db.SmallInt
- faculty String?
- specialization String?
- year_level Int? @db.SmallInt
- users users @relation(fields: [id], references: [id])
- team_members team_members[]
+ id String @id @db.Uuid
+ first_name String
+ last_name String
+ grad_year Int @db.SmallInt
+ faculty String?
+ specialization String?
+ year_level Int? @db.SmallInt
+ discord_id String?
+ github_username String?
+ users users @relation(fields: [id], references: [id])
+ team_members team_members[]
@@schema("public")
}
@@ -438,12 +440,21 @@ model teams {
name String @unique
start_year Int? @db.SmallInt
end_year Int? @db.SmallInt
+ meta Json?
applications applications[]
team_members team_members[]
@@schema("public")
}
+/// This model contains row level security and requires additional setup for migrations. Visit https://pris.ly/d/row-level-security for more info.
+model onboarding {
+ id BigInt @id @default(autoincrement())
+ created_at DateTime @default(now()) @db.Timestamptz(6)
+
+ @@schema("public")
+}
+
enum aal_level {
aal1
aal2
diff --git a/src/app/portal/layout.tsx b/src/app/portal/layout.tsx
index 28c4a86f..7698486b 100644
--- a/src/app/portal/layout.tsx
+++ b/src/app/portal/layout.tsx
@@ -6,11 +6,28 @@ import { db } from "@/db";
import { Toaster } from "@/components/primitives/sonner";
async function getUserMetadata(id: string) {
- return db.roles.findUnique({
+ const rolesP = db.roles.findUnique({
where: {
id: id,
},
});
+ const memberP = db.members.findUnique({
+ where: {
+ id: id,
+ },
+ include: {
+ team_members: {
+ include: {
+ teams: true,
+ },
+ },
+ },
+ });
+ const [roles, member] = await Promise.all([rolesP, memberP]);
+ return {
+ roles: roles,
+ member: member,
+ };
}
export default async function Layout({
@@ -30,7 +47,7 @@ export default async function Layout({
return (
-
+
{children}
diff --git a/src/app/portal/onboarding/page.tsx b/src/app/portal/onboarding/page.tsx
new file mode 100644
index 00000000..b0d8e63c
--- /dev/null
+++ b/src/app/portal/onboarding/page.tsx
@@ -0,0 +1,121 @@
+"use client";
+
+import React, { useState } from "react";
+import Image from "next/image";
+import Link from "next/link";
+import { Button } from "@/components/primitives/button";
+import GithubOnboarding from "@/components/portal/onboarding/githubOnboarding";
+import { ArrowLeftIcon, ArrowRight } from "lucide-react";
+import DiscordOnboarding from "@/components/portal/onboarding/discordOnboarding";
+import NotionOnboarding from "@/components/portal/onboarding/notionOnboarding";
+import FigmaOnboarding from "@/components/portal/onboarding/figmaOnboarding";
+import CalendarOnboarding from "@/components/portal/onboarding/calendarOnboarding";
+import OnboardingGreeter from "@/components/portal/onboarding/onboardingGreeter";
+import OnboardingEnd from "@/components/portal/onboarding/onboardingEnd";
+
+const firstStep = 0;
+const lastStep = 6;
+
+export default function OnboardingPage() {
+ const [step, setStep] = useState(2);
+
+ const handleNextStepClick = () =>
+ setStep((prev) => (prev === 6 ? prev : prev + 1));
+
+ const handlePrevStepClick = () =>
+ setStep((prev) => (prev === 0 ? prev : prev - 1));
+
+ const stepContent = () => {
+ switch (step) {
+ case 0:
+ return ;
+ case 1:
+ return ;
+ case 2:
+ return ;
+ case 3:
+ return ;
+ case 4:
+ return ;
+ case 5:
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+
+ {stepContent()}
+
+
handlePrevStepClick()}
+ className="p-4 font-semibold text-lg rounded-xl bg-background-500"
+ >
+
+
+
handleNextStepClick()}
+ disabled={lastStep === step}
+ className={`p-4 font-semibold text-lg rounded-xl bg-background-500`}
+ >
+
+
+
+
+
+ );
+}
+
+function BackgroundWrapper({ children }: { children: React.ReactNode }) {
+ return (
+
+ );
+}
diff --git a/src/components/portal/onboarding/calendarOnboarding.tsx b/src/components/portal/onboarding/calendarOnboarding.tsx
new file mode 100644
index 00000000..174badfe
--- /dev/null
+++ b/src/components/portal/onboarding/calendarOnboarding.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import React, { useState } from "react";
+import { Button } from "@/components/primitives/button";
+import { toast } from "sonner";
+import Link from "next/link";
+
+const TEXT = {
+ title: "Google Calendar",
+ description:
+ "Add the Google Calendar to your account to have all the events and updates.",
+ calInvite:
+ "https://calendar.google.com/calendar/u/0?cid=Y19rMHJqc3JlZzJjbXQzZWtiYWNuNjBvMGprNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t",
+ loadingButton: "Loading...",
+ successButton: "You're all set!",
+ checkJoin: "Have you added the Google Calendar?",
+ yesJoin: "Yes, I've added!",
+ noJoin: "No, I haven't added yet.",
+ button: "Add Google Calendar",
+ joined: "You're all set!",
+ inital: "Click on the join button to add the Google Calendar.",
+};
+
+export default function CalendarOnboarding() {
+ const [state, setState] = useState<"initial" | "progress" | "success">(
+ "initial",
+ );
+
+ function handleUpdateNotion(toState: "yes" | "no") {
+ if (toState === "no") {
+ setState("initial");
+ toast.info(TEXT.inital);
+ } else {
+ setState("success");
+ toast.success(TEXT.joined);
+ }
+ }
+
+ return (
+
+
+
+ {TEXT.title}
+
+ {TEXT.description}
+
+
+ {state === "initial" && (
+
+ setState("progress")}
+ >
+
+ {TEXT.button}
+
+
+
+ )}
+
+ {state === "progress" && (
+
+ {TEXT.checkJoin}
+
+ handleUpdateNotion("no")}
+ className="p-6 gap-4 w-fit md:min-w-[350px] f text-lg rounded-full"
+ >
+ {TEXT.noJoin}
+
+ handleUpdateNotion("yes")}
+ className="p-6 gap-4 w-fit md:min-w-[350px] f text-lg rounded-full"
+ >
+ {TEXT.yesJoin}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/portal/onboarding/discordOnboarding.tsx b/src/components/portal/onboarding/discordOnboarding.tsx
new file mode 100644
index 00000000..910f4e28
--- /dev/null
+++ b/src/components/portal/onboarding/discordOnboarding.tsx
@@ -0,0 +1,196 @@
+"use client";
+
+import React, { useContext, useState } from "react";
+import { Button } from "@/components/primitives/button";
+import { z } from "zod";
+import { userContext } from "@/lib/context/usercontext";
+import { Input } from "@/components/primitives/input";
+import { toast } from "sonner";
+import { updateDiscordUsername, updateGithubUsername } from "./onboarding";
+import Link from "next/link";
+
+const TEXT = {
+ title: "Discord",
+ description:
+ "Join the Discord server to access the community and how teams communicate.",
+ discordInvite: "https://discord.gg/AgQbWykt",
+ joinDiscord: `First, please join our Discord server. You can join by clicking the link below:`,
+ inputPlaceholder: "Enter Discord Username",
+ button: "Join Discord Server",
+ roles: "The following roles will be assigned to you:",
+ loadingButton: "Loading...",
+ successButton: "You're all set!",
+ successUpdate: "Your Discord username has been updated.",
+ errorMessage: "Discord validation failed. Please try again.",
+ discordusername:
+ "Now, let's confirm your Discord username. Only one Discord account can be linked to you. ",
+ actionBtn: "Add Discord Roles",
+};
+
+const DiscordIntegrationSchema = z.object({
+ discordUsername: z.string(),
+ actions: z.object({
+ roles: z.array(z.string()),
+ }),
+});
+
+export default function DiscordOnboarding() {
+ const { user, userMetadata } = useContext(userContext);
+ const [state, setState] = useState<
+ "initial" | "progress" | "loading" | "success" | "error"
+ >("initial");
+ const [verifiedDiscordUsername, setVerifiedDiscordUsername] = useState(false);
+ const [discordUsername, setDiscordUsername] = useState(
+ userMetadata.member?.discord_id || "",
+ );
+
+ function handleUpdateDiscordUsername() {
+ if (userMetadata.member?.discord_id === discordUsername) {
+ setVerifiedDiscordUsername(true);
+ } else {
+ updateDiscordUsername(discordUsername, user.id)
+ .then(() => {
+ setVerifiedDiscordUsername(true);
+ toast.success(TEXT.successUpdate);
+ })
+ .catch(() => {
+ toast.error(TEXT.errorMessage);
+ });
+ }
+ }
+ const handleGithubSubmit = async () => {
+ try {
+ const roles: string[] = [];
+ if (!userMetadata.member?.team_members) {
+ return;
+ }
+ for (const member of userMetadata.member?.team_members) {
+ roles.push(...member.teams.meta.discord.roles);
+ }
+ const parsed = DiscordIntegrationSchema.parse({
+ discordUsername: discordUsername,
+ actions: {
+ roles: roles,
+ },
+ });
+
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_COLONY_URL}/colony/integrations/discord`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(parsed),
+ },
+ );
+
+ const responseBody = await response.text();
+ if (!response.ok) {
+ toast.error(responseBody);
+ setState("error");
+ } else {
+ toast.success(responseBody);
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error(TEXT.errorMessage);
+ }
+ };
+
+ return (
+
+
+
+ {TEXT.title}
+
+ {TEXT.description}
+
+
+ {state === "initial" && (
+
+ {TEXT.joinDiscord}
+
+ setState("progress")}
+ >
+
+ {TEXT.button}
+
+
+
+
+ )}
+
+ {state === "progress" && (
+
+ )}
+
+ {verifiedDiscordUsername && (
+
+ {TEXT.roles}
+
+
+ {userMetadata.member?.team_members &&
+ userMetadata.member.team_members.map((member) => {
+ const roles: string[] = [];
+ if (member.teams.meta.discord.roles) {
+ roles.push(...member.teams.meta.discord.roles);
+ }
+
+ return roles.map((role) => (
+
+ {role}
+
+ ));
+ })}
+
+
+
+ {state === "loading"
+ ? TEXT.loadingButton
+ : state === "success"
+ ? TEXT.successButton
+ : TEXT.actionBtn}
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/portal/onboarding/figmaOnboarding.tsx b/src/components/portal/onboarding/figmaOnboarding.tsx
new file mode 100644
index 00000000..b8e2b46d
--- /dev/null
+++ b/src/components/portal/onboarding/figmaOnboarding.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import React, { useState } from "react";
+import { Button } from "@/components/primitives/button";
+import { toast } from "sonner";
+import Link from "next/link";
+
+const TEXT = {
+ title: "Figma",
+ description:
+ "Join the Figma workspace to access the design files and resources.",
+ figmaInvite:
+ "https://www.figma.com/team_invite/redeem/LEa6Ds0anjxIFqZx37WF1X",
+ loadingButton: "Loading...",
+ successButton: "You're all set!",
+ checkJoin: "Have you joined the Notion workspace?",
+ yesJoin: "Yes, I've joined!",
+ noJoin: "No, I haven't joined yet.",
+ button: "Join Figma Workspace",
+ joined: "You're all set!",
+ inital: "Click on the join button to join the Figma workspace.",
+};
+
+export default function FigmaOnboarding() {
+ const [state, setState] = useState<"initial" | "progress" | "success">(
+ "initial",
+ );
+
+ function handleUpdateNotion(toState: "yes" | "no") {
+ if (toState === "no") {
+ setState("initial");
+ toast.info(TEXT.inital);
+ } else {
+ setState("success");
+ toast.success(TEXT.joined);
+ }
+ }
+
+ return (
+
+
+
+ {TEXT.title}
+
+ {TEXT.description}
+
+
+ {state === "initial" && (
+
+ setState("progress")}
+ >
+
+ {TEXT.button}
+
+
+
+ )}
+
+ {state === "progress" && (
+
+ {TEXT.checkJoin}
+
+ handleUpdateNotion("no")}
+ className="p-6 gap-4 w-fit md:min-w-[350px] f text-lg rounded-full"
+ >
+ {TEXT.noJoin}
+
+ handleUpdateNotion("yes")}
+ className="p-6 gap-4 w-fit md:min-w-[350px] f text-lg rounded-full"
+ >
+ {TEXT.yesJoin}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/portal/onboarding/githubOnboarding.tsx b/src/components/portal/onboarding/githubOnboarding.tsx
new file mode 100644
index 00000000..30d50725
--- /dev/null
+++ b/src/components/portal/onboarding/githubOnboarding.tsx
@@ -0,0 +1,173 @@
+"use client";
+
+import React, { useContext, useState } from "react";
+import { Button } from "@/components/primitives/button";
+import { z } from "zod";
+import { userContext } from "@/lib/context/usercontext";
+import { Input } from "@/components/primitives/input";
+import { toast } from "sonner";
+import { updateGithubUsername } from "./onboarding";
+
+const TEXT = {
+ title: "Github",
+ description: "Join the GitHub organization to access the codebase.",
+ githubUsername:
+ "First, let's confirm your GitHub username. Only one GitHub account can be linked to you.",
+ inputPlaceholder: "Enter GitHub Username",
+ button: "Join GitHub Organization",
+ errorMessage: "GitHub validation failed. Please try again.",
+ teams: "You are a member of the following teams:",
+ loadingButton: "Loading...",
+ successButton: "You're all set!",
+ successUpdate: "Your GitHub username has been updated.",
+ firstTime:
+ "If you're joining the organization for the first time, Github will invite you to join the organization.",
+};
+
+const GitHubIntegrationSchema = z.object({
+ githubUsername: z.string(),
+ actions: z.object({
+ teams: z.array(
+ z.object({
+ name: z.string(),
+ role: z.enum(["maintainer", "member"]),
+ }),
+ ),
+ }),
+});
+
+export default function GithubOnboarding() {
+ const { user, userMetadata } = useContext(userContext);
+ const [githubSetupState, setGithubSetupState] = useState<
+ "initial" | "loading" | "success" | "error"
+ >("initial");
+ const [verifiedGithubUsername, setVerifiedGithubUsername] = useState(false);
+ const [githubUsername, setGithubUsername] = useState(
+ userMetadata.member?.github_username || "",
+ );
+
+ function handleUpdateGithubUsername() {
+ if (userMetadata.member?.github_username === githubUsername) {
+ setVerifiedGithubUsername(true);
+ } else {
+ updateGithubUsername(githubUsername, user.id)
+ .then(() => {
+ setVerifiedGithubUsername(true);
+ // update github username
+ toast.success(TEXT.successUpdate);
+ })
+ .catch(() => {
+ toast.error(TEXT.errorMessage);
+ });
+ }
+ }
+ const handleGithubSubmit = async () => {
+ try {
+ const teams = userMetadata.member?.team_members.map((member) => ({
+ name: member.teams.meta.github.team.name,
+ role: "maintainer",
+ }));
+ const parsedGithubData = GitHubIntegrationSchema.parse({
+ githubUsername: githubUsername,
+ actions: {
+ teams: teams,
+ },
+ });
+
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_COLONY_URL}/colony/integrations/github`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(parsedGithubData),
+ },
+ );
+
+ const responseBody = await response.text();
+ if (!response.ok) {
+ toast.error(responseBody);
+ setGithubSetupState("error");
+ } else {
+ toast.success(responseBody);
+ toast.success(TEXT.firstTime);
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error(TEXT.errorMessage);
+ }
+ };
+
+ return (
+
+
+
+ {TEXT.title}
+
+ {TEXT.description}
+
+
+
+
+ {verifiedGithubUsername && (
+
+ {TEXT.teams}
+
+
+ {userMetadata.member?.team_members &&
+ userMetadata.member.team_members.map((member) => (
+
+ {member.teams.name}
+
+ ))}
+
+
+
+ {githubSetupState === "loading"
+ ? TEXT.loadingButton
+ : githubSetupState === "success"
+ ? TEXT.successButton
+ : TEXT.button}
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/portal/onboarding/notionOnboarding.tsx b/src/components/portal/onboarding/notionOnboarding.tsx
new file mode 100644
index 00000000..a1dcff52
--- /dev/null
+++ b/src/components/portal/onboarding/notionOnboarding.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import React, { useState } from "react";
+import { Button } from "@/components/primitives/button";
+import { toast } from "sonner";
+import Link from "next/link";
+
+const TEXT = {
+ title: "Notion",
+ description:
+ "Join the Notion workspace to access the documentation and resources.",
+ notionInvite:
+ "https://www.notion.so/team/ad259dbf-777e-4e03-b71e-cff48817296f/join",
+ loadingButton: "Loading...",
+ successButton: "You're all set!",
+ checkJoin: "Have you joined the Notion workspace?",
+ yesJoin: "Yes, I've joined!",
+ noJoin: "No, I haven't joined yet.",
+ button: "Join Notion Workspace",
+ joined: "You're all set!",
+ inital: "Click on the join button to join the Notion workspace.",
+};
+
+export default function NotionOnboarding() {
+ const [state, setState] = useState<"initial" | "progress" | "success">(
+ "initial",
+ );
+
+ function handleUpdateNotion(toState: "yes" | "no") {
+ if (toState === "no") {
+ setState("initial");
+ toast.info(TEXT.inital);
+ } else {
+ setState("success");
+ toast.success(TEXT.joined);
+ }
+ }
+
+ return (
+
+
+
+ {TEXT.title}
+
+ {TEXT.description}
+
+
+ {state === "initial" && (
+
+ setState("progress")}
+ >
+
+ {TEXT.button}
+
+
+
+ )}
+
+ {state === "progress" && (
+
+ {TEXT.checkJoin}
+
+ handleUpdateNotion("no")}
+ className="p-6 gap-4 w-fit md:min-w-[350px] f text-lg rounded-full"
+ >
+ {TEXT.noJoin}
+
+ handleUpdateNotion("yes")}
+ className="p-6 gap-4 w-fit md:min-w-[350px] f text-lg rounded-full"
+ >
+ {TEXT.yesJoin}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/portal/onboarding/onboarding.ts b/src/components/portal/onboarding/onboarding.ts
new file mode 100644
index 00000000..9573109c
--- /dev/null
+++ b/src/components/portal/onboarding/onboarding.ts
@@ -0,0 +1,35 @@
+"use server";
+
+import { db } from "@/db";
+
+export async function updateGithubUsername(username: string, id: string) {
+ try {
+ await db.members.update({
+ where: {
+ id: id,
+ },
+ data: {
+ github_username: username,
+ },
+ });
+ } catch (error) {
+ console.error("Error updating github username", error);
+ throw new Error("Error updating github username");
+ }
+}
+
+export async function updateDiscordUsername(username: string, id: string) {
+ try {
+ await db.members.update({
+ where: {
+ id: id,
+ },
+ data: {
+ discord_id: username,
+ },
+ });
+ } catch (error) {
+ console.error("Error updating discord username", error);
+ throw new Error("Error updating discord username");
+ }
+}
diff --git a/src/components/portal/onboarding/onboardingEnd.tsx b/src/components/portal/onboarding/onboardingEnd.tsx
new file mode 100644
index 00000000..1da2e958
--- /dev/null
+++ b/src/components/portal/onboarding/onboardingEnd.tsx
@@ -0,0 +1,23 @@
+const TEXT = {
+ title: "Member Onboarding",
+ description: "All done! You can now access all the resources and tools. ",
+ description2: "You can close this tab now",
+};
+
+export default function OnboardingEnd() {
+ return (
+
+
+
+
+ {TEXT.description}
+ {TEXT.description2}
+
+
+
+ );
+}
diff --git a/src/components/portal/onboarding/onboardingGreeter.tsx b/src/components/portal/onboarding/onboardingGreeter.tsx
new file mode 100644
index 00000000..02398add
--- /dev/null
+++ b/src/components/portal/onboarding/onboardingGreeter.tsx
@@ -0,0 +1,19 @@
+const TEXT = {
+ title: "Member Onboarding",
+ description:
+ "Here are the steps to get access to all the resources and tools. You can come back to this page to redo the onboarding.",
+ description2: "You can come back to this page to redo the onboarding.",
+};
+
+export default function OnboardingGreeter() {
+ return (
+
+
+
+ {TEXT.title}
+
+ {TEXT.description}
+
+
+ );
+}
diff --git a/src/lib/context/usercontext.tsx b/src/lib/context/usercontext.tsx
index 5c1339e2..6478e28d 100644
--- a/src/lib/context/usercontext.tsx
+++ b/src/lib/context/usercontext.tsx
@@ -6,11 +6,23 @@ import { User } from "@supabase/auth-js";
type UserContext = {
user: User;
- userMetadata: { [key: string]: any } | null;
+ userMetadata: UserMetadata;
};
export const userContext = React.createContext({} as UserContext);
+type UserMetadata = {
+ [key: string]: any;
+ member?: {
+ team_members: {
+ member_id: string;
+ team_id: bigint;
+ joined_on: Date | null;
+ role: string | null;
+ }[];
+ };
+};
+
export function UserContextProvider({
children,
user,
@@ -18,13 +30,13 @@ export function UserContextProvider({
}: {
children: React.ReactNode;
user: User;
- userMetadata: { [key: string]: any } | null;
+ userMetadata: UserMetadata;
}) {
return (
{children}
diff --git a/src/pages/portal/onboarding.tsx b/src/pages/portal/onboarding.tsx
deleted file mode 100644
index 6435d313..00000000
--- a/src/pages/portal/onboarding.tsx
+++ /dev/null
@@ -1,423 +0,0 @@
-import React, { useState } from "react";
-import Image from "next/image";
-import Link from "next/link";
-import "../../app/globals.css";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faGithub } from "@fortawesome/free-brands-svg-icons";
-import { faDiscord } from "@fortawesome/free-brands-svg-icons";
-import { faFigma } from "@fortawesome/free-brands-svg-icons";
-import { faCalendar } from "@fortawesome/free-solid-svg-icons";
-import { SiNotion } from "react-icons/si";
-import { Button } from "@/components/primitives/button";
-import { useRouter } from "next/router";
-import { z } from "zod";
-// import { db } from "@/db";
-
-const URLs = {
- Discord: "https://discord.gg/AgQbWykt",
- Notion:
- "https://www.notion.so/team/ad259dbf-777e-4e03-b71e-cff48817296f/join",
- Figma: "https://www.figma.com/team_invite/redeem/LEa6Ds0anjxIFqZx37WF1X",
- Calendar:
- "https://calendar.google.com/calendar/u/0?cid=Y19rMHJqc3JlZzJjbXQzZWtiYWNuNjBvMGprNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t",
-};
-
-const DiscordIntegrationSchema = z.object({
- discordUsername: z.string(),
- actions: z.object({
- roles: z.array(z.string()),
- }),
-});
-
-const GitHubIntegrationSchema = z.object({
- githubUsername: z.string(),
- actions: z.object({
- teams: z.array(
- z.object({
- name: z.string(),
- role: z.enum(["maintainer", "member"]),
- }),
- ),
- }),
-});
-
-const Onboarding = () => {
- const router = useRouter();
- const [step, setStep] = useState(0);
- const [isNextClickable, setIsNextClickable] = useState(false);
- const [errorMessage, setErrorMessage] = useState("");
- const [githubUsername, setGithubUsername] = useState("");
- const [discordUsername, setDiscordUsername] = useState("");
-
- const handleGithubSubmit = async () => {
- try {
- // Validate GitHub input with schema
- const parsedGithubData = GitHubIntegrationSchema.parse({
- githubUsername: githubUsername,
- actions: {
- teams: [
- {
- name: "execs",
- role: "maintainer",
- },
- ],
- },
- });
-
- const response = await fetch(
- "https://colony-production.up.railway.app/colony/integrations/github",
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(parsedGithubData),
- },
- );
-
- const responseBody = await response.text();
- if (!response.ok) {
- console.error(`Error: ${response.status} - ${responseBody}`);
- throw new Error("Failed to add GitHub user");
- }
-
- setIsNextClickable(true);
- } catch (error) {
- setErrorMessage("GitHub validation failed. Please try again.");
- console.error("GitHub Submission Error:", error);
- }
- };
-
- const handleDiscordSubmit = async () => {
- try {
- const response = await fetch(
- "https://colony-production.up.railway.app/colony/integrations/discord",
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${process.env.NEXT_PUBLIC_DISCORD_API_KEY}`,
- },
- body: JSON.stringify({
- discordUsername,
- actions: {
- roles: ["member", "2024-member", "developer", "designer"],
- },
- }),
- },
- );
-
- if (!response.ok) throw new Error("Failed to add Discord user");
- } catch (error) {
- console.error(error);
- setErrorMessage("Failed to add Discord user. Please try again.");
- }
- };
-
- const handleStartClick = () => {
- setStep(1);
- setIsNextClickable(false);
- setTimeout(() => {
- router.push({
- pathname: "/portal/onboarding",
- query: { step: "1", state: "not_started" },
- });
- }, 1000);
- };
-
- const handleNextStepClick = (nextStep: number, state: string) => {
- setStep(nextStep);
- setIsNextClickable(false);
- setTimeout(() => {
- router.push({
- pathname: "/portal/onboarding",
- query: { step: nextStep.toString(), state },
- });
- }, 1000);
- };
-
- const handlePrevStepClick = (prevStep: number, state: string) => {
- setStep(prevStep);
- setIsNextClickable(false);
- setTimeout(() => {
- router.push({
- pathname: "/portal/onboarding",
- query: { step: prevStep.toString(), state },
- });
- }, 1000);
- };
-
- const handleActionClick = () => {
- setIsNextClickable(true);
- };
-
- const handleEndClick = () => {
- setStep(7);
- router.push("/");
- };
-
- return (
-
-
-
-
-
-
-
-
-
- {step === 0 ? (
- <>
-
- Welcome to Member{" "}
-
Onboarding
-
-
- {"Start"}
-
- >
- ) : step === 1 ? (
- // Step 1: GitHub
- <>
-
- Step 1: Connect to GitHub
-
-
setGithubUsername(e.target.value)}
- />
-
-
- {"Join GitHub Organization"}
-
- {errorMessage && (
-
- {errorMessage}
-
- )}
-
handleNextStepClick(2, "connect_github")}
- disabled={!isNextClickable}
- className={`p-4 mt-12 w-full md:w-[129px] font-semibold text-lg rounded-xl ${
- isNextClickable
- ? "bg-primary"
- : "bg-gray-500 cursor-not-allowed"
- }`}
- >
- {"Next"}
-
-
handlePrevStepClick(0, "not_started")}
- className="p-4 mt-4 w-full md:w-[129px] font-semibold text-lg rounded-xl bg-lightPurple4"
- >
- {"Prev"}
-
- >
- ) : step === 2 ? (
- // Step 2: Discord
- <>
-
- Step 2: Connect to Discord
-
-
setDiscordUsername(e.target.value)}
- className="p-2 border border-gray-400 rounded mb-4"
- />
-
-
- {"Join discord server"}
-
- {errorMessage && (
-
- {errorMessage}
-
- )}
-
handleNextStepClick(3, "connect_discord")}
- disabled={!isNextClickable}
- className={`p-4 mt-12 w-full md:w-[129px] font-semibold text-lg rounded-xl ${
- isNextClickable
- ? "bg-primary"
- : "bg-gray-500 cursor-not-allowed"
- }`}
- >
- {"Next"}
-
-
handlePrevStepClick(1, "connect_github")}
- className="p-4 mt-4 w-full md:w-[129px] font-semibold text-lg rounded-xl bg-lightPurple4"
- >
- {"Prev"}
-
- >
- ) : step === 3 ? (
- // Step 3: Notion
- <>
-
- Step 3: Connect to Notion
-
-
{
- window.open(URLs.Notion, "_blank");
- handleActionClick();
- }}
- className="p-6 mt-4 bg-[#F7F7F7] w-full md:w-[350px] font-semibold text-lg text-black rounded-xl"
- >
-
- {"Join Notion workspace"}
-
-
handleNextStepClick(4, "connect_notion")}
- disabled={!isNextClickable}
- className={`p-4 mt-12 w-full md:w-[129px] font-semibold text-lg rounded-xl ${
- isNextClickable
- ? "bg-primary"
- : "bg-gray-500 cursor-not-allowed"
- }`}
- >
- {"Next"}
-
-
handlePrevStepClick(2, "connect_discord")}
- className="p-4 mt-4 w-full md:w-[129px] font-semibold text-lg rounded-xl bg-lightPurple4"
- >
- {"Prev"}
-
- >
- ) : step === 4 ? (
- // Step 4: Figma
- <>
-
- Step 4: Connect to Figma
-
-
{
- window.open(URLs.Figma, "_blank");
- handleActionClick();
- }}
- className="p-6 mt-4 bg-[#03011B] w-full md:w-[350px] font-semibold text-lg rounded-xl"
- >
-
- {"Join Figma workspace"}
-
-
handleNextStepClick(5, "connect_figma")}
- disabled={!isNextClickable}
- className={`p-4 mt-12 w-full md:w-[129px] font-semibold text-lg rounded-xl ${
- isNextClickable
- ? "bg-primary"
- : "bg-gray-500 cursor-not-allowed"
- }`}
- >
- {"Next"}
-
-
handlePrevStepClick(3, "connect_notion")}
- className="p-4 mt-4 w-full md:w-[129px] font-semibold text-lg rounded-xl bg-lightPurple4"
- >
- {"Prev"}
-
- >
- ) : step === 5 ? (
- // Step 5: Calendar
- <>
-
- Step 5: Connect to our Calendar
-
-
{
- window.open(URLs.Calendar, "_blank");
- handleActionClick();
- }}
- className="p-6 mt-4 bg-blue-500 w-full md:w-[350px] font-semibold text-lg rounded-xl"
- >
-
- {"Add Google Calendar"}
-
-
handleNextStepClick(6, "connect_calendar")}
- disabled={!isNextClickable}
- className="p-4 mt-12 w-full md:w-[129px] font-semibold text-lg rounded-xl bg-primary"
- >
- {"Next"}
-
-
handlePrevStepClick(4, "connect_figma")}
- className="p-4 mt-4 w-full md:w-[129px] font-semibold text-lg rounded-xl bg-lightPurple4"
- >
- {"Prev"}
-
- >
- ) : (
- <>
-
- Onboarding Complete!
-
- You can repeat these steps in future terms when needed
-
-
-
- {"Go to Home"}
-
- >
- )}
-
-
-
-
-
- );
-};
-
-export default Onboarding;