Skip to content
Open
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
21 changes: 3 additions & 18 deletions app/(main)/configurations/prompt-editor/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,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 All @@ -396,19 +393,8 @@ function PromptEditorContent() {
style={{ backgroundColor: colors.bg.secondary }}
>
<div className="flex flex-col items-center gap-3">
<div
className="animate-spin rounded-full border-4 border-solid"
style={{
width: "36px",
height: "36px",
borderColor: colors.bg.primary,
borderTopColor: colors.accent.primary,
}}
/>
<p
className="text-sm"
style={{ color: colors.text.secondary }}
>
<div className="animate-spin rounded-full border-4 border-solid w-9 h-9 border-bg-primary border-t-accent-primary" />
<p className="text-sm text-text-secondary">
Loading configuration...
</p>
</div>
Expand Down Expand Up @@ -469,7 +455,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
70 changes: 50 additions & 20 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 @@ -24,6 +24,12 @@ 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 +65,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 +74,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 +103,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 @@ -211,11 +205,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