diff --git a/app/api/me/route.ts b/app/api/me/route.ts index b8bf6e94..7e525a15 100644 --- a/app/api/me/route.ts +++ b/app/api/me/route.ts @@ -1,19 +1,16 @@ // app/api/me/route.ts import { NextResponse } from "next/server"; -/** Read a cookie value from a raw Cookie header string */ function readCookie(name: string, cookieHeader: string) { const m = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`)); return m?.[1] ?? null; } export async function GET(req: Request) { - // Prefer DIRECTUS_URL if present; fall back to NEXT_PUBLIC_API_BASE_URL - const base = - process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || ""; - const url = `${base.replace(/\/$/, "")}/users/me?fields=id,username,display_name,first_name,last_name,email`; + const base = process.env.NEXT_PUBLIC_API_BASE_URL!; + // NOTE: include username explicitly + const url = `${base}/users/me?fields=id,username,display_name,first_name,last_name,email`; - // Forward the incoming cookies (session), and also ma_at as Bearer (token setups) const cookieHeader = req.headers.get("cookie") ?? ""; const ma_at = readCookie("ma_at", cookieHeader); @@ -21,7 +18,7 @@ export async function GET(req: Request) { if (cookieHeader) headers.cookie = cookieHeader; if (ma_at) headers.authorization = `Bearer ${ma_at}`; - const res = await fetch(url, { headers, cache: "no-store" }); + const res = await fetch(url, { headers, cache: "no-store" }); const body = await res.json().catch(() => ({})); return new NextResponse(JSON.stringify(body), { diff --git a/app/api/options/laser_source/route.ts b/app/api/options/laser_source/route.ts index 2bc4d0cb..fc4ca460 100644 --- a/app/api/options/laser_source/route.ts +++ b/app/api/options/laser_source/route.ts @@ -2,61 +2,56 @@ 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() { - // This collection uses make/model (no `name` field) - const url = new URL(`${BASE}/items/laser_source`); - url.searchParams.set("fields", "id,make,model"); - url.searchParams.set("sort", "make,model"); - return String(url); -} - -async function dFetch(bearer: string) { - const res = await fetch(buildPath(), { - headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` }, - cache: "no-store", - }); - const text = await res.text().catch(() => ""); - let json: any = null; - try { - json = text ? JSON.parse(text) : null; - } catch {} - return { res, json, text }; +function nmForTarget(target?: string | null) { + // Optional: narrow list per tab; adjust if your data differs. + switch (target) { + case "fiber": return 1064; + case "uv": return 355; + case "co2-galvo": + case "co2-gantry": return 10600; + default: return null; + } } export async function GET(req: NextRequest) { try { - const userAt = req.cookies.get("ma_at")?.value; - if (!userAt) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + const ma_at = req.cookies.get("ma_at")?.value; + if (!ma_at) return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + + const target = req.nextUrl.searchParams.get("target"); // "fiber" | "uv" | "co2-galvo" | "co2-gantry" + const url = new URL(`${BASE}/items/laser_source`); + // Your collection has make/model (no single "name") + url.searchParams.set("fields", "id,make,model,nm"); + url.searchParams.set("sort", "make,model"); + + const nm = nmForTarget(target); + if (nm != null) { + url.searchParams.set("filter[nm][_eq]", String(nm)); } - const r = await dFetch(userAt); - if (!r.res.ok) { + const res = await fetch(String(url), { + headers: { Accept: "application/json", Authorization: `Bearer ${ma_at}` }, + cache: "no-store", + }); + + const text = await res.text().catch(() => ""); + if (!res.ok) { return NextResponse.json( - { error: `Directus ${r.res.status}: ${r.text || r.res.statusText}` }, - { status: r.res.status } + { error: `Directus ${res.status}: ${text || res.statusText}` }, + { status: res.status } ); } - const rows: Array<{ id: string | number; make?: string; model?: string }> = - r.json?.data ?? []; - - // Compose human label from make + model - const data = rows.map(({ id, make, model }) => { - const label = [make, model].filter(Boolean).join(" "); - return { id, name: label, label }; - }); + const json = text ? JSON.parse(text) : { data: [] }; + const data = (json?.data ?? []).map((r: any) => ({ + id: r.id, + label: [r.make, r.model].filter(Boolean).join(" ").trim() || String(r.id), + })); return NextResponse.json({ data }); } catch (e: any) { - return NextResponse.json( - { error: e?.message || "Failed to load laser sources" }, - { status: 500 } - ); + return NextResponse.json({ error: e?.message || "Failed to load laser sources" }, { status: 500 }); } } diff --git a/app/api/options/material_opacity/route.ts b/app/api/options/material_opacity/route.ts new file mode 100644 index 00000000..1dd8d702 --- /dev/null +++ b/app/api/options/material_opacity/route.ts @@ -0,0 +1,39 @@ +// app/api/options/material_opacity/route.ts +export const dynamic = "force-dynamic"; + +import { NextRequest, NextResponse } from "next/server"; +const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, ""); + +export async function GET(req: NextRequest) { + try { + const ma_at = req.cookies.get("ma_at")?.value; + if (!ma_at) return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + + const url = new URL(`${BASE}/items/material_opacity`); + url.searchParams.set("fields", "id,opacity"); + url.searchParams.set("sort", "sort,opacity"); + + const res = await fetch(String(url), { + headers: { Accept: "application/json", Authorization: `Bearer ${ma_at}` }, + cache: "no-store", + }); + + const text = await res.text().catch(() => ""); + if (!res.ok) { + return NextResponse.json( + { error: `Directus ${res.status}: ${text || res.statusText}` }, + { status: res.status } + ); + } + + const json = text ? JSON.parse(text) : { data: [] }; + const data = (json?.data ?? []).map((r: any) => ({ + id: r.id, + label: r.opacity ?? String(r.id), + })); + + return NextResponse.json({ data }); + } catch (e: any) { + return NextResponse.json({ error: e?.message || "Failed to load opacity options" }, { status: 500 }); + } +} diff --git a/app/components/forms/SettingsSubmit.tsx b/app/components/forms/SettingsSubmit.tsx index 17c4b1cc..9b6b2206 100644 --- a/app/components/forms/SettingsSubmit.tsx +++ b/app/components/forms/SettingsSubmit.tsx @@ -4,12 +4,7 @@ import { useEffect, useMemo, useState } from "react"; import { useForm, useFieldArray, type UseFormRegister } from "react-hook-form"; import { useRouter, useSearchParams } from "next/navigation"; -type Target = -| "settings_fiber" -| "settings_co2gan" -| "settings_co2gal" -| "settings_uv"; - +type Target = "settings_fiber" | "settings_co2gan" | "settings_co2gal" | "settings_uv"; type Opt = { id: string; label: string }; type Me = { id: string; @@ -20,6 +15,11 @@ type Me = { email?: string; }; +function shortId(s?: string) { + if (!s) return ""; + return s.length <= 12 ? s : `${s.slice(0, 8)}…${s.slice(-4)}`; +} + function useOptions(path: string) { const [opts, setOpts] = useState([]); const [loading, setLoading] = useState(false); @@ -28,9 +28,7 @@ function useOptions(path: string) { useEffect(() => { let alive = true; setLoading(true); - const url = `/api/options/${path}${ - path.includes("?") ? "&" : "?" - }q=${encodeURIComponent(q)}`; + const url = `/api/options/${path}${path.includes("?") ? "&" : "?"}q=${encodeURIComponent(q)}`; fetch(url, { cache: "no-store", credentials: "include" }) .then((r) => r.json()) .then((j) => { @@ -40,14 +38,9 @@ function useOptions(path: string) { ? raw .map((x) => ({ id: String(x?.id ?? x?.value ?? x?.key ?? ""), - // include `opacity` as a label fallback (fixes opacity IDs) + // IMPORTANT: recognize 'opacity' too label: String( - x?.label ?? - x?.name ?? - x?.title ?? - x?.text ?? - x?.opacity ?? - "" + x?.label ?? x?.name ?? x?.title ?? x?.text ?? x?.opacity ?? "" ), })) .filter((o) => o.id && o.label) @@ -104,10 +97,7 @@ function FilterableSelect({ value={filter} onChange={(e) => setFilter(e.target.value)} /> -