@@ -211,11 +201,47 @@ export default function OnboardingPage() {
)}
{view === "users" && selectedOrg && selectedProject && (
-
+
+
+
+
+
+
+ {selectedProject.name}
+
+
+ {selectedOrg.name}
+
+
+
+
+
+
+ {activeProjectTab === "users" && (
+
+ )}
+
+ {activeProjectTab === "credentials" && (
+
+ )}
+
)}
{view === "form" && (
diff --git a/app/api/auth/google/route.ts b/app/api/auth/google/route.ts
index 7dc39ad..b9d1bd0 100644
--- a/app/api/auth/google/route.ts
+++ b/app/api/auth/google/route.ts
@@ -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) {
@@ -31,6 +32,10 @@ export async function POST(request: Request) {
});
}
+ if (status >= 200 && status < 300) {
+ setRoleCookieFromBody(response, data);
+ }
+
return response;
} catch {
return NextResponse.json(
diff --git a/app/api/auth/invite/route.ts b/app/api/auth/invite/route.ts
index 96984c1..e1fd42d 100644
--- a/app/api/auth/invite/route.ts
+++ b/app/api/auth/invite/route.ts
@@ -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 {
@@ -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(
diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts
index 3b5994f..5111c38 100644
--- a/app/api/auth/logout/route.ts
+++ b/app/api/auth/logout/route.ts
@@ -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(
@@ -15,5 +16,7 @@ export async function POST(request: NextRequest) {
res.headers.append("Set-Cookie", cookie);
}
+ clearRoleCookie(res);
+
return res;
}
diff --git a/app/api/auth/magic-link/verify/route.ts b/app/api/auth/magic-link/verify/route.ts
index fc436f5..70475a4 100644
--- a/app/api/auth/magic-link/verify/route.ts
+++ b/app/api/auth/magic-link/verify/route.ts
@@ -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 {
@@ -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(
diff --git a/app/api/credentials/org/[orgId]/[projectId]/provider/[provider]/route.ts b/app/api/credentials/org/[orgId]/[projectId]/provider/[provider]/route.ts
new file mode 100644
index 0000000..583ed96
--- /dev/null
+++ b/app/api/credentials/org/[orgId]/[projectId]/provider/[provider]/route.ts
@@ -0,0 +1,40 @@
+import { apiClient } from "@/app/lib/apiClient";
+import { NextResponse, NextRequest } from "next/server";
+
+type Params = {
+ params: Promise<{ orgId: string; projectId: string; provider: string }>;
+};
+
+export async function GET(request: NextRequest, { params }: Params) {
+ const { orgId, projectId, provider } = await params;
+ try {
+ const { status, data } = await apiClient(
+ request,
+ `/api/v1/credentials/${orgId}/${projectId}/provider/${provider}`,
+ );
+ return NextResponse.json(data, { status });
+ } catch (e: unknown) {
+ return NextResponse.json(
+ { error: e instanceof Error ? e.message : String(e) },
+ { status: 500 },
+ );
+ }
+}
+
+export async function DELETE(request: NextRequest, { params }: Params) {
+ const { orgId, projectId, provider } = await params;
+ try {
+ const { status, data } = await apiClient(
+ request,
+ `/api/v1/credentials/${orgId}/${projectId}/provider/${provider}`,
+ { method: "DELETE" },
+ );
+ if (status === 204) return new NextResponse(null, { status: 204 });
+ return NextResponse.json(data, { status });
+ } catch (e: unknown) {
+ return NextResponse.json(
+ { error: e instanceof Error ? e.message : String(e) },
+ { status: 500 },
+ );
+ }
+}
diff --git a/app/api/credentials/org/[orgId]/[projectId]/route.ts b/app/api/credentials/org/[orgId]/[projectId]/route.ts
new file mode 100644
index 0000000..d98638b
--- /dev/null
+++ b/app/api/credentials/org/[orgId]/[projectId]/route.ts
@@ -0,0 +1,56 @@
+import { apiClient } from "@/app/lib/apiClient";
+import { NextResponse, NextRequest } from "next/server";
+
+type Params = { params: Promise<{ orgId: string; projectId: string }> };
+
+export async function GET(request: NextRequest, { params }: Params) {
+ const { orgId, projectId } = await params;
+ try {
+ const { status, data } = await apiClient(
+ request,
+ `/api/v1/credentials/${orgId}/${projectId}`,
+ );
+ return NextResponse.json(data, { status });
+ } catch (e: unknown) {
+ return NextResponse.json(
+ { error: e instanceof Error ? e.message : String(e) },
+ { status: 500 },
+ );
+ }
+}
+
+export async function PATCH(request: NextRequest, { params }: Params) {
+ const { orgId, projectId } = await params;
+ try {
+ const body = await request.json();
+ const { status, data } = await apiClient(
+ request,
+ `/api/v1/credentials/${orgId}/${projectId}`,
+ { method: "PATCH", body: JSON.stringify(body) },
+ );
+ return NextResponse.json(data, { status });
+ } catch (e: unknown) {
+ return NextResponse.json(
+ { error: e instanceof Error ? e.message : String(e) },
+ { status: 500 },
+ );
+ }
+}
+
+export async function DELETE(request: NextRequest, { params }: Params) {
+ const { orgId, projectId } = await params;
+ try {
+ const { status, data } = await apiClient(
+ request,
+ `/api/v1/credentials/${orgId}/${projectId}`,
+ { method: "DELETE" },
+ );
+ if (status === 204) return new NextResponse(null, { status: 204 });
+ return NextResponse.json(data, { status });
+ } catch (e: unknown) {
+ return NextResponse.json(
+ { error: e instanceof Error ? e.message : String(e) },
+ { status: 500 },
+ );
+ }
+}
diff --git a/app/api/users/me/route.ts b/app/api/users/me/route.ts
index f910e1c..6b9fe0f 100644
--- a/app/api/users/me/route.ts
+++ b/app/api/users/me/route.ts
@@ -1,10 +1,17 @@
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 {
const { status, data } = await apiClient(request, "/api/v1/users/me");
- return NextResponse.json(data, { status });
+ const res = NextResponse.json(data, { status });
+
+ if (status >= 200 && status < 300) {
+ setRoleCookieFromBody(res, data);
+ }
+
+ return res;
} catch {
return NextResponse.json(
{ error: "Failed to connect to backend" },
diff --git a/app/components/Field.tsx b/app/components/Field.tsx
index f6a3a99..7d90b0b 100644
--- a/app/components/Field.tsx
+++ b/app/components/Field.tsx
@@ -48,7 +48,7 @@ export default function Field({