Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
6 changes: 1 addition & 5 deletions app/(main)/configurations/prompt-editor/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,7 @@ function PromptEditorContent() {
};

return (
<div
className="w-full h-screen flex flex-col"
style={{ backgroundColor: colors.bg.secondary }}
>
<div className="w-full h-screen flex flex-col bg-bg-secondary">
<div className="flex flex-1 overflow-hidden">
<Sidebar
collapsed={sidebarCollapsed}
Expand Down Expand Up @@ -470,7 +467,6 @@ function PromptEditorContent() {
/>
) : (
<div className="flex-1 flex flex-col overflow-hidden">
{/* Split View: Prompt (left) + Config (right) */}
<div className="flex flex-1 overflow-hidden">
<div
className="flex"
Expand Down
70 changes: 13 additions & 57 deletions app/(main)/settings/credentials/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/**
* Credentials Settings Page — orchestrator
* State management and API calls only. UI split into:
* ProviderList — left sidebar nav
* ProviderSidebar — left sidebar nav
* CredentialForm — right form with fields and actions
*/

"use client";

import { useState, useEffect } from "react";
import SettingsSidebar from "@/app/components/settings/SettingsSidebar";
import { colors } from "@/app/lib/colors";
import PageHeader from "@/app/components/PageHeader";
import { useToast } from "@/app/components/Toast";
import { useAuth } from "@/app/lib/context/AuthContext";
import {
Expand All @@ -18,7 +18,7 @@ import {
ProviderDef,
} from "@/app/lib/types/credentials";
import { getExistingForProvider } from "@/app/lib/utils";
import ProviderList from "@/app/components/settings/credentials/ProviderList";
import ProviderSidebar from "@/app/components/settings/ProviderSidebar";
import CredentialForm from "@/app/components/settings/credentials/CredentialForm";
import { apiFetch } from "@/app/lib/apiClient";

Expand All @@ -34,15 +34,14 @@ export default function CredentialsPage() {
const [isDeleting, setIsDeleting] = useState(false);
const [formValues, setFormValues] = useState<Record<string, string>>({});
const [isActive, setIsActive] = useState(true);
const [visibleFields, setVisibleFields] = useState<Set<string>>(new Set());
const [existingCredential, setExistingCredential] =
useState<Credential | null>(null);

// Load credentials once we have an API key
// Load credentials once authenticated
useEffect(() => {
if (!isAuthenticated) return;
loadCredentials();
}, [apiKeys]);
}, [isAuthenticated, apiKeys]);

// Re-populate form when provider or credentials change
useEffect(() => {
Expand All @@ -64,7 +63,6 @@ export default function CredentialsPage() {
});
setFormValues(blank);
}
setVisibleFields(new Set());
}, [selectedProvider, credentials]);

const loadCredentials = async () => {
Expand Down Expand Up @@ -151,7 +149,6 @@ export default function CredentialsPage() {
setFormValues(blank);
setIsActive(true);
}
setVisibleFields(new Set());
};

