@primestyleai/tryon 5.10.187 → 5.10.188
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/api-client.d.ts +2 -0
- package/dist/{index-CYT0nGWX.js → index-BduSFARG.js} +60 -51
- package/dist/index-BduSFARG.js.map +1 -0
- package/dist/primestyle-tryon.js +2 -2
- package/dist/react/index.js +6280 -6082
- package/dist/react/index.js.map +1 -1
- package/dist/react/styles.d.ts +1 -1
- package/dist/react/utils/storage.d.ts +2 -0
- package/dist/react/views/BodyProfileView.d.ts +3 -0
- package/dist/react/views/CreateProfileWizard.d.ts +3 -1
- package/dist/react/views/NoChartView.d.ts +2 -1
- package/dist/storefront/primestyle-tryon.js +600 -303
- package/package.json +1 -1
- package/dist/index-CYT0nGWX.js.map +0 -1
|
@@ -6979,7 +6979,7 @@ const es = {
|
|
|
6979
6979
|
"Virtual Try-On": "Probador Virtual",
|
|
6980
6980
|
"Find Your Size & See It On You": "Encuentra tu talla y pruébatelo",
|
|
6981
6981
|
"See Your Fit": "Ver tu ajuste",
|
|
6982
|
-
"
|
|
6982
|
+
"Check your size, then try it on virtually": "Comprueba tu talla y pruébatelo virtualmente",
|
|
6983
6983
|
"Get Your Size": "Tu talla",
|
|
6984
6984
|
"Instant fit recommendation": "Recomendación de ajuste instantánea",
|
|
6985
6985
|
"Try It On": "Pruébatelo",
|
|
@@ -7014,8 +7014,9 @@ const es = {
|
|
|
7014
7014
|
"Edit measurements": "Editar medidas",
|
|
7015
7015
|
"Size guide": "Guía de tallas",
|
|
7016
7016
|
"Your fit": "Tu ajuste",
|
|
7017
|
+
"Your Size Recommendation": "Tu recomendación de talla",
|
|
7017
7018
|
"Your measure": "Tu medida",
|
|
7018
|
-
"
|
|
7019
|
+
"within range": "dentro del rango",
|
|
7019
7020
|
"too tight": "demasiado ajustado",
|
|
7020
7021
|
"tight": "ajustado",
|
|
7021
7022
|
"a bit tight": "un poco ajustado",
|
|
@@ -7236,7 +7237,7 @@ const fr = {
|
|
|
7236
7237
|
"Virtual Try-On": "Essayage Virtuel",
|
|
7237
7238
|
"Find Your Size & See It On You": "Trouvez votre taille et essayez-le",
|
|
7238
7239
|
"See Your Fit": "Voir votre ajustement",
|
|
7239
|
-
"
|
|
7240
|
+
"Check your size, then try it on virtually": "Vérifiez votre taille, puis essayez-le virtuellement",
|
|
7240
7241
|
"Get Your Size": "Votre taille",
|
|
7241
7242
|
"Instant fit recommendation": "Recommandation de coupe instantanée",
|
|
7242
7243
|
"Try It On": "Essayer",
|
|
@@ -7271,8 +7272,9 @@ const fr = {
|
|
|
7271
7272
|
"Edit measurements": "Modifier les mesures",
|
|
7272
7273
|
"Size guide": "Guide des tailles",
|
|
7273
7274
|
"Your fit": "Votre coupe",
|
|
7275
|
+
"Your Size Recommendation": "Votre recommandation de taille",
|
|
7274
7276
|
"Your measure": "Votre mesure",
|
|
7275
|
-
"
|
|
7277
|
+
"within range": "dans la plage",
|
|
7276
7278
|
"too tight": "trop serré",
|
|
7277
7279
|
"tight": "serré",
|
|
7278
7280
|
"a bit tight": "un peu serré",
|
|
@@ -7493,7 +7495,7 @@ const de = {
|
|
|
7493
7495
|
"Virtual Try-On": "Virtuelle Anprobe",
|
|
7494
7496
|
"Find Your Size & See It On You": "Finden Sie Ihre Größe und probieren Sie es an",
|
|
7495
7497
|
"See Your Fit": "Ihre Passform ansehen",
|
|
7496
|
-
"
|
|
7498
|
+
"Check your size, then try it on virtually": "Prüfen Sie Ihre Größe und probieren Sie es virtuell an",
|
|
7497
7499
|
"Get Your Size": "Ihre Größe",
|
|
7498
7500
|
"Instant fit recommendation": "Sofortige Passformempfehlung",
|
|
7499
7501
|
"Try It On": "Anprobieren",
|
|
@@ -7528,8 +7530,9 @@ const de = {
|
|
|
7528
7530
|
"Edit measurements": "Maße bearbeiten",
|
|
7529
7531
|
"Size guide": "Größentabelle",
|
|
7530
7532
|
"Your fit": "Ihre Passform",
|
|
7533
|
+
"Your Size Recommendation": "Ihre Größenempfehlung",
|
|
7531
7534
|
"Your measure": "Ihr Maß",
|
|
7532
|
-
"
|
|
7535
|
+
"within range": "im Bereich",
|
|
7533
7536
|
"too tight": "zu eng",
|
|
7534
7537
|
"tight": "eng",
|
|
7535
7538
|
"a bit tight": "etwas eng",
|
|
@@ -7750,7 +7753,7 @@ const it = {
|
|
|
7750
7753
|
"Virtual Try-On": "Prova Virtuale",
|
|
7751
7754
|
"Find Your Size & See It On You": "Trova la tua taglia e provalo",
|
|
7752
7755
|
"See Your Fit": "Vedi la vestibilità",
|
|
7753
|
-
"
|
|
7756
|
+
"Check your size, then try it on virtually": "Controlla la tua taglia, poi provalo virtualmente",
|
|
7754
7757
|
"Get Your Size": "La tua taglia",
|
|
7755
7758
|
"Instant fit recommendation": "Raccomandazione vestibilità istantanea",
|
|
7756
7759
|
"Try It On": "Provalo",
|
|
@@ -7785,8 +7788,9 @@ const it = {
|
|
|
7785
7788
|
"Edit measurements": "Modifica misure",
|
|
7786
7789
|
"Size guide": "Guida alle taglie",
|
|
7787
7790
|
"Your fit": "La tua vestibilità",
|
|
7791
|
+
"Your Size Recommendation": "La tua taglia consigliata",
|
|
7788
7792
|
"Your measure": "La tua misura",
|
|
7789
|
-
"
|
|
7793
|
+
"within range": "entro l'intervallo",
|
|
7790
7794
|
"too tight": "troppo stretto",
|
|
7791
7795
|
"tight": "stretto",
|
|
7792
7796
|
"a bit tight": "un po' stretto",
|
|
@@ -8007,7 +8011,7 @@ const pt$1 = {
|
|
|
8007
8011
|
"Virtual Try-On": "Provador Virtual",
|
|
8008
8012
|
"Find Your Size & See It On You": "Encontre seu tamanho e experimente",
|
|
8009
8013
|
"See Your Fit": "Veja seu ajuste",
|
|
8010
|
-
"
|
|
8014
|
+
"Check your size, then try it on virtually": "Confira seu tamanho e experimente virtualmente",
|
|
8011
8015
|
"Get Your Size": "Seu tamanho",
|
|
8012
8016
|
"Instant fit recommendation": "Recomendação de caimento instantânea",
|
|
8013
8017
|
"Try It On": "Experimentar",
|
|
@@ -8042,8 +8046,9 @@ const pt$1 = {
|
|
|
8042
8046
|
"Edit measurements": "Editar medidas",
|
|
8043
8047
|
"Size guide": "Guia de tamanhos",
|
|
8044
8048
|
"Your fit": "Seu caimento",
|
|
8049
|
+
"Your Size Recommendation": "Sua recomendação de tamanho",
|
|
8045
8050
|
"Your measure": "Sua medida",
|
|
8046
|
-
"
|
|
8051
|
+
"within range": "dentro da faixa",
|
|
8047
8052
|
"too tight": "muito apertado",
|
|
8048
8053
|
"tight": "apertado",
|
|
8049
8054
|
"a bit tight": "um pouco apertado",
|
|
@@ -8264,7 +8269,7 @@ const ja = {
|
|
|
8264
8269
|
"Virtual Try-On": "バーチャル試着",
|
|
8265
8270
|
"Find Your Size & See It On You": "あなたのサイズを見つけて試着",
|
|
8266
8271
|
"See Your Fit": "フィット感を見る",
|
|
8267
|
-
"
|
|
8272
|
+
"Check your size, then try it on virtually": "サイズを確認して、バーチャルで試着しましょう",
|
|
8268
8273
|
"Get Your Size": "サイズを確認",
|
|
8269
8274
|
"Instant fit recommendation": "瞬時にフィット提案",
|
|
8270
8275
|
"Try It On": "試着する",
|
|
@@ -8299,8 +8304,9 @@ const ja = {
|
|
|
8299
8304
|
"Edit measurements": "寸法を編集",
|
|
8300
8305
|
"Size guide": "サイズガイド",
|
|
8301
8306
|
"Your fit": "あなたのフィット",
|
|
8307
|
+
"Your Size Recommendation": "サイズのおすすめ",
|
|
8302
8308
|
"Your measure": "あなたの寸法",
|
|
8303
|
-
"
|
|
8309
|
+
"within range": "範囲内",
|
|
8304
8310
|
"too tight": "きつすぎる",
|
|
8305
8311
|
"tight": "きつい",
|
|
8306
8312
|
"a bit tight": "少しきつい",
|
|
@@ -8521,7 +8527,7 @@ const zh = {
|
|
|
8521
8527
|
"Virtual Try-On": "虚拟试穿",
|
|
8522
8528
|
"Find Your Size & See It On You": "找到你的尺码并试穿",
|
|
8523
8529
|
"See Your Fit": "查看合身效果",
|
|
8524
|
-
"
|
|
8530
|
+
"Check your size, then try it on virtually": "确认尺码,然后虚拟试穿",
|
|
8525
8531
|
"Get Your Size": "获取尺码",
|
|
8526
8532
|
"Instant fit recommendation": "即时合身推荐",
|
|
8527
8533
|
"Try It On": "试穿",
|
|
@@ -8556,8 +8562,9 @@ const zh = {
|
|
|
8556
8562
|
"Edit measurements": "编辑尺寸",
|
|
8557
8563
|
"Size guide": "尺码指南",
|
|
8558
8564
|
"Your fit": "你的合身度",
|
|
8565
|
+
"Your Size Recommendation": "你的尺码建议",
|
|
8559
8566
|
"Your measure": "你的尺寸",
|
|
8560
|
-
"
|
|
8567
|
+
"within range": "在范围内",
|
|
8561
8568
|
"too tight": "太紧",
|
|
8562
8569
|
"tight": "偏紧",
|
|
8563
8570
|
"a bit tight": "略紧",
|
|
@@ -8778,7 +8785,7 @@ const ko = {
|
|
|
8778
8785
|
"Virtual Try-On": "가상 피팅",
|
|
8779
8786
|
"Find Your Size & See It On You": "사이즈를 찾고 입어보세요",
|
|
8780
8787
|
"See Your Fit": "핏 확인하기",
|
|
8781
|
-
"
|
|
8788
|
+
"Check your size, then try it on virtually": "사이즈를 확인하고 가상으로 입어보세요",
|
|
8782
8789
|
"Get Your Size": "사이즈 확인",
|
|
8783
8790
|
"Instant fit recommendation": "즉시 핏 추천",
|
|
8784
8791
|
"Try It On": "입어보기",
|
|
@@ -8813,8 +8820,9 @@ const ko = {
|
|
|
8813
8820
|
"Edit measurements": "치수 수정",
|
|
8814
8821
|
"Size guide": "사이즈 가이드",
|
|
8815
8822
|
"Your fit": "나의 핏",
|
|
8823
|
+
"Your Size Recommendation": "사이즈 추천",
|
|
8816
8824
|
"Your measure": "내 치수",
|
|
8817
|
-
"
|
|
8825
|
+
"within range": "범위 내",
|
|
8818
8826
|
"too tight": "너무 타이트",
|
|
8819
8827
|
"tight": "타이트",
|
|
8820
8828
|
"a bit tight": "약간 타이트",
|
|
@@ -9035,7 +9043,7 @@ const ar = {
|
|
|
9035
9043
|
"Virtual Try-On": "تجربة افتراضية",
|
|
9036
9044
|
"Find Your Size & See It On You": "اعثر على مقاسك وجرّبه",
|
|
9037
9045
|
"See Your Fit": "شاهد الملاءمة",
|
|
9038
|
-
"
|
|
9046
|
+
"Check your size, then try it on virtually": "تحقق من مقاسك ثم جرّبه افتراضياً",
|
|
9039
9047
|
"Get Your Size": "مقاسك",
|
|
9040
9048
|
"Instant fit recommendation": "توصية فورية بالمقاس",
|
|
9041
9049
|
"Try It On": "جرّبه",
|
|
@@ -9070,8 +9078,9 @@ const ar = {
|
|
|
9070
9078
|
"Edit measurements": "تعديل القياسات",
|
|
9071
9079
|
"Size guide": "دليل المقاسات",
|
|
9072
9080
|
"Your fit": "ملاءمتك",
|
|
9081
|
+
"Your Size Recommendation": "توصية المقاس",
|
|
9073
9082
|
"Your measure": "قياسك",
|
|
9074
|
-
"
|
|
9083
|
+
"within range": "ضمن النطاق",
|
|
9075
9084
|
"too tight": "ضيق جداً",
|
|
9076
9085
|
"tight": "ضيق",
|
|
9077
9086
|
"a bit tight": "ضيق قليلاً",
|
|
@@ -9465,6 +9474,8 @@ class ApiClient {
|
|
|
9465
9474
|
if (category && category !== "apparel") body.category = category;
|
|
9466
9475
|
if (context?.productId) body.productId = context.productId;
|
|
9467
9476
|
if (context?.productTitle) body.productTitle = context.productTitle;
|
|
9477
|
+
if (context?.productCategory) body.productCategory = context.productCategory;
|
|
9478
|
+
if (context?.productSubcategory) body.productSubcategory = context.productSubcategory;
|
|
9468
9479
|
if (context?.productFitType) body.productFitType = context.productFitType;
|
|
9469
9480
|
if (context?.productType) body.productType = context.productType;
|
|
9470
9481
|
if (context?.productTags?.length) body.productTags = context.productTags;
|
|
@@ -10280,6 +10291,9 @@ function formatUserMeasurementValue(measurement, value) {
|
|
|
10280
10291
|
if (!isUnitlessShoeSizeMeasurement(measurement)) return value;
|
|
10281
10292
|
return value.replace(/\s*(cm|mm|in|inch|inches)\b/ig, "").trim();
|
|
10282
10293
|
}
|
|
10294
|
+
function normalizePromptSizeLabel(size) {
|
|
10295
|
+
return String(size || "").trim().replace(/\s+[–—-]\s+(?:UK|US|EU|IT|FR|DE|ES|JP|CN|KR|AU|BR)\s+.+$/i, "").replace(/\s*\((?:UK|US|EU|IT|FR|DE|ES|JP|CN|KR|AU|BR)\s+[^)]*\)\s*$/i, "").trim();
|
|
10296
|
+
}
|
|
10283
10297
|
function computeFit(userValue, chartRange, unit) {
|
|
10284
10298
|
const targetUnit = normalizeUnit(unit) || detectUnitFromText(chartRange) || "in";
|
|
10285
10299
|
const chartUnit = detectUnitFromText(chartRange);
|
|
@@ -10340,7 +10354,7 @@ function buildSilhouetteContext(sizingResult, sizeGuide, selectedSizeOverride, u
|
|
|
10340
10354
|
if (!sizingResult && !sizeGuide && !userHeight && !userWeight) return void 0;
|
|
10341
10355
|
const out = {};
|
|
10342
10356
|
const promptUnit = normalizeUnit(sizingResult?.unit) || detectUnitFromText(sizingResult?.matchDetails?.[0]?.userValue) || detectUnitFromText(Object.values(sizingResult?.sections || {})[0]?.matchDetails?.[0]?.userValue) || "in";
|
|
10343
|
-
const baseSize = (selectedSizeOverride || sizingResult?.recommendedSize || "")
|
|
10357
|
+
const baseSize = normalizePromptSizeLabel(selectedSizeOverride || sizingResult?.recommendedSize || "");
|
|
10344
10358
|
if (userHeight) out.userHeight = userHeight;
|
|
10345
10359
|
if (userWeight) out.userWeight = userWeight;
|
|
10346
10360
|
let chartRowLength = null;
|
|
@@ -10390,7 +10404,7 @@ function buildSilhouetteContext(sizingResult, sizeGuide, selectedSizeOverride, u
|
|
|
10390
10404
|
const labelParts = [];
|
|
10391
10405
|
const measurementParts = [];
|
|
10392
10406
|
for (const [secName, secResult] of sectionEntries) {
|
|
10393
|
-
const secSize = (secResult?.recommendedSize ?? "")
|
|
10407
|
+
const secSize = normalizePromptSizeLabel(secResult?.recommendedSize ?? "");
|
|
10394
10408
|
if (!secSize) continue;
|
|
10395
10409
|
const cleanSec = /\bsize\s*$/i.test(secName) ? secName.replace(/\s*\bsize\s*$/i, "").trim() : secName;
|
|
10396
10410
|
labelParts.push(`${cleanSec} ${secSize}`);
|
|
@@ -10496,6 +10510,24 @@ function lsSet(key, value) {
|
|
|
10496
10510
|
} catch {
|
|
10497
10511
|
}
|
|
10498
10512
|
}
|
|
10513
|
+
function lsRemove(key) {
|
|
10514
|
+
try {
|
|
10515
|
+
localStorage.removeItem(LS_PREFIX + key);
|
|
10516
|
+
} catch {
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10519
|
+
function clearProfileLocalStorage() {
|
|
10520
|
+
lsRemove(PROFILES_KEY);
|
|
10521
|
+
lsRemove(ACTIVE_PROFILE_KEY);
|
|
10522
|
+
lsRemove("profile_completion_draft");
|
|
10523
|
+
try {
|
|
10524
|
+
localStorage.removeItem(PROFILES_KEY);
|
|
10525
|
+
localStorage.removeItem(ACTIVE_PROFILE_KEY);
|
|
10526
|
+
localStorage.removeItem("profile_completion_draft");
|
|
10527
|
+
} catch {
|
|
10528
|
+
}
|
|
10529
|
+
emitStorageChange("profile-clear");
|
|
10530
|
+
}
|
|
10499
10531
|
function getProfiles() {
|
|
10500
10532
|
return lsGet(PROFILES_KEY, []);
|
|
10501
10533
|
}
|
|
@@ -10510,19 +10542,6 @@ function setActiveProfileId(id2) {
|
|
|
10510
10542
|
lsSet(ACTIVE_PROFILE_KEY, id2);
|
|
10511
10543
|
emitStorageChange("active-profile");
|
|
10512
10544
|
}
|
|
10513
|
-
function getActiveProfile() {
|
|
10514
|
-
const profiles = getProfiles();
|
|
10515
|
-
if (profiles.length === 0) return null;
|
|
10516
|
-
const activeId = getActiveProfileId();
|
|
10517
|
-
if (activeId) {
|
|
10518
|
-
const found = profiles.find((p2) => p2.id === activeId);
|
|
10519
|
-
if (found) return found;
|
|
10520
|
-
}
|
|
10521
|
-
const sorted = [...profiles].sort(
|
|
10522
|
-
(a, b) => (b.lastUsedAt || b.createdAt || 0) - (a.lastUsedAt || a.createdAt || 0)
|
|
10523
|
-
);
|
|
10524
|
-
return sorted[0] || null;
|
|
10525
|
-
}
|
|
10526
10545
|
function updateProfile(id2, patch) {
|
|
10527
10546
|
const profiles = getProfiles();
|
|
10528
10547
|
const idx = profiles.findIndex((p2) => p2.id === id2);
|
|
@@ -10937,6 +10956,179 @@ function getApiUrl(override) {
|
|
|
10937
10956
|
}
|
|
10938
10957
|
return envUrl || "http://localhost:4000";
|
|
10939
10958
|
}
|
|
10959
|
+
const SESSION_KEY = "primestyle_profile_session";
|
|
10960
|
+
const AUTH_MESSAGE_TYPE = "PRIMESTYLE_SDK_AUTH";
|
|
10961
|
+
const POPUP_TIMEOUT_MS = 12e4;
|
|
10962
|
+
const POLL_INTERVAL_MS = 900;
|
|
10963
|
+
function isTrustedAuthOrigin(eventOrigin, expectedOrigin) {
|
|
10964
|
+
if (eventOrigin === expectedOrigin) return true;
|
|
10965
|
+
try {
|
|
10966
|
+
const host = new URL(eventOrigin).hostname;
|
|
10967
|
+
return host === "localhost" || host === "127.0.0.1" || host.endsWith(".primestyleai.com") || host.endsWith(".myaifitting.com");
|
|
10968
|
+
} catch {
|
|
10969
|
+
return false;
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
function readStorage() {
|
|
10973
|
+
if (typeof window === "undefined") return null;
|
|
10974
|
+
try {
|
|
10975
|
+
return window.localStorage;
|
|
10976
|
+
} catch {
|
|
10977
|
+
return null;
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
function createAuthRequestId() {
|
|
10981
|
+
try {
|
|
10982
|
+
const randomId = window.crypto?.randomUUID?.();
|
|
10983
|
+
if (randomId) return randomId;
|
|
10984
|
+
} catch {
|
|
10985
|
+
}
|
|
10986
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
10987
|
+
}
|
|
10988
|
+
async function readAuthStatus(statusUrl) {
|
|
10989
|
+
const response = await fetch(statusUrl.toString(), {
|
|
10990
|
+
method: "GET",
|
|
10991
|
+
credentials: "omit",
|
|
10992
|
+
cache: "no-store"
|
|
10993
|
+
});
|
|
10994
|
+
if (!response.ok) return null;
|
|
10995
|
+
const data = await response.json();
|
|
10996
|
+
return data.status === "done" ? data : null;
|
|
10997
|
+
}
|
|
10998
|
+
function getStoredProfileSession() {
|
|
10999
|
+
const storage = readStorage();
|
|
11000
|
+
if (!storage) return null;
|
|
11001
|
+
try {
|
|
11002
|
+
const raw = storage.getItem(SESSION_KEY);
|
|
11003
|
+
if (!raw) return null;
|
|
11004
|
+
const parsed = JSON.parse(raw);
|
|
11005
|
+
return parsed?.accessToken ? parsed : null;
|
|
11006
|
+
} catch {
|
|
11007
|
+
return null;
|
|
11008
|
+
}
|
|
11009
|
+
}
|
|
11010
|
+
function setStoredProfileSession(session) {
|
|
11011
|
+
const storage = readStorage();
|
|
11012
|
+
if (!storage) return;
|
|
11013
|
+
try {
|
|
11014
|
+
storage.setItem(SESSION_KEY, JSON.stringify(session));
|
|
11015
|
+
} catch {
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
function clearStoredProfileSession() {
|
|
11019
|
+
const storage = readStorage();
|
|
11020
|
+
if (!storage) return;
|
|
11021
|
+
try {
|
|
11022
|
+
storage.removeItem(SESSION_KEY);
|
|
11023
|
+
} catch {
|
|
11024
|
+
}
|
|
11025
|
+
}
|
|
11026
|
+
function startSocialProfileLogin(provider, apiUrl) {
|
|
11027
|
+
if (typeof window === "undefined") {
|
|
11028
|
+
return Promise.reject(new Error("Social login must run in the browser."));
|
|
11029
|
+
}
|
|
11030
|
+
const baseUrl = getApiUrl(apiUrl);
|
|
11031
|
+
const backendOrigin = new URL(baseUrl).origin;
|
|
11032
|
+
const authRequestId = createAuthRequestId();
|
|
11033
|
+
const startUrl = new URL(`${baseUrl.replace(/\/$/, "")}/api/sdk/v1/auth/${provider}/start`);
|
|
11034
|
+
startUrl.searchParams.set("origin", window.location.origin);
|
|
11035
|
+
startUrl.searchParams.set("requestId", authRequestId);
|
|
11036
|
+
const statusUrl = new URL(`${baseUrl.replace(/\/$/, "")}/api/sdk/v1/auth/${provider}/status`);
|
|
11037
|
+
statusUrl.searchParams.set("origin", window.location.origin);
|
|
11038
|
+
statusUrl.searchParams.set("requestId", authRequestId);
|
|
11039
|
+
const popup = window.open(
|
|
11040
|
+
startUrl.toString(),
|
|
11041
|
+
"primestyle-profile-login",
|
|
11042
|
+
"width=520,height=720"
|
|
11043
|
+
);
|
|
11044
|
+
if (!popup) {
|
|
11045
|
+
return Promise.reject(new Error("Popup was blocked. Please allow popups and try again."));
|
|
11046
|
+
}
|
|
11047
|
+
return new Promise((resolve, reject) => {
|
|
11048
|
+
let settled = false;
|
|
11049
|
+
const cleanup = () => {
|
|
11050
|
+
window.removeEventListener("message", onMessage);
|
|
11051
|
+
window.clearInterval(statusTimer);
|
|
11052
|
+
window.clearTimeout(timeout);
|
|
11053
|
+
};
|
|
11054
|
+
const finish = (fn) => {
|
|
11055
|
+
if (settled) return;
|
|
11056
|
+
settled = true;
|
|
11057
|
+
cleanup();
|
|
11058
|
+
fn();
|
|
11059
|
+
};
|
|
11060
|
+
const onMessage = (event) => {
|
|
11061
|
+
if (!isTrustedAuthOrigin(event.origin, backendOrigin)) return;
|
|
11062
|
+
const data = event.data;
|
|
11063
|
+
if (!data || data.type !== AUTH_MESSAGE_TYPE) return;
|
|
11064
|
+
if (!data.ok || !data.accessToken) {
|
|
11065
|
+
finish(() => reject(new Error(data.error || "Social login failed.")));
|
|
11066
|
+
return;
|
|
11067
|
+
}
|
|
11068
|
+
finish(() => resolve({
|
|
11069
|
+
accessToken: data.accessToken,
|
|
11070
|
+
isNewUser: Boolean(data.isNewUser),
|
|
11071
|
+
signedInAt: Date.now()
|
|
11072
|
+
}));
|
|
11073
|
+
};
|
|
11074
|
+
const handleAuthPayload = (data) => {
|
|
11075
|
+
if (!data.ok || !data.accessToken) {
|
|
11076
|
+
finish(() => reject(new Error(data.error || "Social login failed.")));
|
|
11077
|
+
return;
|
|
11078
|
+
}
|
|
11079
|
+
finish(() => resolve({
|
|
11080
|
+
accessToken: data.accessToken,
|
|
11081
|
+
isNewUser: Boolean(data.isNewUser),
|
|
11082
|
+
signedInAt: Date.now()
|
|
11083
|
+
}));
|
|
11084
|
+
};
|
|
11085
|
+
const statusTimer = window.setInterval(() => {
|
|
11086
|
+
readAuthStatus(statusUrl).then((data) => {
|
|
11087
|
+
if (data) handleAuthPayload(data);
|
|
11088
|
+
}).catch(() => {
|
|
11089
|
+
});
|
|
11090
|
+
}, POLL_INTERVAL_MS);
|
|
11091
|
+
const timeout = window.setTimeout(() => {
|
|
11092
|
+
finish(() => reject(new Error("Login did not finish. Please close the sign-in window and try again.")));
|
|
11093
|
+
}, POPUP_TIMEOUT_MS);
|
|
11094
|
+
window.addEventListener("message", onMessage);
|
|
11095
|
+
});
|
|
11096
|
+
}
|
|
11097
|
+
function endpoint(apiUrl) {
|
|
11098
|
+
return `${getApiUrl(apiUrl).replace(/\/$/, "")}/api/sdk/v1/profiles`;
|
|
11099
|
+
}
|
|
11100
|
+
function normalizeStore(data) {
|
|
11101
|
+
return {
|
|
11102
|
+
profiles: Array.isArray(data.profiles) ? data.profiles : [],
|
|
11103
|
+
activeProfileId: data.activeProfileId ?? null
|
|
11104
|
+
};
|
|
11105
|
+
}
|
|
11106
|
+
async function parseResponse(response) {
|
|
11107
|
+
const data = await response.json().catch(() => ({}));
|
|
11108
|
+
if (!response.ok) {
|
|
11109
|
+
const message = typeof data.message === "string" ? data.message : "Profile sync failed.";
|
|
11110
|
+
throw new Error(message);
|
|
11111
|
+
}
|
|
11112
|
+
return data;
|
|
11113
|
+
}
|
|
11114
|
+
async function fetchRemoteProfiles(apiUrl, accessToken) {
|
|
11115
|
+
const response = await fetch(endpoint(apiUrl), {
|
|
11116
|
+
method: "GET",
|
|
11117
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
11118
|
+
});
|
|
11119
|
+
return normalizeStore(await parseResponse(response));
|
|
11120
|
+
}
|
|
11121
|
+
async function saveRemoteProfiles(apiUrl, accessToken, profiles, activeProfileId) {
|
|
11122
|
+
const response = await fetch(endpoint(apiUrl), {
|
|
11123
|
+
method: "PUT",
|
|
11124
|
+
headers: {
|
|
11125
|
+
Authorization: `Bearer ${accessToken}`,
|
|
11126
|
+
"Content-Type": "application/json"
|
|
11127
|
+
},
|
|
11128
|
+
body: JSON.stringify({ profiles, activeProfileId })
|
|
11129
|
+
});
|
|
11130
|
+
return normalizeStore(await parseResponse(response));
|
|
11131
|
+
}
|
|
10940
11132
|
let cachedMP = null;
|
|
10941
11133
|
const MP_CACHE_TTL_MS = 6e4;
|
|
10942
11134
|
function setCachedMediaPipe(landmarks) {
|
|
@@ -10962,9 +11154,26 @@ async function recommendForProduct(input) {
|
|
|
10962
11154
|
const t0 = Date.now();
|
|
10963
11155
|
log("ENTER", { productId: input.productId, apiUrl: input.apiUrl, skipCache: !!input.skipCache });
|
|
10964
11156
|
const sessionId = getOrCreateSessionId();
|
|
10965
|
-
const
|
|
11157
|
+
const apiKey = input.apiKey ?? getApiKey();
|
|
11158
|
+
const apiUrl = (input.apiUrl ?? getApiUrl()).replace(/\/+$/, "");
|
|
11159
|
+
const session = getStoredProfileSession();
|
|
11160
|
+
if (!session) {
|
|
11161
|
+
log("no signed-in profile session — returning null");
|
|
11162
|
+
return null;
|
|
11163
|
+
}
|
|
11164
|
+
let remoteProfiles = [];
|
|
11165
|
+
let activeProfileId = null;
|
|
11166
|
+
try {
|
|
11167
|
+
const remoteStore = await fetchRemoteProfiles(apiUrl, session.accessToken);
|
|
11168
|
+
remoteProfiles = remoteStore.profiles;
|
|
11169
|
+
activeProfileId = remoteStore.activeProfileId;
|
|
11170
|
+
} catch (error) {
|
|
11171
|
+
log("remote profile fetch failed — returning null", error);
|
|
11172
|
+
return null;
|
|
11173
|
+
}
|
|
11174
|
+
const profile = input.profile && input.profile.id === activeProfileId ? input.profile : activeProfileId ? remoteProfiles.find((p2) => p2.id === activeProfileId) ?? null : null;
|
|
10966
11175
|
if (!profile) {
|
|
10967
|
-
log("no
|
|
11176
|
+
log("no selected backend profile — returning null");
|
|
10968
11177
|
return null;
|
|
10969
11178
|
}
|
|
10970
11179
|
log("profile resolved", {
|
|
@@ -11017,8 +11226,6 @@ async function recommendForProduct(input) {
|
|
|
11017
11226
|
}
|
|
11018
11227
|
}
|
|
11019
11228
|
log(`cache MISS — calling backend (elapsed in pre-flight: ${Date.now() - t0}ms)`);
|
|
11020
|
-
const apiKey = input.apiKey ?? getApiKey();
|
|
11021
|
-
const apiUrl = (input.apiUrl ?? getApiUrl()).replace(/\/+$/, "");
|
|
11022
11229
|
let sizeGuide = null;
|
|
11023
11230
|
if (input.sizeGuideData != null) {
|
|
11024
11231
|
const tSg = Date.now();
|
|
@@ -11063,6 +11270,34 @@ async function recommendForProduct(input) {
|
|
|
11063
11270
|
if (value != null) measurements[key] = value;
|
|
11064
11271
|
}
|
|
11065
11272
|
}
|
|
11273
|
+
const legacyKnownMeasurements = {};
|
|
11274
|
+
for (const key of [
|
|
11275
|
+
"chest",
|
|
11276
|
+
"bust",
|
|
11277
|
+
"waist",
|
|
11278
|
+
"hips",
|
|
11279
|
+
"shoulderWidth",
|
|
11280
|
+
"sleeveLength",
|
|
11281
|
+
"inseam",
|
|
11282
|
+
"neckCircumference",
|
|
11283
|
+
"thighCircumference",
|
|
11284
|
+
"wristCircumference",
|
|
11285
|
+
"footLengthCm"
|
|
11286
|
+
]) {
|
|
11287
|
+
const value = profile[key];
|
|
11288
|
+
if (typeof value === "number" && value > 0) {
|
|
11289
|
+
measurements[key] = value;
|
|
11290
|
+
legacyKnownMeasurements[key] = value;
|
|
11291
|
+
}
|
|
11292
|
+
}
|
|
11293
|
+
if (profile.customMeasurements) {
|
|
11294
|
+
for (const [key, value] of Object.entries(profile.customMeasurements)) {
|
|
11295
|
+
if (typeof value === "number" && value > 0) {
|
|
11296
|
+
measurements[key] = value;
|
|
11297
|
+
legacyKnownMeasurements[key] = value;
|
|
11298
|
+
}
|
|
11299
|
+
}
|
|
11300
|
+
}
|
|
11066
11301
|
if (profile.height != null) measurements.height = profile.height;
|
|
11067
11302
|
if (profile.weight != null) measurements.weight = profile.weight;
|
|
11068
11303
|
if (profile.heightUnit) measurements.heightUnit = profile.heightUnit;
|
|
@@ -11099,6 +11334,8 @@ async function recommendForProduct(input) {
|
|
|
11099
11334
|
}
|
|
11100
11335
|
if (profile.knownMeasurements) {
|
|
11101
11336
|
payload.knownMeasurements = profile.knownMeasurements;
|
|
11337
|
+
} else if (Object.keys(legacyKnownMeasurements).length > 0) {
|
|
11338
|
+
payload.knownMeasurements = legacyKnownMeasurements;
|
|
11102
11339
|
}
|
|
11103
11340
|
if (sizeGuide && sizeGuide.found) {
|
|
11104
11341
|
payload.sizeGuide = sizeGuide;
|
|
@@ -11146,7 +11383,7 @@ async function recommendForProduct(input) {
|
|
|
11146
11383
|
}
|
|
11147
11384
|
])
|
|
11148
11385
|
) : void 0;
|
|
11149
|
-
|
|
11386
|
+
const newHistoryEntry = {
|
|
11150
11387
|
productId: input.productId,
|
|
11151
11388
|
productTitle: input.productTitle,
|
|
11152
11389
|
productImage: input.productImage,
|
|
@@ -11156,6 +11393,15 @@ async function recommendForProduct(input) {
|
|
|
11156
11393
|
sectionsFull,
|
|
11157
11394
|
recommendedLength: result.recommendedLength || void 0,
|
|
11158
11395
|
savedAt: Date.now()
|
|
11396
|
+
};
|
|
11397
|
+
const updatedProfiles = remoteProfiles.map((p2) => {
|
|
11398
|
+
if (p2.id !== profile.id) return p2;
|
|
11399
|
+
const history = (p2.sizeHistory || []).filter((h) => h.productId !== input.productId);
|
|
11400
|
+
history.unshift(newHistoryEntry);
|
|
11401
|
+
return { ...p2, sizeHistory: history.slice(0, 50), lastUsedAt: Date.now() };
|
|
11402
|
+
});
|
|
11403
|
+
void saveRemoteProfiles(apiUrl, session.accessToken, updatedProfiles, activeProfileId).catch((error) => {
|
|
11404
|
+
log("remote size-history save failed", error);
|
|
11159
11405
|
});
|
|
11160
11406
|
setLastSizeSelection({
|
|
11161
11407
|
productId: input.productId,
|
|
@@ -11345,179 +11591,6 @@ async function pickBestGarmentImage(images) {
|
|
|
11345
11591
|
for (const s of scored) console.log(`[ps-sdk:garment-pick] ${s.score.toString().padStart(4, " ")} ${s.url}`);
|
|
11346
11592
|
return best;
|
|
11347
11593
|
}
|
|
11348
|
-
const SESSION_KEY = "primestyle_profile_session";
|
|
11349
|
-
const AUTH_MESSAGE_TYPE = "PRIMESTYLE_SDK_AUTH";
|
|
11350
|
-
const POPUP_TIMEOUT_MS = 12e4;
|
|
11351
|
-
const POLL_INTERVAL_MS = 900;
|
|
11352
|
-
function isTrustedAuthOrigin(eventOrigin, expectedOrigin) {
|
|
11353
|
-
if (eventOrigin === expectedOrigin) return true;
|
|
11354
|
-
try {
|
|
11355
|
-
const host = new URL(eventOrigin).hostname;
|
|
11356
|
-
return host === "localhost" || host === "127.0.0.1" || host.endsWith(".primestyleai.com") || host.endsWith(".myaifitting.com");
|
|
11357
|
-
} catch {
|
|
11358
|
-
return false;
|
|
11359
|
-
}
|
|
11360
|
-
}
|
|
11361
|
-
function readStorage() {
|
|
11362
|
-
if (typeof window === "undefined") return null;
|
|
11363
|
-
try {
|
|
11364
|
-
return window.localStorage;
|
|
11365
|
-
} catch {
|
|
11366
|
-
return null;
|
|
11367
|
-
}
|
|
11368
|
-
}
|
|
11369
|
-
function createAuthRequestId() {
|
|
11370
|
-
try {
|
|
11371
|
-
const randomId = window.crypto?.randomUUID?.();
|
|
11372
|
-
if (randomId) return randomId;
|
|
11373
|
-
} catch {
|
|
11374
|
-
}
|
|
11375
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
11376
|
-
}
|
|
11377
|
-
async function readAuthStatus(statusUrl) {
|
|
11378
|
-
const response = await fetch(statusUrl.toString(), {
|
|
11379
|
-
method: "GET",
|
|
11380
|
-
credentials: "omit",
|
|
11381
|
-
cache: "no-store"
|
|
11382
|
-
});
|
|
11383
|
-
if (!response.ok) return null;
|
|
11384
|
-
const data = await response.json();
|
|
11385
|
-
return data.status === "done" ? data : null;
|
|
11386
|
-
}
|
|
11387
|
-
function getStoredProfileSession() {
|
|
11388
|
-
const storage = readStorage();
|
|
11389
|
-
if (!storage) return null;
|
|
11390
|
-
try {
|
|
11391
|
-
const raw = storage.getItem(SESSION_KEY);
|
|
11392
|
-
if (!raw) return null;
|
|
11393
|
-
const parsed = JSON.parse(raw);
|
|
11394
|
-
return parsed?.accessToken ? parsed : null;
|
|
11395
|
-
} catch {
|
|
11396
|
-
return null;
|
|
11397
|
-
}
|
|
11398
|
-
}
|
|
11399
|
-
function setStoredProfileSession(session) {
|
|
11400
|
-
const storage = readStorage();
|
|
11401
|
-
if (!storage) return;
|
|
11402
|
-
try {
|
|
11403
|
-
storage.setItem(SESSION_KEY, JSON.stringify(session));
|
|
11404
|
-
} catch {
|
|
11405
|
-
}
|
|
11406
|
-
}
|
|
11407
|
-
function clearStoredProfileSession() {
|
|
11408
|
-
const storage = readStorage();
|
|
11409
|
-
if (!storage) return;
|
|
11410
|
-
try {
|
|
11411
|
-
storage.removeItem(SESSION_KEY);
|
|
11412
|
-
} catch {
|
|
11413
|
-
}
|
|
11414
|
-
}
|
|
11415
|
-
function startSocialProfileLogin(provider, apiUrl) {
|
|
11416
|
-
if (typeof window === "undefined") {
|
|
11417
|
-
return Promise.reject(new Error("Social login must run in the browser."));
|
|
11418
|
-
}
|
|
11419
|
-
const baseUrl = getApiUrl(apiUrl);
|
|
11420
|
-
const backendOrigin = new URL(baseUrl).origin;
|
|
11421
|
-
const authRequestId = createAuthRequestId();
|
|
11422
|
-
const startUrl = new URL(`${baseUrl.replace(/\/$/, "")}/api/sdk/v1/auth/${provider}/start`);
|
|
11423
|
-
startUrl.searchParams.set("origin", window.location.origin);
|
|
11424
|
-
startUrl.searchParams.set("requestId", authRequestId);
|
|
11425
|
-
const statusUrl = new URL(`${baseUrl.replace(/\/$/, "")}/api/sdk/v1/auth/${provider}/status`);
|
|
11426
|
-
statusUrl.searchParams.set("origin", window.location.origin);
|
|
11427
|
-
statusUrl.searchParams.set("requestId", authRequestId);
|
|
11428
|
-
const popup = window.open(
|
|
11429
|
-
startUrl.toString(),
|
|
11430
|
-
"primestyle-profile-login",
|
|
11431
|
-
"width=520,height=720"
|
|
11432
|
-
);
|
|
11433
|
-
if (!popup) {
|
|
11434
|
-
return Promise.reject(new Error("Popup was blocked. Please allow popups and try again."));
|
|
11435
|
-
}
|
|
11436
|
-
return new Promise((resolve, reject) => {
|
|
11437
|
-
let settled = false;
|
|
11438
|
-
const cleanup = () => {
|
|
11439
|
-
window.removeEventListener("message", onMessage);
|
|
11440
|
-
window.clearInterval(statusTimer);
|
|
11441
|
-
window.clearTimeout(timeout);
|
|
11442
|
-
};
|
|
11443
|
-
const finish = (fn) => {
|
|
11444
|
-
if (settled) return;
|
|
11445
|
-
settled = true;
|
|
11446
|
-
cleanup();
|
|
11447
|
-
fn();
|
|
11448
|
-
};
|
|
11449
|
-
const onMessage = (event) => {
|
|
11450
|
-
if (!isTrustedAuthOrigin(event.origin, backendOrigin)) return;
|
|
11451
|
-
const data = event.data;
|
|
11452
|
-
if (!data || data.type !== AUTH_MESSAGE_TYPE) return;
|
|
11453
|
-
if (!data.ok || !data.accessToken) {
|
|
11454
|
-
finish(() => reject(new Error(data.error || "Social login failed.")));
|
|
11455
|
-
return;
|
|
11456
|
-
}
|
|
11457
|
-
finish(() => resolve({
|
|
11458
|
-
accessToken: data.accessToken,
|
|
11459
|
-
isNewUser: Boolean(data.isNewUser),
|
|
11460
|
-
signedInAt: Date.now()
|
|
11461
|
-
}));
|
|
11462
|
-
};
|
|
11463
|
-
const handleAuthPayload = (data) => {
|
|
11464
|
-
if (!data.ok || !data.accessToken) {
|
|
11465
|
-
finish(() => reject(new Error(data.error || "Social login failed.")));
|
|
11466
|
-
return;
|
|
11467
|
-
}
|
|
11468
|
-
finish(() => resolve({
|
|
11469
|
-
accessToken: data.accessToken,
|
|
11470
|
-
isNewUser: Boolean(data.isNewUser),
|
|
11471
|
-
signedInAt: Date.now()
|
|
11472
|
-
}));
|
|
11473
|
-
};
|
|
11474
|
-
const statusTimer = window.setInterval(() => {
|
|
11475
|
-
readAuthStatus(statusUrl).then((data) => {
|
|
11476
|
-
if (data) handleAuthPayload(data);
|
|
11477
|
-
}).catch(() => {
|
|
11478
|
-
});
|
|
11479
|
-
}, POLL_INTERVAL_MS);
|
|
11480
|
-
const timeout = window.setTimeout(() => {
|
|
11481
|
-
finish(() => reject(new Error("Login did not finish. Please close the sign-in window and try again.")));
|
|
11482
|
-
}, POPUP_TIMEOUT_MS);
|
|
11483
|
-
window.addEventListener("message", onMessage);
|
|
11484
|
-
});
|
|
11485
|
-
}
|
|
11486
|
-
function endpoint(apiUrl) {
|
|
11487
|
-
return `${getApiUrl(apiUrl).replace(/\/$/, "")}/api/sdk/v1/profiles`;
|
|
11488
|
-
}
|
|
11489
|
-
function normalizeStore(data) {
|
|
11490
|
-
return {
|
|
11491
|
-
profiles: Array.isArray(data.profiles) ? data.profiles : [],
|
|
11492
|
-
activeProfileId: data.activeProfileId ?? null
|
|
11493
|
-
};
|
|
11494
|
-
}
|
|
11495
|
-
async function parseResponse(response) {
|
|
11496
|
-
const data = await response.json().catch(() => ({}));
|
|
11497
|
-
if (!response.ok) {
|
|
11498
|
-
const message = typeof data.message === "string" ? data.message : "Profile sync failed.";
|
|
11499
|
-
throw new Error(message);
|
|
11500
|
-
}
|
|
11501
|
-
return data;
|
|
11502
|
-
}
|
|
11503
|
-
async function fetchRemoteProfiles(apiUrl, accessToken) {
|
|
11504
|
-
const response = await fetch(endpoint(apiUrl), {
|
|
11505
|
-
method: "GET",
|
|
11506
|
-
headers: { Authorization: `Bearer ${accessToken}` }
|
|
11507
|
-
});
|
|
11508
|
-
return normalizeStore(await parseResponse(response));
|
|
11509
|
-
}
|
|
11510
|
-
async function saveRemoteProfiles(apiUrl, accessToken, profiles, activeProfileId) {
|
|
11511
|
-
const response = await fetch(endpoint(apiUrl), {
|
|
11512
|
-
method: "PUT",
|
|
11513
|
-
headers: {
|
|
11514
|
-
Authorization: `Bearer ${accessToken}`,
|
|
11515
|
-
"Content-Type": "application/json"
|
|
11516
|
-
},
|
|
11517
|
-
body: JSON.stringify({ profiles, activeProfileId })
|
|
11518
|
-
});
|
|
11519
|
-
return normalizeStore(await parseResponse(response));
|
|
11520
|
-
}
|
|
11521
11594
|
function normalizeProfilePhotoSource(value) {
|
|
11522
11595
|
if (!value) return null;
|
|
11523
11596
|
if (/^https?:\/\//i.test(value)) return value;
|
|
@@ -16611,7 +16684,7 @@ const STYLES = `
|
|
|
16611
16684
|
to { opacity: 1; }
|
|
16612
16685
|
}
|
|
16613
16686
|
|
|
16614
|
-
/*
|
|
16687
|
+
/* Active profile status above body details */
|
|
16615
16688
|
.ps-bp-profile-hint {
|
|
16616
16689
|
margin: 0; padding: 0;
|
|
16617
16690
|
text-align: center;
|
|
@@ -16631,6 +16704,65 @@ const STYLES = `
|
|
|
16631
16704
|
cursor: pointer; padding: 0;
|
|
16632
16705
|
}
|
|
16633
16706
|
.ps-bp-profile-hint-link:hover { color: var(--ps-text-secondary); }
|
|
16707
|
+
.ps-bp-profile-card {
|
|
16708
|
+
display: flex;
|
|
16709
|
+
align-items: center;
|
|
16710
|
+
justify-content: space-between;
|
|
16711
|
+
gap: clamp(10px, 0.9vw, 18px);
|
|
16712
|
+
width: min(100%, 520px);
|
|
16713
|
+
margin: 0 auto clamp(10px, 0.9vw, 18px);
|
|
16714
|
+
padding: clamp(10px, 0.85vw, 16px) clamp(12px, 1vw, 20px);
|
|
16715
|
+
border: 1px solid rgba(33, 84, 239, 0.18);
|
|
16716
|
+
border-radius: clamp(8px, 0.7vw, 12px);
|
|
16717
|
+
background: linear-gradient(135deg, rgba(33, 84, 239, 0.08), rgba(255, 255, 255, 0.96));
|
|
16718
|
+
box-shadow: 0 10px 28px -24px rgba(33, 84, 239, 0.55);
|
|
16719
|
+
}
|
|
16720
|
+
.ps-bp-profile-card-copy {
|
|
16721
|
+
display: flex;
|
|
16722
|
+
flex-direction: column;
|
|
16723
|
+
gap: clamp(2px, 0.18vw, 4px);
|
|
16724
|
+
min-width: 0;
|
|
16725
|
+
}
|
|
16726
|
+
.ps-bp-profile-card-eyebrow {
|
|
16727
|
+
font-size: clamp(8px, 0.55vw, 10px);
|
|
16728
|
+
font-weight: 800;
|
|
16729
|
+
letter-spacing: 0.14em;
|
|
16730
|
+
text-transform: uppercase;
|
|
16731
|
+
color: var(--ps-accent);
|
|
16732
|
+
}
|
|
16733
|
+
.ps-bp-profile-card-copy strong {
|
|
16734
|
+
font-size: clamp(13px, 0.95vw, 17px);
|
|
16735
|
+
line-height: 1.1;
|
|
16736
|
+
color: var(--ps-text-primary);
|
|
16737
|
+
overflow: hidden;
|
|
16738
|
+
text-overflow: ellipsis;
|
|
16739
|
+
white-space: nowrap;
|
|
16740
|
+
}
|
|
16741
|
+
.ps-bp-profile-card-copy span:last-child {
|
|
16742
|
+
font-size: clamp(10px, 0.68vw, 12px);
|
|
16743
|
+
line-height: 1.35;
|
|
16744
|
+
color: var(--ps-text-secondary);
|
|
16745
|
+
}
|
|
16746
|
+
.ps-bp-profile-card-action {
|
|
16747
|
+
flex-shrink: 0;
|
|
16748
|
+
border: 1px solid rgba(33, 84, 239, 0.22);
|
|
16749
|
+
background: #FFFFFF;
|
|
16750
|
+
color: var(--ps-accent);
|
|
16751
|
+
border-radius: 999px;
|
|
16752
|
+
padding: clamp(7px, 0.55vw, 10px) clamp(10px, 0.8vw, 16px);
|
|
16753
|
+
font-family: inherit;
|
|
16754
|
+
font-size: clamp(9px, 0.62vw, 11px);
|
|
16755
|
+
font-weight: 800;
|
|
16756
|
+
letter-spacing: 0.08em;
|
|
16757
|
+
text-transform: uppercase;
|
|
16758
|
+
cursor: pointer;
|
|
16759
|
+
transition: border-color 0.15s, background 0.15s, transform 0.15s;
|
|
16760
|
+
}
|
|
16761
|
+
.ps-bp-profile-card-action:hover {
|
|
16762
|
+
border-color: var(--ps-accent);
|
|
16763
|
+
background: rgba(33, 84, 239, 0.06);
|
|
16764
|
+
}
|
|
16765
|
+
.ps-bp-profile-card-action:active { transform: scale(0.98); }
|
|
16634
16766
|
|
|
16635
16767
|
/* Typography */
|
|
16636
16768
|
.ps-bp-title {
|
|
@@ -19259,6 +19391,9 @@ const STYLES = `
|
|
|
19259
19391
|
background: var(--ps-bg-primary);
|
|
19260
19392
|
flex-shrink: 0;
|
|
19261
19393
|
}
|
|
19394
|
+
.ps-cpw-footer-no-back {
|
|
19395
|
+
justify-content: flex-end;
|
|
19396
|
+
}
|
|
19262
19397
|
.ps-cpw-back-btn {
|
|
19263
19398
|
background: none; border: none;
|
|
19264
19399
|
color: var(--ps-text-secondary);
|
|
@@ -19902,6 +20037,7 @@ const STYLES = `
|
|
|
19902
20037
|
display: flex; flex-direction: column;
|
|
19903
20038
|
gap: max(14px, 1.2vw);
|
|
19904
20039
|
min-width: 0; width: 100%;
|
|
20040
|
+
padding-bottom: clamp(78px, 5.4vw, 104px);
|
|
19905
20041
|
}
|
|
19906
20042
|
|
|
19907
20043
|
/* Basics list — height / weight / age, inline icon + label + value */
|
|
@@ -20137,12 +20273,12 @@ const STYLES = `
|
|
|
20137
20273
|
gap: clamp(8px, 0.7vw, 14px);
|
|
20138
20274
|
position: sticky;
|
|
20139
20275
|
bottom: 0;
|
|
20140
|
-
background: var(--ps-bg-primary);
|
|
20276
|
+
background: color-mix(in srgb, var(--ps-bg-primary) 96%, #FFFFFF);
|
|
20141
20277
|
border-top: 1px solid var(--ps-border-subtle);
|
|
20142
|
-
padding
|
|
20143
|
-
|
|
20144
|
-
z-index: 2;
|
|
20278
|
+
padding: clamp(10px, 0.8vw, 16px) clamp(4px, 0.35vw, 8px) calc(clamp(10px, 0.8vw, 16px) + env(safe-area-inset-bottom, 0px));
|
|
20279
|
+
z-index: 5;
|
|
20145
20280
|
margin-top: auto;
|
|
20281
|
+
box-shadow: 0 -14px 30px -28px rgba(17, 24, 39, 0.35);
|
|
20146
20282
|
}
|
|
20147
20283
|
.ps-pmv-actions-right {
|
|
20148
20284
|
display: flex; align-items: center;
|
|
@@ -20154,7 +20290,7 @@ const STYLES = `
|
|
|
20154
20290
|
background: none;
|
|
20155
20291
|
border: 1px solid var(--ps-border-color);
|
|
20156
20292
|
border-radius: clamp(4px, 0.35vw, 8px);
|
|
20157
|
-
padding: clamp(6px, 0.55vw, 12px) clamp(
|
|
20293
|
+
padding: clamp(6px, 0.55vw, 12px) clamp(13px, 1.05vw, 24px);
|
|
20158
20294
|
font-family: inherit;
|
|
20159
20295
|
font-size: clamp(9px, 0.65vw, 12px);
|
|
20160
20296
|
font-weight: 600;
|
|
@@ -20176,7 +20312,7 @@ const STYLES = `
|
|
|
20176
20312
|
background: var(--ps-accent); color: #FFFFFF;
|
|
20177
20313
|
border: none;
|
|
20178
20314
|
border-radius: clamp(4px, 0.35vw, 8px);
|
|
20179
|
-
padding: clamp(7px, 0.65vw, 14px) clamp(
|
|
20315
|
+
padding: clamp(7px, 0.65vw, 14px) clamp(15px, 1.15vw, 26px);
|
|
20180
20316
|
font-family: inherit;
|
|
20181
20317
|
font-size: clamp(9px, 0.65vw, 12px);
|
|
20182
20318
|
font-weight: 700;
|
|
@@ -21824,7 +21960,7 @@ function WelcomeView({
|
|
|
21824
21960
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-welcome-sparkle", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SparkleIcon, { size: 20 }) })
|
|
21825
21961
|
] }),
|
|
21826
21962
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-tryon-welcome-title", children: t2("See Your Fit") }),
|
|
21827
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-welcome-sub", children: t2("
|
|
21963
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-welcome-sub", children: t2("Check your size, then try it on virtually") })
|
|
21828
21964
|
] }),
|
|
21829
21965
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-features", children: [
|
|
21830
21966
|
{ icon: /* @__PURE__ */ jsxRuntimeExports.jsx(RulerIcon$1, { size: 22 }), title: t2("Get Your Size"), desc: t2("Instant fit recommendation") },
|
|
@@ -22155,7 +22291,7 @@ function MobileScanningView({
|
|
|
22155
22291
|
{ title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation."), viewfinderText: t2("FINALIZING") }
|
|
22156
22292
|
] : [
|
|
22157
22293
|
{ title: t2("DETECTING POSE"), desc: t2("Identifying body landmarks from your photo."), viewfinderText: t2("DETECTING POSE") },
|
|
22158
|
-
{ title: t2("SCANNING FRAME"), desc: t2("Our AI is mapping your proportions
|
|
22294
|
+
{ title: t2("SCANNING FRAME"), desc: t2("Our AI is mapping your proportions for a size recommendation."), viewfinderText: t2("SCANNING FRAME") },
|
|
22159
22295
|
{ title: t2("ANALYZING BODY"), desc: t2("Measuring shoulders, chest, waist and hips."), viewfinderText: t2("ANALYZING") },
|
|
22160
22296
|
{ title: t2("MATCHING SIZE"), desc: t2("Comparing your measurements to the size guide."), viewfinderText: t2("MATCHING SIZE") },
|
|
22161
22297
|
{ title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation."), viewfinderText: t2("FINALIZING") }
|
|
@@ -23168,10 +23304,10 @@ const cellValFn = (row, colIdx, header) => {
|
|
|
23168
23304
|
}
|
|
23169
23305
|
return "";
|
|
23170
23306
|
};
|
|
23171
|
-
const fitLabelFn = (fit, t2) => fit === "good" ? t2("
|
|
23307
|
+
const fitLabelFn = (fit, t2) => fit === "good" ? t2("within range") : fit === "too-tight" ? t2("too tight") : fit === "tight" ? t2("tight") : fit === "a-bit-tight" ? t2("a bit tight") : fit === "too-loose" ? t2("too loose") : fit === "loose" ? t2("loose") : t2("a bit loose");
|
|
23172
23308
|
const accessoryFitLabelFn = (fit, t2) => fit === "good" ? t2("within range") : fitLabelFn(fit, t2);
|
|
23173
23309
|
const lengthFitLabelFn = (fit, t2) => {
|
|
23174
|
-
if (fit === "good") return t2("
|
|
23310
|
+
if (fit === "good") return t2("within range");
|
|
23175
23311
|
if (fit === "too-short" || fit === "too-tight") return t2("too short");
|
|
23176
23312
|
if (fit === "short" || fit === "tight") return t2("short");
|
|
23177
23313
|
if (fit === "a-bit-short" || fit === "a-bit-tight") return t2("a bit short");
|
|
@@ -24929,7 +25065,7 @@ function SizeResultView({
|
|
|
24929
25065
|
] }),
|
|
24930
25066
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-panel ps-tryon-v2-result-panel", children: [
|
|
24931
25067
|
profileCompletionCta ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-v2-profile-head ps-expanded", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProfileCompletionCta, { onClick: profileCompletionCta.onClick, placement: "header", t: t2 }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-result-copy", children: [
|
|
24932
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-v2-title", children: t2("Your
|
|
25068
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-v2-title", children: t2("Your Size Recommendation") }),
|
|
24933
25069
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-v2-subtitle", children: t2("Tap any section for detailed breakdown") })
|
|
24934
25070
|
] }),
|
|
24935
25071
|
mismatchNotice,
|
|
@@ -25186,7 +25322,7 @@ function SizeResultView({
|
|
|
25186
25322
|
/* CARD VIEW — clickable summary card + gallery strip */
|
|
25187
25323
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
25188
25324
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-v2-result-head", children: profileCompletionCta ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-v2-profile-head ps-expanded", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProfileCompletionCta, { onClick: profileCompletionCta.onClick, placement: "header", t: t2 }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-result-copy", children: [
|
|
25189
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-v2-title", children: guideOnlyResult ? t2("Product Size Guide") : t2("Your
|
|
25325
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-v2-title", children: guideOnlyResult ? t2("Product Size Guide") : t2("Your Size Recommendation") }),
|
|
25190
25326
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-v2-subtitle", children: guideOnlyResult ? t2("Tap the card to view product measurements") : t2("Tap the card for detailed breakdown") })
|
|
25191
25327
|
] }) }),
|
|
25192
25328
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-v2-sep" }),
|
|
@@ -26048,7 +26184,7 @@ function ProcessingView({
|
|
|
26048
26184
|
}
|
|
26049
26185
|
}, []);
|
|
26050
26186
|
const aiFacts = [
|
|
26051
|
-
t2("Our model is analyzing 150+ body landmarks for
|
|
26187
|
+
t2("Our model is analyzing 150+ body landmarks for your size recommendation"),
|
|
26052
26188
|
t2("Calibrating fabric drape against your body proportions"),
|
|
26053
26189
|
t2("Cross-checking fit against millions of garment patterns"),
|
|
26054
26190
|
t2("Rendering shadows and highlights to match your photo's lighting")
|
|
@@ -26151,20 +26287,22 @@ function ProcessingView({
|
|
|
26151
26287
|
function NoChartView({
|
|
26152
26288
|
productImage,
|
|
26153
26289
|
productTitle,
|
|
26290
|
+
reason = "no-chart",
|
|
26154
26291
|
onTryOn,
|
|
26155
26292
|
onClose,
|
|
26156
26293
|
t: t2
|
|
26157
26294
|
}) {
|
|
26158
26295
|
const isMobile = useIsMobile();
|
|
26296
|
+
const isNoMatch = reason === "no-match";
|
|
26159
26297
|
const RightColumn = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-no-chart-content", children: [
|
|
26160
26298
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: "44", height: "44", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
26161
26299
|
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3 17l6 6 12-12-6-6L3 17z" }),
|
|
26162
26300
|
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M14 4l-3 3M16 6l-2 2M18 8l-2 2M11 7l-2 2M13 9l-2 2M15 11l-2 2" })
|
|
26163
26301
|
] }) }),
|
|
26164
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-no-chart-title", children: t2("No size chart available") }),
|
|
26165
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-no-chart-msg", children: t2("The merchant hasn't uploaded sizing data for this product yet. You can still see how it looks on you with a virtual try-on.") }),
|
|
26302
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-no-chart-title", children: isNoMatch ? t2("No matching size available") : t2("No size chart available") }),
|
|
26303
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-no-chart-msg", children: isNoMatch ? t2("This product's size chart doesn't include a reliable fit for your measurements.") : t2("The merchant hasn't uploaded sizing data for this product yet. You can still see how it looks on you with a virtual try-on.") }),
|
|
26166
26304
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-no-chart-actions", children: [
|
|
26167
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "ps-tryon-no-chart-cta", onClick: onTryOn, children: [
|
|
26305
|
+
!isNoMatch && /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "ps-tryon-no-chart-cta", onClick: onTryOn, children: [
|
|
26168
26306
|
t2("See how it looks on you"),
|
|
26169
26307
|
" →"
|
|
26170
26308
|
] }),
|
|
@@ -26348,7 +26486,7 @@ function ProgressiveStep({
|
|
|
26348
26486
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-card-row", children })
|
|
26349
26487
|
] }, stepKey);
|
|
26350
26488
|
}
|
|
26351
|
-
function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, apiUrl, apiKey, onPhotoPreview, onEstimate, t: t2 }) {
|
|
26489
|
+
function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, directAnalysisFlow = false, apiUrl, apiKey, onPhotoPreview, onEstimate, t: t2 }) {
|
|
26352
26490
|
const seededUnit = draftUnit(initialDraft);
|
|
26353
26491
|
const seededHeight = draftHeightParts(initialDraft, seededUnit);
|
|
26354
26492
|
const seededGender = initialDraft?.gender === "female" ? "female" : "male";
|
|
@@ -26383,6 +26521,7 @@ function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, apiU
|
|
|
26383
26521
|
const [scanStageIdx, setScanStageIdx] = reactExports.useState(0);
|
|
26384
26522
|
const [photoHelpOpen, setPhotoHelpOpen] = reactExports.useState(false);
|
|
26385
26523
|
const [uploadHoverCpw, setUploadHoverCpw] = reactExports.useState(false);
|
|
26524
|
+
const directAutoStartedRef = reactExports.useRef(false);
|
|
26386
26525
|
reactExports.useEffect(() => {
|
|
26387
26526
|
if (imageStep !== "calculating" || !photoBase64) return;
|
|
26388
26527
|
let cancelled = false;
|
|
@@ -26681,7 +26820,7 @@ function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, apiU
|
|
|
26681
26820
|
return;
|
|
26682
26821
|
}
|
|
26683
26822
|
const a = parseFloat(ageVal);
|
|
26684
|
-
if (isWomen && (!bandSize || !cupSize)) {
|
|
26823
|
+
if (isWomen && !directAnalysisFlow && (!bandSize || !cupSize)) {
|
|
26685
26824
|
setError(t2("Please select your bra band and cup size"));
|
|
26686
26825
|
return;
|
|
26687
26826
|
}
|
|
@@ -26731,6 +26870,15 @@ function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, apiU
|
|
|
26731
26870
|
}
|
|
26732
26871
|
onSave(payload);
|
|
26733
26872
|
};
|
|
26873
|
+
reactExports.useEffect(() => {
|
|
26874
|
+
if (!directAnalysisFlow || directAutoStartedRef.current) return;
|
|
26875
|
+
if (mode !== "image" || imageStep !== "name-photo") return;
|
|
26876
|
+
const hasHeight = unit === "in" ? parseFloat(heightFt) > 0 || parseFloat(heightInch) > 0 : parseFloat(heightVal) > 0;
|
|
26877
|
+
const hasWeight = parseFloat(weightVal) > 0;
|
|
26878
|
+
if (!photoBase64 || !name.trim() || !hasHeight || !hasWeight) return;
|
|
26879
|
+
directAutoStartedRef.current = true;
|
|
26880
|
+
void advanceImage();
|
|
26881
|
+
}, [directAnalysisFlow, mode, imageStep, photoBase64, name, heightFt, heightInch, heightVal, weightVal, unit]);
|
|
26734
26882
|
const goBackImage = () => {
|
|
26735
26883
|
setError("");
|
|
26736
26884
|
if (imageStep === "details") {
|
|
@@ -26772,9 +26920,12 @@ function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, apiU
|
|
|
26772
26920
|
if (photoBase64) pct += 50;
|
|
26773
26921
|
return pct;
|
|
26774
26922
|
})();
|
|
26775
|
-
const hideGlobalBack = mode === "image" && imageStep === "name-photo";
|
|
26776
|
-
|
|
26777
|
-
|
|
26923
|
+
const hideGlobalBack = mode === "image" && imageStep === "name-photo" && !directAnalysisFlow;
|
|
26924
|
+
const hideStepHeader = directAnalysisFlow && mode === "image";
|
|
26925
|
+
const hideFooter = directAnalysisFlow && mode === "image" && imageStep === "calculating" && estimating;
|
|
26926
|
+
const hideFooterBack = directAnalysisFlow && mode === "image";
|
|
26927
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-cpw-root${hideGlobalBack ? " ps-cpw-hide-global-back" : ""}${directAnalysisFlow ? " ps-cpw-direct-flow" : ""}`, children: [
|
|
26928
|
+
!hideStepHeader && !(mode === "image" && imageStep === "name-photo") && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-step-head", children: [
|
|
26778
26929
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-cpw-step-title", children: headerLabel }),
|
|
26779
26930
|
mode != null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-progress", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-progress-track", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-progress-fill", style: { width: `${progressPct}%` } }) }) })
|
|
26780
26931
|
] }),
|
|
@@ -27702,7 +27853,7 @@ function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, apiU
|
|
|
27702
27853
|
mode === "image" && imageStep === "calculating" && (() => {
|
|
27703
27854
|
const stages = [
|
|
27704
27855
|
{ title: t2("DETECTING POSE"), desc: t2("Identifying body landmarks from your photo.") },
|
|
27705
|
-
{ title: t2("SCANNING FRAME"), desc: t2("Our AI is mapping your proportions
|
|
27856
|
+
{ title: t2("SCANNING FRAME"), desc: t2("Our AI is mapping your proportions for a size recommendation.") },
|
|
27706
27857
|
{ title: t2("ANALYZING BODY"), desc: t2("Measuring shoulders, chest, waist and hips.") },
|
|
27707
27858
|
{ title: t2("MATCHING SIZE"), desc: t2("Comparing your measurements to the size guide.") },
|
|
27708
27859
|
{ title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
|
|
@@ -27790,13 +27941,13 @@ function CreateProfileWizard({ onSave, onCancel, initialMode, initialDraft, apiU
|
|
|
27790
27941
|
] }) }, "image-calculating");
|
|
27791
27942
|
})()
|
|
27792
27943
|
] }),
|
|
27793
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-footer"
|
|
27794
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-back-btn", onClick: handleBack, children: mode == null ? t2("Cancel") : `← ${t2("Back")}` }),
|
|
27944
|
+
!hideFooter && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-cpw-footer${hideFooterBack ? " ps-cpw-footer-no-back" : ""}`, children: [
|
|
27945
|
+
!hideFooterBack && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-back-btn", onClick: handleBack, children: mode == null ? t2("Cancel") : `← ${t2("Back")}` }),
|
|
27795
27946
|
mode === "manual" && !isAutoAdvanceStep && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-next-btn", onClick: advanceManual, children: isLastManualStep ? t2("CALCULATE MY FIT") : t2("Continue") }),
|
|
27796
27947
|
mode === "image" && imageStep === "name-photo" && (() => {
|
|
27797
27948
|
const heightOk = unit === "in" ? parseFloat(heightFt) > 0 || parseFloat(heightInch) > 0 : parseFloat(heightVal) > 0;
|
|
27798
27949
|
const weightOk = parseFloat(weightVal) > 0;
|
|
27799
|
-
const braOk = !isWomen || !!bandSize && !!cupSize;
|
|
27950
|
+
const braOk = !isWomen || directAnalysisFlow || !!bandSize && !!cupSize;
|
|
27800
27951
|
const nameOk = !!name.trim();
|
|
27801
27952
|
const photoOk = !!photoBase64;
|
|
27802
27953
|
const analyzing = photoUploading;
|
|
@@ -28252,6 +28403,7 @@ function MySizingProfilesView({
|
|
|
28252
28403
|
apiKey,
|
|
28253
28404
|
initialMode: initialCreateDraft ? "image" : void 0,
|
|
28254
28405
|
initialDraft: initialCreateDraft,
|
|
28406
|
+
directAnalysisFlow: !!initialCreateDraft,
|
|
28255
28407
|
onSave: (data) => {
|
|
28256
28408
|
onSaveNewProfile(data);
|
|
28257
28409
|
onProfileDraftConsumed?.();
|
|
@@ -28488,14 +28640,13 @@ function BasicsStepMobile({
|
|
|
28488
28640
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-bpm-title", children: t2("Body Measurements") }),
|
|
28489
28641
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-bpm-subtitle", children: t2("Enter your details for a bespoke size recommendation") })
|
|
28490
28642
|
] }),
|
|
28491
|
-
activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
28492
|
-
|
|
28493
|
-
|
|
28494
|
-
|
|
28495
|
-
|
|
28496
|
-
|
|
28497
|
-
|
|
28498
|
-
] })
|
|
28643
|
+
activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-profile-card", children: [
|
|
28644
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-profile-card-copy", children: [
|
|
28645
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-profile-card-eyebrow", children: t2("Active Profile") }),
|
|
28646
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: activeProfileName }),
|
|
28647
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Using saved measurements for this fit.") })
|
|
28648
|
+
] }),
|
|
28649
|
+
onStartFresh && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-bp-profile-card-action", onClick: onStartFresh, children: t2("Start Fresh") })
|
|
28499
28650
|
] }),
|
|
28500
28651
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bpm-toggle", children: [
|
|
28501
28652
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -29555,6 +29706,9 @@ function BodyProfileView({
|
|
|
29555
29706
|
formRef.current.weight = String(weightVal);
|
|
29556
29707
|
formRef.current.gender = gender;
|
|
29557
29708
|
if (age) formRef.current.age = age;
|
|
29709
|
+
if (chestProfile) formRef.current.chestProfile = chestProfile;
|
|
29710
|
+
if (midsectionProfile) formRef.current.midsectionProfile = midsectionProfile;
|
|
29711
|
+
if (hipProfile) formRef.current.hipProfile = hipProfile;
|
|
29558
29712
|
if (bandSize) formRef.current.bandSize = bandSize;
|
|
29559
29713
|
if (cupSize) formRef.current.cupSize = cupSize;
|
|
29560
29714
|
if (braSizeRegion) formRef.current.braSizeRegion = braSizeRegion;
|
|
@@ -29611,6 +29765,9 @@ function BodyProfileView({
|
|
|
29611
29765
|
weightUnit: isShoeReferenceMode ? "kg" : wUnit,
|
|
29612
29766
|
gender,
|
|
29613
29767
|
...ageForSubmit ? { age: ageForSubmit } : {},
|
|
29768
|
+
...chestProfile ? { chestProfile } : {},
|
|
29769
|
+
...midsectionProfile ? { midsectionProfile } : {},
|
|
29770
|
+
...hipProfile ? { hipProfile } : {},
|
|
29614
29771
|
...currentShoeReference ? {
|
|
29615
29772
|
extraMeasurements: {
|
|
29616
29773
|
referenceShoeBrandId: currentShoeReference.brandId,
|
|
@@ -30508,14 +30665,13 @@ function BodyProfileView({
|
|
|
30508
30665
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToImperial, type: "button", children: t2("Imperial") })
|
|
30509
30666
|
] }),
|
|
30510
30667
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginTop: "auto", marginBottom: "auto" }, children: [
|
|
30511
|
-
activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
] })
|
|
30668
|
+
activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-profile-card", children: [
|
|
30669
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-profile-card-copy", children: [
|
|
30670
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-profile-card-eyebrow", children: t2("Active Profile") }),
|
|
30671
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: activeProfileName }),
|
|
30672
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Using saved measurements for this fit.") })
|
|
30673
|
+
] }),
|
|
30674
|
+
onStartFresh && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-bp-profile-card-action", onClick: handleClearFromProfile, children: t2("Start Fresh") })
|
|
30519
30675
|
] }),
|
|
30520
30676
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-inline-fields", children: [
|
|
30521
30677
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-inline-row", children: [
|
|
@@ -30817,14 +30973,13 @@ function BodyProfileView({
|
|
|
30817
30973
|
}
|
|
30818
30974
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-step ps-bp-step-enter", children: [
|
|
30819
30975
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-bp-title", children: basicOnly || simplePhotoOnly ? t2("Your Details") : t2("Body Measurements") }),
|
|
30820
|
-
activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
30821
|
-
|
|
30822
|
-
|
|
30823
|
-
|
|
30824
|
-
|
|
30825
|
-
|
|
30826
|
-
|
|
30827
|
-
] })
|
|
30976
|
+
activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-profile-card", children: [
|
|
30977
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-profile-card-copy", children: [
|
|
30978
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-profile-card-eyebrow", children: t2("Active Profile") }),
|
|
30979
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: activeProfileName }),
|
|
30980
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Using saved measurements for this fit.") })
|
|
30981
|
+
] }),
|
|
30982
|
+
onStartFresh && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-bp-profile-card-action", onClick: handleClearFromProfile, children: t2("Start Fresh") })
|
|
30828
30983
|
] }),
|
|
30829
30984
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", children: [
|
|
30830
30985
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${!isImperialMode ? " ps-bp-system-active" : ""}`, onClick: switchToMetric, type: "button", children: t2("Metric") }),
|
|
@@ -32600,6 +32755,7 @@ function PrimeStyleTryonInner({
|
|
|
32600
32755
|
setCssReady(true);
|
|
32601
32756
|
}, []);
|
|
32602
32757
|
const [view, setView] = reactExports.useState(initialView ?? "idle");
|
|
32758
|
+
const [noSizeReason, setNoSizeReason] = reactExports.useState("no-chart");
|
|
32603
32759
|
const [selectedFile, setSelectedFile] = reactExports.useState(null);
|
|
32604
32760
|
const [previewUrl, setPreviewUrl] = reactExports.useState(null);
|
|
32605
32761
|
const [resultImageUrl, setResultImageUrl] = reactExports.useState(null);
|
|
@@ -32679,7 +32835,9 @@ function PrimeStyleTryonInner({
|
|
|
32679
32835
|
const [estimationLoading, setEstimationLoading] = reactExports.useState(false);
|
|
32680
32836
|
const [profiles, setProfiles] = reactExports.useState(() => lsGet("profiles", []));
|
|
32681
32837
|
const [profileSession, setProfileSession] = reactExports.useState(() => getStoredProfileSession());
|
|
32682
|
-
const
|
|
32838
|
+
const profileSessionRef = reactExports.useRef(profileSession);
|
|
32839
|
+
const suppressNextProfilePersistRef = reactExports.useRef(false);
|
|
32840
|
+
const [profileCompletionDraft, setProfileCompletionDraft] = reactExports.useState(() => getStoredProfileSession() ? null : lsGet(PROFILE_COMPLETION_DRAFT_KEY, null));
|
|
32683
32841
|
const [profileAuthError, setProfileAuthError] = reactExports.useState(null);
|
|
32684
32842
|
const [profileAuthLoadingProvider, setProfileAuthLoadingProvider] = reactExports.useState(null);
|
|
32685
32843
|
const [history, setHistory] = reactExports.useState(() => lsGet("history", []).map(normalizeHistoryEntry));
|
|
@@ -32698,8 +32856,11 @@ function PrimeStyleTryonInner({
|
|
|
32698
32856
|
const [deleteConfirmId, setDeleteConfirmId] = reactExports.useState(null);
|
|
32699
32857
|
const setActiveProfileId$1 = reactExports.useCallback((id2) => {
|
|
32700
32858
|
setActiveProfileIdState(id2);
|
|
32701
|
-
setActiveProfileId(id2);
|
|
32859
|
+
if (!profileSessionRef.current) setActiveProfileId(id2);
|
|
32702
32860
|
}, []);
|
|
32861
|
+
reactExports.useEffect(() => {
|
|
32862
|
+
profileSessionRef.current = profileSession;
|
|
32863
|
+
}, [profileSession]);
|
|
32703
32864
|
const [profileSaved, setProfileSaved] = reactExports.useState(false);
|
|
32704
32865
|
const [drawer, setDrawer] = reactExports.useState(null);
|
|
32705
32866
|
const [profileDetail, setProfileDetail] = reactExports.useState(null);
|
|
@@ -32715,7 +32876,7 @@ function PrimeStyleTryonInner({
|
|
|
32715
32876
|
profilesGoBackRef.current = goBack;
|
|
32716
32877
|
}, []);
|
|
32717
32878
|
const clearProfileCompletionDraft = reactExports.useCallback(() => {
|
|
32718
|
-
|
|
32879
|
+
lsRemove(PROFILE_COMPLETION_DRAFT_KEY);
|
|
32719
32880
|
setProfileCompletionDraft(null);
|
|
32720
32881
|
}, []);
|
|
32721
32882
|
const handleCompleteProfileFromResult = reactExports.useCallback(() => {
|
|
@@ -32748,31 +32909,33 @@ function PrimeStyleTryonInner({
|
|
|
32748
32909
|
...normalizedProfilePhoto ? { photoBase64: normalizedProfilePhoto } : {},
|
|
32749
32910
|
...Object.keys(customMeasurements).length > 0 ? { customMeasurements } : {}
|
|
32750
32911
|
};
|
|
32751
|
-
|
|
32912
|
+
if (profileSessionRef.current) lsRemove(PROFILE_COMPLETION_DRAFT_KEY);
|
|
32913
|
+
else lsSet(PROFILE_COMPLETION_DRAFT_KEY, draft);
|
|
32752
32914
|
setProfileCompletionDraft(draft);
|
|
32753
32915
|
setProfileAuthError(null);
|
|
32754
32916
|
setView("profiles");
|
|
32755
32917
|
}, [activeProfileId, effectiveProductId, heightUnit, productTitle, profiles, sizingUnit, weightUnit]);
|
|
32756
|
-
const applyProfileStore = reactExports.useCallback((nextProfiles, nextActiveProfileId) => {
|
|
32757
|
-
saveProfiles(nextProfiles);
|
|
32918
|
+
const applyProfileStore = reactExports.useCallback((nextProfiles, nextActiveProfileId, persistLocal = !profileSessionRef.current) => {
|
|
32919
|
+
if (persistLocal) saveProfiles(nextProfiles);
|
|
32758
32920
|
setProfiles(nextProfiles);
|
|
32759
|
-
|
|
32921
|
+
setActiveProfileIdState(nextActiveProfileId);
|
|
32922
|
+
if (persistLocal) setActiveProfileId(nextActiveProfileId);
|
|
32760
32923
|
profileSyncSnapshotRef.current = JSON.stringify({ profiles: nextProfiles, activeProfileId: nextActiveProfileId });
|
|
32761
|
-
}, [
|
|
32924
|
+
}, []);
|
|
32762
32925
|
const hydrateProfileStore = reactExports.useCallback(async (session, seedProfiles, seedActiveProfileId) => {
|
|
32763
32926
|
profileSyncHydratingRef.current = true;
|
|
32764
32927
|
try {
|
|
32765
32928
|
const remoteStore = await fetchRemoteProfiles(apiUrl, session.accessToken);
|
|
32766
32929
|
if (remoteStore.profiles.length > 0) {
|
|
32767
|
-
applyProfileStore(remoteStore.profiles, remoteStore.activeProfileId);
|
|
32930
|
+
applyProfileStore(remoteStore.profiles, remoteStore.activeProfileId, false);
|
|
32768
32931
|
return;
|
|
32769
32932
|
}
|
|
32770
32933
|
if (seedProfiles.length > 0) {
|
|
32771
32934
|
const savedStore = await saveRemoteProfiles(apiUrl, session.accessToken, seedProfiles, seedActiveProfileId);
|
|
32772
|
-
applyProfileStore(savedStore.profiles, savedStore.activeProfileId);
|
|
32935
|
+
applyProfileStore(savedStore.profiles, savedStore.activeProfileId, false);
|
|
32773
32936
|
return;
|
|
32774
32937
|
}
|
|
32775
|
-
applyProfileStore([], null);
|
|
32938
|
+
applyProfileStore([], null, false);
|
|
32776
32939
|
} finally {
|
|
32777
32940
|
profileSyncHydratingRef.current = false;
|
|
32778
32941
|
}
|
|
@@ -32783,6 +32946,7 @@ function PrimeStyleTryonInner({
|
|
|
32783
32946
|
try {
|
|
32784
32947
|
const session = await startSocialProfileLogin(provider, apiUrl);
|
|
32785
32948
|
setStoredProfileSession(session);
|
|
32949
|
+
profileSessionRef.current = session;
|
|
32786
32950
|
setProfileSession(session);
|
|
32787
32951
|
await hydrateProfileStore(session, profiles, activeProfileId);
|
|
32788
32952
|
} catch (error) {
|
|
@@ -32793,10 +32957,28 @@ function PrimeStyleTryonInner({
|
|
|
32793
32957
|
}, [activeProfileId, apiUrl, hydrateProfileStore, profiles, t2]);
|
|
32794
32958
|
const handleProfileLogout = reactExports.useCallback(() => {
|
|
32795
32959
|
clearStoredProfileSession();
|
|
32960
|
+
clearProfileLocalStorage();
|
|
32961
|
+
suppressNextProfilePersistRef.current = true;
|
|
32962
|
+
profileSessionRef.current = null;
|
|
32796
32963
|
setProfileSession(null);
|
|
32964
|
+
setProfiles([]);
|
|
32965
|
+
setActiveProfileIdState(null);
|
|
32966
|
+
setProfileCompletionDraft(null);
|
|
32967
|
+
setProfileSaved(false);
|
|
32968
|
+
setProfileDetail(null);
|
|
32969
|
+
formRef.current = {};
|
|
32970
|
+
setFormKey((k2) => k2 + 1);
|
|
32971
|
+
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
|
32972
|
+
setSelectedFile(null);
|
|
32973
|
+
selectedFileRef.current = null;
|
|
32974
|
+
setPreviewUrl(null);
|
|
32975
|
+
setBodyLandmarks(null);
|
|
32976
|
+
setFaceLandmarks(null);
|
|
32977
|
+
setEstimatedValues(null);
|
|
32978
|
+
setEstimatingProfileIds(/* @__PURE__ */ new Set());
|
|
32797
32979
|
setProfileAuthError(null);
|
|
32798
32980
|
setProfileAuthLoadingProvider(null);
|
|
32799
|
-
}, []);
|
|
32981
|
+
}, [previewUrl]);
|
|
32800
32982
|
const fileInputRef = reactExports.useRef(null);
|
|
32801
32983
|
const apiRef = reactExports.useRef(null);
|
|
32802
32984
|
const sseRef = reactExports.useRef(null);
|
|
@@ -33079,6 +33261,11 @@ function PrimeStyleTryonInner({
|
|
|
33079
33261
|
};
|
|
33080
33262
|
}, [history]);
|
|
33081
33263
|
reactExports.useEffect(() => {
|
|
33264
|
+
if (suppressNextProfilePersistRef.current) {
|
|
33265
|
+
suppressNextProfilePersistRef.current = false;
|
|
33266
|
+
return;
|
|
33267
|
+
}
|
|
33268
|
+
if (profileSessionRef.current) return;
|
|
33082
33269
|
lsSet("profiles", profiles);
|
|
33083
33270
|
}, [profiles]);
|
|
33084
33271
|
reactExports.useEffect(() => {
|
|
@@ -33107,7 +33294,10 @@ function PrimeStyleTryonInner({
|
|
|
33107
33294
|
};
|
|
33108
33295
|
}, [activeProfileId, apiUrl, applyProfileStore, profileSession, profiles, t2]);
|
|
33109
33296
|
reactExports.useEffect(() => {
|
|
33110
|
-
const handler = () =>
|
|
33297
|
+
const handler = () => {
|
|
33298
|
+
if (profileSessionRef.current) return;
|
|
33299
|
+
setProfiles(lsGet("profiles", []));
|
|
33300
|
+
};
|
|
33111
33301
|
window.addEventListener(PS_STORAGE_CHANGE_EVENT, handler);
|
|
33112
33302
|
return () => window.removeEventListener(PS_STORAGE_CHANGE_EVENT, handler);
|
|
33113
33303
|
}, []);
|
|
@@ -33155,6 +33345,14 @@ function PrimeStyleTryonInner({
|
|
|
33155
33345
|
return 1;
|
|
33156
33346
|
}
|
|
33157
33347
|
}, [view]);
|
|
33348
|
+
const updateProfilesForCurrentSession = reactExports.useCallback((updater) => {
|
|
33349
|
+
setProfiles((prev) => {
|
|
33350
|
+
const source = profileSessionRef.current ? prev : lsGet("profiles", prev);
|
|
33351
|
+
const next = updater(source);
|
|
33352
|
+
if (!profileSessionRef.current) saveProfiles(next);
|
|
33353
|
+
return next;
|
|
33354
|
+
});
|
|
33355
|
+
}, []);
|
|
33158
33356
|
const persistResultToProfile = reactExports.useCallback(
|
|
33159
33357
|
(formData, recommendation, options) => {
|
|
33160
33358
|
let targetId = activeProfileId;
|
|
@@ -33164,27 +33362,63 @@ function PrimeStyleTryonInner({
|
|
|
33164
33362
|
return;
|
|
33165
33363
|
}
|
|
33166
33364
|
if (targetId && formData.gender === "female" && (formData.bandSize || formData.cupSize || formData.braSizeRegion)) {
|
|
33167
|
-
|
|
33365
|
+
const patch = {
|
|
33168
33366
|
...formData.bandSize ? { bandSize: formData.bandSize } : {},
|
|
33169
33367
|
...formData.cupSize ? { cupSize: formData.cupSize } : {},
|
|
33170
33368
|
...formData.braSizeRegion ? { braSizeRegion: formData.braSizeRegion } : {}
|
|
33171
|
-
}
|
|
33172
|
-
|
|
33369
|
+
};
|
|
33370
|
+
if (profileSessionRef.current) {
|
|
33371
|
+
updateProfilesForCurrentSession((list) => list.map((p2) => p2.id === targetId ? { ...p2, ...patch, lastEditedAt: Date.now() } : p2));
|
|
33372
|
+
} else {
|
|
33373
|
+
updateProfile(targetId, patch);
|
|
33374
|
+
setProfiles(lsGet("profiles", []));
|
|
33375
|
+
}
|
|
33173
33376
|
}
|
|
33377
|
+
const persistMeasurements = (est, unit) => {
|
|
33378
|
+
if (!targetId) return;
|
|
33379
|
+
if (profileSessionRef.current) {
|
|
33380
|
+
updateProfilesForCurrentSession((list) => list.map((p2) => p2.id === targetId ? {
|
|
33381
|
+
...p2,
|
|
33382
|
+
measurements: est,
|
|
33383
|
+
measurementsUnit: unit,
|
|
33384
|
+
lastEditedAt: Date.now()
|
|
33385
|
+
} : p2));
|
|
33386
|
+
} else {
|
|
33387
|
+
updateProfileMeasurements(targetId, est, unit);
|
|
33388
|
+
setProfiles(lsGet("profiles", []));
|
|
33389
|
+
}
|
|
33390
|
+
};
|
|
33391
|
+
const persistSizeHistory = (entry) => {
|
|
33392
|
+
if (!targetId) return;
|
|
33393
|
+
if (profileSessionRef.current) {
|
|
33394
|
+
updateProfilesForCurrentSession((list) => list.map((p2) => {
|
|
33395
|
+
if (p2.id !== targetId) return p2;
|
|
33396
|
+
const sizeHistory = (p2.sizeHistory || []).filter((h) => h.productId !== entry.productId);
|
|
33397
|
+
sizeHistory.unshift(entry);
|
|
33398
|
+
return {
|
|
33399
|
+
...p2,
|
|
33400
|
+
sizeHistory: sizeHistory.slice(0, 50),
|
|
33401
|
+
lastUsedAt: Date.now()
|
|
33402
|
+
};
|
|
33403
|
+
}));
|
|
33404
|
+
} else {
|
|
33405
|
+
addSizeToHistory(targetId, entry);
|
|
33406
|
+
setProfiles(lsGet("profiles", []));
|
|
33407
|
+
}
|
|
33408
|
+
};
|
|
33174
33409
|
if (options?.skipBodyEstimate) {
|
|
33175
33410
|
console.log("[ps-sdk:persist] skipping body estimates — face/head flow (no body context)");
|
|
33176
33411
|
} else if (targetId && recommendation?.estimates) {
|
|
33177
33412
|
const est = recommendation.estimates;
|
|
33178
33413
|
const unit = recommendation.estimatesUnit || "cm";
|
|
33179
|
-
|
|
33180
|
-
setProfiles(lsGet("profiles", []));
|
|
33414
|
+
persistMeasurements(est, unit);
|
|
33181
33415
|
}
|
|
33182
33416
|
setEstimationDone(true);
|
|
33183
33417
|
if (recommendation?.recommendedSize && targetId) {
|
|
33184
33418
|
const sectionsMap = recommendation.sections ? Object.fromEntries(
|
|
33185
33419
|
Object.entries(recommendation.sections).map(([name, sec]) => [name, sec.recommendedSize])
|
|
33186
33420
|
) : void 0;
|
|
33187
|
-
|
|
33421
|
+
persistSizeHistory({
|
|
33188
33422
|
productId: effectiveProductId,
|
|
33189
33423
|
productTitle,
|
|
33190
33424
|
productImage,
|
|
@@ -33193,10 +33427,9 @@ function PrimeStyleTryonInner({
|
|
|
33193
33427
|
sections: sectionsMap,
|
|
33194
33428
|
savedAt: Date.now()
|
|
33195
33429
|
});
|
|
33196
|
-
setProfiles(lsGet("profiles", []));
|
|
33197
33430
|
}
|
|
33198
33431
|
},
|
|
33199
|
-
[activeProfileId, profiles, productImage, productTitle, effectiveProductId]
|
|
33432
|
+
[activeProfileId, profiles, productImage, productTitle, effectiveProductId, updateProfilesForCurrentSession]
|
|
33200
33433
|
);
|
|
33201
33434
|
const snapSubmitRef = reactExports.useRef(null);
|
|
33202
33435
|
const [confirmProfile, setConfirmProfile] = reactExports.useState(null);
|
|
@@ -33205,7 +33438,7 @@ function PrimeStyleTryonInner({
|
|
|
33205
33438
|
const profileWeight = p2.weight ?? p2.weightKg ?? 0;
|
|
33206
33439
|
const hasStored = !!p2.measurements && Object.keys(p2.measurements).length > 0;
|
|
33207
33440
|
const storedPhoto = p2.photoUrl || p2.photoBase64;
|
|
33208
|
-
if (
|
|
33441
|
+
if (storedPhoto && profileHeight > 0 && snapSubmitRef.current) {
|
|
33209
33442
|
try {
|
|
33210
33443
|
const dataUrl = await profilePhotoToDataUrl(storedPhoto);
|
|
33211
33444
|
const blob = await fetch(dataUrl).then((r2) => r2.blob());
|
|
@@ -33219,6 +33452,7 @@ function PrimeStyleTryonInner({
|
|
|
33219
33452
|
weightUnit: p2.weightUnit || "kg",
|
|
33220
33453
|
gender: p2.gender,
|
|
33221
33454
|
age: p2.age,
|
|
33455
|
+
...hasStored && p2.measurements ? { knownMeasurements: p2.measurements } : {},
|
|
33222
33456
|
...snapBraFields({
|
|
33223
33457
|
gender: p2.gender,
|
|
33224
33458
|
bandSize: p2.bandSize,
|
|
@@ -33262,6 +33496,7 @@ function PrimeStyleTryonInner({
|
|
|
33262
33496
|
}
|
|
33263
33497
|
}
|
|
33264
33498
|
setView("size-result");
|
|
33499
|
+
const minVisible = new Promise((resolve) => setTimeout(resolve, 6e3));
|
|
33265
33500
|
recommendForProduct({
|
|
33266
33501
|
productId: effectiveProductId,
|
|
33267
33502
|
productTitle,
|
|
@@ -33280,7 +33515,9 @@ function PrimeStyleTryonInner({
|
|
|
33280
33515
|
if (res?.raw) setSizingResult(res.raw);
|
|
33281
33516
|
setEstimationDone(true);
|
|
33282
33517
|
}).catch(() => {
|
|
33283
|
-
}).finally(() =>
|
|
33518
|
+
}).finally(() => {
|
|
33519
|
+
void minVisible.then(() => setSizingLoading(false));
|
|
33520
|
+
});
|
|
33284
33521
|
}, [effectiveProductId, productTitle, productImage, productCategory, productSubcategory, resolvedProductFitType, productType, productTagsList, productDescription, sizeGuideData, apiUrl, previewUrl]);
|
|
33285
33522
|
const handleUseActiveProfile = reactExports.useCallback(async () => {
|
|
33286
33523
|
const p2 = profiles.find((x2) => x2.id === activeProfileId);
|
|
@@ -33502,6 +33739,8 @@ function PrimeStyleTryonInner({
|
|
|
33502
33739
|
}, [sizeGuide, formGender]);
|
|
33503
33740
|
const submitSizing = reactExports.useCallback(async (methodOverride) => {
|
|
33504
33741
|
if (!apiRef.current) return;
|
|
33742
|
+
setActiveSection(null);
|
|
33743
|
+
noFitFoundRef.current = false;
|
|
33505
33744
|
const method = methodOverride || sizingMethod;
|
|
33506
33745
|
const baseUrl = getApiUrl(apiUrl);
|
|
33507
33746
|
const key = getApiKey();
|
|
@@ -33562,6 +33801,12 @@ function PrimeStyleTryonInner({
|
|
|
33562
33801
|
if (resp.ok) {
|
|
33563
33802
|
const data = await resp.json();
|
|
33564
33803
|
await minVisible;
|
|
33804
|
+
if (data?.found === false) {
|
|
33805
|
+
setNoSizeReason(data?.reasoning === "NO_SIZE_CHART" ? "no-chart" : "no-match");
|
|
33806
|
+
setView("no-chart");
|
|
33807
|
+
setEstimationDone(true);
|
|
33808
|
+
return;
|
|
33809
|
+
}
|
|
33565
33810
|
setSizingResult(data);
|
|
33566
33811
|
onComplete?.(data);
|
|
33567
33812
|
} else {
|
|
@@ -33623,6 +33868,10 @@ function PrimeStyleTryonInner({
|
|
|
33623
33868
|
if (formRef.current.shoeUS) m2.shoeUS = formRef.current.shoeUS;
|
|
33624
33869
|
if (formRef.current.shoeUK) m2.shoeUK = formRef.current.shoeUK;
|
|
33625
33870
|
if (formRef.current.fitPreference) m2.fitPreference = formRef.current.fitPreference;
|
|
33871
|
+
if (formRef.current.bodyType) m2.bodyType = formRef.current.bodyType;
|
|
33872
|
+
if (formRef.current.chestProfile) m2.chestProfile = formRef.current.chestProfile;
|
|
33873
|
+
if (formRef.current.midsectionProfile) m2.midsectionProfile = formRef.current.midsectionProfile;
|
|
33874
|
+
if (formRef.current.hipProfile) m2.hipProfile = formRef.current.hipProfile;
|
|
33626
33875
|
payload.measurements = m2;
|
|
33627
33876
|
console.log("[PS-SDK] FINAL measurements:", JSON.stringify(m2));
|
|
33628
33877
|
} else {
|
|
@@ -33661,7 +33910,8 @@ function PrimeStyleTryonInner({
|
|
|
33661
33910
|
if (res.ok) {
|
|
33662
33911
|
const data = await res.json();
|
|
33663
33912
|
console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
|
|
33664
|
-
if (data?.found === false
|
|
33913
|
+
if (data?.found === false) {
|
|
33914
|
+
setNoSizeReason(data?.reasoning === "NO_SIZE_CHART" ? "no-chart" : "no-match");
|
|
33665
33915
|
setView("no-chart");
|
|
33666
33916
|
setEstimationDone(true);
|
|
33667
33917
|
return;
|
|
@@ -33750,6 +34000,7 @@ function PrimeStyleTryonInner({
|
|
|
33750
34000
|
setFaceLandmarks(null);
|
|
33751
34001
|
setSizingMethod("quick");
|
|
33752
34002
|
setSizingLoading(true);
|
|
34003
|
+
setActiveSection(null);
|
|
33753
34004
|
setView("size-result");
|
|
33754
34005
|
submitSizing("quick");
|
|
33755
34006
|
return;
|
|
@@ -33768,6 +34019,7 @@ function PrimeStyleTryonInner({
|
|
|
33768
34019
|
setResultImageUrl(null);
|
|
33769
34020
|
currentHistoryEntryIdRef.current = null;
|
|
33770
34021
|
currentTryOnJobIdRef.current = null;
|
|
34022
|
+
setActiveSection(null);
|
|
33771
34023
|
setRestoredProductImage(null);
|
|
33772
34024
|
setRestoredProductImages(null);
|
|
33773
34025
|
setRestoredProductCarouselItems(null);
|
|
@@ -33845,6 +34097,7 @@ function PrimeStyleTryonInner({
|
|
|
33845
34097
|
historyTryonSavedRef.current = false;
|
|
33846
34098
|
currentHistoryEntryIdRef.current = null;
|
|
33847
34099
|
currentTryOnJobIdRef.current = null;
|
|
34100
|
+
setActiveSection(null);
|
|
33848
34101
|
setSizingLoading(true);
|
|
33849
34102
|
setEstimationDone(false);
|
|
33850
34103
|
setView("size-result");
|
|
@@ -33891,6 +34144,14 @@ function PrimeStyleTryonInner({
|
|
|
33891
34144
|
});
|
|
33892
34145
|
if (recRes.ok) {
|
|
33893
34146
|
const recData = await recRes.json();
|
|
34147
|
+
if (recData?.found === false) {
|
|
34148
|
+
await minVisible2;
|
|
34149
|
+
setNoSizeReason(recData?.reasoning === "NO_SIZE_CHART" ? "no-chart" : "no-match");
|
|
34150
|
+
setView("no-chart");
|
|
34151
|
+
setEstimationDone(true);
|
|
34152
|
+
setSizingLoading(false);
|
|
34153
|
+
return;
|
|
34154
|
+
}
|
|
33894
34155
|
setSizingResult(recData);
|
|
33895
34156
|
onComplete?.(recData);
|
|
33896
34157
|
persistResultToProfile(
|
|
@@ -34010,6 +34271,7 @@ function PrimeStyleTryonInner({
|
|
|
34010
34271
|
if (recRes.ok) {
|
|
34011
34272
|
const recData = await recRes.json();
|
|
34012
34273
|
if (recData?.found === false && recData?.reasoning === "NO_SIZE_CHART") {
|
|
34274
|
+
setNoSizeReason("no-chart");
|
|
34013
34275
|
setView("no-chart");
|
|
34014
34276
|
setEstimationDone(true);
|
|
34015
34277
|
setSizingLoading(false);
|
|
@@ -34023,8 +34285,13 @@ function PrimeStyleTryonInner({
|
|
|
34023
34285
|
noFitFoundRef.current = true;
|
|
34024
34286
|
setTryOnProcessing(false);
|
|
34025
34287
|
setTryOnStartedAt(null);
|
|
34288
|
+
setNoSizeReason("no-match");
|
|
34026
34289
|
setSizingResult({ ...recData, found: false });
|
|
34027
34290
|
onComplete?.({ ...recData, found: false });
|
|
34291
|
+
setView("no-chart");
|
|
34292
|
+
setEstimationDone(true);
|
|
34293
|
+
setSizingLoading(false);
|
|
34294
|
+
return;
|
|
34028
34295
|
} else {
|
|
34029
34296
|
setSizingResult(recData);
|
|
34030
34297
|
onComplete?.(recData);
|
|
@@ -34185,6 +34452,8 @@ function PrimeStyleTryonInner({
|
|
|
34185
34452
|
{
|
|
34186
34453
|
productId: effectiveProductId,
|
|
34187
34454
|
productTitle,
|
|
34455
|
+
productCategory,
|
|
34456
|
+
productSubcategory,
|
|
34188
34457
|
productFitType: resolvedProductFitType,
|
|
34189
34458
|
productType,
|
|
34190
34459
|
productTags: productTagsList,
|
|
@@ -34253,7 +34522,7 @@ function PrimeStyleTryonInner({
|
|
|
34253
34522
|
setView("error");
|
|
34254
34523
|
onError?.({ message, code });
|
|
34255
34524
|
}
|
|
34256
|
-
}, [selectedFile, productImage, effectiveProductImages, garmentReferenceImage, productTitle, resolvedProductFitType, productType, productTagsList, productDescription, productMaterial, measurementType, sizingResult, sizeGuide, apiUrl, onProcessing, onError, handleVtoUpdate]);
|
|
34525
|
+
}, [selectedFile, productImage, effectiveProductImages, garmentReferenceImage, productTitle, productCategory, productSubcategory, resolvedProductFitType, productType, productTagsList, productDescription, productMaterial, measurementType, sizingResult, sizeGuide, apiUrl, onProcessing, onError, handleVtoUpdate]);
|
|
34257
34526
|
reactExports.useEffect(() => {
|
|
34258
34527
|
if (view !== "size-result") {
|
|
34259
34528
|
autoTryOnFiredRef.current = false;
|
|
@@ -35000,12 +35269,31 @@ function PrimeStyleTryonInner({
|
|
|
35000
35269
|
setView("body-profile");
|
|
35001
35270
|
},
|
|
35002
35271
|
onSaveProfileMeasurements: (id2, measurements, unit) => {
|
|
35003
|
-
|
|
35004
|
-
|
|
35272
|
+
const nextUnit = unit ?? profiles.find((x2) => x2.id === id2)?.measurementsUnit ?? "cm";
|
|
35273
|
+
if (profileSessionRef.current) {
|
|
35274
|
+
updateProfilesForCurrentSession((list) => list.map((p2) => p2.id === id2 ? {
|
|
35275
|
+
...p2,
|
|
35276
|
+
measurements,
|
|
35277
|
+
measurementsUnit: nextUnit,
|
|
35278
|
+
lastEditedAt: Date.now()
|
|
35279
|
+
} : p2));
|
|
35280
|
+
} else {
|
|
35281
|
+
updateProfileMeasurements(id2, measurements, nextUnit);
|
|
35282
|
+
setProfiles(lsGet("profiles", []));
|
|
35283
|
+
}
|
|
35005
35284
|
},
|
|
35006
35285
|
onSaveBraSize: (id2, bandSize, cupSize) => {
|
|
35007
|
-
|
|
35008
|
-
|
|
35286
|
+
if (profileSessionRef.current) {
|
|
35287
|
+
updateProfilesForCurrentSession((list) => list.map((p2) => p2.id === id2 ? {
|
|
35288
|
+
...p2,
|
|
35289
|
+
bandSize,
|
|
35290
|
+
cupSize,
|
|
35291
|
+
lastEditedAt: Date.now()
|
|
35292
|
+
} : p2));
|
|
35293
|
+
} else {
|
|
35294
|
+
updateProfile(id2, { bandSize, cupSize });
|
|
35295
|
+
setProfiles(lsGet("profiles", []));
|
|
35296
|
+
}
|
|
35009
35297
|
},
|
|
35010
35298
|
onEditProfile: (p2) => {
|
|
35011
35299
|
setProfileDetail(p2);
|
|
@@ -35078,13 +35366,24 @@ function PrimeStyleTryonInner({
|
|
|
35078
35366
|
bodyLandmarks: landmarks ?? void 0
|
|
35079
35367
|
})).then((est) => {
|
|
35080
35368
|
if (est) {
|
|
35081
|
-
|
|
35369
|
+
if (profileSessionRef.current) {
|
|
35370
|
+
updateProfilesForCurrentSession((list) => list.map((p2) => p2.id === newProfile.id ? {
|
|
35371
|
+
...p2,
|
|
35372
|
+
measurements: est.estimates,
|
|
35373
|
+
measurementsUnit: est.unit,
|
|
35374
|
+
lastEditedAt: Date.now()
|
|
35375
|
+
} : p2));
|
|
35376
|
+
} else {
|
|
35377
|
+
updateProfileMeasurements(newProfile.id, est.estimates, est.unit);
|
|
35378
|
+
}
|
|
35082
35379
|
if (est.userEstimates) {
|
|
35083
|
-
const
|
|
35084
|
-
|
|
35085
|
-
|
|
35380
|
+
const userEstimates = est.userEstimates;
|
|
35381
|
+
updateProfilesForCurrentSession((list) => {
|
|
35382
|
+
const idx = list.findIndex((p2) => p2.id === newProfile.id);
|
|
35383
|
+
if (idx < 0) return list;
|
|
35384
|
+
const all = [...list];
|
|
35086
35385
|
const target = all[idx];
|
|
35087
|
-
const u2 =
|
|
35386
|
+
const u2 = userEstimates;
|
|
35088
35387
|
const patched = { ...target };
|
|
35089
35388
|
if (u2.height && !(target.height || target.heightCm)) {
|
|
35090
35389
|
patched.height = u2.height;
|
|
@@ -35100,10 +35399,10 @@ function PrimeStyleTryonInner({
|
|
|
35100
35399
|
patched.age = u2.age;
|
|
35101
35400
|
}
|
|
35102
35401
|
all[idx] = patched;
|
|
35103
|
-
|
|
35104
|
-
}
|
|
35402
|
+
return all;
|
|
35403
|
+
});
|
|
35105
35404
|
}
|
|
35106
|
-
setProfiles(lsGet("profiles", []));
|
|
35405
|
+
if (!profileSessionRef.current) setProfiles(lsGet("profiles", []));
|
|
35107
35406
|
}
|
|
35108
35407
|
}).catch(() => {
|
|
35109
35408
|
}).finally(() => {
|
|
@@ -35116,9 +35415,8 @@ function PrimeStyleTryonInner({
|
|
|
35116
35415
|
}
|
|
35117
35416
|
},
|
|
35118
35417
|
onDeleteProfile: (id2) => {
|
|
35119
|
-
|
|
35418
|
+
updateProfilesForCurrentSession((prev) => prev.filter((p2) => p2.id !== id2));
|
|
35120
35419
|
if (activeProfileId === id2) setActiveProfileId$1(null);
|
|
35121
|
-
lsSet("profiles", lsGet("profiles", []).filter((p2) => p2.id !== id2));
|
|
35122
35420
|
},
|
|
35123
35421
|
onRequestDelete: (id2) => setDeleteConfirmId(id2),
|
|
35124
35422
|
onLogout: handleProfileLogout,
|
|
@@ -35152,6 +35450,7 @@ function PrimeStyleTryonInner({
|
|
|
35152
35450
|
{
|
|
35153
35451
|
productImage,
|
|
35154
35452
|
productTitle,
|
|
35453
|
+
reason: noSizeReason,
|
|
35155
35454
|
onTryOn: () => setView("photo-guide"),
|
|
35156
35455
|
onClose: onClose ?? (() => {
|
|
35157
35456
|
}),
|
|
@@ -35378,9 +35677,7 @@ function PrimeStyleTryonInner({
|
|
|
35378
35677
|
{
|
|
35379
35678
|
onConfirm: () => {
|
|
35380
35679
|
const id2 = deleteConfirmId;
|
|
35381
|
-
|
|
35382
|
-
lsSet("profiles", updated);
|
|
35383
|
-
setProfiles(updated);
|
|
35680
|
+
updateProfilesForCurrentSession((prev) => prev.filter((p2) => p2.id !== id2));
|
|
35384
35681
|
if (activeProfileId === id2) setActiveProfileId$1(null);
|
|
35385
35682
|
setDeleteConfirmId(null);
|
|
35386
35683
|
},
|