PageSourceSearch

https://ridedna.ai/assets/Technologies-BQG3rhpi.js

js ridedna.ai collected 2026-10-05 14:39:19 UTC 30,561 bytes, 204 lines download raw bytes

1import { d as ye, r as h, e as we, J as d, y as l, K as U, j as e, B as p, C as oe, a$ as se, b0 as ae, a4 as ie, L as y, I as F, S as Ne, f as Se, g as Ce, h as De, l as Pe, bk as Ee, a9 as D, b6 as de, by as ke, av as Ue, bA as le, a5 as I, bv as Fe, a7 as Re, T as Te } from "./index-B9miRpwC.js";
2import { u as Me, a as re, D as Ie, c as Le, S as qe, v as Ae, s as He, K as Be, P as Ge, b as Oe, d as $e, C as Ke } from "./sortable.esm-CDvrBh3y.js";
3import { A as Ve, a as ze, b as Qe, c as We, d as Xe, e as Ye, f as _e, g as Ze } from "./alert-dialog-Cv39x3F5.js";
4import { T as Je } from "./techIcons-t-BrxsxX.js";
5import { P as et } from "./plus-BCaMPs2R.js";
6import { V as ne } from "./video-CTqYjO0y.js";
7import { U as ce } from "./upload-u0DU9Zyt.js";
8import { D as tt } from "./download-DF6N6enX.js";
9import { F as ot } from "./file-plus-C9qSoVlr.js";
10import { S as st } from "./save-fTnHtNzX.js";
11import { S as at } from "./star-DznhT8iW.js";
12import { G as it } from "./grip-vertical-2GXaSeBG.js";
13import { P as rt } from "./pencil-CWIGhVLH.js";
14import "./eye-3Ma80GoX.js";
15import "./clock-Dc7t8MP3.js";
16import "./git-merge-F2YjpL8M.js";
17import "./git-fork-D8ArLQHf.js";
18import "./mountain-C-VJcXCc.js";
19import "./plug-B26_PXxP.js";
20import "./gavel-6qyjvZ4l.js";
21import "./link-BVyy0oqf.js";
22import "./maximize-2-C73Pk9JW.js";
23import "./badge-check-DvuFmJfK.js";
24import "./trophy-Cv3Pfe_2.js";
25import "./award-CiftxGEL.js";
26import "./power-C8VVVjHA.js";
27import "./wind-BGoF4DOB.js";
28import "./arrow-up-DbKPdIZp.js";
29import "./chart-line-DpDMmHOp.js";
30import "./trending-up-Cbn6FDrL.js";
31import "./package-CQTVcVCl.js";
32import "./refresh-cw-D2_WynW5.js";
33import "./move-DMbQm-df.js";
34import "./settings-2-CNQDCEPK.js";
35import "./cog-C08CXJte.js";
36import "./ruler-BxeAmtre.js";
37import "./gauge-CuWCZacX.js";
38import "./wrench-jGq628IX.js";
39import "./cpu-CY2sXBhd.js";
40import "./sliders-horizontal-BolG3mKq.js";
41import "./compass-DzB6ojpN.js";
42const nt = ["Star", "Zap", "Shield", "Settings", "Compass", "Sliders", "SlidersHorizontal", "Layers", "Cpu", "Wrench", "Target", "Gauge", "Ruler", "PenTool", "Crosshair", "Scan", "Cog", "Settings2", "GitBranch", "Maximize2", "Move", "RotateCw", "RefreshCw", "Timer", "Hourglass", "Frame", "FlaskConical", "FlaskRound", "TestTube2", "Microscope", "Atom", "Thermometer", "Radiation", "Dna", "Stethoscope", "HeartPulse", "Droplets", "Waves", "Snowflake", "Feather", "Leaf", "Gem", "Diamond", "Droplet", "GlassWater", "Haze", "Rainbow", "Package", "Box", "Layers2", "Cylinder", "Circle", "Hexagon", "Triangle", "Pentagon", "Octagon", "TrendingUp", "BarChart3", "Activity", "LineChart", "Percent", "Ratio", "Scaling", "ArrowUpRight", "ChevronUp", "ChevronsUp", "ArrowUp", "Radar", "RotateCcw", "Rotate3d", "Move3d", "MoveHorizontal", "MoveVertical", "GripHorizontal", "GripVertical", "Grip", "StretchHorizontal", "StretchVertical", "Maximize", "Minimize", "Expand", "Flame", "Wind", "Bolt", "Power", "Battery", "BatteryFull", "BatteryCharging", "ZapOff", "Rocket", "Award", "Trophy", "Medal", "Crown", "Ribbon", "BadgeCheck", "CheckCircle", "CheckCircle2", "CircleDot", "Sparkles", "Lock", "ShieldPlus", "ShieldX", "Globe", "Globe2", "Radio", "RadioTower", "Network", "Link", "Anchor", "Satellite", "SatelliteDish", "Wifi", "WifiHigh", "Bluetooth", "Nfc", "Usb", "Signal", "Orbit", "Navigation", "Navigation2", "Map", "MapPin", "Route", "Hammer", "Anvil", "Gavel", "HardHat", "PencilRuler", "HardDrive", "HardDriveDownload", "HardDriveUpload", "CircuitBoard", "Cable", "Plug", "Plug2", "Magnet", "GaugeCircle", "Weight", "Scale", "ScanEye", "ScanFace", "ScanLine", "ScanBarcode", "Focus", "Bike", "Mountain", "MountainSnow", "Tent", "Footprints", "Dumbbell", "Hand", "HandMetal", "HeartHandshake", "GitFork", "GitMerge", "GitBranchPlus", "GitGraph", "GitCompare", "GitCommitVertical", "Camera", "Film", "Music", "Clock", "TimerReset", "Sun", "Moon", "Cloud", "CloudLightning", "CloudRain", "CloudSnow", "SunMoon", "ThermometerSun", "ThermometerSnowflake", "Recycle", "RefreshCcw", "Filter", "Palette", "Brush", "PaintBucket", "Pipette", "Eye", "Fingerprint"];
43function q({ name: o, className: g = "w-5 h-5" }) {
44  const c = Je[o];
45  return c ? e.jsx(c, { className: g }) : e.jsx(at, { className: g });
46}
47const C = { name: "", iconName: "Star", shortDescription: "", longDescription: "" };
48function L({ label: o, hint: g, photoUrl: c, isPending: i, onUpload: f, onRemove: m, testIdUpload: u }) {
49  const v = h.useRef(null);
50  return e.jsxs("div", { className: "flex flex-col gap-2", children: [e.jsx("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: o }), e.jsx("p", { className: "text-xs text-muted-foreground", children: g }), e.jsxs("div", { className: "flex items-start gap-3", children: [c ? e.jsxs("div", { className: "relative group shrink-0", children: [e.jsx("img", { src: c, alt: o, className: "w-28 h-18 object-cover rounded-md border border-border", style: { height: "72px" } }), e.jsx("button", { type: "button", onClick: m, disabled: i, className: "absolute -top-2 -right-2 w-5 h-5 rounded-full bg-destructive text-destructive-foreground flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity", title: "Remove photo", children: e.jsx(D, { className: "w-3 h-3" }) })] }) : e.jsxs("div", { className: "w-28 rounded-md border border-dashed border-border bg-muted flex flex-col items-center justify-center gap-1 shrink-0", style: { height: "72px" }, children: [e.jsx(le, { className: "w-4 h-4 text-muted-foreground" }), e.jsx("span", { className: "text-xs text-muted-foreground", children: "N
50o photo" })] }), e.jsxs("div", { className: "flex flex-col gap-2", children: [e.jsx("input", { ref: v, type: "file", accept: "image/png,image/jpeg,image/webp", className: "hidden", onChange: (x) => {
51    var w;
52    const b = (w = x.target.files) == null ? void 0 : w[0];
53    b && f(b), x.target.value = "";
54  } }), e.jsxs(p, { type: "button", variant: "outline", size: "sm", disabled: i, onClick: () => {
55    var x;
56    return (x = v.current) == null ? void 0 : x.click();
57  }, "data-testid": u, children: [e.jsx(ce, { className: "w-3.5 h-3.5 mr-2" }), i ? "Uploading\u2026" : c ? "Replace" : "Upload"] }), e.jsx("p", { className: "text-xs text-muted-foreground", children: "PNG, JPEG, WebP \xB7 Max 8 MB" })] })] })] });
58}
59function dt({ tech: o, idx: g, expandedId: c, onToggleExpand: i, onEdit: f, onDelete: m }) {
60  const { attributes: u, listeners: v, setNodeRef: x, transform: b, transition: w, isDragging: P } = $e({ id: o.id }), E = { transform: Ke.Transform.toString(b), transition: w, opacity: P ? 0.5 : 1 };
61  return e.jsxs("div", { ref: x, style: E, className: "border rounded-md transition-all border-border", "data-testid": `tech-item-${o.id}`, children: [e.jsxs("div", { className: "flex items-center gap-3 p-4", children: [e.jsx("button", { type: "button", ...u, ...v, className: "cursor-grab active:cursor-grabbing touch-none text-muted-foreground shrink-0 p-1 -m-1", "aria-label": "Drag to reorder", "data-testid": `drag-handle-tech-${o.id}`, children: e.jsx(it, { className: "w-4 h-4" }) }), e.jsx("div", { className: "w-8 h-8 rounded-md bg-[#8DB5A8]/15 text-[#8DB5A8] flex items-center justify-center shrink-0", children: e.jsx(q, { name: o.iconName, className: "w-4 h-4" }) }), o.photoUrl && e.jsx("img", { src: o.photoUrl, alt: o.name, className: "w-12 h-8 object-cover rounded shrink-0 border border-border" }), e.jsxs("div", { className: "flex-1 min-w-0", children: [e.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [e.jsx("p", { className: "font-medium text-sm", children: o.name }), e.jsxs(I, { variant: "outline", className: "text-xs", children: ["#", g + 1] }), o.photoUrl && e.jsxs(I, { variant: "secondary", className: "text-xs", children: [e.jsx(le, { className: "w-2.5 h-2.5 mr-1" }), [o.photoUrl, o.photo2Url, o.photo3Url].filter(Boolean).length, " photo", [o.photoUrl, o.photo2Url, o.photo3Url].filter(Boolean).length !== 1 ? "s" : ""] }), Array.isArray(o.documents) && o.documents.length > 0 && e.jsxs(I, { variant: "secondary", className: "text-xs", children: [e.jsx(de, { className: "w-2.5 h-2.5 mr-1" }), o.documents.length, " doc", o.documents.length !== 1 ? "s" : ""] })] }), o.shortDescription && e.jsx("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: o.shortDescription })] }), e.jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [o.longDescription && e.jsx(p, { variant: "ghost", size: "icon", onClick: () => i(o.id), "data-testid": `button-expand-tech-${o.id}`, children: c === o.id ? e.jsx(Fe, { className: "w-4 h-4" }) : e.jsx(Re, { className: "w-4 h-4" }) }), e.jsx(p, { variant: "ghost", size: "icon", onClick: () => f(o), "data-testid": `button-edit-tech-${o.id}`, children: e.jsx(rt, { className: "w-4 h-4" }) }), e.jsx(p, { variant: "ghost", size: "icon", onClick: () => m(o.id), "data-testid": `button-delete-tech-${o.id}`, children: e.jsx(Te, { className: "w-4 h-4" }) })] })] }), c === o.id && o.longDescription && e.jsx("div", { className: "px-4 pb-4 pt-0 border-t border-border/50", children: e.jsx("p", { className: "text-sm text-muted-foreground whitespace-pre-wrap", children: o.longDescription }) })] });
62}
63function Yt() {
64  const { toast: o } = ye(), [g, c] = h.useState(!1), [i, f] = h.useState(null), [m, u] = h.useState(C), [v, x] = h.useState(null), [b, w] = h.useState(null), [P, E] = h.useState(""), [A, H] = h.useState(""), B = h.useRef(null), G = h.useRef(null), { data: R = [], isLoading: me } = we({ queryKey: ["/api/admin/technologies"] }), [ue, O] = h.useState(null);
65  h.useEffect(() => {
66    O(R);
67  }, [R]);
68  const j = ue ?? R, he = Me(re(Ge, { activationConstraint: { distance: 8 } }), re(Be, { coordinateGetter: He })), r = i ? j.find((t) => t.id === i) : null, $ = d({ mutationFn: async (t) => (await U("POST", "/api/admin/technologies", { ...t, displayOrder: j.length })).json(), onSuccess: () => {
69    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), c(!1), u(C), o({ title: "Technology created" });
70  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), K = d({ mutationFn: async ({ id: t, data: s }) => (await U("PUT", `/api/admin/technologies/${t}`, s)).json(), onSuccess: () => {
71    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), f(null), u(C), o({ title: "Technology updated" });
72  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), pe = d({ mutationFn: async (t) => {
73    await U("DELETE", `/api/admin/technologies/${t}`);
74  }, onSuccess: () => {
75    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), x(null), o({ title: "Technology deleted" });
76  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), xe = d({ mutationFn: async (t) => {
77    await U("POST", "/api/admin/technologies/reorder", { ids: t });
78  }, onSuccess: () => {
79    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] });
80  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), V = d({ mutationFn: async ({ id: t, file: s }) => {
81    const a = new FormData();
82    a.append("photo", s);
83    const n = await fetch(`/api/admin/technologies/${t}/photo`, { method: "POST", body: a, credentials: "include", headers: { "X-Requested-With": "XMLHttpRequest" } });
84    if (!n.ok) throw new Error((await n.json()).error || "Upload failed");
85    return n.json();
86  }, onSuccess: () => {
87    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Photo 1 uploaded" });
88  }, onError: (t) => o({ title: "Upload error", description: t.message, variant: "destructive" }) }), z = d({ mutationFn: async ({ id: t, file: s }) => {
89    const a = new FormData();
90    a.append("photo", s);
91    const n = await fetch(`/api/admin/technologies/${t}/photo2`, { method: "POST", body: a, credentials: "include", headers: { "X-Requested-With": "XMLHttpRequest" } });
92    if (!n.ok) throw new Error((await n.json()).error || "Upload failed");
93    return n.json();
94  }, onSuccess: () => {
95    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Photo 2 uploaded" });
96  }, onError: (t) => o({ title: "Upload error", description: t.message, variant: "destructive" }) }), Q = d({ mutationFn: async ({ id: t, file: s }) => {
97    const a = new FormData();
98    a.append("photo", s);
99    const n = await fetch(`/api/admin/technologies/${t}/photo3`, { method: "POST", body: a, credentials: "include", headers: { "X-Requested-With": "XMLHttpRequest" } });
100    if (!n.ok) throw new Error((await n.json()).error || "Upload failed");
101    return n.json();
102  }, onSuccess: () => {
103    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Photo 3 uploaded" });
104  }, onError: (t) => o({ title: "Upload error", description: t.message, variant: "destructive" }) }), W = d({ mutationFn: async (t) => {
105    const s = await fetch(`/api/admin/technologies/${t}/photo`, { method: "DELETE", credentials: "include" });
106    if (!s.ok) throw new Error("Failed to remove photo");
107    return s.json();
108  }, onSuccess: () => {
109    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Photo 1 removed" });
110  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), X = d({ mutationFn: async (t) => {
111    const s = await fetch(`/api/admin/technologies/${t}/photo2`, { method: "DELETE", credentials: "include" });
112    if (!s.ok) throw new Error("Failed to remove photo 2");
113    return s.json();
114  }, onSuccess: () => {
115    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Photo 2 removed" });
116  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), Y = d({ mutationFn: async (t) => {
117    const s = await fetch(`/api/admin/technologies/${t}/photo3`, { method: "DELETE", credentials: "include" });
118    if (!s.ok) throw new Error("Failed to remove photo 3");
119    return s.json();
120  }, onSuccess: () => {
121    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Photo 3 removed" });
122  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), T = d({ mutationFn: async ({ id: t, file: s, title: a }) => {
123    const n = new FormData();
124    n.append("document", s), n.append("title", a || s.name);
125    const N = await fetch(`/api/admin/technologies/${t}/documents`, { method: "POST", body: n, credentials: "include", headers: { "X-Requested-With": "XMLHttpRequest" } });
126    if (!N.ok) throw new Error((await N.json()).error || "Upload failed");
127    return N.json();
128  }, onSuccess: () => {
129    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), E(""), o({ title: "Document uploaded" });
130  }, onError: (t) => o({ title: "Upload error", description: t.message, variant: "destructive" }) }), _ = d({ mutationFn: async ({ id: t, index: s }) => {
131    const a = await fetch(`/api/admin/technologies/${t}/documents/${s}`, { method: "DELETE", credentials: "include" });
132    if (!a.ok) throw new Error("Failed to remove document");
133    return a.json();
134  }, onSuccess: () => {
135    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Document removed" });
136  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), M = d({ mutationFn: async ({ id: t, file: s }) => {
137    const a = new FormData();
138    a.append("video", s);
139    const n = await fetch(`/api/admin/technologies/${t}/video`, { method: "POST", body: a, credentials: "include", headers: { "X-Requested-With": "XMLHttpRequest" } });
140    if (!n.ok) throw new Error((await n.json()).error || "Upload failed");
141    return n.json();
142  }, onSuccess: () => {
143    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Video uploaded" });
144  }, onError: (t) => o({ title: "Upload error", description: t.message, variant: "destructive" }) }), Z = d({ mutationFn: async (t) => {
145    const s = await fetch(`/api/admin/technologies/${t}/video`, { method: "DELETE", credentials: "include" });
146    if (!s.ok) throw new Error("Failed to remove video");
147    return s.json();
148  }, onSuccess: () => {
149    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), o({ title: "Video removed" });
150  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) }), k = d({ mutationFn: async ({ id: t, youtubeVideoId: s }) => {
151    const a = await fetch(`/api/admin/technologies/${t}/youtube`, { method: "PATCH", credentials: "include", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, body: JSON.stringify({ youtubeVideoId: s }) });
152    if (!a.ok) throw new Error((await a.json()).error || "Update failed");
153    return a.json();
154  }, onSuccess: () => {
155    l.invalidateQueries({ queryKey: ["/api/admin/technologies"] }), H(""), o({ title: "YouTube video updated" });
156  }, onError: (t) => o({ title: "Error", description: t.message, variant: "destructive" }) });
157  function ge(t) {
158    const s = t.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([A-Za-z0-9_-]{11})/);
159    return s ? s[1] : null;
160  }
161  const fe = (t) => {
162    t.preventDefault(), m.name.trim() && (i ? K.mutate({ id: i, data: m }) : $.mutate(m));
163  }, ve = (t) => {
164    f(t.id), u({ name: t.name, iconName: t.iconName, shortDescription: t.shortDescription ?? "", longDescription: t.longDescription ?? "" }), c(!0);
165  }, je = () => {
166    c(!1), f(null), u(C);
167  }, be = (t) => {
168    const { active: s, over: a } = t;
169    if (!a || s.id === a.id) return;
170    const n = j.findIndex((S) => S.id === s.id), N = j.findIndex((S) => S.id === a.id);
171    if (n === -1 || N === -1) return;
172    const te = Oe(j, n, N);
173    O(te), xe.mutate(te.map((S) => S.id));
174  }, J = $.isPending || K.isPending, ee = Array.isArray(r?.documents) ? r.documents : [];
175  return e.jsxs("div", { className: "space-y-6", children: [e.jsxs("div", { className: "flex items-center justify-between flex-wrap gap-2", children: [e.jsxs("div", { children: [e.jsx("h1", { className: "text-2xl font-semibold", children: "Technologies" }), e.jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Manage the global technology library. Assign technologies to specific models in the Bike Models admin." })] }), !g && e.jsxs(p, { onClick: () => {
176    c(!0), f(null), u(C);
177  }, "data-testid": "button-add-technology", children: [e.jsx(et, { className: "w-4 h-4 mr-2" }), "Add Technology"] })] }), g && e.jsxs(oe, { children: [e.jsx(se, { children: e.jsx(ae, { className: "text-base", children: i ? "Edit Technology" : "New Technology" }) }), e.jsx(ie, { children: e.jsxs("form", { onSubmit: fe, className: "space-y-5", children: [e.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [e.jsxs("div", { className: "space-y-2", children: [e.jsx(y, { htmlFor: "tech-name", children: "Name *" }), e.jsx(F, { id: "tech-name", placeholder: "e.g. Helix\u2122 Adjustable Headset System", value: m.name, onChange: (t) => u((s) => ({ ...s, name: t.target.value })), "data-testid": "input-tech-name" })] }), e.jsxs("div", { className: "space-y-2", children: [e.jsx(y, { htmlFor: "tech-icon", children: "Icon" }), e.jsxs(Ne, { value: m.iconName, onValueChange: (t) => u((s) => ({ ...s, iconName: t })), children: [e.jsx(Se, { id: "tech-icon", "data-testid": "select-tech-icon", children: e.jsx(Ce, { children: e.jsxs("span", { className: "flex items-center gap-2", children: [e.jsx(q, { name: m.iconName, className: "w-4 h-4" }), m.iconName] }) }) }), e.jsx(De, { className: "max-h-60", children: nt.map((t) => e.jsx(Pe, { value: t, children: e.jsxs("span", { className: "flex items-center gap-2", children: [e.jsx(q, { name: t, className: "w-4 h-4" }), t] }) }, t)) })] })] })] }), e.jsxs("div", { className: "space-y-2", children: [e.jsx(y, { htmlFor: "tech-short", children: "Short Description" }), e.jsx(F, { id: "tech-short", placeholder: "~20 words shown on the Overview tab feature card", value: m.shortDe
177scription, onChange: (t) => u((s) => ({ ...s, shortDescription: t.target.value })), "data-testid": "input-tech-short-desc" })] }), e.jsxs("div", { className: "space-y-2", children: [e.jsx(y, { htmlFor: "tech-long", children: "Long Description" }), e.jsx("p", { className: "text-xs text-muted-foreground", children: "Separate sections with a blank line. Photo 2 appears after the first third, Photo 3 after the second third." }), e.jsx(Ee, { id: "tech-long", placeholder: `Paragraph one...
178
179Paragraph two...
180
181Paragraph three...`, rows: 6, value: m.longDescription, onChange: (t) => u((s) => ({ ...s, longDescription: t.target.value })), "data-testid": "input-tech-long-desc" })] }), i ? e.jsxs("div", { className: "border border-border rounded-md p-4 bg-muted/30 space-y-5", children: [e.jsx("p", { className: "text-sm font-medium", children: "Photography" }), e.jsx("p", { className: "text-xs text-muted-foreground -mt-3", children: "Photo 1 is the full-screen hero. Photo 2 breaks the body text as a cinematic full-width image. Photo 3 floats beside the final body section." }), e.jsxs("div", { className: "grid gap-5 sm:grid-cols-3", children: [e.jsx(L, { label: "Photo 1 \u2014 Hero", hint: "Full-screen opener. Landscape, 1600\xD7900px recommended.", photoUrl: r?.photoUrl, isPending: V.isPending || W.isPending, onUpload: (t) => V.mutate({ id: i, file: t }), onRemove: () => W.mutate(i), testIdUpload: "button-upload-tech-photo1" }), e.jsx(L, { label: "Photo 2 \u2014 Break", hint: "Full-width mid-body divider. Landscape, 1200\xD7600px recommended.", photoUrl: r?.photo2Url, isPending: z.isPending || X.isPending, onUpload: (t) => z.mutate({ id: i, file: t }), onRemove: () => X.mutate(i), testIdUpload: "button-upload-tech-photo2" }), e.jsx(L, { label: "Photo 3 \u2014 Feature", hint: "Floats beside final body copy. Portrait or square preferred.", photoUrl: r?.photo3Url, isPending: Q.isPending || Y.isPending, onUpload: (t) => Q.mutate({ id: i, file: t }), onRemove: () => Y.mutate(i), testIdUpload: "button-upload-tech-photo3" })] })] }) : e.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Save this technology first, then edit it to upload photos and documents." }), i && e.jsxs("div", { className: "border border-border rounded-md p-4 bg-muted/30 space-y-5", children: [e.jsxs("div", { children: [e.jsx("p", { className: "text-sm font-medium mb-0.5", children: "Video" }), e.jsx("p", { className: "text-xs text-muted-foreground", children: "Embed a YouTube video or upload a self-hosted file. The video appears on the technology detail page between the photo gallery and technical documents." })] }), e.jsxs("div", { className: "space-y-2", children: [e.jsx(y, { className: "text-xs text-muted-foreground uppercase tracking-wide", children: "YouTube" }), e.jsxs("div", { className: "flex gap-2 flex-wrap", children: [e.jsx(F, { placeholder: "https://youtube.com/watch?v=\u2026", value: A, onChange: (t) => H(t.target.value), className: "flex-1 min-w-48", "data-testid": "input-tech-youtube-url" }), e.jsx(p, { type: "button", variant: "outline", size: "default", disabled: k.isPending, onClick: () => {
182    const t = ge(A);
183    if (!t) {
184      o({ title: "Invalid URL", description: "Paste a valid YouTube watch or share URL", variant: "destructive" });
185      return;
186    }
187    k.mutate({ id: i, youtubeVideoId: t });
188  }, "data-testid": "button-set-tech-youtube", children: "Set" })] }), r?.youtubeVideoId && e.jsxs("div", { className: "flex items-center gap-2", children: [e.jsxs("p", { className: "text-xs text-muted-foreground", children: ["YouTube ID: ", e.jsx("span", { className: "font-mono", children: r.youtubeVideoId })] }), e.jsx(p, { type: "button", variant: "ghost", size: "icon", onClick: () => k.mutate({ id: i, youtubeVideoId: null }), disabled: k.isPending, "data-testid": "button-remove-tech-youtube", children: e.jsx(D, { className: "w-3.5 h-3.5" }) })] })] }), e.jsxs("div", { className: "space-y-2", children: [e.jsx(y, { className: "text-xs text-muted-foreground uppercase tracking-wide", children: "Self-hosted Video" }), e.jsx("div", { className: "flex items-center gap-3", children: r != null && r.videoStorageUrl ? e.jsxs(e.Fragment, { children: [e.jsx("div", { className: "w-28 rounded-md border border-border bg-zinc-900 flex items-center justify-center shrink-0", style: { height: "72px" }, children: e.jsx(ne, { className: "w-5 h-5 text-muted-foreground" }) }), e.jsx("div", { className: "flex-1 min-w-0", children: e.jsx("p", { className: "text-xs text-muted-foreground truncate", children: r.videoStorageUrl.split("/").pop() }) }), e.jsx("button", { type: "button", onClick: () => Z.mutate(i), disabled: Z.isPending, className: "w-5 h-5 rounded-full bg-destructive text-destructive-foreground flex items-center justify-center", title: "Remove video", "data-testid": "button-remove-tech-video", children: e.jsx(D, { className: "w-3 h-3" }) })] }) : e.jsxs("div", { className: "w-28 rounded-md border border-dashed border-border bg-muted flex flex-col items-center justify-center gap-1 shrink-0", style: { height: "72px" }, children: [e.jsx(ne, { className: "w-4 h-4 text-muted-foreground" }), e.jsx("span", { className: "text-xs text-muted-foreground", children: "N
188o video" })] }) }), e.jsx("input", { ref: G, type: "file", accept: "video/mp4,video/webm,video/quicktime", className: "hidden", onChange: (t) => {
189    var a;
190    const s = (a = t.target.files) == null ? void 0 : a[0];
191    s && i && M.mutate({ id: i, file: s }), t.target.value = "";
192  } }), e.jsxs(p, { type: "button", variant: "outline", disabled: M.isPending, onClick: () => {
193    var t;
194    return (t = G.current) == null ? void 0 : t.click();
195  }, "data-testid": "button-upload-tech-video", children: [e.jsx(ce, { className: "w-3.5 h-3.5 mr-2" }), M.isPending ? "Uploading\u2026" : r != null && r.videoStorageUrl ? "Replace Video" : "Upload Video"] }), e.jsx("p", { className: "text-xs text-muted-foreground", children: "MP4, WebM, or MOV \xB7 Max 200 MB \xB7 Self-hosted takes priority over YouTube if both are set." })] })] }), i && e.jsxs("div", { className: "border border-border rounded-md p-4 bg-muted/30 space-y-4", children: [e.jsxs("div", { children: [e.jsx("p", { className: "text-sm font-medium mb-0.5", children: "Technical Documents" }), e.jsx("p", { className: "text-xs text-muted-foreground", children: "PDFs, whitepapers, or engineering docs. These appear as a download section at the bottom of the technology page." })] }), ee.length > 0 && e.jsx("div", { className: "space-y-2", children: ee.map((t, s) => e.jsxs("div", { className: "flex items-center gap-3 p-3 rounded-md border border-border bg-background", children: [e.jsx(de, { className: "w-4 h-4 text-muted-foreground shrink-0" }), e.jsxs("div", { className: "flex-1 min-w-0", children: [e.jsx("p", { className: "text-sm font-medium truncate", children: t.title }), t.fileSize && e.jsx("p", { className: "text-xs text-muted-foreground", children: t.fileSize })] }), e.jsx("a", { href: t.url, target: "_blank", rel: "noopener noreferrer", className: "text-muted-foreground hover:text-foreground transition-colors", children: e.jsx(tt, { className: "w-3.5 h-3.5" }) }), e.jsx(p, { type: "button", variant: "ghost", size: "icon", onClick: () => _.mutate({ id: i, index: s }), disabled: _.isPending, "data-testid": `button-remove-doc-${s}`, children: e.jsx(D, { className: "w-3.5 h-3.5" }) })] }, s)) }), e.jsxs("div", { className: "flex flex-col gap-2", children: [e.jsx(y, { className: "text-xs text-muted-foreground", children: "Add Document" }), e.jsxs("div", { className: "flex gap-2 flex-wrap", children: [e.jsx(F, { placeholder: "Document title (e.g. Carbon Layup Whitepaper)", value: P, onChange: (t) => E(t.target.value), className: "flex-1 min-w-48", "data-testid": "input-doc-title" }), e.jsx("input", { ref: B, type: "file", accept: ".pdf,.doc,.docx", className: "hidden", onChange: (t) => {
196    var a;
197    const s = (a = t.target.files) == null ? void 0 : a[0];
198    s && i && T.mutate({ id: i, file: s, title: P || s.name }), t.target.value = "";
199  } }), e.jsxs(p, { type: "button", variant: "outline", disabled: T.isPending, onClick: () => {
200    var t;
201    return (t = B.current) == null ? void 0 : t.click();
202  }, "data-testid": "button-upload-doc", children: [e.jsx(ot, { className: "w-3.5 h-3.5 mr-2" }), T.isPending ? "Uploading\u2026" : "Upload PDF"] })] }), e.jsx("p", { className: "text-xs text-muted-foreground", children: "PDF, DOC, DOCX \xB7 Max 20 MB" })] })] }), e.jsxs("div", { className: "flex gap-2 justify-end", children: [e.jsxs(p, { type: "button", variant: "outline", onClick: je, "data-testid": "button-cancel-tech", children: [e.jsx(D, { className: "w-4 h-4 mr-2" }), "Cancel"] }), e.jsxs(p, { type: "submit", disabled: J || !m.name.trim(), "data-testid": "button-save-tech", children: [e.jsx(st, { className: "w-4 h-4 mr-2" }), J ? "Saving\u2026" : "Save"] })] })] }) })] }), e.jsxs(oe, { children: [e.jsxs(se, { children: [e.jsx(ae, { className: "text-base", children: "Technology Library" }), e.jsx(ke, { children: "Drag to reorder the default global order. Individual model priority can be set in the Bike Models admin." })] }), e.jsx(ie, { children: me ? e.jsx("p", { className: "text-sm text-muted-foreground", children: "Loading..." }) : j.length === 0 ? e.jsxs("div", { className: "text-center py-12 space-y-3", children: [e.jsx(Ue, { className: "w-10 h-10 mx-auto text-muted-foreground" }), e.jsx("p", { className: "text-sm text-muted-foreground", children: "N
202o technologies yet. Create the first one above." })] }) : e.jsx(Ie, { sensors: he, collisionDetection: Le, onDragEnd: be, children: e.jsx(qe, { items: j.map((t) => t.id), strategy: Ae, children: e.jsx("div", { className: "space-y-2", children: j.map((t, s) => e.jsx(dt, { tech: t, idx: s, expandedId: b, onToggleExpand: (a) => w(b === a ? null : a), onEdit: ve, onDelete: (a) => x(a) }, t.id)) }) }) }) })] }), e.jsx(Ve, { open: v !== null, onOpenChange: () => x(null), children: e.jsxs(ze, { children: [e.jsxs(Qe, { children: [e.jsx(We, { children: "Delete Technology" }), e.jsx(Xe, { children: "This will remove this technology from the library and unassign it from all models. This cannot be undone." })] }), e.jsxs(Ye, { children: [e.jsx(_e, { children: "Cancel" }), e.jsx(Ze, { onClick: () => v && pe.mutate(v), className: "bg-destructive text-destructive-foreground", children: "Delete" })] })] }) })] });
203}
204export { Yt as default };

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.