Skip to content
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
277 changes: 189 additions & 88 deletions app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"use client";

import { useState } from "react";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/context/AuthContext";
import OnboardingQuestionnaire from "@/components/onboarding/OnboardingQuestionnaire";

type OnboardingStep = "organization" | "questionnaire" | "complete";

export default function OnboardingPage() {
const router = useRouter();
Expand All @@ -12,20 +15,45 @@ export default function OnboardingPage() {
return null;
}

const [step, setStep] = useState<OnboardingStep>("organization");
const [role, setRole] = useState<"admin" | "employee">("employee");
const [orgName, setOrgName] = useState("");
const [orgInvite, setOrgInvite] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [initializing, setInitializing] = useState(true);

useEffect(() => {
const checkUserOrganization = async () => {
try {
const response = await fetch("/api/me", { credentials: "include" });
if (response.ok) {
const userData = await response.json();
if (userData.organisation) {
if (userData.organisation.role === "admin") {
completeOnboarding();
} else {
setStep("questionnaire");
}
}
}
} catch (err) {
console.error("Error checking user organization:", err);
} finally {
setInitializing(false);
}
};

const handleSubmit = async (e: React.FormEvent) => {
checkUserOrganization();
}, []);

const handleOrganizationSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);

try {
if (role === "admin") {
// 1) Create the org + link you as admin
const create = await fetch("/api/orgs", {
method: "POST",
credentials: "include",
Expand All @@ -36,6 +64,7 @@ export default function OnboardingPage() {
const body = await create.json().catch(() => ({}));
throw new Error(body.message || "Failed to create organization");
}
completeOnboarding();
} else {
const addemp = await fetch("/api/orgs/addemployee", {
method: "POST",
Expand All @@ -49,13 +78,47 @@ export default function OnboardingPage() {
const body = await addemp.json().catch(() => ({}));
throw new Error(body.message || "Failed to add employee");
}
setStep("questionnaire");
setLoading(false);
}
} catch (err: any) {
setError(err.message);
setLoading(false);
}
};

const handleQuestionnaireComplete = async (responses: number[]) => {
setError(null);
setLoading(true);

try {
if (responses.length > 0) {
const responseSubmit = await fetch("/api/onboarding/responses", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ option_ids: responses }),
});
if (!responseSubmit.ok) {
const body = await responseSubmit.json().catch(() => ({}));
throw new Error(body.message || "Failed to submit responses");
}
}
completeOnboarding();
} catch (err: any) {
setError(err.message);
setLoading(false);
}
};

const completeOnboarding = async () => {
try {
const done = await fetch("/api/complete-onboarding", {
method: "POST",
credentials: "include",
});
if (!done.ok) throw new Error("Could not complete onboarding");

const updatedUser = await fetch("/api/me", {
credentials: "include",
}).then((r) => r.json());
Expand All @@ -66,100 +129,138 @@ export default function OnboardingPage() {
setLoading(false);
}
};

return (
<div className="max-w-md mx-auto p-6">
<h1 className="text-2xl font-semibold mb-4">Onboarding</h1>
<p className="mb-6 text-gray-600">
Welcome, {user.firstname || user.email}! Let’s get you set up.
</p>

{/* Step 1: Choose role */}
<div className="flex space-x-4 mb-6">
<button
type="button"
className={`px-4 py-2 rounded ${
role === "employee"
? "bg-purple-600 text-white"
: "bg-gray-200 text-gray-700"
}`}
onClick={() => setRole("employee")}
>
Join Organization
</button>
<button
type="button"
className={`px-4 py-2 rounded ${
role === "admin"
? "bg-purple-600 text-white"
: "bg-gray-200 text-gray-700"
}`}
onClick={() => setRole("admin")}
>
Create Organization
</button>
if (initializing) {
return (
<div className="max-w-md mx-auto p-6">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-600 mx-auto mb-4"></div>
<p>Loading...</p>
</div>
</div>
);
}

{role === "admin" && (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="orgName"
className="block text-sm font-medium text-gray-700 mb-1"
>
Organization Name
</label>
<input
id="orgName"
type="text"
value={orgName}
onChange={(e) => setOrgName(e.target.value)}
required
className="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
</div>

{error && <p className="text-red-600 text-sm">{error}</p>}
if (step === "organization") {
return (
<div className="max-w-md mx-auto p-6">
<h1 className="text-2xl font-semibold mb-4">Onboarding</h1>
<p className="mb-6 text-gray-600">
Welcome, {user.firstname || user.email}! Let's get you set up.
</p>

<div className="flex space-x-4 mb-6">
<button
type="submit"
disabled={loading}
className="w-full py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 disabled:opacity-50 transition-colors"
type="button"
className={`px-4 py-2 rounded ${
role === "employee"
? "bg-purple-600 text-white"
: "bg-gray-200 text-gray-700"
}`}
onClick={() => setRole("employee")}
>
{loading ? "Creating…" : "Create & Continue"}
Join Organization
</button>
</form>
)}
{role === "employee" && (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="orgName"
className="block text-sm font-medium text-gray-700 mb-1"
>
Organization Invite Code
</label>
<input
id="orgName"
type="text"
value={orgInvite}
onChange={(e) => setOrgInvite(e.target.value)}
required
className="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
</div>

{error && <p className="text-red-600 text-sm">{error}</p>}

<button
type="submit"
disabled={loading}
className="w-full py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 disabled:opacity-50 transition-colors"
type="button"
className={`px-4 py-2 rounded ${
role === "admin"
? "bg-purple-600 text-white"
: "bg-gray-200 text-gray-700"
}`}
onClick={() => setRole("admin")}
>
{loading ? "Joining..." : "Join & Continue"}
Create Organization
</button>
</form>
)}
</div>

{role === "admin" && (
<form onSubmit={handleOrganizationSubmit} className="space-y-4">
<div>
<label
htmlFor="orgName"
className="block text-sm font-medium text-gray-700 mb-1"
>
Organization Name
</label>
<input
id="orgName"
type="text"
value={orgName}
onChange={(e) => setOrgName(e.target.value)}
required
className="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
</div>

{error && <p className="text-red-600 text-sm">{error}</p>}

<button
type="submit"
disabled={loading}
className="w-full py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 disabled:opacity-50 transition-colors"
>
{loading ? "Creating…" : "Create & Continue"}
</button>
</form>
)}
{role === "employee" && (
<form onSubmit={handleOrganizationSubmit} className="space-y-4">
<div>
<label
htmlFor="orgInvite"
className="block text-sm font-medium text-gray-700 mb-1"
>
Organization Invite Code
</label>
<input
id="orgInvite"
type="text"
value={orgInvite}
onChange={(e) => setOrgInvite(e.target.value)}
required
className="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
</div>

{error && <p className="text-red-600 text-sm">{error}</p>}

<button
type="submit"
disabled={loading}
className="w-full py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 disabled:opacity-50 transition-colors"
>
{loading ? "Joining..." : "Join & Continue"}
</button>
</form>
)}
</div>
);
}

if (step === "questionnaire") {
return (
<div>
{error && (
<div className="max-w-2xl mx-auto p-6">
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
</div>
)}
<OnboardingQuestionnaire
onComplete={handleQuestionnaireComplete}
loading={loading}
/>
</div>
);
}

return (
<div className="max-w-md mx-auto p-6">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-600 mx-auto mb-4"></div>
<p>Completing onboarding...</p>
</div>
</div>
);
}
39 changes: 37 additions & 2 deletions app/organisation/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"use client";

import { useAuth } from "@/context/AuthContext";
import { useState } from "react";
import ManageTags from "@/components/organisation/settings/ManageTags";
import OnboardingConfig from "@/components/organisation/settings/OnboardingConfig";

export default function OrganisationsPage() {
const { user } = useAuth();
const [activeTab, setActiveTab] = useState<"tags" | "onboarding">("tags");

if (!user || !user.hasCompletedOnboarding) {
return null;
}
Expand All @@ -22,10 +26,41 @@ export default function OrganisationsPage() {

return (
<div>
<h1 className="text-3xl font-bold mb-4 text-purple-600">
<h1 className="text-3xl font-bold mb-6 text-purple-600">
My Organisation
</h1>
<ManageTags />

{/* Tab Navigation */}
<div className="mb-6">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8">
<button
onClick={() => setActiveTab("tags")}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === "tags"
? "border-purple-500 text-purple-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
}`}
>
Manage Tags
</button>
<button
onClick={() => setActiveTab("onboarding")}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === "onboarding"
? "border-purple-500 text-purple-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
}`}
>
Onboarding Form
</button>
</nav>
</div>
</div>

{/* Tab Content */}
{activeTab === "tags" && <ManageTags />}
{activeTab === "onboarding" && <OnboardingConfig />}
</div>
);
}
Loading