built user portal behind auth
This commit is contained in:
parent
5c6962f4a5
commit
37d474d7c8
48 changed files with 822 additions and 496 deletions
|
|
@ -1,3 +1,4 @@
|
|||
// app/my/rigs/RigBuilderClient.tsx
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
|
@ -5,12 +6,18 @@ import { useForm } from "react-hook-form";
|
|||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
let _redirectingDueToAuth = false;
|
||||
function handleAuthError(err: any): boolean {
|
||||
const status = (err as any)?.status;
|
||||
const code = (err as any)?.code;
|
||||
if (status === 401 || code === "TOKEN_EXPIRED") {
|
||||
const next = encodeURIComponent(window.location.pathname + window.location.search);
|
||||
window.location.assign(`/auth/sign-in?next=${next}`);
|
||||
if (_redirectingDueToAuth) return true;
|
||||
_redirectingDueToAuth = true;
|
||||
|
||||
const here = window.location.pathname + window.location.search;
|
||||
const onSignIn = window.location.pathname.startsWith("/auth");
|
||||
const next = encodeURIComponent(here);
|
||||
window.location.replace(onSignIn ? `/auth/sign-in` : `/auth/sign-in?next=${next}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -40,7 +47,7 @@ import { Badge } from "@/components/ui/badge";
|
|||
// Types
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
type Option = { id: string | number; label: string };
|
||||
type Option = { id: string | number; name: string };
|
||||
type RigType = { id: number | string; name: "fiber" | "uv" | "co2_galvo" | "co2_gantry" | string };
|
||||
|
||||
type RigRow = {
|
||||
|
|
@ -54,7 +61,6 @@ type RigRow = {
|
|||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// Builder rig_type -> settings form target expected by options API
|
||||
const SETTINGS_TARGET_MAP: Record<string, string> = {
|
||||
fiber: "settings_fiber",
|
||||
co2_gantry: "settings_co2gan",
|
||||
|
|
@ -73,7 +79,6 @@ async function apiJson<T>(url: string, init?: RequestInit): Promise<T> {
|
|||
if (res.ok) {
|
||||
try { return JSON.parse(txt) as T; } catch { return undefined as T; }
|
||||
}
|
||||
// try to unwrap nested error format
|
||||
let body: any = undefined;
|
||||
try { body = JSON.parse(txt); } catch {}
|
||||
if (body && typeof body.error === "string") {
|
||||
|
|
@ -121,10 +126,8 @@ export default function RigBuilderClient() {
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const swJson = await apiJson<any>(`/api/options/laser_software`);
|
||||
const sw =
|
||||
Array.isArray(swJson?.data) ? swJson.data :
|
||||
Array.isArray(swJson) ? swJson : [];
|
||||
const swJson = await apiJson<{ data: Option[] }>(`/api/options/laser_software`);
|
||||
const sw = Array.isArray(swJson?.data) ? swJson.data : [];
|
||||
setSoftwareOpts(sw);
|
||||
} catch (e: any) {
|
||||
if (!handleAuthError(e)) {
|
||||
|
|
@ -168,13 +171,13 @@ export default function RigBuilderClient() {
|
|||
(async () => {
|
||||
try {
|
||||
const [typesRes, rigsRes] = await Promise.all([
|
||||
apiJson<{ data: { id: number | string; label?: string; name?: string }[] }>(`/api/options/user_rig_type`),
|
||||
apiJson<{ data: { id: number | string; name: string }[] }>(`/api/options/user_rig_type`),
|
||||
apiJson<{ data: RigRow[] }>(`/api/my/rigs`),
|
||||
]);
|
||||
|
||||
const mappedTypes: RigType[] = (typesRes?.data ?? []).map((t) => ({
|
||||
id: t.id,
|
||||
name: (t.label ?? t.name ?? String(t.id)) as any,
|
||||
name: t.name as any,
|
||||
}));
|
||||
setRigTypes(mappedTypes);
|
||||
setRigs(rigsRes.data ?? []);
|
||||
|
|
@ -255,7 +258,10 @@ export default function RigBuilderClient() {
|
|||
try {
|
||||
const payload = {
|
||||
name: values.name,
|
||||
rig_type: rigTypes.find((t) => String(t.name) === String(values.rig_type))?.id ?? values.rig_type,
|
||||
// Your select uses the slug (name) as value; map it back to id for save:
|
||||
rig_type:
|
||||
rigTypes.find((t) => String(t.name) === String(values.rig_type))?.id ??
|
||||
values.rig_type,
|
||||
laser_source: values.laser_source || null,
|
||||
laser_software: values.laser_software || null,
|
||||
laser_focus_lens: isGantry ? values.laser_focus_lens || null : null,
|
||||
|
|
@ -325,16 +331,12 @@ export default function RigBuilderClient() {
|
|||
const rigTypeItems = useMemo(
|
||||
() =>
|
||||
rigTypes.map((t) => ({
|
||||
value: String(t.name),
|
||||
value: String(t.name), // using slug as value per your current pattern
|
||||
label: String(t.name).replaceAll("_", " "),
|
||||
})),
|
||||
[rigTypes]
|
||||
);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// UI
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Card>
|
||||
|
|
@ -380,7 +382,7 @@ export default function RigBuilderClient() {
|
|||
<SelectItem value="none">—</SelectItem>
|
||||
{sourceOpts.map((o) => (
|
||||
<SelectItem key={o.id} value={String(o.id)}>
|
||||
{o.label}
|
||||
{o.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
@ -401,14 +403,14 @@ export default function RigBuilderClient() {
|
|||
<SelectItem value="none">—</SelectItem>
|
||||
{softwareOpts.map((o) => (
|
||||
<SelectItem key={o.id} value={String(o.id)}>
|
||||
{o.label}
|
||||
{o.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Notes spans 2 cols */}
|
||||
{/* Notes */}
|
||||
<div className="col-span-1 md:col-span-2 space-y-2">
|
||||
<label className="text-sm font-medium">Notes</label>
|
||||
<Textarea placeholder="Optional notes…" rows={4} {...register("notes")} />
|
||||
|
|
@ -430,7 +432,7 @@ export default function RigBuilderClient() {
|
|||
<SelectItem value="none">—</SelectItem>
|
||||
{focusLensOpts.map((o) => (
|
||||
<SelectItem key={o.id} value={String(o.id)}>
|
||||
{o.label}
|
||||
{o.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
@ -455,7 +457,7 @@ export default function RigBuilderClient() {
|
|||
<SelectItem value="none">—</SelectItem>
|
||||
{scanLensOpts.map((o) => (
|
||||
<SelectItem key={o.id} value={String(o.id)}>
|
||||
{o.label}
|
||||
{o.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue