Files
tehran_taj/apps/user-app/dist/assets/cartStore-qaurfaGv.js
T
s.esmaeiliandClaude Opus 5 5ad1083aee parity(apps+api): complete_profile in both apps [ref: henza_server Major/CustomerController::complete]
Ports "تکمیل پروفایل" end to end (D22).

API
- POST customer/me/complete-profile — three required document ids, the
  customer taken from the token, verification step set to the reference's
  LIVE value (approved outright; its `= 1` line is commented out at :83 in
  favour of `= 2` at :85). Both SMS messages byte-identical and non-blocking
  per D20.
- POST customer/me/documents — the reference let the app upload through the
  same file/create the admin used, inside its auth:api-customer group
  (routes/api.php:95); the target's copy is admin-guarded, so the page could
  not have uploaded at all. Reuses AdminUploadsService unchanged, so both
  paths share one size limit, MIME whitelist and naming scheme (UPL-01).
- PROFILE_ADMIN_MOBILES replaces the reference's four hardcoded personal
  numbers; blank means nobody is notified and the upload still succeeds.

Three reference behaviours deliberately NOT ported, all in ISSUES.md:
- SEC-14: `$customer->update($request->all())` mass-assigned the whole request
  body, letting the app write its own credit limit, making charge, gold debt,
  active flag or mobile. Replaced with a three-field whitelist.
- SEC-15: four real personal mobiles hardcoded in the source.
- APP-03: refresh()'s unterminated setInterval page reload.

Apps (both, template+script byte-identical, per-app styling only)
- CompleteProfileView + SingleImageUploader
- route /complete_profile, redirecting home once the documents are in, as the
  reference's guard did (router/index.ts:266)
- the entry point on the profile screen, shown only while they are missing
  (profile.vue:96-102)

Also (D23): JWT_CUSTOMER_SECRET now warns loudly at boot when unset. It falls
back to JWT_SECRET, which signs app and admin tokens with the same key and
leaves the `typ` check as the only barrier — and an expert's app token carries
their AdminUser id in `sub`. Warn rather than refuse to boot (D19, lowest
risk); DELIVERY.md lists it as the first pre-release step (REL-03).

Verified: build 5/5 · jest 45/45 · complete-profile 38/38 (new) ·
expert login 36/36 · apps smoke 43/43 · admin smoke 43/43 ·
B2B scan 34 ok/0 broken · typecheck clean on all four apps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 01:42:07 +03:30

2 lines
2.7 KiB
JavaScript

import{g as d,u as C,h as a,m as F,q as S}from"./index-0lMAZRQB.js";function W(t){return(t.qty??0)>0||!!t.isPreOrder}function Q(t){if(!t||t.length===0)return null;const e=t.filter(W);return(e.length?e:t).reduce((s,r)=>parseFloat(r.defaultWagePercent)<parseFloat(s.defaultWagePercent)?r:s)}function M(t,e){const i=d(t),s=a(()=>i.value.id),r=a(()=>i.value.title),n=a(()=>i.value.variants),o=d(e??Q(C(n))),y=a(()=>{var u,f,m,p;return((f=(u=o.value)==null?void 0:u.image)==null?void 0:f.url)??((p=(m=i.value.images)==null?void 0:m[0])==null?void 0:p.url)}),g=a(()=>o.value?parseFloat(o.value.rashWeightGrams):0),x=a(()=>o.value?parseFloat(o.value.defaultWagePercent):null),q=d(null),l=a(()=>{var u;return((u=o.value)==null?void 0:u.qty)??0}),h=a(()=>{var u;return((u=o.value)==null?void 0:u.isPreOrder)??!1}),P=a(()=>l.value>0||h.value),O=a(()=>i.value.productCode??null),_=d(!1);return{product:i,image:y,id:s,title:r,productCode:O,variants:n,selectedVariant:o,min_gram:g,wage:x,hasDiscountWage:q,qty:l,isPreOrder:h,is_exist:P,has_warranty:_}}const v="appCart";function w(){try{const t=localStorage.getItem(v);return t?JSON.parse(t):[]}catch{return[]}}function c(t){localStorage.setItem(v,JSON.stringify(t))}const N=F("cart",{state:()=>({items:w()}),actions:{async init(){try{const t=await S.listProducts(),e=[];for(const i of this.items){const s=t.find(n=>n.id===i.pid),r=s==null?void 0:s.variants.find(n=>n.id===i.vid);s&&r&&e.push({...i,product:s})}this.items=e,c(this.items)}catch(t){console.log(t)}},addToCart(t){var s,r;const e=(r=(s=t.product)==null?void 0:s.variants)==null?void 0:r.find(n=>n.id===t.vid);if(!e||!((e.qty??0)>0||e.isPreOrder))return!1;const i=this.find_index(t);return i===-1?this.items.push({...t,qty:this.capQty(t.qty,e)}):(this.items[i].product=t.product,this.items[i].qty=this.capQty(t.qty,e)),c(this.items),!0},capQty(t,e){const i=Math.max(1,Math.floor(t)||1);if(e.isPreOrder)return i;const s=e.qty??0;return s>0?Math.min(i,s):i},maxQty(t){const e=this.find_index(t);if(e===-1)return null;const i=this.variant(this.items[e]);return!i||i.isPreOrder?null:(i.qty??0)>0?i.qty:null},remove(t){const e=this.find_index(t);e>-1&&this.items.splice(e,1),c(this.items)},updateFeild(t,e,i,s){if(!this.isInCart(i))return!1;const r=e??this.find_index(i);if(s==="qty"){const n=this.variant(this.items[r]);t=n?this.capQty(Number(t),n):Math.max(1,Number(t)||1)}return this.items[r][s]=t,c(this.items),!0},find_index(t){return this.items.findIndex(e=>e.pid===t.pid&&e.vid===t.vid)},isInCart(t){return this.find_index(t)>-1},variant(t){return t.product.variants.find(e=>e.id===t.vid)},total(){return this.items.reduce((t,e)=>{const i=this.variant(e);return t+(i?parseFloat(i.rashWeightGrams)*e.qty:0)},0).toFixed(2)},clear(){this.items=[],c(this.items)}}});export{N as a,W as i,M as u};