diff --git a/app/api/me/route.ts b/app/api/me/route.ts index 046a1593..b8bf6e94 100644 --- a/app/api/me/route.ts +++ b/app/api/me/route.ts @@ -1,28 +1,34 @@ // 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) { - const base = process.env.NEXT_PUBLIC_API_BASE_URL!; - const url = `${base}/users/me?fields=id,username,display_name,first_name,last_name,email`; + // 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`; + // 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); const headers: Record = { "cache-control": "no-store" }; - if (cookieHeader) headers.cookie = cookieHeader; // session cookie - if (ma_at) headers.authorization = `Bearer ${ma_at}`; // direct token (if present) + if (cookieHeader) headers.cookie = cookieHeader; + if (ma_at) headers.authorization = `Bearer ${ma_at}`; - const res = await fetch(url, { headers, cache: "no-store" }); - const raw = await res.json().catch(() => ({})); - const data = raw?.data ?? raw ?? null; // normalize + const res = await fetch(url, { headers, cache: "no-store" }); + const body = await res.json().catch(() => ({})); - return NextResponse.json( - { data }, - { status: res.status, headers: { "content-type": "application/json", "cache-control": "no-store" } } - ); + return new NextResponse(JSON.stringify(body), { + 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 db1c552f..2bc4d0cb 100644 --- a/app/api/options/laser_source/route.ts +++ b/app/api/options/laser_source/route.ts @@ -3,41 +3,39 @@ export const dynamic = "force-dynamic"; import { NextRequest, NextResponse } from "next/server"; -const BASE = -(process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); +const BASE = ( + process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "" +).replace(/\/$/, ""); -function buildPath(target?: string | null) { +function buildPath() { + // This collection uses make/model (no `name` field) const url = new URL(`${BASE}/items/laser_source`); - url.searchParams.set("fields", "id,name"); - url.searchParams.set("sort", "name"); - // if (target) url.searchParams.set("filter[target][_eq]", target); + url.searchParams.set("fields", "id,make,model"); + url.searchParams.set("sort", "make,model"); return String(url); } -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 ${token}` }, +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 {} + try { + json = text ? JSON.parse(text) : null; + } catch {} return { res, json, text }; } export async function GET(req: NextRequest) { try { - 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 userAt = req.cookies.get("ma_at")?.value; + if (!userAt) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + } - const target = req.nextUrl.searchParams.get("target"); - const r = await dFetch(userAt, target); + const r = await dFetch(userAt); if (!r.res.ok) { return NextResponse.json( { error: `Directus ${r.res.status}: ${r.text || r.res.statusText}` }, @@ -45,14 +43,20 @@ export async function GET(req: NextRequest) { ); } - const rows: Array<{ id: number | string; name?: string; label?: string; title?: string }> = + const rows: Array<{ id: string | number; make?: string; model?: string }> = r.json?.data ?? []; - const data = rows.map(({ id, name, label, title }) => ({ - id, - name: name || label || title || "", - })); + + // 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 }; + }); + 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/lens/route.ts b/app/api/options/lens/route.ts index 51a17bc8..43621906 100644 --- a/app/api/options/lens/route.ts +++ b/app/api/options/lens/route.ts @@ -3,38 +3,47 @@ export const dynamic = "force-dynamic"; import { NextRequest, NextResponse } from "next/server"; -const BASE = -(process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); +const BASE = ( + process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "" +).replace(/\/$/, ""); function buildPath(target?: string | null) { + // CO2 Gantry → focus lenses (name) + // everything else (Fiber/UV/CO2 Galvo) → scan lenses (field_size + focal_length) + const isGantry = target === "co2-gantry"; + + if (isGantry) { + const url = new URL(`${BASE}/items/laser_focus_lens`); + url.searchParams.set("fields", "id,name"); + url.searchParams.set("sort", "name"); + return String(url); + } + const url = new URL(`${BASE}/items/laser_scan_lens`); - url.searchParams.set("fields", "id,name"); - url.searchParams.set("sort", "name"); - // if (target) url.searchParams.set("filter[target][_eq]", target); + url.searchParams.set("fields", "id,field_size,focal_length"); + url.searchParams.set("sort", "field_size,focal_length"); return String(url); } -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) { +async function dFetch(bearer: string, target?: string | null) { const res = await fetch(buildPath(target), { - headers: { Accept: "application/json", Authorization: `Bearer ${token}` }, + 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 {} + try { + json = text ? JSON.parse(text) : null; + } catch {} return { res, json, text }; } export async function GET(req: NextRequest) { try { - 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 userAt = req.cookies.get("ma_at")?.value; + if (!userAt) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + } const target = req.nextUrl.searchParams.get("target"); const r = await dFetch(userAt, target); @@ -45,14 +54,32 @@ export async function GET(req: NextRequest) { ); } - 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 || "", - })); + const rows: any[] = r.json?.data ?? []; + const isGantry = target === "co2-gantry"; + + const data = rows.map((row) => { + if (isGantry) { + // Focus lens: label is just the stored name + return { id: row.id, name: row.name, label: row.name }; + } + // Scan lens: label "300x300 mm | F420" etc + const fs = + row.field_size != null && row.field_size !== "" + ? `${row.field_size} mm` + : ""; + const fl = + row.focal_length != null && row.focal_length !== "" + ? `F${row.focal_length}` + : ""; + const label = [fs, fl].filter(Boolean).join(" | "); + return { id: row.id, name: label, label }; + }); + return NextResponse.json({ data }); } catch (e: any) { - return NextResponse.json({ error: e?.message || "Failed to load lenses" }, { status: 500 }); + 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 948940c8..03e6641f 100644 --- a/app/components/forms/SettingsSubmit.tsx +++ b/app/components/forms/SettingsSubmit.tsx @@ -1,11 +1,15 @@ -// app/components/forms/SettingsSubmit.tsx "use client"; 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; @@ -24,7 +28,9 @@ 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) => { @@ -34,7 +40,15 @@ function useOptions(path: string) { ? raw .map((x) => ({ id: String(x?.id ?? x?.value ?? x?.key ?? ""), - label: String(x?.label ?? x?.name ?? x?.title ?? x?.text ?? ""), + // include `opacity` as a label fallback (fixes opacity IDs) + label: String( + x?.label ?? + x?.name ?? + x?.title ?? + x?.text ?? + x?.opacity ?? + "" + ), })) .filter((o) => o.id && o.label) : []; @@ -50,7 +64,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; @@ -62,7 +83,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 +104,32 @@ function FilterableSelect({ value={filter} onChange={(e) => setFilter(e.target.value)} /> - + {filtered.map((o) => ( - + ))} ); } -function BoolBox({ label, name, register }:{ - label: string; name: string; register: UseFormRegister; +function BoolBox({ + label, + name, + register, +}: { + label: string; + name: string; + register: UseFormRegister; }) { return (