@primestyleai/tryon 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/react/index.js +120 -73
  2. package/package.json +1 -1
@@ -189,6 +189,7 @@ function PrimeStyleTryonInner({
189
189
  const [dragOver, setDragOver] = useState(false);
190
190
  const [sizingMethod, setSizingMethod] = useState(null);
191
191
  const [sizingResult, setSizingResult] = useState(null);
192
+ const [sizingLoading, setSizingLoading] = useState(false);
192
193
  const [sizeGuide, setSizeGuide] = useState(null);
193
194
  const [sizeGuideFetching, setSizeGuideFetching] = useState(false);
194
195
  const sizeGuideFetchedRef = useRef(false);
@@ -348,6 +349,7 @@ function PrimeStyleTryonInner({
348
349
  setErrorMessage(null);
349
350
  setSizingMethod(null);
350
351
  setSizingResult(null);
352
+ setSizingLoading(false);
351
353
  setSizeGuide(null);
352
354
  setProfileSaved(false);
353
355
  formRef.current = {};
@@ -444,9 +446,16 @@ function PrimeStyleTryonInner({
444
446
  if (formRef.current.fitPreference) m.fitPreference = formRef.current.fitPreference;
445
447
  payload.measurements = m;
446
448
  } else {
449
+ const qHeight = heightUnit === "ft" ? ftInToCm(parseFloat(formRef.current.heightFeet || "0"), parseFloat(formRef.current.heightInches || "0")) : parseFloat(formRef.current.height || "0");
450
+ const qWeight = weightUnit === "lbs" ? lbsToKg(parseFloat(formRef.current.weight || "0")) : parseFloat(formRef.current.weight || "0");
451
+ if (!qHeight || qHeight < 100 || !qWeight || qWeight < 30) {
452
+ console.warn("[PrimeStyle] Skipping sizing — invalid height/weight:", { qHeight, qWeight });
453
+ setSizingLoading(false);
454
+ return;
455
+ }
447
456
  payload.quickEstimate = {
448
- heightCm: heightUnit === "ft" ? ftInToCm(parseFloat(formRef.current.heightFeet || "0"), parseFloat(formRef.current.heightInches || "0")) : parseFloat(formRef.current.height || "0"),
449
- weightKg: weightUnit === "lbs" ? lbsToKg(parseFloat(formRef.current.weight || "0")) : parseFloat(formRef.current.weight || "0"),
457
+ heightCm: qHeight,
458
+ weightKg: qWeight,
450
459
  gender: formRef.current.gender || "male"
451
460
  };
452
461
  }
@@ -459,8 +468,13 @@ function PrimeStyleTryonInner({
459
468
  if (res.ok) {
460
469
  const data = await res.json();
461
470
  setSizingResult(data);
471
+ } else {
472
+ console.warn("[PrimeStyle] Sizing API error:", res.status, await res.text().catch(() => ""));
462
473
  }
463
- } catch {
474
+ } catch (err) {
475
+ console.warn("[PrimeStyle] Sizing request failed:", err);
476
+ } finally {
477
+ setSizingLoading(false);
464
478
  }
465
479
  }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle]);
466
480
  const handleSubmit = useCallback(async () => {
@@ -473,7 +487,10 @@ function PrimeStyleTryonInner({
473
487
  }
474
488
  completedRef.current = false;
475
489
  setView("processing");
476
- if (sizingMethod) submitSizing();
490
+ if (sizingMethod) {
491
+ setSizingLoading(true);
492
+ submitSizing();
493
+ }
477
494
  try {
478
495
  const modelImage = await compressImage(selectedFile);
479
496
  const response = await apiRef.current.submitTryOn(modelImage, productImage);
@@ -536,6 +553,7 @@ function PrimeStyleTryonInner({
536
553
  setErrorMessage(null);
537
554
  setSizingMethod(null);
538
555
  setSizingResult(null);
556
+ setSizingLoading(false);
539
557
  setProfileSaved(false);
540
558
  setView("upload");
541
559
  }, [previewUrl, cleanupJob]);
@@ -733,75 +751,71 @@ function PrimeStyleTryonInner({
733
751
  ] });
734
752
  }
735
753
  function UploadView() {
736
- return /* @__PURE__ */ jsxs(Fragment, { children: [
737
- profiles.length > 0 && /* @__PURE__ */ jsx("div", { className: "ps-tryon-profile-bar", children: /* @__PURE__ */ jsxs("select", { className: "ps-tryon-profile-select", value: activeProfileId || "", onChange: (e) => {
738
- if (e.target.value) applyProfile(e.target.value);
739
- }, children: [
740
- /* @__PURE__ */ jsx("option", { value: "", children: "Auto-fill from saved profile..." }),
741
- profiles.map((p) => /* @__PURE__ */ jsxs("option", { value: p.id, children: [
742
- p.name,
743
- " (",
744
- p.gender === "female" ? "Women's" : "Men's",
745
- ")"
746
- ] }, p.id))
747
- ] }) }),
748
- selectedFile && previewUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
749
- /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-preview", cn.preview), children: [
750
- /* @__PURE__ */ jsx("img", { src: previewUrl, alt: "Your photo", className: cn.previewImage }),
751
- /* @__PURE__ */ jsx("button", { onClick: handleRemovePreview, className: cx("ps-tryon-preview-remove", cn.removeButton), children: "×" })
752
- ] }),
753
- /* @__PURE__ */ jsxs("button", { onClick: () => setView("sizing-choice"), className: cx("ps-tryon-submit", cn.submitButton), children: [
754
- "Continue to Sizing ",
755
- /* @__PURE__ */ jsx(ArrowRightIcon, {})
756
- ] })
757
- ] }) : /* @__PURE__ */ jsxs(
758
- "div",
759
- {
760
- className: cx(`ps-tryon-upload${dragOver ? " ps-tryon-drag-over" : ""}`, cn.uploadZone),
761
- onClick: () => fileInputRef.current?.click(),
762
- onDragOver: (e) => {
763
- e.preventDefault();
764
- setDragOver(true);
765
- },
766
- onDragLeave: () => setDragOver(false),
767
- onDrop: (e) => {
768
- e.preventDefault();
769
- setDragOver(false);
770
- const f = e.dataTransfer?.files?.[0];
771
- if (f) handleFileSelect(f);
772
- },
773
- children: [
774
- /* @__PURE__ */ jsx(
775
- "input",
776
- {
777
- ref: fileInputRef,
778
- type: "file",
779
- accept: "image/jpeg,image/png,image/webp",
780
- style: { display: "none" },
781
- onChange: (e) => {
782
- const f = e.target.files?.[0];
783
- if (f) handleFileSelect(f);
784
- }
754
+ return /* @__PURE__ */ jsx(Fragment, { children: selectedFile && previewUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
755
+ /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-preview", cn.preview), children: [
756
+ /* @__PURE__ */ jsx("img", { src: previewUrl, alt: "Your photo", className: cn.previewImage }),
757
+ /* @__PURE__ */ jsx("button", { onClick: handleRemovePreview, className: cx("ps-tryon-preview-remove", cn.removeButton), children: "×" })
758
+ ] }),
759
+ /* @__PURE__ */ jsxs("button", { onClick: () => setView("sizing-choice"), className: cx("ps-tryon-submit", cn.submitButton), children: [
760
+ "Continue to Sizing ",
761
+ /* @__PURE__ */ jsx(ArrowRightIcon, {})
762
+ ] })
763
+ ] }) : /* @__PURE__ */ jsxs(
764
+ "div",
765
+ {
766
+ className: cx(`ps-tryon-upload${dragOver ? " ps-tryon-drag-over" : ""}`, cn.uploadZone),
767
+ onClick: () => fileInputRef.current?.click(),
768
+ onDragOver: (e) => {
769
+ e.preventDefault();
770
+ setDragOver(true);
771
+ },
772
+ onDragLeave: () => setDragOver(false),
773
+ onDrop: (e) => {
774
+ e.preventDefault();
775
+ setDragOver(false);
776
+ const f = e.dataTransfer?.files?.[0];
777
+ if (f) handleFileSelect(f);
778
+ },
779
+ children: [
780
+ /* @__PURE__ */ jsx(
781
+ "input",
782
+ {
783
+ ref: fileInputRef,
784
+ type: "file",
785
+ accept: "image/jpeg,image/png,image/webp",
786
+ style: { display: "none" },
787
+ onChange: (e) => {
788
+ const f = e.target.files?.[0];
789
+ if (f) handleFileSelect(f);
785
790
  }
786
- ),
787
- /* @__PURE__ */ jsx(UploadIcon, {}),
788
- /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-text", cn.uploadText), children: "Drop or upload your full body photo!" }),
789
- /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-hint", cn.uploadHint), children: "JPEG, PNG or WebP (max 10MB)" })
790
- ]
791
- }
792
- )
793
- ] });
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsx(UploadIcon, {}),
794
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-text", cn.uploadText), children: "Drop or upload your full body photo!" }),
795
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-hint", cn.uploadHint), children: "JPEG, PNG or WebP (max 10MB)" })
796
+ ]
797
+ }
798
+ ) });
794
799
  }
