PageSourceSearch

https://sfa28.org/assets/products-tab-CY5QI0dJ.js

js sfa28.org collected 2026-10-02 23:19:32 UTC 18,764 bytes, 1 lines download raw bytes

1import{g as ye,l as ge,r as s,j as t,a as G,X as Y}from"./index-BXIYCH34.js";import{u as we}from"./useQuery-33O0-TlW.js";import{u as Q}from"./useMutation-CaTQ2lDw.js";import{C as ve}from"./collection-query-boundary-HNJ7HbN7.js";import{a as je,f as Ne,b as ke,S as Ce}from"./staged-upload-CBYAWhWM.js";import{P as Pe}from"./user-CUeQWGSb.js";import{C as Se}from"./check-CNKS9A8-.js";import{P as Ee}from"./pencil-CK2wVi61.js";import{T as Z}from"./trash-2-HvslD9GK.js";import{S as _}from"./star-FsG38SCn.js";import{C as De}from"./chevron-left-DRT3wVdo.js";import{C as Oe}from"./chevron-right-Dysr7DEt.js";import{I as $e}from"./image-BRULuHXG.js";import"./admin-CnOD71_6.js";import"./session-ui-C-POaWMJ.js";import"./requestTimeout-CGfxqkvX.js";import"./lock-CGjwE0E3.js";import"./proxy-Da6_ogfH.js";import"./clock-CeljP79C.js";import"./users-C6gUDcHj.js";import"./nav-f7zqxAva.js";import"./site-footer-DpUPRjsr.js";import"./chevron-down-CHEgG9Jw.js";import"./phone-C3AUpouz.js";import"./popover-Bos1Zpoa.js";import"./Combination-CN9PJC4j.js";import"./user-check-DCm-2E6u.js";import"./eye-ppzeFDN6.js";import"./download-BehACOWp.js";import"./api-kJjT7zS5.js";import"./useAuth-DRd7ykUu.js";import"./circle-check-9gsAb6fE.js";import"./chevron-up-CHCSXfV-.js";import"./index-BEWXqa1D.js";import"./circle-alert-HNTYnzEW.js";import"./circle-x-BVV6kqEU.js";import"./mail-CndlgLWQ.js";import"./inbox-DTKOlHSQ.js";import"./index-hr5J3RAn.js";import"./book-open-EcW-uZns.js";import"./file-text-D5WvXTle.js";import"./passwordComplexity-CzhVdltz.js";import"./resetUsernameHint-CCEidW8j.js";import"./mfa-enroll-screen-CdImjciC.js";import"./card-C3fDH9ZB.js";import"./mail-check-Cjrp3k4L.js";import"./respiteDates-CTWqz3Hq.js";import"./circle-check-big-B561_rrc.js";import"./calendar-D1udPFV5.js";import"./map-pin-DF8VN_UL.js";import"./external-link-lCn2PUNe.js";import"./graduation-cap-y6q4wK7k.js";import"./rotate-cw-BE5RcWLh.js";import"./camera-1srZCtsV.js";function z(o){return(o/100).toFixed(2)}function Te(o){if(!o)return"";const i=new Date(o);if(isNaN(i.getTime()))return"";const n=b=>String(b).padStart(2,"0");return`${i.getFullYear()}-${n(i.getMonth()+1)}-${n(i.getDate())}T${n(i.getHours())}:${n(i.getMinutes())}`}function Re({product:o,onUpload:i,onSetDefault:n,onDelete:b,onReorder:y,isUploading:g}){const w=[...o.photos].sort((l,u)=>l.displayOrder-u.displayOrder);return t.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[w.map((l,u)=>t.jsxs("div",{className:"relative w-14 h-14 group shrink-0",children:[t.jsx("img",{src:`/api/admin/products/photos/${l.id}`,alt:"",className:`w-14 h-14 object-cover rounded-sm bg-background/50 border ${l.isDefault?"border-amber-400":"border-border/20"}`}),l.isDefault&&t.jsx("span",{className:"absolute -top-1.5 -left-1.5 bg-amber-400 text-black rounded-full p-0.5",title:"Default image",children:t.jsx(_,{className:"w-2.5 h-2.5 fill-current"})}),t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center gap-0.5 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity rounded-sm",children:[t.jsxs("div",{className:"flex items-center gap-1",children:[!l.isDefault&&t.jsx("button",{onClick:()=>n(o.id,l.id),className:"text-amber-300 hover:text-amber-200","aria-label":"Set as default",title:"Set as default",children:t.jsx(_,{className:"w-3.5 h-3.5"})}),t.jsx("button",{onClick:()=>{confirm("Remove this photo?")&&b(o.id,l.id)},className:"text-red-400 hover:text-red-300","aria-label":"Remove photo",title:"Remove photo",children:t.jsx(Z,{className:"w-3.5 h-3.5"})})]}),t.jsxs("div",{className:"flex items-center gap-1",children:[t.jsx("button",{onClick:()=>y(o.id,l.id,"left"),disabled:u===0,className:"text-white/80 hover:text-white disabled:opacity-20 disabled:cursor-not-allowed","aria-label":"Move photo earlier",title:"Move earlier",children:t.jsx(De,{className:"w-3.5 h-3.5"})}),t.jsx("button",{onClick:()=>y(o.id,l.id,"right"),disabled:u===w.length-1,className:"text-white/80 hover:text-white disabled:opacity-20 disabled:cursor-not-allowed","aria-label":"Move photo later",title:"Move later",children:t.jsx(Oe,{className:"w-3.5 h-3.5"})})]})]})]},l.id)),t.jsxs("label",{className:"w-14 h-14 flex items-center justify-center border border-dashed border-border/40 hover:border-primary/40 cursor-pointer transition-colors rounded-sm bg-background/30 shrink-0",title:"Add photo",children:[g?t.jsx(G,{className:"w-4 h-4 text-primary animate-spin"}
1):t.jsx($e,{className:"w-4 h-4 text-muted-foreground/40 hover:text-primary transition-colors"}),t.jsx("input",{type:"file",accept:"image/*",className:"hidden",onChange:l=>{const u=l.target.files?.[0];u&&i(o.id,u),l.target.value=""}})]})]})}function Ft({authFetch:o}){const i=ye(),{toast:n}=ge(),[b,y]=s.useState(!1),[g,w]=s.useState(""),[l,u]=s.useState(""),[I,D]=s.useState(""),[O,$]=s.useState(null),[C,L]=s.useState(""),[T,U]=s.useState(""),[R,B]=s.useState(""),[ee,q]=s.useState(null),{rows:te,busy:F,run:P,cancel:re,retryableFiles:J}=je(),H=s.useRef(new Map),{data:V,isLoading:ae,isError:oe,isFetching:se,refetch:ie}=we({queryKey:["admin-products"],queryFn:async()=>{const e=await o("/api/admin/products");if(!e.ok)throw new Error("Failed to load products");return e.json()},refetchOnWindowFocus:!1}),v=V?.data??[],ne=s.useCallback(e=>{i.setQueryData(["admin-products"],r=>r&&{data:r.data.map(a=>a.id===e.id?e:a)})},[i]),M=Q({mutationFn:async e=>{const r=await o("/api/admin/products",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),a=await r.json();if(!r.ok)throw new Error(a.error??"Failed to create");return a},onSuccess:e=>{i.setQueryData(["admin-products"],r=>r?{data:[...r.data,e.data]}:{data:[e.data]}),y(!1),w(""),u(""),D(""),n({title:"Product added"})},onError:e=>{n({title:"Failed to add product",description:e.message,variant:"destructive"})}}),c=Q({mutationFn:async({id:e,data:r})=>{const a=await o(`/api/admin/products/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)}),d=await a.json();if(!a.ok)throw new Error(d.error??"Failed to update");return d},onSuccess:e=>{ne(e.data)},onError:e=>{n({title:"Failed to update product",description:e.message,variant:"destructive"})}}),A=Q({mutationFn:async e=>{const r=await o(`/api/admin/products/${e}`,{method:"DELETE"}),a=await r.json();if(!r.ok)throw new Error(a.error??"Failed to delete");return a},onSuccess:(e,r)=>{i.setQueryData(["admin-products"],a=>a&&{data:a.data.filter(d=>d.id!==r)}),n({title:"Product removed"})},onError:e=>{n({title:"Failed to remove product",description:e.message,variant:"destructive"})}}),S=s.useCallback(async(e,r,a,d)=>{const x=H.current.get(e);if(!x)throw new Error("Product photo upload target is unavailable");const h=await o(`/api/admin/products/${x}/photos`,{method:"POST"}),p=await h.json().catch(()=>({}));if(!h.ok||!p.uploadURL||!p.objectPath)throw new Error(p.error??"Upload URL failed");await Ne(p.uploadURL,e,r,d),a();const f=await o(`/api/admin/products/${x}/photos/confirm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({objectPath:p.objectPath,filename:e.name})});if(!f.ok)throw await ke(f,"Photo confirmation failed")},[o]),de=s.useCallback(async(e,r)=>{if(F)return;q(e),H.current.set(r,e),await P([r],S)&&(await i.invalidateQueries({queryKey:["admin-products"]}),n({title:"Photo uploaded"})),q(null)},[F,P,S,i,n]),le=s.useCallback(async()=>{await P(J,S)&&(await i.invalidateQueries({queryKey:["admin-products"]}),n({title:"Photo uploaded"}))},[P,J,S,i,n]),ce=s.useCallback(async(e,r)=>{try{if(!(await o(`/api/admin/products/${e}/photos/${r}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({isDefault:!0})})).ok)throw new Error("Failed to set default photo");await i.invalidateQueries({queryKey:["admin-products"]})}catch(a){n({title:"Failed to set default photo",description:a.message,variant:"destructive"})}},[o,i,n]),ue=s.useCallback(async(e,r)=>{try{if(!(await o(`/api/admin/products/${e}/photos/${r}`,{method:"DELETE"})).ok)throw new Error("Failed to remove photo");await i.invalidateQueries({queryKey:["admin-products"]})}catch(a){n({title:"Failed to remove photo",description:a.message,variant:"destructive"})}},[o,i,n]),me=s.useCallback(async(e,r,a)=>{const d=v.find(m=>m.id===e);if(!d)return;const x=[...d.photos].sort((m,N)=>m.displayOrder-N.displayOrder),h=x.findIndex(m=>m.id===r),p=a==="left"?h-1:h+1;if(h===-1||p<0||p>=x.length)return;const f=x[h],E=x[p];i.setQueryData(["admin-products"],m=>m&&{data:m.data.map(N=>N.id!==e?N:{...N,photos:N.photos.map(k=>k.id===f.id?{...k,displayOrder:E.displayOrder}:k.id===E.id?{...k,displayOrder:f.displayOrder}:k)})});try{await Promise.all([o(`/api/admin/products/${e}/photos/${f.id}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({displayOrder:E.displayOrder})}),o(`/api/admin/products/${e}/photos/${E.id}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({displayOrder:f.displayOrder})})]),await i.invalidateQueries({queryKey:["admin-products"]})}catch(m){n({title:"Failed to reorder photo",description:m.message,variant:"destructive"}),await i.invalidateQueries({queryKey:["admin-products"]})}},[o,i,v,n]),W=s.useCallback(e=>{$(e.id),L(e.name),U(e.description??""),B(z(e.priceCents))},[]),K=s.useCallback(()=>$(null),[]),j=s.useCallback(e=>{const r=Math.round(parseFloat(R||"0")*100);if(!C.trim()||isNaN(r)||r<0){n({title:"Invalid name or price",variant:"destructive"});return}c.mutate({id:e,data:{name:C.trim(),description:T.trim()||null,priceCents:r}}),$(null)},[C,T,R,c,n]),pe=s.useCallback((e,r)=>{c.mutate({id:e,data:{active:r}})},[c]),xe=s.useCallback((e,r)=>{c.mutate({id:e,data:{inventoryTracking:r}})},[c]),he=s.useCallback((e,r,a)=>{const d=parseInt(a,10);!isNaN(d)&&d!==r&&d>=0&&c.mutate({id:e,data:{inventoryCount:d}})},[c]),X=s.useCallback((e,r)=>{const a=r?new Date(r).toISOString():null;c.mutate({id:e,data:{salesCutoffAt:a}})},[c]),fe=s.useCallback((e,r,a)=>{const d=parseInt(a,10);!isNaN(d)&&d!==r&&c.mutate({id:e,data:{displayOrder:d}})},[c]),be=s.useCallback((e,r)=>{confirm(`Delete "${r}"? This cannot be undone.`)&&A.mutate(e)},[A]);return t.jsxs("div",{className:"space-y-4",children:[t.jsxs("div",{className:"flex flex-wrap items-center gap-3 justify-between pb-2 border-b border-border/20",children:[t.jsxs("div",{children:[t.jsx("h2",{className:"text-sm font-semibold text-white",children:"Products"}
1),t.jsxs("p",{className:"text-xs text-muted-foreground/50",children:[v.length," product",v.length!==1?"s":""]})]}),t.jsxs("button",{onClick:()=>y(!0),disabled:b,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-mono uppercase tracking-wide border border-primary/40 text-primary hover:bg-primary/10 transition-colors disabled:opacity-40",children:[t.jsx(Pe,{className:"w-3 h-3"})," Add Product"]})]}),t.jsx(Ce,{rows:te,busy:F,onCancel:re,onRetry:()=>{le()},confirmationLabel:"Scanning photo… awaiting server confirmation",publishedLocation:"product photos"}),b&&t.jsxs("div",{className:"border border-primary/30 bg-primary/5 p-4 flex flex-wrap gap-3 items-end",children:[t.jsxs("div",{className:"flex flex-col gap-1 flex-1 min-w-[160px]",children:[t.jsx("label",{className:"text-xs font-mono uppercase text-muted-foreground/60",children:"Name"}),t.jsx("input",{autoFocus:!0,value:g,onChange:e=>w(e.target.value),placeholder:"Product name",className:"bg-background border border-border/40 text-sm text-white px-3 py-1.5 w-full placeholder:text-muted-foreground/30 focus:outline-none focus:border-primary/50"})]}),t.jsxs("div",{className:"flex flex-col gap-1 min-w-[100px]",children:[t.jsx("label",{className:"text-xs font-mono uppercase text-muted-foreground/60",children:"Price ($)"}),t.jsx("input",{value:l,onChange:e=>u(e.target.value),placeholder:"0.00",inputMode:"decimal",className:"bg-background border border-border/40 text-sm text-white px-3 py-1.5 w-full placeholder:text-muted-foreground/30 focus:outline-none focus:border-primary/50"})]}),t.jsxs("div",{className:"flex flex-col gap-1 flex-1 min-w-[200px]",children:[t.jsx("label",{className:"text-xs font-mono uppercase text-muted-foreground/60",children:"Description (optional)"}),t.jsx("input",{value:I,onChange:e=>D(e.target.value),placeholder:"Short description",className:"bg-background border border-border/40 text-sm text-white px-3 py-1.5 w-full placeholder:text-muted-foreground/30 focus:outline-none focus:border-primary/50"})]}),t.jsxs("div",{className:"flex gap-2 pb-0.5",children:[t.jsxs("button",{onClick:()=>{const e=Math.round(parseFloat(l||"0")*100);if(!g.trim()||isNaN(e)||e<0){n({title:"Enter a name and valid price",variant:"destructive"});return}M.mutate({name:g.trim(),priceCents:e,description:I.trim()||void 0})},disabled:M.isPending||!g.trim(),className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-mono uppercase tracking-wide border border-primary/40 text-primary hover:bg-primary/10 transition-colors disabled:opacity-40",children:[M.isPending?t.jsx(G,{className:"w-3 h-3 animate-spin"}):t.jsx(Se,{className:"w-3 h-3"}),"Save"]}),t.jsxs("button",{onClick:()=>{y(!1),w(""),u(""),D("")},className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-mono uppercase tracking-wide border border-border/30 text-muted-foreground hover:text-white transition-colors",children:[t.jsx(Y,{className:"w-3 h-3"})," Cancel"]})]})]}),t.jsx(ve,{collectionKey:"products",itemLabel:"products",isLoading:ae,isError:oe,hasData:V!==void 0,isFetching:se,onRetry:ie,children:v.length===0?t.jsx("p",{className:"text-xs text-muted-foreground/50 py-6 text-center",children:"No products yet. Add one above."}):t.jsx("div",{className:"divide-y divide-border/10 border border-border/10",children:[...v].sort((e,r)=>e.displayOrder-r.displayOrder).map(e=>t.jsxs("div",{className:"px-4 py-3 space-y-2.5",children:[t.jsxs("div",{className:"flex flex-wrap items-start gap-3",children:[t.jsx("div",{className:"flex-1 min-w-[160px] flex items-center gap-1.5",children:O===e.id?t.jsx("input",{autoFocus:!0,value:C,onChange:r=>L(r.target.value),onBlur:()=>j(e.id),onKeyDown:r=>{r.key==="Enter"&&j(e.id),r.key==="Escape"&&K()},className:"flex-1 bg-background border border-primary/40 text-sm text-white px-2 py-0.5 focus:outline-none"}):t.jsxs(t.Fragment,{children:[t.jsx("span",{className:"text-sm text-white truncate",children:e.name}),t.jsx("button",{onClick:()=>W(e),className:"shrink-0 text-muted-foreground/30 hover:text-amber-300 transition-colors","aria-label":"Edit",children:t.jsx(Ee,{className:"w-3 h-3"})})]})}),t.jsxs("div",{className:"flex items-center gap-1 shrink-0",children:[t.jsx("span",{className:"text-xs text-muted-foreground
1/40",children:"$"}),O===e.id?t.jsx("input",{value:R,onChange:r=>B(r.target.value),onBlur:()=>j(e.id),onKeyDown:r=>{r.key==="Enter"&&j(e.id),r.key==="Escape"&&K()},inputMode:"decimal",className:"w-20 bg-background border border-primary/40 text-xs text-white px-2 py-0.5 focus:outline-none"}):t.jsx("span",{className:"text-xs text-amber-300 font-mono",children:z(e.priceCents)})]}),t.jsxs("div",{className:"flex items-center gap-1 shrink-0",children:[t.jsx("span",{className:"text-xs text-muted-foreground/40 hidden sm:inline",children:"Order"}),t.jsx("input",{type:"number",defaultValue:e.displayOrder,onBlur:r=>fe(e.id,e.displayOrder,r.target.value),onKeyDown:r=>{r.key==="Enter"&&r.currentTarget.blur()},className:"w-12 bg-background border border-border/30 text-xs text-white text-center px-1 py-0.5 focus:outline-none focus:border-primary/50 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none","aria-label":"Display order"},`order-${e.id}-${e.displayOrder}`)]}),t.jsxs("label",{className:"flex items-center gap-1.5 cursor-pointer shrink-0",children:[t.jsx("input",{type:"checkbox",checked:e.active,onChange:r=>pe(e.id,r.target.checked),className:"w-3.5 h-3.5 accent-primary"}),t.jsx("span",{className:"text-xs text-muted-foreground/60",children:"Visible"})]}),e.soldOut&&t.jsx("span",{className:"text-xs font-mono uppercase text-red-400 border border-red-400/30 px-1.5 py-0.5",children:"Sold Out"}),e.cutoffPassed&&t.jsx("span",{className:"text-xs font-mono uppercase text-orange-400 border border-orange-400/30 px-1.5 py-0.5",children:"Sales Ended"}),t.jsx("button",{onClick:()=>be(e.id,e.name),disabled:A.isPending,className:"shrink-0 text-muted-foreground/30 hover:text-red-400 transition-colors disabled:opacity-40 ml-auto","aria-label":"Delete product",children:t.jsx(Z,{className:"w-3.5 h-3.5"})})]}),O===e.id?t.jsx("input",{value:T,onChange:r=>U(r.target.value),onBlur:()=>j(e.id),onKeyDown:r=>{r.key==="Enter"&&j(e.id),r.key==="Escape"&&K()},placeholder:"Description",className:"w-full bg-background border border-primary/40 text-xs text-white px-2 py-1 focus:outline-none placeholder:text-muted-foreground/30"}):t.jsx("button",{onClick:()=>W(e),className:`text-xs text-left transition-colors ${e.description?"text-muted-foreground/60 hover:text-white":"text-muted-foreground/30 hover:text-amber-300 italic"}`,children:e.description||"Add description…"}),t.jsxs("div",{className:"flex flex-wrap items-center gap-4 pt-1",children:[t.jsxs("label",{className:"flex items-center gap-1.5 cursor-pointer shrink-0",children:[t.jsx("input",{type:"checkbox",checked:e.inventoryTracking,onChange:r=>xe(e.id,r.target.checked),className:"w-3.5 h-3.5 accent-primary"}),t.jsx("span",{className:"text-xs text-muted-foreground/60",children:"Track inventory"})]}),e.inventoryTracking&&t.jsxs("div",{className:"flex items-center gap-1.5",children:[t.jsx("span",{className:"text-xs text-muted-foreground/40",children:"In stock"}),t.jsx("input",{type:"number",min:0,defaultValue:e.inventoryCount??0,onBlur:r=>he(e.id,e.inventoryCount,r.target.value),onKeyDown:r=>{r.key==="Enter"&&r.currentTarget.blur()},className:"w-16 bg-background border border-border/30 text-xs text-white text-center px-1 py-0.5 focus:outline-none focus:border-primary/50"},`inv-${e.id}-${e.inventoryCount}`)]}),t.jsxs("div",{className:"flex items-center gap-1.5",children:[t.jsx("span",{className:"text-xs text-muted-foreground/40",children:"Sales cutoff"}),t.jsx("input",{type:"datetime-local",defaultValue:Te(e.salesCutoffAt),onBlur:r=>X(e.id,r.target.value),className:"bg-background border border-border/30 text-xs text-white px-2 py-0.5 focus:outline-none focus:border-primary/50"},`cutoff-${e.id}-${e.salesCutoffAt}`),e.salesCutoffAt&&t.jsx("button",{onClick:()=>X(e.id,""),className:"text-xs text-muted-foreground/40 hover:text-red-400 transition-colors",title:"Clear cutoff",children:t.jsx(Y,{className:"w-3 h-3"})})]})]}),t.jsx(Re,{product:e,onUpload:de,onSetDefault:ce,onDelete:ue,onReorder:me,isUploading:ee===e.id})]},e.id))})})]})}export{Ft as ProductsTab,Ft 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.