@primestyleai/tryon 5.9.1 → 5.10.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.
- package/dist/react/components/ConfirmMeasurementsModal.d.ts +22 -0
- package/dist/react/index.js +199 -54
- package/dist/react/recommendForProduct.d.ts +4 -0
- package/dist/react/styles.d.ts +1 -1
- package/dist/react/utils/units.d.ts +6 -0
- package/dist/storefront/primestyle-tryon.js +199 -54
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../i18n";
|
|
2
|
+
import type { Profile } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Confirmation modal shown before the "Find My Best Fit" fast path fires a
|
|
5
|
+
* recommendation against the user's stored profile. The user sees exactly
|
|
6
|
+
* what measurements we're about to send (and in which unit system) so they
|
|
7
|
+
* can catch an imperial vs metric mismatch before the API call.
|
|
8
|
+
*
|
|
9
|
+
* - Proceed → parent runs `recommendForProduct`
|
|
10
|
+
* - Edit / close-X → parent returns to the basics step with the profile
|
|
11
|
+
* values intact, ready to be edited and resubmitted.
|
|
12
|
+
*
|
|
13
|
+
* Reuses the existing `.ps-confirm-overlay` / `.ps-confirm-modal` CSS already
|
|
14
|
+
* shipped for `DeleteConfirmModal` in `MySizingProfilesView` — same visual
|
|
15
|
+
* language, no new CSS class to maintain.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ConfirmMeasurementsModal({ profile, onProceed, onEdit, t, }: {
|
|
18
|
+
profile: Profile;
|
|
19
|
+
onProceed: () => void;
|
|
20
|
+
onEdit: () => void;
|
|
21
|
+
t: TranslateFn;
|
|
22
|
+
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/react/index.js
CHANGED
|
@@ -655,7 +655,8 @@ async function recommendForProduct(input) {
|
|
|
655
655
|
sections: sectionsMap,
|
|
656
656
|
profileId: profile.id,
|
|
657
657
|
fromCache: false,
|
|
658
|
-
raw: result
|
|
658
|
+
raw: result,
|
|
659
|
+
found: result.found
|
|
659
660
|
};
|
|
660
661
|
}
|
|
661
662
|
async function estimateFullMeasurements(args) {
|
|
@@ -714,6 +715,12 @@ async function estimateFullMeasurements(args) {
|
|
|
714
715
|
function isImperial(locale) {
|
|
715
716
|
return ["US", "UK", "AU"].includes(locale);
|
|
716
717
|
}
|
|
718
|
+
function getUnitLabel(unit) {
|
|
719
|
+
if (unit === "in" || unit === "inches" || unit === "lbs") return "Imperial";
|
|
720
|
+
if (unit === "cm" || unit === "kg") return "Metric";
|
|
721
|
+
if (unit === "mm") return "mm";
|
|
722
|
+
return "";
|
|
723
|
+
}
|
|
717
724
|
function cx(base, override) {
|
|
718
725
|
return override ? `${base} ${override}` : base;
|
|
719
726
|
}
|
|
@@ -2099,21 +2106,21 @@ const STYLES = `
|
|
|
2099
2106
|
/* Shared progress layout used inside StageCycler (desktop) and
|
|
2100
2107
|
MobileScanningView — row of ring + bar + percent, same tokens. */
|
|
2101
2108
|
.ps-tryon-progress-wrap {
|
|
2102
|
-
display: flex; align-items: center; gap:
|
|
2103
|
-
width: 100%; max-width:
|
|
2109
|
+
display: flex; align-items: center; gap: 12px;
|
|
2110
|
+
width: 100%; max-width: 360px; margin-top: 18px;
|
|
2104
2111
|
}
|
|
2105
2112
|
.ps-tryon-progress-wrap .ps-tryon-progress-bar-wrap {
|
|
2106
|
-
flex: 1; height:
|
|
2113
|
+
flex: 1; height: 6px; border-radius: 4px; overflow: hidden;
|
|
2107
2114
|
position: relative; background: var(--ps-border-color);
|
|
2108
2115
|
}
|
|
2109
2116
|
.ps-tryon-progress-wrap .ps-tryon-progress-bar-fill {
|
|
2110
2117
|
height: 100%; width: 0%;
|
|
2111
2118
|
background: linear-gradient(90deg, var(--ps-accent), var(--ps-accent-light));
|
|
2112
|
-
border-radius:
|
|
2119
|
+
border-radius: 4px; transition: width 0.3s ease;
|
|
2113
2120
|
}
|
|
2114
2121
|
.ps-tryon-progress-wrap .ps-tryon-progress-pct {
|
|
2115
|
-
font-size:
|
|
2116
|
-
min-width:
|
|
2122
|
+
font-size: 13px; font-weight: 700; color: var(--ps-accent);
|
|
2123
|
+
min-width: 36px; text-align: right;
|
|
2117
2124
|
font-variant-numeric: tabular-nums;
|
|
2118
2125
|
}
|
|
2119
2126
|
.ps-tryon-progress-bar-wrap {
|
|
@@ -2141,25 +2148,25 @@ const STYLES = `
|
|
|
2141
2148
|
font-variant-numeric: tabular-nums;
|
|
2142
2149
|
}
|
|
2143
2150
|
|
|
2144
|
-
/* Circular ETA ring —
|
|
2151
|
+
/* Circular ETA ring — 64×64 px SVG with a track + progress circle; ETA
|
|
2145
2152
|
text centered. strokeDashoffset is driven by the ticker in
|
|
2146
2153
|
PrimeStyleTryonInner, so CSS only styles the appearance. */
|
|
2147
2154
|
.ps-tryon-progress-ring {
|
|
2148
|
-
position: relative; width:
|
|
2155
|
+
position: relative; width: 64px; height: 64px; flex: 0 0 64px;
|
|
2149
2156
|
display: flex; align-items: center; justify-content: center;
|
|
2150
2157
|
}
|
|
2151
2158
|
.ps-tryon-progress-ring svg { transform: rotate(-90deg); }
|
|
2152
2159
|
.ps-tryon-progress-ring-track {
|
|
2153
|
-
fill: none; stroke: var(--ps-border-color); stroke-width:
|
|
2160
|
+
fill: none; stroke: var(--ps-border-color); stroke-width: 5;
|
|
2154
2161
|
}
|
|
2155
2162
|
.ps-tryon-progress-ring-fill {
|
|
2156
|
-
fill: none; stroke: var(--ps-accent); stroke-width:
|
|
2163
|
+
fill: none; stroke: var(--ps-accent); stroke-width: 5;
|
|
2157
2164
|
stroke-linecap: round;
|
|
2158
2165
|
transition: stroke-dashoffset 0.3s ease;
|
|
2159
2166
|
}
|
|
2160
2167
|
.ps-tryon-progress-eta {
|
|
2161
2168
|
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
2162
|
-
font-size:
|
|
2169
|
+
font-size: 13px; font-weight: 700; color: var(--ps-accent);
|
|
2163
2170
|
font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
|
|
2164
2171
|
pointer-events: none;
|
|
2165
2172
|
}
|
|
@@ -7246,6 +7253,92 @@ function ProfileDetailModal({
|
|
|
7246
7253
|
document.body
|
|
7247
7254
|
);
|
|
7248
7255
|
}
|
|
7256
|
+
function ConfirmMeasurementsModal({
|
|
7257
|
+
profile,
|
|
7258
|
+
onProceed,
|
|
7259
|
+
onEdit,
|
|
7260
|
+
t
|
|
7261
|
+
}) {
|
|
7262
|
+
const heightUnit = profile.heightUnit === "in" || profile.heightUnit === "ft" ? "in" : "cm";
|
|
7263
|
+
const weightUnit = profile.weightUnit === "lbs" ? "lbs" : "kg";
|
|
7264
|
+
const systemLabel = getUnitLabel(heightUnit);
|
|
7265
|
+
const formatHeight = (h) => {
|
|
7266
|
+
if (!h) return "—";
|
|
7267
|
+
if (heightUnit === "in") {
|
|
7268
|
+
const ft = Math.floor(h / 12);
|
|
7269
|
+
const inches = Math.round(h % 12);
|
|
7270
|
+
return `${ft}'${inches}"`;
|
|
7271
|
+
}
|
|
7272
|
+
return `${Math.round(h)} cm`;
|
|
7273
|
+
};
|
|
7274
|
+
const formatWeight = (w) => {
|
|
7275
|
+
if (!w) return "—";
|
|
7276
|
+
return `${Math.round(w)} ${weightUnit}`;
|
|
7277
|
+
};
|
|
7278
|
+
const height = profile.height ?? profile.heightCm;
|
|
7279
|
+
const weight = profile.weight ?? profile.weightKg;
|
|
7280
|
+
return /* @__PURE__ */ jsx("div", { className: "ps-confirm-overlay", onClick: onEdit, children: /* @__PURE__ */ jsxs("div", { className: "ps-confirm-modal", onClick: (e) => e.stopPropagation(), children: [
|
|
7281
|
+
/* @__PURE__ */ jsx(
|
|
7282
|
+
"button",
|
|
7283
|
+
{
|
|
7284
|
+
type: "button",
|
|
7285
|
+
"aria-label": t("Close"),
|
|
7286
|
+
onClick: onEdit,
|
|
7287
|
+
style: {
|
|
7288
|
+
position: "absolute",
|
|
7289
|
+
top: "0.75vw",
|
|
7290
|
+
right: "0.75vw",
|
|
7291
|
+
width: "1.8vw",
|
|
7292
|
+
height: "1.8vw",
|
|
7293
|
+
borderRadius: "50%",
|
|
7294
|
+
background: "transparent",
|
|
7295
|
+
border: "none",
|
|
7296
|
+
cursor: "pointer",
|
|
7297
|
+
display: "flex",
|
|
7298
|
+
alignItems: "center",
|
|
7299
|
+
justifyContent: "center",
|
|
7300
|
+
color: "var(--ps-text-muted)"
|
|
7301
|
+
},
|
|
7302
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "16", height: "16", children: [
|
|
7303
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
7304
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
7305
|
+
] })
|
|
7306
|
+
}
|
|
7307
|
+
),
|
|
7308
|
+
/* @__PURE__ */ jsx("p", { style: { fontWeight: 700, marginBottom: "0.4vw" }, children: t("Confirm your measurements") }),
|
|
7309
|
+
/* @__PURE__ */ jsx("small", { style: { color: "var(--ps-text-muted)" }, children: systemLabel ? t("You chose") + " " + systemLabel + ". " + t("Review before continuing.") : t("Review before continuing.") }),
|
|
7310
|
+
/* @__PURE__ */ jsxs("ul", { style: {
|
|
7311
|
+
listStyle: "none",
|
|
7312
|
+
padding: 0,
|
|
7313
|
+
margin: "0.8vw 0 0.2vw 0",
|
|
7314
|
+
width: "100%",
|
|
7315
|
+
textAlign: "left",
|
|
7316
|
+
fontSize: "0.78vw",
|
|
7317
|
+
lineHeight: 1.6
|
|
7318
|
+
}, children: [
|
|
7319
|
+
/* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
|
|
7320
|
+
/* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Height") }),
|
|
7321
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: formatHeight(height) })
|
|
7322
|
+
] }),
|
|
7323
|
+
/* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
|
|
7324
|
+
/* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Weight") }),
|
|
7325
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: formatWeight(weight) })
|
|
7326
|
+
] }),
|
|
7327
|
+
profile.age ? /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
|
|
7328
|
+
/* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Age") }),
|
|
7329
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: profile.age })
|
|
7330
|
+
] }) : null,
|
|
7331
|
+
/* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
|
|
7332
|
+
/* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Gender") }),
|
|
7333
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: profile.gender === "female" ? t("Female") : t("Male") })
|
|
7334
|
+
] })
|
|
7335
|
+
] }),
|
|
7336
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-confirm-actions", children: [
|
|
7337
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ps-confirm-cancel", onClick: onEdit, children: t("Edit") }),
|
|
7338
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ps-confirm-delete", onClick: onProceed, children: t("Proceed") })
|
|
7339
|
+
] })
|
|
7340
|
+
] }) });
|
|
7341
|
+
}
|
|
7249
7342
|
function WelcomeView({
|
|
7250
7343
|
productImage,
|
|
7251
7344
|
setView,
|
|
@@ -7482,7 +7575,7 @@ function MobileSkeleton({ landmarks, w, h }) {
|
|
|
7482
7575
|
);
|
|
7483
7576
|
}
|
|
7484
7577
|
const MSC_TRYON_TARGET_SECONDS = 22;
|
|
7485
|
-
const MSC_RING_RADIUS =
|
|
7578
|
+
const MSC_RING_RADIUS = 27;
|
|
7486
7579
|
const MSC_RING_CIRC = 2 * Math.PI * MSC_RING_RADIUS;
|
|
7487
7580
|
function MscTryOnProgress({ t }) {
|
|
7488
7581
|
const startRef = useRef(Date.now());
|
|
@@ -7505,17 +7598,17 @@ function MscTryOnProgress({ t }) {
|
|
|
7505
7598
|
}
|
|
7506
7599
|
}, 200);
|
|
7507
7600
|
return () => clearInterval(id);
|
|
7508
|
-
}, [
|
|
7601
|
+
}, []);
|
|
7509
7602
|
return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-wrap", children: [
|
|
7510
7603
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-ring", children: [
|
|
7511
|
-
/* @__PURE__ */ jsxs("svg", { width: "
|
|
7512
|
-
/* @__PURE__ */ jsx("circle", { cx: "
|
|
7604
|
+
/* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
|
|
7605
|
+
/* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: MSC_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
|
|
7513
7606
|
/* @__PURE__ */ jsx(
|
|
7514
7607
|
"circle",
|
|
7515
7608
|
{
|
|
7516
7609
|
ref: ringRef,
|
|
7517
|
-
cx: "
|
|
7518
|
-
cy: "
|
|
7610
|
+
cx: "32",
|
|
7611
|
+
cy: "32",
|
|
7519
7612
|
r: MSC_RING_RADIUS,
|
|
7520
7613
|
className: "ps-tryon-progress-ring-fill",
|
|
7521
7614
|
strokeDasharray: MSC_RING_CIRC,
|
|
@@ -7564,11 +7657,12 @@ function MobileScanningView({
|
|
|
7564
7657
|
};
|
|
7565
7658
|
const [stageIdx, setStageIdx] = useState(0);
|
|
7566
7659
|
useEffect(() => {
|
|
7660
|
+
const intervalMs = tryOnProcessing ? 2200 : 1500;
|
|
7567
7661
|
const id = setInterval(() => {
|
|
7568
7662
|
setStageIdx((i) => (i + 1) % stages.length);
|
|
7569
|
-
},
|
|
7663
|
+
}, intervalMs);
|
|
7570
7664
|
return () => clearInterval(id);
|
|
7571
|
-
}, [stages.length]);
|
|
7665
|
+
}, [stages.length, tryOnProcessing]);
|
|
7572
7666
|
useEffect(() => {
|
|
7573
7667
|
if (isPhotoMode && bodyLandmarks && stageIdx === 0) {
|
|
7574
7668
|
setStageIdx(1);
|
|
@@ -7725,7 +7819,7 @@ function MultiSectionMobile({
|
|
|
7725
7819
|
/* @__PURE__ */ jsx("div", { className: "ps-msr-sections", children: sectionEntries.map(({ name, secResult }) => {
|
|
7726
7820
|
const cleanName = name.replace(/\s*[—–-]\s*.*/g, "");
|
|
7727
7821
|
const sec = secResult;
|
|
7728
|
-
const sizeValue = sec.size || secResult.recommendedSize;
|
|
7822
|
+
const sizeValue = sec.found === false ? t("No fit") : sec.size || secResult.recommendedSize;
|
|
7729
7823
|
return /* @__PURE__ */ jsxs(
|
|
7730
7824
|
"button",
|
|
7731
7825
|
{
|
|
@@ -7760,7 +7854,19 @@ function MultiSectionMobile({
|
|
|
7760
7854
|
children: t("Continue Shopping")
|
|
7761
7855
|
}
|
|
7762
7856
|
)
|
|
7763
|
-
] }) :
|
|
7857
|
+
] }) : sizingResult?.found === false ? (
|
|
7858
|
+
// Backend couldn't find a size that fits — Try-On is meaningless
|
|
7859
|
+
// without a recommendation, so surface a clear terminal action.
|
|
7860
|
+
/* @__PURE__ */ jsx(
|
|
7861
|
+
"button",
|
|
7862
|
+
{
|
|
7863
|
+
type: "button",
|
|
7864
|
+
className: "ps-msr-tryon-cta",
|
|
7865
|
+
onClick: onClose,
|
|
7866
|
+
children: t("Continue Shopping")
|
|
7867
|
+
}
|
|
7868
|
+
)
|
|
7869
|
+
) : /* @__PURE__ */ jsxs(
|
|
7764
7870
|
"button",
|
|
7765
7871
|
{
|
|
7766
7872
|
type: "button",
|
|
@@ -7793,7 +7899,7 @@ const SKELETON_CONNECTIONS = [
|
|
|
7793
7899
|
["rightKnee", "rightAnkle"]
|
|
7794
7900
|
];
|
|
7795
7901
|
const TRYON_TARGET_SECONDS = 22;
|
|
7796
|
-
const TRYON_RING_RADIUS =
|
|
7902
|
+
const TRYON_RING_RADIUS = 27;
|
|
7797
7903
|
const TRYON_RING_CIRC = 2 * Math.PI * TRYON_RING_RADIUS;
|
|
7798
7904
|
function TryOnProgress({ t, isActive }) {
|
|
7799
7905
|
const startRef = useRef(null);
|
|
@@ -7823,18 +7929,18 @@ function TryOnProgress({ t, isActive }) {
|
|
|
7823
7929
|
}
|
|
7824
7930
|
}, 200);
|
|
7825
7931
|
return () => clearInterval(id);
|
|
7826
|
-
}, [isActive
|
|
7932
|
+
}, [isActive]);
|
|
7827
7933
|
if (!isActive) return null;
|
|
7828
7934
|
return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-wrap", children: [
|
|
7829
7935
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-ring", children: [
|
|
7830
|
-
/* @__PURE__ */ jsxs("svg", { width: "
|
|
7831
|
-
/* @__PURE__ */ jsx("circle", { cx: "
|
|
7936
|
+
/* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
|
|
7937
|
+
/* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
|
|
7832
7938
|
/* @__PURE__ */ jsx(
|
|
7833
7939
|
"circle",
|
|
7834
7940
|
{
|
|
7835
7941
|
ref: ringRef,
|
|
7836
|
-
cx: "
|
|
7837
|
-
cy: "
|
|
7942
|
+
cx: "32",
|
|
7943
|
+
cy: "32",
|
|
7838
7944
|
r: TRYON_RING_RADIUS,
|
|
7839
7945
|
className: "ps-tryon-progress-ring-fill",
|
|
7840
7946
|
strokeDasharray: TRYON_RING_CIRC,
|
|
@@ -7985,11 +8091,12 @@ function StageCycler({
|
|
|
7985
8091
|
}, [tryOnProcessing]);
|
|
7986
8092
|
useEffect(() => {
|
|
7987
8093
|
if (isDone) return;
|
|
8094
|
+
const intervalMs = tryOnProcessing ? 2200 : 900;
|
|
7988
8095
|
const id = setInterval(() => {
|
|
7989
8096
|
setIdx((i) => Math.min(i + 1, active.length - 1));
|
|
7990
|
-
},
|
|
8097
|
+
}, intervalMs);
|
|
7991
8098
|
return () => clearInterval(id);
|
|
7992
|
-
}, [isDone, active.length]);
|
|
8099
|
+
}, [isDone, active.length, tryOnProcessing]);
|
|
7993
8100
|
const current = active[idx] ?? active[0];
|
|
7994
8101
|
return /* @__PURE__ */ jsxs("div", { className: "ps-msc-stage", style: { alignSelf: "center", marginTop: "auto", marginBottom: "auto" }, children: [
|
|
7995
8102
|
/* @__PURE__ */ jsxs("div", { className: "ps-msc-stage-slot", children: [
|
|
@@ -8236,7 +8343,8 @@ function SectionDetailView({
|
|
|
8236
8343
|
backLabel,
|
|
8237
8344
|
internationalSizes,
|
|
8238
8345
|
continueLabel,
|
|
8239
|
-
renderRaw = false
|
|
8346
|
+
renderRaw = false,
|
|
8347
|
+
sectionFound
|
|
8240
8348
|
}) {
|
|
8241
8349
|
const recSize = sectionResult?.recommendedSize || "";
|
|
8242
8350
|
const [selectedSize, setSelectedSize] = useState(null);
|
|
@@ -8281,7 +8389,8 @@ function SectionDetailView({
|
|
|
8281
8389
|
const hasBadFit = details.some((d) => BAD_FIT.test(d.fit || ""));
|
|
8282
8390
|
return hasBadFit ? t("Not Recommended") : t("Your Selection");
|
|
8283
8391
|
}, [isRecommended, sectionResult, t]);
|
|
8284
|
-
const
|
|
8392
|
+
const noFitMessage = t("We couldn't find a size that fits for this product");
|
|
8393
|
+
const displaySizeLabel = sectionFound === false ? noFitMessage : selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
|
|
8285
8394
|
const columnUnits = useMemo(() => {
|
|
8286
8395
|
const units = [];
|
|
8287
8396
|
for (let i = 0; i < section.headers.length; i++) {
|
|
@@ -9135,6 +9244,7 @@ function SizeResultView({
|
|
|
9135
9244
|
const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
|
|
9136
9245
|
const isMobile = useIsMobile();
|
|
9137
9246
|
const isAccessory = measurementType === "face" || measurementType === "head";
|
|
9247
|
+
const noFit = sizingResult?.found === false;
|
|
9138
9248
|
const vtoExcluded = measurementType === "foot";
|
|
9139
9249
|
console.log("[PS-SDK] SizeResultView render:", {
|
|
9140
9250
|
hasPhoto,
|
|
@@ -9219,6 +9329,7 @@ function SizeResultView({
|
|
|
9219
9329
|
sectionName: entry.name,
|
|
9220
9330
|
section: entry.section,
|
|
9221
9331
|
sectionResult: entry.secResult,
|
|
9332
|
+
sectionFound: entry.secResult?.found,
|
|
9222
9333
|
userMeasurements: entry.userMeasurements,
|
|
9223
9334
|
unitLbl,
|
|
9224
9335
|
chartUnit: resultUnit,
|
|
@@ -9268,6 +9379,7 @@ function SizeResultView({
|
|
|
9268
9379
|
sectionName: entry.name,
|
|
9269
9380
|
section: entry.section,
|
|
9270
9381
|
sectionResult: entry.secResult,
|
|
9382
|
+
sectionFound: entry.secResult?.found,
|
|
9271
9383
|
userMeasurements: entry.userMeasurements,
|
|
9272
9384
|
unitLbl,
|
|
9273
9385
|
chartUnit: resultUnit,
|
|
@@ -9389,7 +9501,7 @@ function SizeResultView({
|
|
|
9389
9501
|
return /* @__PURE__ */ jsxs("button", { className: `ps-tryon-sr-card-v2${isLast ? " ps-full" : ""}`, onClick: () => setActiveSection(name), style: { animationDelay: `${idx * 0.07}s` }, children: [
|
|
9390
9502
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-card-v2-text", children: [
|
|
9391
9503
|
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-label", children: name.replace(/\s*[—–-]\s*.*/g, "") }),
|
|
9392
|
-
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-value", children: sec.size || secResult.recommendedSize }),
|
|
9504
|
+
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-value", children: sec.found === false ? t("No fit") : sec.size || secResult.recommendedSize }),
|
|
9393
9505
|
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-rec", children: t("recommended") })
|
|
9394
9506
|
] }),
|
|
9395
9507
|
sectionImg && /* @__PURE__ */ jsx("img", { src: sectionImg, alt: name, className: "ps-tryon-sr-card-v2-img" }),
|
|
@@ -9413,7 +9525,7 @@ function SizeResultView({
|
|
|
9413
9525
|
" →"
|
|
9414
9526
|
]
|
|
9415
9527
|
}
|
|
9416
|
-
) : vtoExcluded ? /* @__PURE__ */ jsxs(
|
|
9528
|
+
) : vtoExcluded || noFit ? /* @__PURE__ */ jsxs(
|
|
9417
9529
|
"button",
|
|
9418
9530
|
{
|
|
9419
9531
|
className: "ps-tryon-v2-cta",
|
|
@@ -9466,6 +9578,7 @@ function SizeResultView({
|
|
|
9466
9578
|
sectionName,
|
|
9467
9579
|
section: singleSection,
|
|
9468
9580
|
sectionResult: singleResult,
|
|
9581
|
+
sectionFound: sizingResult?.found,
|
|
9469
9582
|
userMeasurements: singleUserMeasurements,
|
|
9470
9583
|
unitLbl,
|
|
9471
9584
|
chartUnit: resultUnit,
|
|
@@ -9473,7 +9586,7 @@ function SizeResultView({
|
|
|
9473
9586
|
onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
|
|
9474
9587
|
backLabel: t("Back"),
|
|
9475
9588
|
internationalSizes: sizingResult?.internationalSizes,
|
|
9476
|
-
onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
|
|
9589
|
+
onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
|
|
9477
9590
|
continueLabel: resultImageUrl ? t("Continue Shopping") : void 0,
|
|
9478
9591
|
tryOnProcessing,
|
|
9479
9592
|
productImage: resultImageUrl || productImage,
|
|
@@ -9513,6 +9626,7 @@ function SizeResultView({
|
|
|
9513
9626
|
sectionName,
|
|
9514
9627
|
section: singleSection,
|
|
9515
9628
|
sectionResult: singleResult,
|
|
9629
|
+
sectionFound: sizingResult?.found,
|
|
9516
9630
|
userMeasurements: singleUserMeasurements,
|
|
9517
9631
|
unitLbl,
|
|
9518
9632
|
chartUnit: resultUnit,
|
|
@@ -9520,7 +9634,7 @@ function SizeResultView({
|
|
|
9520
9634
|
onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
|
|
9521
9635
|
backLabel: t("Back"),
|
|
9522
9636
|
internationalSizes: sizingResult?.internationalSizes,
|
|
9523
|
-
onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
|
|
9637
|
+
onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
|
|
9524
9638
|
continueLabel: resultImageUrl ? t("Continue Shopping") : void 0,
|
|
9525
9639
|
tryOnProcessing,
|
|
9526
9640
|
t,
|
|
@@ -9896,7 +10010,7 @@ function UploadView({
|
|
|
9896
10010
|
}
|
|
9897
10011
|
) });
|
|
9898
10012
|
}
|
|
9899
|
-
const RING_RADIUS =
|
|
10013
|
+
const RING_RADIUS = 27;
|
|
9900
10014
|
const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
|
|
9901
10015
|
function ProcessingView({
|
|
9902
10016
|
previewUrl,
|
|
@@ -9941,12 +10055,12 @@ function ProcessingView({
|
|
|
9941
10055
|
] }),
|
|
9942
10056
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-section", children: [
|
|
9943
10057
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-ring", children: [
|
|
9944
|
-
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0
|
|
10058
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 64 64", width: "64", height: "64", "aria-hidden": "true", children: [
|
|
9945
10059
|
/* @__PURE__ */ jsx(
|
|
9946
10060
|
"circle",
|
|
9947
10061
|
{
|
|
9948
|
-
cx: "
|
|
9949
|
-
cy: "
|
|
10062
|
+
cx: "32",
|
|
10063
|
+
cy: "32",
|
|
9950
10064
|
r: RING_RADIUS,
|
|
9951
10065
|
className: "ps-tryon-progress-ring-track"
|
|
9952
10066
|
}
|
|
@@ -9955,8 +10069,8 @@ function ProcessingView({
|
|
|
9955
10069
|
"circle",
|
|
9956
10070
|
{
|
|
9957
10071
|
ref: ringCb,
|
|
9958
|
-
cx: "
|
|
9959
|
-
cy: "
|
|
10072
|
+
cx: "32",
|
|
10073
|
+
cy: "32",
|
|
9960
10074
|
r: RING_RADIUS,
|
|
9961
10075
|
className: "ps-tryon-progress-ring-fill",
|
|
9962
10076
|
strokeDasharray: RING_CIRCUMFERENCE,
|
|
@@ -12526,7 +12640,7 @@ function BodyProfileView({
|
|
|
12526
12640
|
hidePhotoOptions: hasActiveProfileWithMeasurements,
|
|
12527
12641
|
onNext: hasActiveProfileWithMeasurements && onUseActiveProfile ? onUseActiveProfile : handleNext,
|
|
12528
12642
|
canProceed: true,
|
|
12529
|
-
fastPathLabel: hasActiveProfileWithMeasurements ? t("Find My Best Fit") : void 0,
|
|
12643
|
+
fastPathLabel: hasActiveProfileWithMeasurements ? t("Find My Best Fit") + (getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "") : void 0,
|
|
12530
12644
|
activeProfileName: hasActiveProfileWithMeasurements ? activeProfileName : null,
|
|
12531
12645
|
onStartFresh,
|
|
12532
12646
|
error,
|
|
@@ -12781,7 +12895,8 @@ function BodyProfileView({
|
|
|
12781
12895
|
!(isMobile && step === "basics") && (() => {
|
|
12782
12896
|
const useProfileFast = step === "basics" && hasActiveProfileWithMeasurements && !!onUseActiveProfile;
|
|
12783
12897
|
const handleClick = useProfileFast ? onUseActiveProfile : handleNext;
|
|
12784
|
-
const
|
|
12898
|
+
const unitSuffix = getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "";
|
|
12899
|
+
const label = useProfileFast ? t("Find My Best Fit") + unitSuffix : isLastStep ? t("Find My Size") + unitSuffix : t("Next");
|
|
12785
12900
|
return /* @__PURE__ */ jsxs("div", { className: "ps-bp-nav", children: [
|
|
12786
12901
|
step !== "basics" ? /* @__PURE__ */ jsxs("button", { className: "ps-bp-back-btn", onClick: handleBackStep, type: "button", children: [
|
|
12787
12902
|
/* @__PURE__ */ jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
|
|
@@ -12971,6 +13086,7 @@ function AccessorySizeView({
|
|
|
12971
13086
|
/* @__PURE__ */ jsxs("div", { className: "ps-bpm-bottom", children: [
|
|
12972
13087
|
/* @__PURE__ */ jsxs("button", { type: "button", className: "ps-bpm-next-btn", onClick: handleManualSubmit, children: [
|
|
12973
13088
|
t("Find My Size"),
|
|
13089
|
+
getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
|
|
12974
13090
|
" ",
|
|
12975
13091
|
/* @__PURE__ */ jsx(ArrowRightIcon, {})
|
|
12976
13092
|
] }),
|
|
@@ -13305,6 +13421,7 @@ function AccessorySizeView({
|
|
|
13305
13421
|
/* @__PURE__ */ jsx("div", {}),
|
|
13306
13422
|
/* @__PURE__ */ jsxs("button", { className: "ps-bp-next-btn", onClick: handleManualSubmit, type: "button", children: [
|
|
13307
13423
|
t("Find My Size"),
|
|
13424
|
+
getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
|
|
13308
13425
|
" ",
|
|
13309
13426
|
/* @__PURE__ */ jsx(ArrowRightIcon, {})
|
|
13310
13427
|
] })
|
|
@@ -13538,6 +13655,9 @@ function PrimeStyleTryonInner({
|
|
|
13538
13655
|
const [sizingUnit, setSizingUnit] = useState(imperial ? "in" : "cm");
|
|
13539
13656
|
const [heightUnit, setHeightUnit] = useState(imperial ? "in" : "cm");
|
|
13540
13657
|
const [weightUnit, setWeightUnit] = useState(imperial ? "lbs" : "kg");
|
|
13658
|
+
useEffect(() => {
|
|
13659
|
+
if (detectMeasurementType(productTitle) === "foot") setSizingUnit("cm");
|
|
13660
|
+
}, [productTitle]);
|
|
13541
13661
|
const formRef = useRef({});
|
|
13542
13662
|
const [formGender, setFormGender] = useState("male");
|
|
13543
13663
|
const [formKey, setFormKey] = useState(0);
|
|
@@ -13612,7 +13732,7 @@ function PrimeStyleTryonInner({
|
|
|
13612
13732
|
{ at: 75, text: t("Refining details...") },
|
|
13613
13733
|
{ at: 90, text: t("Almost there...") }
|
|
13614
13734
|
];
|
|
13615
|
-
const RING_CIRCUMFERENCE2 = 2 * Math.PI *
|
|
13735
|
+
const RING_CIRCUMFERENCE2 = 2 * Math.PI * 27;
|
|
13616
13736
|
progressIntervalRef.current = setInterval(() => {
|
|
13617
13737
|
if (completedRef.current) return;
|
|
13618
13738
|
const startTs = progressStartTsRef.current || Date.now();
|
|
@@ -13790,13 +13910,10 @@ function PrimeStyleTryonInner({
|
|
|
13790
13910
|
[activeProfileId, profiles, apiUrl, productImage, productTitle, effectiveProductId, setActiveProfileId$1]
|
|
13791
13911
|
);
|
|
13792
13912
|
const snapSubmitRef = useRef(null);
|
|
13793
|
-
const
|
|
13794
|
-
|
|
13795
|
-
if (!p) return;
|
|
13913
|
+
const [confirmProfile, setConfirmProfile] = useState(null);
|
|
13914
|
+
const runRecommendWithProfile = useCallback(async (p) => {
|
|
13796
13915
|
const profileHeight = p.height ?? p.heightCm ?? 0;
|
|
13797
13916
|
const profileWeight = p.weight ?? p.weightKg ?? 0;
|
|
13798
|
-
const hasIdentity = profileHeight > 0 && profileWeight > 0;
|
|
13799
|
-
if (!hasIdentity) return;
|
|
13800
13917
|
const hasStored = !!p.measurements && Object.keys(p.measurements).length > 0;
|
|
13801
13918
|
const storedPhoto = p.photoBase64;
|
|
13802
13919
|
if (!hasStored && storedPhoto && profileHeight > 0 && snapSubmitRef.current) {
|
|
@@ -13820,9 +13937,8 @@ function PrimeStyleTryonInner({
|
|
|
13820
13937
|
}
|
|
13821
13938
|
setSizingResult(null);
|
|
13822
13939
|
setSizingLoading(true);
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
if (hasStoredMeasurements) {
|
|
13940
|
+
setEstimationDone(hasStored);
|
|
13941
|
+
if (hasStored) {
|
|
13826
13942
|
setPreviewUrl(null);
|
|
13827
13943
|
setBodyLandmarks(null);
|
|
13828
13944
|
}
|
|
@@ -13855,7 +13971,27 @@ function PrimeStyleTryonInner({
|
|
|
13855
13971
|
setEstimationDone(true);
|
|
13856
13972
|
}).catch(() => {
|
|
13857
13973
|
}).finally(() => setSizingLoading(false));
|
|
13858
|
-
}, [
|
|
13974
|
+
}, [effectiveProductId, productTitle, productImage, sizeGuideData, apiUrl, previewUrl]);
|
|
13975
|
+
const handleUseActiveProfile = useCallback(async () => {
|
|
13976
|
+
const p = profiles.find((x) => x.id === activeProfileId);
|
|
13977
|
+
if (!p) return;
|
|
13978
|
+
const profileHeight = p.height ?? p.heightCm ?? 0;
|
|
13979
|
+
const profileWeight = p.weight ?? p.weightKg ?? 0;
|
|
13980
|
+
const hasIdentity = profileHeight > 0 && profileWeight > 0;
|
|
13981
|
+
if (!hasIdentity) return;
|
|
13982
|
+
setConfirmProfile(p);
|
|
13983
|
+
}, [profiles, activeProfileId]);
|
|
13984
|
+
const proceedFromConfirmProfile = useCallback(() => {
|
|
13985
|
+
if (!confirmProfile) return;
|
|
13986
|
+
const p = confirmProfile;
|
|
13987
|
+
setConfirmProfile(null);
|
|
13988
|
+
void runRecommendWithProfile(p);
|
|
13989
|
+
}, [confirmProfile, runRecommendWithProfile]);
|
|
13990
|
+
const cancelFromConfirmProfile = useCallback(() => {
|
|
13991
|
+
setConfirmProfile(null);
|
|
13992
|
+
setFormKey((k) => k + 1);
|
|
13993
|
+
setView("body-profile");
|
|
13994
|
+
}, []);
|
|
13859
13995
|
const applyProfileRef = useRef(() => {
|
|
13860
13996
|
});
|
|
13861
13997
|
const handleOpen = useCallback(() => {
|
|
@@ -15230,6 +15366,15 @@ function PrimeStyleTryonInner({
|
|
|
15230
15366
|
onCancel: () => setDeleteConfirmId(null),
|
|
15231
15367
|
t
|
|
15232
15368
|
}
|
|
15369
|
+
),
|
|
15370
|
+
confirmProfile && /* @__PURE__ */ jsx(
|
|
15371
|
+
ConfirmMeasurementsModal,
|
|
15372
|
+
{
|
|
15373
|
+
profile: confirmProfile,
|
|
15374
|
+
onProceed: proceedFromConfirmProfile,
|
|
15375
|
+
onEdit: cancelFromConfirmProfile,
|
|
15376
|
+
t
|
|
15377
|
+
}
|
|
15233
15378
|
)
|
|
15234
15379
|
] }) }),
|
|
15235
15380
|
document.body
|
|
@@ -27,6 +27,10 @@ export interface RecommendForProductResult {
|
|
|
27
27
|
fromCache: boolean;
|
|
28
28
|
/** Full sizing result for downstream use */
|
|
29
29
|
raw?: SizingResult;
|
|
30
|
+
/** True when the backend found a real fit. False when the user's body is
|
|
31
|
+
* outside every row in the size chart — hosts should suppress their
|
|
32
|
+
* "Recommended: X" badge and surface a "size unavailable" message. */
|
|
33
|
+
found?: boolean;
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
32
36
|
* Headless auto-sizing — given a product and an active profile, returns the
|