795
800
  function SizingChoiceView() {
796
801
  const sgAvailable = sizeGuide?.found === true;
797
- const sgDisabled = !sizeGuideFetching && !sgAvailable;
798
- const disabledClass = sgDisabled ? " ps-tryon-choice-disabled" : sizeGuideFetching ? " ps-tryon-choice-loading" : "";
802
+ const sgChecked = !sizeGuideFetching && sizeGuide !== null;
803
+ if (sizeGuideFetching) {
804
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-sizing-choice", children: /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sg-checking", children: [
805
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sg-checking-spinner" }),
806
+ /* @__PURE__ */ jsx("h3", { className: "ps-tryon-section-title", children: "Checking size guide..." }),
807
+ /* @__PURE__ */ jsx("p", { className: "ps-tryon-sg-checking-sub", children: "Looking for size chart data for this product" })
808
+ ] }) });
809
+ }
799
810
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sizing-choice", children: [
800
811
  /* @__PURE__ */ jsx("h3", { className: "ps-tryon-section-title", children: "How would you like to find your size?" }),
801
- sgDisabled && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sg-notice", children: "Size guide is not available for this product" }),
802
- sizeGuideFetching && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sg-notice ps-tryon-sg-loading", children: "Checking size guide availability..." }),
812
+ sgChecked && !sgAvailable && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sg-notice", children: "Size guide is not available for this product — sizing will use standard measurements" }),
813
+ sgChecked && sgAvailable && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sg-notice ps-tryon-sg-found", children: [
814
+ /* @__PURE__ */ jsx(CheckIcon, { size: 14 }),
815
+ " Size guide found for this product"
816
+ ] }),
803
817
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-choice-cards", children: [
804
- /* @__PURE__ */ jsxs("button", { className: `ps-tryon-choice-card${disabledClass}`, disabled: sgDisabled || sizeGuideFetching, onClick: () => {
818
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
805
819
  setSizingMethod("exact");
806
820
  setView("sizing-form");
807
821
  }, children: [
@@ -812,7 +826,7 @@ function PrimeStyleTryonInner({
812
826
  ] }),
813
827
  /* @__PURE__ */ jsx("span", { className: "ps-tryon-choice-badge", children: "Best accuracy" })
814
828
  ] }),
