diff --git a/app/api/me/route.ts b/app/api/me/route.ts index 9d8f09a4..046a1593 100644 --- a/app/api/me/route.ts +++ b/app/api/me/route.ts @@ -8,24 +8,21 @@ function readCookie(name: string, cookieHeader: string) { export async function GET(req: Request) { const base = process.env.NEXT_PUBLIC_API_BASE_URL!; - // include username so the form can show it - const url = `${base}/users/me?fields=id,username,display_name,first_name,last_name,email`; + const url = `${base}/users/me?fields=id,username,display_name,first_name,last_name,email`; const cookieHeader = req.headers.get("cookie") ?? ""; const ma_at = readCookie("ma_at", cookieHeader); const headers: Record = { "cache-control": "no-store" }; - if (cookieHeader) headers.cookie = cookieHeader; - if (ma_at) headers.authorization = `Bearer ${ma_at}`; + if (cookieHeader) headers.cookie = cookieHeader; // session cookie + if (ma_at) headers.authorization = `Bearer ${ma_at}`; // direct token (if present) - const res = await fetch(url, { headers, cache: "no-store" }); - const body = await res.json().catch(() => ({})); + const res = await fetch(url, { headers, cache: "no-store" }); + const raw = await res.json().catch(() => ({})); + const data = raw?.data ?? raw ?? null; // normalize - return new NextResponse(JSON.stringify(body), { - status: res.status, - headers: { - "content-type": "application/json", - "cache-control": "no-store", - }, - }); + return NextResponse.json( + { data }, + { status: res.status, headers: { "content-type": "application/json", "cache-control": "no-store" } } + ); } diff --git a/app/api/options/laser_source/route.ts b/app/api/options/laser_source/route.ts index 645bede6..db1c552f 100644 --- a/app/api/options/laser_source/route.ts +++ b/app/api/options/laser_source/route.ts @@ -3,21 +3,25 @@ export const dynamic = "force-dynamic"; import { NextRequest, NextResponse } from "next/server"; -const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, ""); +const BASE = +(process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); function buildPath(target?: string | null) { - // If your schema supports target filtering, add it here. Otherwise we return all. const url = new URL(`${BASE}/items/laser_source`); url.searchParams.set("fields", "id,name"); url.searchParams.set("sort", "name"); - // Example (uncomment/adjust if you actually have a `target` field or relation): // if (target) url.searchParams.set("filter[target][_eq]", target); return String(url); } -async function dFetch(bearer: string, target?: string | null) { +function readCookieFromHeader(name: string, cookieHeader: string) { + const m = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`)); + return m?.[1] ?? null; +} + +async function dFetch(token: string, target?: string | null) { const res = await fetch(buildPath(target), { - headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` }, + headers: { Accept: "application/json", Authorization: `Bearer ${token}` }, cache: "no-store", }); const text = await res.text().catch(() => ""); @@ -28,7 +32,8 @@ async function dFetch(bearer: string, target?: string | null) { export async function GET(req: NextRequest) { try { - const userAt = req.cookies.get("ma_at")?.value; + const cookieHeader = req.headers.get("cookie") ?? ""; + const userAt = req.cookies.get("ma_at")?.value || readCookieFromHeader("ma_at", cookieHeader); if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); const target = req.nextUrl.searchParams.get("target"); @@ -40,8 +45,12 @@ export async function GET(req: NextRequest) { ); } - const rows: Array<{ id: number | string; name: string }> = r.json?.data ?? []; - const data = rows.map(({ id, name }) => ({ id, name })); + const rows: Array<{ id: number | string; name?: string; label?: string; title?: string }> = + r.json?.data ?? []; + const data = rows.map(({ id, name, label, title }) => ({ + id, + name: name || label || title || "", + })); return NextResponse.json({ data }); } catch (e: any) { return NextResponse.json({ error: e?.message || "Failed to load laser sources" }, { status: 500 }); diff --git a/app/api/options/lens/route.ts b/app/api/options/lens/route.ts index dff09311..51a17bc8 100644 --- a/app/api/options/lens/route.ts +++ b/app/api/options/lens/route.ts @@ -3,21 +3,25 @@ export const dynamic = "force-dynamic"; import { NextRequest, NextResponse } from "next/server"; -const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, ""); +const BASE = +(process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); function buildPath(target?: string | null) { - // Adjust the collection name if yours differs (e.g., laser_scan_lens) const url = new URL(`${BASE}/items/laser_scan_lens`); url.searchParams.set("fields", "id,name"); url.searchParams.set("sort", "name"); - // Example if you model per-target lenses: // if (target) url.searchParams.set("filter[target][_eq]", target); return String(url); } -async function dFetch(bearer: string, target?: string | null) { +function readCookieFromHeader(name: string, cookieHeader: string) { + const m = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`)); + return m?.[1] ?? null; +} + +async function dFetch(token: string, target?: string | null) { const res = await fetch(buildPath(target), { - headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` }, + headers: { Accept: "application/json", Authorization: `Bearer ${token}` }, cache: "no-store", }); const text = await res.text().catch(() => ""); @@ -28,7 +32,8 @@ async function dFetch(bearer: string, target?: string | null) { export async function GET(req: NextRequest) { try { - const userAt = req.cookies.get("ma_at")?.value; + const cookieHeader = req.headers.get("cookie") ?? ""; + const userAt = req.cookies.get("ma_at")?.value || readCookieFromHeader("ma_at", cookieHeader); if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); const target = req.nextUrl.searchParams.get("target"); @@ -40,8 +45,12 @@ export async function GET(req: NextRequest) { ); } - const rows: Array<{ id: number | string; name: string }> = r.json?.data ?? []; - const data = rows.map(({ id, name }) => ({ id, name })); + const rows: Array<{ id: number | string; name?: string; label?: string; title?: string }> = + r.json?.data ?? []; + const data = rows.map(({ id, name, label, title }) => ({ + id, + name: name || label || title || "", + })); return NextResponse.json({ data }); } catch (e: any) { return NextResponse.json({ error: e?.message || "Failed to load lenses" }, { status: 500 }); diff --git a/app/components/forms/SettingsSubmit.tsx b/app/components/forms/SettingsSubmit.tsx index 8d74b1c2..889323e8 100644 --- a/app/components/forms/SettingsSubmit.tsx +++ b/app/components/forms/SettingsSubmit.tsx @@ -49,7 +49,14 @@ function useOptions(path: string) { } function FilterableSelect({ - label, name, register, options, loading, onQuery, placeholder = "—", required = false, + label, + name, + register, + options, + loading, + onQuery, + placeholder = "—", + required = false, }: { label: string; name: string; @@ -61,7 +68,9 @@ function FilterableSelect({ required?: boolean; }) { const [filter, setFilter] = useState(""); - useEffect(() => { onQuery?.(filter); }, [filter, onQuery]); + useEffect(() => { + onQuery?.(filter); + }, [filter, onQuery]); const filtered = useMemo(() => { if (!filter) return options; @@ -81,18 +90,21 @@ function FilterableSelect({ onChange={(e) => setFilter(e.target.value)} /> ); } -function BoolBox({ label, name, register }:{ - label: string; name: string; register: UseFormRegister; -}) { +function BoolBox({ label, name, register }: { label: string; name: string; register: UseFormRegister }) { return (