const handleDelete = async () => {
Expand All @@ -178,68 +175,29 @@ export default function CredentialsPage() {
setFormValues((prev) => ({ ...prev, [key]: value }));
};

const handleToggleVisibility = (key: string) => {
setVisibleFields((prev) => {
const next = new Set(prev);
if (next.has(key)) {
next.delete(key);
} else {
next.add(key);
}
return next;
});
};

return (
<div
className="w-full h-screen flex flex-col"
style={{ backgroundColor: colors.bg.secondary }}
>
<div className="w-full h-screen flex flex-col bg-bg-secondary">
<div className="flex flex-1 overflow-hidden">
<SettingsSidebar />

<div className="flex-1 flex flex-col overflow-hidden">
<div
className="border-b px-4 py-3 flex items-center justify-between shrink-0"
style={{
backgroundColor: colors.bg.primary,
borderColor: colors.border,
}}
>
<div>
<h1
className="text-base font-semibold"
style={{
color: colors.text.primary,
letterSpacing: "-0.01em",
}}
>
Credentials
</h1>
<p className="text-xs" style={{ color: colors.text.secondary }}>
Manage provider credentials
</p>
</div>
</div>
<PageHeader
title="Credentials"
subtitle="Manage provider credentials"
/>

<div className="flex flex-1 overflow-hidden">
<ProviderList
<ProviderSidebar
providers={PROVIDERS}
selectedProvider={selectedProvider}
credentials={credentials}
onSelect={setSelectedProvider}
className="w-56 border-r border-border overflow-y-auto bg-bg-secondary"
/>

<div className="flex-1 overflow-y-auto p-8">
{!isAuthenticated ? (
<div
className="max-w-lg rounded-lg border p-6 text-sm"
style={{
borderColor: colors.border,
backgroundColor: colors.bg.primary,
color: colors.text.secondary,
}}
>
<div className="max-w-lg rounded-lg border border-border p-6 text-sm bg-bg-primary text-text-secondary">
Please log in to manage credentials.
</div>
) : (
Expand All @@ -251,10 +209,8 @@ export default function CredentialsPage() {
isLoading={isLoading}
isSaving={isSaving}
isDeleting={isDeleting}
visibleFields={visibleFields}
onChange={handleFieldChange}
onActiveChange={setIsActive}
onToggleVisibility={handleToggleVisibility}
onSave={handleSave}
onCancel={handleCancel}
onDelete={handleDelete}
Expand Down
83 changes: 54 additions & 29 deletions app/(main)/settings/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { useState, useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
import SettingsSidebar from "@/app/components/settings/SettingsSidebar";
import PageHeader from "@/app/components/PageHeader";
import { useAuth } from "@/app/lib/context/AuthContext";
Expand All @@ -13,6 +12,7 @@ import {
ProjectList,
StepIndicator,
UserList,
OnboardingCredentials,
} from "@/app/components/settings/onboarding";
import {
Organization,
Expand All @@ -21,9 +21,14 @@ import {
OnboardResponseData,
} from "@/app/lib/types/onboarding";
import { apiFetch } from "@/app/lib/apiClient";
import { colors } from "@/app/lib/colors";
import { ArrowLeftIcon } from "@/app/components/icons";
import { DEFAULT_PAGE_LIMIT } from "@/app/lib/constants";
import TabNavigation from "@/app/components/TabNavigation";

const PROJECT_TABS = [
{ id: "users", label: "Users" },
{ id: "credentials", label: "Credentials" },
];

type View = "loading" | "list" | "projects" | "users" | "form" | "success";

Expand Down Expand Up @@ -59,8 +64,7 @@ function OrganizationListSkeleton() {
}

export default function OnboardingPage() {
const router = useRouter();
const { activeKey, currentUser, isHydrated, isAuthenticated } = useAuth();
const { activeKey } = useAuth();
const [view, setView] = useState<View>("loading");
const [selectedOrg, setSelectedOrg] = useState<Organization | null>(null);
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
Expand All @@ -69,6 +73,7 @@ export default function OnboardingPage() {
const [onboardData, setOnboardData] = useState<OnboardResponseData | null>(
null,
);
const [activeProjectTab, setActiveProjectTab] = useState("users");

const {
items: organizations,
Expand Down Expand Up @@ -97,18 +102,6 @@ export default function OnboardingPage() {
}
}, [isLoadingOrgs, organizations.length]);

// Redirect if no API key or not a superuser
useEffect(() => {
if (!isHydrated) return;
if (!isAuthenticated) {
router.replace("/");
return;
}
if (currentUser && !currentUser.is_superuser) {
router.replace("/settings/credentials");
}
}, [isHydrated, activeKey, currentUser, router]);

const fetchProjects = useCallback(
async (org: Organization) => {
setSelectedOrg(org);
Expand Down Expand Up @@ -172,20 +165,17 @@ export default function OnboardingPage() {
};

return (
<div
className="w-full h-screen flex flex-col"
style={{ backgroundColor: colors.bg.secondary }}
>
<div className="w-full h-screen flex flex-col bg-bg-secondary">
<div className="flex flex-1 overflow-hidden">
<SettingsSidebar />

<div className="flex-1 flex flex-col overflow-hidden">
<PageHeader
title="Onboarding"
subtitle="Manage organizations and set up new projects"
title="Organizations"
subtitle="Manage organizations, projects, users, and credentials"
/>

<div className="flex-1 overflow-y-auto">
<div ref={scrollRef} className="flex-1 overflow-y-auto">
<div className="max-w-2xl py-5 px-8">
{view === "loading" && <OrganizationListSkeleton />}

Expand All @@ -195,7 +185,6 @@ export default function OnboardingPage() {
isLoadingMore={isLoadingMore}
onNewOrg={() => setView("form")}
onSelectOrg={fetchProjects}
scrollRef={scrollRef}
/>
)}

Expand All @@ -211,11 +200,47 @@ export default function OnboardingPage() {
)}

{view === "users" && selectedOrg && selectedProject && (
<UserList
organization={selectedOrg}
project={selectedProject}
onBack={handleBackToProjects}
/>
<div>
<button
onClick={handleBackToProjects}
className="text-sm text-text-secondary hover:text-text-primary mb-4 flex items-center gap-1 transition-colors cursor-pointer"
>
<ArrowLeftIcon className="w-3.5 h-3.5" /> Back to projects
</button>

<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-lg font-semibold text-text-primary">
{selectedProject.name}
</h2>
<p className="text-xs text-text-secondary mt-0.5">
{selectedOrg.name}
</p>
</div>
</div>

<TabNavigation
tabs={PROJECT_TABS}
activeTab={activeProjectTab}
onTabChange={setActiveProjectTab}
/>

{activeProjectTab === "users" && (
<UserList
organization={selectedOrg}
project={selectedProject}
onBack={handleBackToProjects}
hideHeader
/>
)}

{activeProjectTab === "credentials" && (
<OnboardingCredentials
organizationId={selectedOrg.id}
projectId={selectedProject.id}
/>
)}
</div>
)}

{view === "form" && (
Expand Down
5 changes: 5 additions & 0 deletions app/api/auth/google/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { apiClient } from "@/app/lib/apiClient";
import { setRoleCookieFromBody } from "@/app/lib/authCookie";

/** Proxy Google login token to backend. Forwards Set-Cookie headers back to the browser. */
export async function POST(request: Request) {
Expand Down Expand Up @@ -31,6 +32,10 @@ export async function POST(request: Request) {
});
}

if (status >= 200 && status < 300) {
setRoleCookieFromBody(response, data);
}

return response;
} catch {
return NextResponse.json(
Expand Down
5 changes: 5 additions & 0 deletions app/api/auth/invite/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { apiClient } from "@/app/lib/apiClient";
import { setRoleCookieFromBody } from "@/app/lib/authCookie";

export async function GET(request: NextRequest) {
try {
Expand All @@ -25,6 +26,10 @@ export async function GET(request: NextRequest) {
res.headers.append("Set-Cookie", cookie);
}

if (status >= 200 && status < 300) {
setRoleCookieFromBody(res, data);
}

return res;
} catch {
return NextResponse.json(
Expand Down
3 changes: 3 additions & 0 deletions app/api/auth/logout/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { apiClient } from "@/app/lib/apiClient";
import { clearRoleCookie } from "@/app/lib/authCookie";

export async function POST(request: NextRequest) {
const { status, data, headers } = await apiClient(
Expand All @@ -15,5 +16,7 @@ export async function POST(request: NextRequest) {
res.headers.append("Set-Cookie", cookie);
}

clearRoleCookie(res);

return res;
}
5 changes: 5 additions & 0 deletions app/api/auth/magic-link/verify/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { apiClient } from "@/app/lib/apiClient";
import { setRoleCookieFromBody } from "@/app/lib/authCookie";

export async function GET(request: NextRequest) {
try {
Expand All @@ -25,6 +26,10 @@ export async function GET(request: NextRequest) {
res.headers.append("Set-Cookie", cookie);
}

if (status >= 200 && status < 300) {
setRoleCookieFromBody(res, data);
}

return res;
} catch {
return NextResponse.json(
Expand Down
Loading
Loading