815
- /* @__PURE__ */ jsxs("button", { className: `ps-tryon-choice-card${disabledClass}`, disabled: sgDisabled || sizeGuideFetching, onClick: () => {
829
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
816
830
  setSizingMethod("quick");
817
831
  setView("sizing-form");
818
832
  }, children: [
@@ -839,6 +853,17 @@ function PrimeStyleTryonInner({
839
853
  const isFemale = formGender === "female";
840
854
  const isCm = sizingUnit === "cm";
841
855
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sizing-form", children: [
856
+ profiles.length > 0 && /* @__PURE__ */ jsx("div", { className: "ps-tryon-profile-bar", children: /* @__PURE__ */ jsxs("select", { className: "ps-tryon-profile-select", value: activeProfileId || "", onChange: (e) => {
857
+ if (e.target.value) applyProfile(e.target.value);
858
+ }, children: [
859
+ /* @__PURE__ */ jsx("option", { value: "", children: "Auto-fill from saved profile..." }),
860
+ profiles.map((p) => /* @__PURE__ */ jsxs("option", { value: p.id, children: [
861
+ p.name,
862
+ " (",
863
+ p.gender === "female" ? "Women's" : "Men's",
864
+ ")"
865
+ ] }, p.id))
866
+ ] }) }),
842
867
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
843
868
  /* @__PURE__ */ jsx("label", { children: "I'm shopping for" }),
844
869
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-unit-toggle", children: [
@@ -934,11 +959,16 @@ function PrimeStyleTryonInner({
934
959
  ] });
935
960
  }
936
961
  function ResultView() {
937
- const hasBoth = !!resultImageUrl && !!sizingResult;
962
+ const hasSizing = !!sizingResult || sizingLoading;
963
+ const hasBoth = !!resultImageUrl && hasSizing;
938
964
  const [profileName, setProfileName] = useState("");
939
965
  return /* @__PURE__ */ jsxs("div", { className: `ps-tryon-result-layout${hasBoth ? " ps-tryon-result-split" : ""}`, children: [
940
966
  resultImageUrl && /* @__PURE__ */ jsx("div", { className: "ps-tryon-result-image-col", children: /* @__PURE__ */ jsx("img", { src: resultImageUrl, alt: "Try-on result", className: cn.resultImage }) }),
941
967
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-result-info-col", children: [
968
+ sizingLoading && !sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-recommend ps-tryon-sizing-loading", children: [
969
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner" }),
970
+ /* @__PURE__ */ jsx("p", { className: "ps-tryon-size-reasoning", children: "Analyzing your size..." })
971
+ ] }),
942
972
  sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-recommend", children: [
943
973
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-badge", children: sizingResult.recommendedSize }),
944
974
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-confidence", children: [
@@ -1312,15 +1342,25 @@ const STYLES = `
1312
1342
  padding: 3px 10px; border-radius: 20px; flex-shrink: 0;
1313
1343
  background: rgba(187,148,92,0.12); color: #bb945c; font-size: 10px; font-weight: 600;
1314
1344
  }
1315
- .ps-tryon-choice-disabled { opacity: 0.4; cursor: not-allowed !important; pointer-events: none; }
1316
- .ps-tryon-choice-disabled:hover { border-color: #333; transform: none; box-shadow: none; }
1317
- .ps-tryon-choice-disabled::before { display: none; }
1318
- .ps-tryon-choice-loading { opacity: 0.5; cursor: wait !important; pointer-events: none; }
1319
1345
  .ps-tryon-sg-notice {
1320
1346
  font-size: 12px; color: #999; text-align: center; padding: 10px 14px;
1321
1347
  margin-bottom: 12px; border: 1px solid #333; border-radius: 10px; background: #1a1b1a;
1322
1348
  }
1323
- .ps-tryon-sg-loading { border-style: dashed; }
1349
+ .ps-tryon-sg-found {
1350
+ color: #4ade80; border-color: rgba(74,222,128,0.2); background: rgba(74,222,128,0.05);
1351
+ display: flex; align-items: center; justify-content: center; gap: 6px;
1352
+ }
1353
+ .ps-tryon-sg-found svg { stroke: #4ade80; }
1354
+ .ps-tryon-sg-checking {
1355
+ display: flex; flex-direction: column; align-items: center; padding: 40px 20px; text-align: center;
1356
+ }
1357
+ .ps-tryon-sg-checking-spinner {
1358
+ width: 40px; height: 40px; border: 3px solid #333;
1359
+ border-top-color: #bb945c; border-radius: 50%;
1360
+ animation: ps-spin 0.8s linear infinite; margin-bottom: 20px;
1361
+ }
1362
+ .ps-tryon-sg-checking .ps-tryon-section-title { margin-bottom: 6px; }
1363
+ .ps-tryon-sg-checking-sub { font-size: 13px; color: #999; margin: 0; }
1324
1364
 
1325
1365
  /* Sizing form */
1326
1366
  .ps-tryon-sizing-form { display: flex; flex-direction: column; gap: 12px; }
@@ -1431,6 +1471,13 @@ const STYLES = `
1431
1471
  background: linear-gradient(135deg, #bb945c, #d6ba7d);
1432
1472
  color: #111; font-size: 20px; font-weight: 700; margin-bottom: 10px;
1433
1473
  }
1474
+ .ps-tryon-sizing-loading { text-align: center; padding: 20px 0; }
1475
+ .ps-tryon-size-loading-spinner {
1476
+ width: 36px; height: 36px; border: 3px solid #333;
1477
+ border-top-color: #bb945c; border-radius: 50%;
1478
+ animation: ps-spin 0.8s linear infinite; margin: 0 auto 12px;
1479
+ }
1480
+ @keyframes ps-spin { to { transform: rotate(360deg); } }
1434
1481
  .ps-tryon-size-confidence { font-size: 12px; color: #999; margin-bottom: 8px; }
1435
1482
  .ps-conf-high { color: #4ade80; } .ps-conf-medium { color: #bb945c; } .ps-conf-low { color: #ef4444; }
1436
1483
  .ps-tryon-size-reasoning { font-size: 13px; color: #ccc; line-height: 1.5; margin-bottom: 12px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",