@primestyleai/tryon 5.5.25 → 5.6.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/PrimeStyleTryonInner.d.ts +1 -1
- package/dist/react/index.d.ts +6 -1
- package/dist/react/index.js +841 -18
- package/dist/react/recommendForProduct.d.ts +61 -0
- package/dist/react/styles.d.ts +1 -1
- package/dist/react/types.d.ts +42 -1
- package/dist/react/usePrimeStyleSize.d.ts +21 -0
- package/dist/react/utils/storage.d.ts +15 -0
- package/dist/react/views/MySizingProfilesView.d.ts +23 -0
- package/dist/react/views/SizeResultView.d.ts +2 -1
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -218,6 +218,8 @@ const SIZE_CONVERSIONS = {
|
|
|
218
218
|
};
|
|
219
219
|
const TOTAL_STEPS = 3;
|
|
220
220
|
const LS_PREFIX = "primestyle_";
|
|
221
|
+
const PROFILES_KEY = "profiles";
|
|
222
|
+
const ACTIVE_PROFILE_KEY = "active_profile_id";
|
|
221
223
|
function lsGet(key, fallback) {
|
|
222
224
|
if (typeof window === "undefined") return fallback;
|
|
223
225
|
try {
|
|
@@ -233,11 +235,67 @@ function lsSet(key, value) {
|
|
|
233
235
|
} catch {
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
|
-
function
|
|
237
|
-
return
|
|
238
|
+
function getProfiles() {
|
|
239
|
+
return lsGet(PROFILES_KEY, []);
|
|
238
240
|
}
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
+
function saveProfiles(profiles) {
|
|
242
|
+
lsSet(PROFILES_KEY, profiles);
|
|
243
|
+
}
|
|
244
|
+
function getActiveProfileId() {
|
|
245
|
+
return lsGet(ACTIVE_PROFILE_KEY, null);
|
|
246
|
+
}
|
|
247
|
+
function setActiveProfileId(id) {
|
|
248
|
+
lsSet(ACTIVE_PROFILE_KEY, id);
|
|
249
|
+
}
|
|
250
|
+
function getActiveProfile() {
|
|
251
|
+
const profiles = getProfiles();
|
|
252
|
+
if (profiles.length === 0) return null;
|
|
253
|
+
const activeId = getActiveProfileId();
|
|
254
|
+
if (activeId) {
|
|
255
|
+
const found = profiles.find((p) => p.id === activeId);
|
|
256
|
+
if (found) return found;
|
|
257
|
+
}
|
|
258
|
+
const sorted = [...profiles].sort(
|
|
259
|
+
(a, b) => (b.lastUsedAt || b.createdAt || 0) - (a.lastUsedAt || a.createdAt || 0)
|
|
260
|
+
);
|
|
261
|
+
return sorted[0] || null;
|
|
262
|
+
}
|
|
263
|
+
function updateProfile(id, patch) {
|
|
264
|
+
const profiles = getProfiles();
|
|
265
|
+
const idx = profiles.findIndex((p) => p.id === id);
|
|
266
|
+
if (idx < 0) return null;
|
|
267
|
+
const updated = { ...profiles[idx], ...patch, lastEditedAt: Date.now() };
|
|
268
|
+
profiles[idx] = updated;
|
|
269
|
+
saveProfiles(profiles);
|
|
270
|
+
return updated;
|
|
271
|
+
}
|
|
272
|
+
function updateProfileMeasurements(id, measurements, unit = "cm") {
|
|
273
|
+
return updateProfile(id, { measurements, measurementsUnit: unit });
|
|
274
|
+
}
|
|
275
|
+
function addSizeToHistory(profileId, entry) {
|
|
276
|
+
const profiles = getProfiles();
|
|
277
|
+
const idx = profiles.findIndex((p) => p.id === profileId);
|
|
278
|
+
if (idx < 0) return null;
|
|
279
|
+
const profile = profiles[idx];
|
|
280
|
+
const history = (profile.sizeHistory || []).filter(
|
|
281
|
+
(h) => h.productId !== entry.productId
|
|
282
|
+
);
|
|
283
|
+
history.unshift(entry);
|
|
284
|
+
const trimmed = history.slice(0, 50);
|
|
285
|
+
profiles[idx] = {
|
|
286
|
+
...profile,
|
|
287
|
+
sizeHistory: trimmed,
|
|
288
|
+
lastUsedAt: Date.now()
|
|
289
|
+
};
|
|
290
|
+
saveProfiles(profiles);
|
|
291
|
+
return profiles[idx];
|
|
292
|
+
}
|
|
293
|
+
function getCachedSize(profile, productId) {
|
|
294
|
+
if (!profile.sizeHistory || profile.sizeHistory.length === 0) return null;
|
|
295
|
+
const entry = profile.sizeHistory.find((h) => h.productId === productId);
|
|
296
|
+
if (!entry) return null;
|
|
297
|
+
if (profile.lastEditedAt && profile.lastEditedAt > entry.savedAt) return null;
|
|
298
|
+
return entry;
|
|
241
299
|
}
|
|
242
300
|
function detectLocale() {
|
|
243
301
|
if (typeof navigator === "undefined") return "US";
|
|
@@ -266,6 +324,159 @@ function getApiKey() {
|
|
|
266
324
|
function getApiUrl(override) {
|
|
267
325
|
return override || process.env.NEXT_PUBLIC_PRIMESTYLE_API_URL || "http://localhost:4000";
|
|
268
326
|
}
|
|
327
|
+
async function recommendForProduct(input) {
|
|
328
|
+
const profile = input.profile ?? getActiveProfile();
|
|
329
|
+
if (!profile) return null;
|
|
330
|
+
if (!input.skipCache) {
|
|
331
|
+
const cached = getCachedSize(profile, input.productId);
|
|
332
|
+
if (cached) {
|
|
333
|
+
return {
|
|
334
|
+
recommendedSize: cached.recommendedSize,
|
|
335
|
+
confidence: cached.confidence,
|
|
336
|
+
sections: cached.sections,
|
|
337
|
+
profileId: profile.id,
|
|
338
|
+
fromCache: true
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
let apiKey;
|
|
343
|
+
try {
|
|
344
|
+
apiKey = input.apiKey ?? getApiKey();
|
|
345
|
+
} catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
const apiUrl = (input.apiUrl ?? getApiUrl()).replace(/\/+$/, "");
|
|
349
|
+
if (!apiKey) return null;
|
|
350
|
+
let sizeGuide = null;
|
|
351
|
+
if (input.sizeGuideData != null) {
|
|
352
|
+
try {
|
|
353
|
+
const sgRes = await fetch(`${apiUrl}/api/v1/sizing/sizeguide`, {
|
|
354
|
+
method: "POST",
|
|
355
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
356
|
+
body: JSON.stringify({
|
|
357
|
+
product: { title: input.productTitle, productId: input.productId },
|
|
358
|
+
sizeGuideRaw: input.sizeGuideData
|
|
359
|
+
})
|
|
360
|
+
});
|
|
361
|
+
if (sgRes.ok) sizeGuide = await sgRes.json();
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const measurements = {
|
|
366
|
+
gender: profile.gender,
|
|
367
|
+
sizingUnit: profile.measurementsUnit || "cm"
|
|
368
|
+
};
|
|
369
|
+
if (profile.measurements) {
|
|
370
|
+
for (const [key, value] of Object.entries(profile.measurements)) {
|
|
371
|
+
if (value != null) measurements[key] = value;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (profile.height != null) measurements.height = profile.height;
|
|
375
|
+
if (profile.weight != null) measurements.weight = profile.weight;
|
|
376
|
+
if (profile.heightUnit) measurements.heightUnit = profile.heightUnit;
|
|
377
|
+
if (profile.weightUnit) measurements.weightUnit = profile.weightUnit;
|
|
378
|
+
if (profile.age) measurements.age = profile.age;
|
|
379
|
+
if (profile.chestProfile) measurements.chestProfile = profile.chestProfile;
|
|
380
|
+
if (profile.midsectionProfile) measurements.midsectionProfile = profile.midsectionProfile;
|
|
381
|
+
if (profile.hipProfile) measurements.hipProfile = profile.hipProfile;
|
|
382
|
+
const payload = {
|
|
383
|
+
method: "exact",
|
|
384
|
+
locale: profile.country || "US",
|
|
385
|
+
sizingUnit: profile.measurementsUnit || profile.sizingUnit || "cm",
|
|
386
|
+
product: { title: input.productTitle, productId: input.productId, description: "", variants: [] },
|
|
387
|
+
measurements
|
|
388
|
+
};
|
|
389
|
+
if (sizeGuide && sizeGuide.found) {
|
|
390
|
+
payload.sizeGuide = sizeGuide;
|
|
391
|
+
}
|
|
392
|
+
let result = null;
|
|
393
|
+
try {
|
|
394
|
+
const res = await fetch(`${apiUrl}/api/v1/sizing/recommend`, {
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
397
|
+
body: JSON.stringify(payload)
|
|
398
|
+
});
|
|
399
|
+
if (!res.ok) return null;
|
|
400
|
+
result = await res.json();
|
|
401
|
+
} catch {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
if (!result || !result.recommendedSize) return null;
|
|
405
|
+
const sectionsMap = result.sections ? Object.fromEntries(
|
|
406
|
+
Object.entries(result.sections).map(([name, sec]) => [name, sec.recommendedSize])
|
|
407
|
+
) : void 0;
|
|
408
|
+
addSizeToHistory(profile.id, {
|
|
409
|
+
productId: input.productId,
|
|
410
|
+
productTitle: input.productTitle,
|
|
411
|
+
productImage: input.productImage,
|
|
412
|
+
recommendedSize: result.recommendedSize,
|
|
413
|
+
confidence: result.confidence,
|
|
414
|
+
sections: sectionsMap,
|
|
415
|
+
savedAt: Date.now()
|
|
416
|
+
});
|
|
417
|
+
return {
|
|
418
|
+
recommendedSize: result.recommendedSize,
|
|
419
|
+
confidence: result.confidence,
|
|
420
|
+
sections: sectionsMap,
|
|
421
|
+
profileId: profile.id,
|
|
422
|
+
fromCache: false,
|
|
423
|
+
raw: result
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
async function estimateFullMeasurements(args) {
|
|
427
|
+
let apiKey;
|
|
428
|
+
try {
|
|
429
|
+
apiKey = args.apiKey ?? getApiKey();
|
|
430
|
+
} catch {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const apiUrl = (args.apiUrl ?? getApiUrl()).replace(/\/+$/, "");
|
|
434
|
+
if (!apiKey) return null;
|
|
435
|
+
const requiredFields = [
|
|
436
|
+
"chest",
|
|
437
|
+
"bust",
|
|
438
|
+
"waist",
|
|
439
|
+
"hips",
|
|
440
|
+
"shoulderWidth",
|
|
441
|
+
"sleeveLength",
|
|
442
|
+
"inseam",
|
|
443
|
+
"neckCircumference",
|
|
444
|
+
"thighCircumference",
|
|
445
|
+
"footLengthCm"
|
|
446
|
+
];
|
|
447
|
+
const payload = {
|
|
448
|
+
height: args.height,
|
|
449
|
+
weight: args.weight,
|
|
450
|
+
heightUnit: args.heightUnit,
|
|
451
|
+
weightUnit: args.weightUnit,
|
|
452
|
+
gender: args.gender,
|
|
453
|
+
requiredFields
|
|
454
|
+
};
|
|
455
|
+
if (args.age) payload.age = args.age;
|
|
456
|
+
if (args.chestProfile) payload.chestProfile = args.chestProfile;
|
|
457
|
+
if (args.midsectionProfile) payload.midsectionProfile = args.midsectionProfile;
|
|
458
|
+
if (args.hipProfile) payload.hipProfile = args.hipProfile;
|
|
459
|
+
if (args.bodyImage) payload.bodyImage = args.bodyImage;
|
|
460
|
+
try {
|
|
461
|
+
const res = await fetch(`${apiUrl}/api/v1/sizing/estimate`, {
|
|
462
|
+
method: "POST",
|
|
463
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
464
|
+
body: JSON.stringify(payload)
|
|
465
|
+
});
|
|
466
|
+
if (!res.ok) return null;
|
|
467
|
+
const data = await res.json();
|
|
468
|
+
if (!data?.estimates) return null;
|
|
469
|
+
return { estimates: data.estimates, unit: data.unit || "cm" };
|
|
470
|
+
} catch {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function isImperial(locale) {
|
|
475
|
+
return ["US", "UK", "AU"].includes(locale);
|
|
476
|
+
}
|
|
477
|
+
function cx(base, override) {
|
|
478
|
+
return override ? `${base} ${override}` : base;
|
|
479
|
+
}
|
|
269
480
|
const STYLES = `
|
|
270
481
|
/* Variable defaults must live on BOTH the root (for the trigger button)
|
|
271
482
|
and the overlay (which is React-portaled to <body> and therefore not
|
|
@@ -3557,6 +3768,292 @@ const STYLES = `
|
|
|
3557
3768
|
margin: 14px 12px 0;
|
|
3558
3769
|
}
|
|
3559
3770
|
|
|
3771
|
+
/* ════════════════════════════════════════════════════════════════
|
|
3772
|
+
MySizingProfilesView (.ps-msp-*) — full profile management screen
|
|
3773
|
+
that replaces the old profile drawer. Grid of profile cards
|
|
3774
|
+
matching the ATELIER reference design.
|
|
3775
|
+
════════════════════════════════════════════════════════════════ */
|
|
3776
|
+
.ps-msp-root {
|
|
3777
|
+
display: flex; flex-direction: column;
|
|
3778
|
+
height: 100%; min-height: 0;
|
|
3779
|
+
margin: 0 -16px;
|
|
3780
|
+
}
|
|
3781
|
+
.ps-msp-topbar {
|
|
3782
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3783
|
+
padding: 8px 16px;
|
|
3784
|
+
border-bottom: 1px solid var(--ps-border-subtle);
|
|
3785
|
+
flex-shrink: 0;
|
|
3786
|
+
}
|
|
3787
|
+
.ps-msp-back {
|
|
3788
|
+
background: none; border: none;
|
|
3789
|
+
color: var(--ps-text-primary); cursor: pointer;
|
|
3790
|
+
padding: 6px;
|
|
3791
|
+
display: flex; align-items: center; justify-content: center;
|
|
3792
|
+
transition: opacity 0.15s;
|
|
3793
|
+
}
|
|
3794
|
+
.ps-msp-back:hover { opacity: 0.7; }
|
|
3795
|
+
.ps-msp-back svg { width: 18px; height: 18px; }
|
|
3796
|
+
.ps-msp-topbar-title {
|
|
3797
|
+
font-size: 12px; font-weight: 700;
|
|
3798
|
+
letter-spacing: 0.16em; text-transform: uppercase;
|
|
3799
|
+
color: var(--ps-text-primary);
|
|
3800
|
+
flex: 1; text-align: center;
|
|
3801
|
+
}
|
|
3802
|
+
.ps-msp-topbar-spacer { width: 30px; flex-shrink: 0; }
|
|
3803
|
+
|
|
3804
|
+
.ps-msp-scroll {
|
|
3805
|
+
flex: 1; min-height: 0;
|
|
3806
|
+
overflow-y: auto; -webkit-overflow-scrolling: touch;
|
|
3807
|
+
padding: 18px 16px 32px;
|
|
3808
|
+
}
|
|
3809
|
+
.ps-msp-header { margin-bottom: 18px; }
|
|
3810
|
+
.ps-msp-title {
|
|
3811
|
+
font-size: 26px; font-weight: 700;
|
|
3812
|
+
color: var(--ps-text-primary);
|
|
3813
|
+
margin: 0 0 6px;
|
|
3814
|
+
letter-spacing: -0.01em;
|
|
3815
|
+
}
|
|
3816
|
+
.ps-msp-subtitle {
|
|
3817
|
+
font-size: 13px; color: var(--ps-text-muted);
|
|
3818
|
+
margin: 0; line-height: 1.5;
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
/* Card grid */
|
|
3822
|
+
.ps-msp-grid {
|
|
3823
|
+
display: grid;
|
|
3824
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
3825
|
+
gap: 14px;
|
|
3826
|
+
margin-bottom: 28px;
|
|
3827
|
+
}
|
|
3828
|
+
.ps-msp-card {
|
|
3829
|
+
background: var(--ps-bg-primary);
|
|
3830
|
+
border: 1px solid var(--ps-border-subtle);
|
|
3831
|
+
border-radius: 12px;
|
|
3832
|
+
padding: 14px;
|
|
3833
|
+
display: flex; flex-direction: column;
|
|
3834
|
+
transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
|
|
3835
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
|
3836
|
+
position: relative;
|
|
3837
|
+
}
|
|
3838
|
+
.ps-msp-card:hover {
|
|
3839
|
+
border-color: var(--ps-text-primary);
|
|
3840
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
3841
|
+
}
|
|
3842
|
+
.ps-msp-card.ps-active {
|
|
3843
|
+
border-color: var(--ps-accent);
|
|
3844
|
+
box-shadow: 0 4px 16px rgba(33,84,239,0.12);
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
.ps-msp-card-header {
|
|
3848
|
+
display: flex; align-items: center; justify-content: flex-start;
|
|
3849
|
+
margin-bottom: 8px;
|
|
3850
|
+
}
|
|
3851
|
+
.ps-msp-card-tag {
|
|
3852
|
+
font-size: 9px; font-weight: 700;
|
|
3853
|
+
letter-spacing: 0.12em; text-transform: uppercase;
|
|
3854
|
+
background: var(--ps-bg-secondary);
|
|
3855
|
+
color: var(--ps-text-muted);
|
|
3856
|
+
padding: 4px 8px; border-radius: 4px;
|
|
3857
|
+
border: 1px solid var(--ps-border-subtle);
|
|
3858
|
+
}
|
|
3859
|
+
.ps-msp-card.ps-active .ps-msp-card-tag {
|
|
3860
|
+
background: var(--ps-accent); color: #FFFFFF;
|
|
3861
|
+
border-color: var(--ps-accent);
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
.ps-msp-card-thumb {
|
|
3865
|
+
height: 90px;
|
|
3866
|
+
background: var(--ps-bg-secondary);
|
|
3867
|
+
border-radius: 8px;
|
|
3868
|
+
display: flex; align-items: center; justify-content: center;
|
|
3869
|
+
margin-bottom: 12px;
|
|
3870
|
+
color: var(--ps-text-secondary);
|
|
3871
|
+
}
|
|
3872
|
+
.ps-msp-avatar {
|
|
3873
|
+
position: relative;
|
|
3874
|
+
display: flex; align-items: center; justify-content: center;
|
|
3875
|
+
width: 56px; height: 56px;
|
|
3876
|
+
border-radius: 50%;
|
|
3877
|
+
background: var(--ps-bg-primary);
|
|
3878
|
+
color: var(--ps-text-secondary);
|
|
3879
|
+
border: 1.5px solid var(--ps-border-subtle);
|
|
3880
|
+
}
|
|
3881
|
+
.ps-msp-avatar-tag {
|
|
3882
|
+
position: absolute; bottom: -2px; right: -2px;
|
|
3883
|
+
width: 18px; height: 18px; border-radius: 50%;
|
|
3884
|
+
background: var(--ps-accent); color: #FFFFFF;
|
|
3885
|
+
font-size: 10px; font-weight: 700;
|
|
3886
|
+
display: flex; align-items: center; justify-content: center;
|
|
3887
|
+
border: 2px solid var(--ps-bg-primary);
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
.ps-msp-card-name {
|
|
3891
|
+
font-size: 16px; font-weight: 700;
|
|
3892
|
+
color: var(--ps-text-primary);
|
|
3893
|
+
margin-bottom: 8px;
|
|
3894
|
+
}
|
|
3895
|
+
.ps-msp-card-meta {
|
|
3896
|
+
display: flex; flex-direction: column;
|
|
3897
|
+
gap: 4px;
|
|
3898
|
+
margin-bottom: 12px;
|
|
3899
|
+
}
|
|
3900
|
+
.ps-msp-meta-row {
|
|
3901
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3902
|
+
padding: 4px 0;
|
|
3903
|
+
border-bottom: 1px solid var(--ps-border-subtle);
|
|
3904
|
+
}
|
|
3905
|
+
.ps-msp-meta-row:last-child { border-bottom: none; }
|
|
3906
|
+
.ps-msp-meta-label {
|
|
3907
|
+
font-size: 9px; font-weight: 600;
|
|
3908
|
+
letter-spacing: 0.1em; text-transform: uppercase;
|
|
3909
|
+
color: var(--ps-text-muted);
|
|
3910
|
+
}
|
|
3911
|
+
.ps-msp-meta-value {
|
|
3912
|
+
font-size: 12px; font-weight: 600;
|
|
3913
|
+
color: var(--ps-text-primary);
|
|
3914
|
+
font-feature-settings: "tnum" 1;
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
.ps-msp-card-actions {
|
|
3918
|
+
display: flex; gap: 8px;
|
|
3919
|
+
margin-top: auto;
|
|
3920
|
+
}
|
|
3921
|
+
.ps-msp-card-select {
|
|
3922
|
+
flex: 1;
|
|
3923
|
+
background: var(--ps-text-primary); color: #FFFFFF;
|
|
3924
|
+
border: none; border-radius: 6px;
|
|
3925
|
+
padding: 10px;
|
|
3926
|
+
font-family: inherit; font-size: 11px; font-weight: 700;
|
|
3927
|
+
letter-spacing: 0.12em; text-transform: uppercase;
|
|
3928
|
+
cursor: pointer;
|
|
3929
|
+
transition: opacity 0.15s, transform 0.15s;
|
|
3930
|
+
}
|
|
3931
|
+
.ps-msp-card-select:hover { opacity: 0.85; }
|
|
3932
|
+
.ps-msp-card-select:active { transform: scale(0.97); }
|
|
3933
|
+
.ps-msp-card-select.ps-active {
|
|
3934
|
+
background: var(--ps-accent);
|
|
3935
|
+
}
|
|
3936
|
+
.ps-msp-card-edit {
|
|
3937
|
+
width: 36px; height: 36px;
|
|
3938
|
+
background: var(--ps-bg-primary);
|
|
3939
|
+
border: 1px solid var(--ps-border-subtle);
|
|
3940
|
+
border-radius: 6px;
|
|
3941
|
+
display: flex; align-items: center; justify-content: center;
|
|
3942
|
+
color: var(--ps-text-secondary); cursor: pointer;
|
|
3943
|
+
transition: border-color 0.15s, color 0.15s;
|
|
3944
|
+
}
|
|
3945
|
+
.ps-msp-card-edit:hover {
|
|
3946
|
+
border-color: var(--ps-accent);
|
|
3947
|
+
color: var(--ps-accent);
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
/* "Create New Profile" empty card */
|
|
3951
|
+
.ps-msp-card-create {
|
|
3952
|
+
background: var(--ps-bg-secondary);
|
|
3953
|
+
border: 2px dashed var(--ps-border-color);
|
|
3954
|
+
align-items: center; justify-content: center;
|
|
3955
|
+
text-align: center;
|
|
3956
|
+
cursor: pointer;
|
|
3957
|
+
min-height: 280px;
|
|
3958
|
+
transition: border-color 0.15s, background 0.15s;
|
|
3959
|
+
}
|
|
3960
|
+
.ps-msp-card-create:hover {
|
|
3961
|
+
border-color: var(--ps-accent);
|
|
3962
|
+
background: rgba(33,84,239,0.04);
|
|
3963
|
+
}
|
|
3964
|
+
.ps-msp-create-icon {
|
|
3965
|
+
width: 50px; height: 50px;
|
|
3966
|
+
background: var(--ps-text-primary); color: #FFFFFF;
|
|
3967
|
+
border-radius: 8px;
|
|
3968
|
+
display: flex; align-items: center; justify-content: center;
|
|
3969
|
+
margin-bottom: 12px;
|
|
3970
|
+
}
|
|
3971
|
+
.ps-msp-create-title {
|
|
3972
|
+
font-size: 15px; font-weight: 700;
|
|
3973
|
+
color: var(--ps-text-primary);
|
|
3974
|
+
margin-bottom: 4px;
|
|
3975
|
+
}
|
|
3976
|
+
.ps-msp-create-sub {
|
|
3977
|
+
font-size: 9px; font-weight: 700;
|
|
3978
|
+
letter-spacing: 0.14em; text-transform: uppercase;
|
|
3979
|
+
color: var(--ps-text-muted);
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
/* Recent size calculations section */
|
|
3983
|
+
.ps-msp-history-section {
|
|
3984
|
+
margin-top: 20px;
|
|
3985
|
+
padding-top: 20px;
|
|
3986
|
+
border-top: 1px solid var(--ps-border-subtle);
|
|
3987
|
+
}
|
|
3988
|
+
.ps-msp-history-title {
|
|
3989
|
+
font-size: 13px; font-weight: 700;
|
|
3990
|
+
letter-spacing: 0.1em; text-transform: uppercase;
|
|
3991
|
+
color: var(--ps-text-primary);
|
|
3992
|
+
margin: 0 0 14px;
|
|
3993
|
+
}
|
|
3994
|
+
.ps-msp-history-list {
|
|
3995
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
3996
|
+
}
|
|
3997
|
+
.ps-msp-history-card {
|
|
3998
|
+
display: flex; align-items: center; gap: 12px;
|
|
3999
|
+
padding: 12px;
|
|
4000
|
+
background: var(--ps-bg-primary);
|
|
4001
|
+
border: 1px solid var(--ps-border-subtle);
|
|
4002
|
+
border-radius: 8px;
|
|
4003
|
+
}
|
|
4004
|
+
.ps-msp-history-thumb {
|
|
4005
|
+
width: 48px; height: 48px;
|
|
4006
|
+
border-radius: 6px;
|
|
4007
|
+
background: var(--ps-bg-secondary);
|
|
4008
|
+
overflow: hidden;
|
|
4009
|
+
flex-shrink: 0;
|
|
4010
|
+
display: flex; align-items: center; justify-content: center;
|
|
4011
|
+
}
|
|
4012
|
+
.ps-msp-history-thumb img {
|
|
4013
|
+
max-width: 100%; max-height: 100%; object-fit: contain;
|
|
4014
|
+
}
|
|
4015
|
+
.ps-msp-history-info {
|
|
4016
|
+
flex: 1; min-width: 0;
|
|
4017
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
4018
|
+
}
|
|
4019
|
+
.ps-msp-history-name {
|
|
4020
|
+
font-size: 13px; font-weight: 600;
|
|
4021
|
+
color: var(--ps-text-primary);
|
|
4022
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
4023
|
+
}
|
|
4024
|
+
.ps-msp-history-profile {
|
|
4025
|
+
font-size: 10px;
|
|
4026
|
+
color: var(--ps-text-muted);
|
|
4027
|
+
}
|
|
4028
|
+
.ps-msp-history-meta {
|
|
4029
|
+
font-size: 10px;
|
|
4030
|
+
color: var(--ps-text-muted);
|
|
4031
|
+
display: flex; align-items: center; gap: 4px;
|
|
4032
|
+
}
|
|
4033
|
+
.ps-msp-history-meta svg { color: var(--ps-text-muted); }
|
|
4034
|
+
.ps-msp-history-size {
|
|
4035
|
+
display: flex; flex-direction: column; align-items: flex-end;
|
|
4036
|
+
flex-shrink: 0;
|
|
4037
|
+
}
|
|
4038
|
+
.ps-msp-history-size-label {
|
|
4039
|
+
font-size: 9px; font-weight: 600;
|
|
4040
|
+
letter-spacing: 0.1em; text-transform: uppercase;
|
|
4041
|
+
color: var(--ps-text-muted);
|
|
4042
|
+
}
|
|
4043
|
+
.ps-msp-history-size-value {
|
|
4044
|
+
font-size: 22px; font-weight: 700;
|
|
4045
|
+
color: var(--ps-text-primary);
|
|
4046
|
+
font-feature-settings: "tnum" 1;
|
|
4047
|
+
line-height: 1;
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
@media (max-width: 768px) {
|
|
4051
|
+
.ps-msp-grid { grid-template-columns: 1fr; }
|
|
4052
|
+
.ps-msp-card-create { min-height: 200px; }
|
|
4053
|
+
.ps-msp-title { font-size: 22px; }
|
|
4054
|
+
.ps-msp-subtitle { font-size: 12px; }
|
|
4055
|
+
}
|
|
4056
|
+
|
|
3560
4057
|
/* Big product image */
|
|
3561
4058
|
.ps-msd-image {
|
|
3562
4059
|
width: 100%; height: 240px;
|
|
@@ -4550,7 +5047,7 @@ function ProfileDetailModal({
|
|
|
4550
5047
|
setProfileDetail,
|
|
4551
5048
|
setProfiles,
|
|
4552
5049
|
activeProfileId,
|
|
4553
|
-
setActiveProfileId,
|
|
5050
|
+
setActiveProfileId: setActiveProfileId2,
|
|
4554
5051
|
t
|
|
4555
5052
|
}) {
|
|
4556
5053
|
if (!profileDetail) return null;
|
|
@@ -4598,7 +5095,7 @@ function ProfileDetailModal({
|
|
|
4598
5095
|
] }),
|
|
4599
5096
|
/* @__PURE__ */ jsxs("button", { className: "ps-tryon-detail-delete", onClick: () => {
|
|
4600
5097
|
setProfiles((prev) => prev.filter((x) => x.id !== p.id));
|
|
4601
|
-
if (activeProfileId === p.id)
|
|
5098
|
+
if (activeProfileId === p.id) setActiveProfileId2(null);
|
|
4602
5099
|
setProfileDetail(null);
|
|
4603
5100
|
}, children: [
|
|
4604
5101
|
/* @__PURE__ */ jsx(TrashIcon, {}),
|
|
@@ -5715,6 +6212,7 @@ function SizeResultView({
|
|
|
5715
6212
|
handleTryOnSubmit,
|
|
5716
6213
|
tryOnProcessing,
|
|
5717
6214
|
bodyLandmarks,
|
|
6215
|
+
estimationDone = false,
|
|
5718
6216
|
activeSection,
|
|
5719
6217
|
setActiveSection,
|
|
5720
6218
|
t
|
|
@@ -5791,6 +6289,7 @@ function SizeResultView({
|
|
|
5791
6289
|
const [poseLines, setPoseLines] = useState(null);
|
|
5792
6290
|
const [poseReady, setPoseReady] = useState(false);
|
|
5793
6291
|
const [imgDims, setImgDims] = useState({ w: 800, h: 1200 });
|
|
6292
|
+
const analyzingDone = estimationDone;
|
|
5794
6293
|
const handleImgLoad = useCallback((e) => {
|
|
5795
6294
|
const el = e.currentTarget;
|
|
5796
6295
|
if (el.naturalWidth && el.naturalHeight) {
|
|
@@ -5960,12 +6459,12 @@ function SizeResultView({
|
|
|
5960
6459
|
/* @__PURE__ */ jsx("span", { children: t("Detecting body pose") })
|
|
5961
6460
|
] }),
|
|
5962
6461
|
!sizingDone && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5963
|
-
/* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${bodyLandmarks ? " ps-active" : ""}`, children: [
|
|
5964
|
-
/* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: bodyLandmarks ? /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) : /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-
|
|
6462
|
+
/* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-done" : bodyLandmarks ? " ps-active" : ""}`, children: [
|
|
6463
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: !bodyLandmarks ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-num", children: "2" }) : !analyzingDone ? /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) : /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) }),
|
|
5965
6464
|
/* @__PURE__ */ jsx("span", { children: t("Analyzing your size") })
|
|
5966
6465
|
] }),
|
|
5967
|
-
/* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step`, children: [
|
|
5968
|
-
/* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-num", children: "3" }) }),
|
|
6466
|
+
/* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-active" : ""}`, children: [
|
|
6467
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: !analyzingDone ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-num", children: "3" }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
|
|
5969
6468
|
/* @__PURE__ */ jsx("span", { children: t("Finding best fit for you") })
|
|
5970
6469
|
] })
|
|
5971
6470
|
] }),
|
|
@@ -6565,6 +7064,154 @@ function ErrorView({
|
|
|
6565
7064
|
/* @__PURE__ */ jsx("button", { onClick: handleRetry, className: cx("ps-tryon-submit", cn.submitButton), children: t("Try Again") })
|
|
6566
7065
|
] });
|
|
6567
7066
|
}
|
|
7067
|
+
function ProfileAvatar({ gender }) {
|
|
7068
|
+
return /* @__PURE__ */ jsxs("div", { className: "ps-msp-avatar", children: [
|
|
7069
|
+
/* @__PURE__ */ jsx(UserIcon, { size: 28 }),
|
|
7070
|
+
gender && /* @__PURE__ */ jsx("span", { className: `ps-msp-avatar-tag ps-${gender}`, children: gender === "female" ? "♀" : "♂" })
|
|
7071
|
+
] });
|
|
7072
|
+
}
|
|
7073
|
+
function ProfileCard({
|
|
7074
|
+
profile,
|
|
7075
|
+
isActive,
|
|
7076
|
+
onSelect,
|
|
7077
|
+
onEdit,
|
|
7078
|
+
t
|
|
7079
|
+
}) {
|
|
7080
|
+
const heightDisplay = (() => {
|
|
7081
|
+
const h = profile.height ?? profile.heightCm;
|
|
7082
|
+
if (!h) return null;
|
|
7083
|
+
if (profile.heightUnit === "in" || profile.heightUnit === "ft") {
|
|
7084
|
+
const ft = Math.floor(h / 12);
|
|
7085
|
+
const inches = Math.round(h % 12);
|
|
7086
|
+
return `${ft}'${inches}"`;
|
|
7087
|
+
}
|
|
7088
|
+
return `${Math.round(h)} cm`;
|
|
7089
|
+
})();
|
|
7090
|
+
const weightDisplay = (() => {
|
|
7091
|
+
const w = profile.weight ?? profile.weightKg;
|
|
7092
|
+
if (!w) return null;
|
|
7093
|
+
return `${Math.round(w)} ${profile.weightUnit || "kg"}`;
|
|
7094
|
+
})();
|
|
7095
|
+
const updatedDisplay = (() => {
|
|
7096
|
+
const ts = profile.lastEditedAt || profile.lastUsedAt || profile.createdAt;
|
|
7097
|
+
if (!ts) return null;
|
|
7098
|
+
return new Date(ts).toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
7099
|
+
})();
|
|
7100
|
+
return /* @__PURE__ */ jsxs("div", { className: `ps-msp-card${isActive ? " ps-active" : ""}`, children: [
|
|
7101
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-card-header", children: /* @__PURE__ */ jsx("span", { className: "ps-msp-card-tag", children: isActive ? t("DEFAULT PROFILE") : profile.gender === "female" ? t("WOMEN'S FIT") : t("MEN'S FIT") }) }),
|
|
7102
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-card-thumb", children: /* @__PURE__ */ jsx(ProfileAvatar, { gender: profile.gender }) }),
|
|
7103
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-card-name", children: profile.name }),
|
|
7104
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-card-meta", children: [
|
|
7105
|
+
heightDisplay && /* @__PURE__ */ jsxs("div", { className: "ps-msp-meta-row", children: [
|
|
7106
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-label", children: t("HEIGHT") }),
|
|
7107
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-value", children: heightDisplay })
|
|
7108
|
+
] }),
|
|
7109
|
+
weightDisplay && /* @__PURE__ */ jsxs("div", { className: "ps-msp-meta-row", children: [
|
|
7110
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-label", children: t("WEIGHT") }),
|
|
7111
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-value", children: weightDisplay })
|
|
7112
|
+
] }),
|
|
7113
|
+
updatedDisplay && /* @__PURE__ */ jsxs("div", { className: "ps-msp-meta-row", children: [
|
|
7114
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-label", children: t("LAST UPDATE") }),
|
|
7115
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-value", children: updatedDisplay })
|
|
7116
|
+
] })
|
|
7117
|
+
] }),
|
|
7118
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-card-actions", children: [
|
|
7119
|
+
/* @__PURE__ */ jsx(
|
|
7120
|
+
"button",
|
|
7121
|
+
{
|
|
7122
|
+
type: "button",
|
|
7123
|
+
className: `ps-msp-card-select${isActive ? " ps-active" : ""}`,
|
|
7124
|
+
onClick: onSelect,
|
|
7125
|
+
children: isActive ? t("SELECTED") : t("SELECT")
|
|
7126
|
+
}
|
|
7127
|
+
),
|
|
7128
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ps-msp-card-edit", onClick: onEdit, "aria-label": t("Edit"), children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "14", height: "14", children: [
|
|
7129
|
+
/* @__PURE__ */ jsx("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
|
|
7130
|
+
/* @__PURE__ */ jsx("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
|
|
7131
|
+
] }) })
|
|
7132
|
+
] })
|
|
7133
|
+
] });
|
|
7134
|
+
}
|
|
7135
|
+
function CreateProfileCard({ onClick, t }) {
|
|
7136
|
+
return /* @__PURE__ */ jsxs("button", { type: "button", className: "ps-msp-card ps-msp-card-create", onClick, children: [
|
|
7137
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-create-icon", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "22", height: "22", children: [
|
|
7138
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
7139
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
|
|
7140
|
+
] }) }),
|
|
7141
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-create-title", children: t("Create New Profile") }),
|
|
7142
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-create-sub", children: t("ADD MEASUREMENTS & PHOTO") })
|
|
7143
|
+
] });
|
|
7144
|
+
}
|
|
7145
|
+
function SizeHistoryCard({ entry, profileName, t }) {
|
|
7146
|
+
const date = new Date(entry.savedAt).toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
7147
|
+
return /* @__PURE__ */ jsxs("div", { className: "ps-msp-history-card", children: [
|
|
7148
|
+
entry.productImage && /* @__PURE__ */ jsx("div", { className: "ps-msp-history-thumb", children: /* @__PURE__ */ jsx("img", { src: entry.productImage, alt: entry.productTitle }) }),
|
|
7149
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-history-info", children: [
|
|
7150
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-history-name", children: entry.productTitle }),
|
|
7151
|
+
profileName && /* @__PURE__ */ jsx("div", { className: "ps-msp-history-profile", children: profileName }),
|
|
7152
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-history-meta", children: [
|
|
7153
|
+
/* @__PURE__ */ jsx(ClockIcon, { size: 11 }),
|
|
7154
|
+
" ",
|
|
7155
|
+
date
|
|
7156
|
+
] })
|
|
7157
|
+
] }),
|
|
7158
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-history-size", children: [
|
|
7159
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-history-size-label", children: t("SIZE") }),
|
|
7160
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-history-size-value", children: entry.recommendedSize })
|
|
7161
|
+
] })
|
|
7162
|
+
] });
|
|
7163
|
+
}
|
|
7164
|
+
function MySizingProfilesView({
|
|
7165
|
+
profiles,
|
|
7166
|
+
activeProfileId,
|
|
7167
|
+
onSelectProfile,
|
|
7168
|
+
onEditProfile,
|
|
7169
|
+
onCreateProfile,
|
|
7170
|
+
onDeleteProfile,
|
|
7171
|
+
onClose,
|
|
7172
|
+
t
|
|
7173
|
+
}) {
|
|
7174
|
+
const allHistory = useMemo(() => {
|
|
7175
|
+
const items = [];
|
|
7176
|
+
for (const p of profiles) {
|
|
7177
|
+
for (const h of p.sizeHistory || []) {
|
|
7178
|
+
items.push({ entry: h, profileName: p.name });
|
|
7179
|
+
}
|
|
7180
|
+
}
|
|
7181
|
+
return items.sort((a, b) => b.entry.savedAt - a.entry.savedAt).slice(0, 12);
|
|
7182
|
+
}, [profiles]);
|
|
7183
|
+
return /* @__PURE__ */ jsxs("div", { className: "ps-msp-root", children: [
|
|
7184
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-topbar", children: [
|
|
7185
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ps-msp-back", onClick: onClose, "aria-label": t("Back"), children: /* @__PURE__ */ jsx(ArrowLeftIcon, {}) }),
|
|
7186
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-topbar-title", children: t("MY SIZING PROFILES") }),
|
|
7187
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-topbar-spacer" })
|
|
7188
|
+
] }),
|
|
7189
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-scroll", children: [
|
|
7190
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-header", children: [
|
|
7191
|
+
/* @__PURE__ */ jsx("h2", { className: "ps-msp-title", children: t("My Sizing Profiles") }),
|
|
7192
|
+
/* @__PURE__ */ jsx("p", { className: "ps-msp-subtitle", children: t("Manage your bespoke silhouettes. Switch between profiles for different fits or create a new one for specific garment types.") })
|
|
7193
|
+
] }),
|
|
7194
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-grid", children: [
|
|
7195
|
+
/* @__PURE__ */ jsx(CreateProfileCard, { onClick: onCreateProfile, t }),
|
|
7196
|
+
profiles.map((p) => /* @__PURE__ */ jsx(
|
|
7197
|
+
ProfileCard,
|
|
7198
|
+
{
|
|
7199
|
+
profile: p,
|
|
7200
|
+
isActive: p.id === activeProfileId,
|
|
7201
|
+
onSelect: () => onSelectProfile(p.id),
|
|
7202
|
+
onEdit: () => onEditProfile(p),
|
|
7203
|
+
t
|
|
7204
|
+
},
|
|
7205
|
+
p.id
|
|
7206
|
+
))
|
|
7207
|
+
] }),
|
|
7208
|
+
allHistory.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-msp-history-section", children: [
|
|
7209
|
+
/* @__PURE__ */ jsx("h3", { className: "ps-msp-history-title", children: t("Recent Size Calculations") }),
|
|
7210
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-history-list", children: allHistory.map(({ entry, profileName }) => /* @__PURE__ */ jsx(SizeHistoryCard, { entry, profileName, t }, `${entry.productId}-${entry.savedAt}`)) })
|
|
7211
|
+
] })
|
|
7212
|
+
] })
|
|
7213
|
+
] });
|
|
7214
|
+
}
|
|
6568
7215
|
function ResultView({ setView }) {
|
|
6569
7216
|
useEffect(() => {
|
|
6570
7217
|
setView("size-result");
|
|
@@ -7687,6 +8334,7 @@ if (typeof document !== "undefined") {
|
|
|
7687
8334
|
function PrimeStyleTryonInner({
|
|
7688
8335
|
productImage,
|
|
7689
8336
|
productTitle = "Product",
|
|
8337
|
+
productId,
|
|
7690
8338
|
buttonText,
|
|
7691
8339
|
apiUrl,
|
|
7692
8340
|
showPoweredBy = true,
|
|
@@ -7706,6 +8354,7 @@ function PrimeStyleTryonInner({
|
|
|
7706
8354
|
onError,
|
|
7707
8355
|
sizeGuideData
|
|
7708
8356
|
}) {
|
|
8357
|
+
const effectiveProductId = productId || productImage;
|
|
7709
8358
|
const [activeLocale, setActiveLocale] = useState(() => locale || "");
|
|
7710
8359
|
useEffect(() => {
|
|
7711
8360
|
if (locale !== void 0) setActiveLocale(locale);
|
|
@@ -7723,6 +8372,7 @@ function PrimeStyleTryonInner({
|
|
|
7723
8372
|
const [sizingMethod, setSizingMethod] = useState(null);
|
|
7724
8373
|
const [sizingResult, setSizingResult] = useState(null);
|
|
7725
8374
|
const [sizingLoading, setSizingLoading] = useState(false);
|
|
8375
|
+
const [estimationDone, setEstimationDone] = useState(false);
|
|
7726
8376
|
const [tryOnProcessing, setTryOnProcessing] = useState(false);
|
|
7727
8377
|
const [sizeGuide, setSizeGuide] = useState(null);
|
|
7728
8378
|
const [sizeGuideFetching, setSizeGuideFetching] = useState(false);
|
|
@@ -7739,7 +8389,11 @@ function PrimeStyleTryonInner({
|
|
|
7739
8389
|
const [estimationLoading, setEstimationLoading] = useState(false);
|
|
7740
8390
|
const [profiles, setProfiles] = useState(() => lsGet("profiles", []));
|
|
7741
8391
|
const [history, setHistory] = useState(() => lsGet("history", []));
|
|
7742
|
-
const [activeProfileId,
|
|
8392
|
+
const [activeProfileId, setActiveProfileIdState] = useState(() => getActiveProfileId());
|
|
8393
|
+
const setActiveProfileId$1 = useCallback((id) => {
|
|
8394
|
+
setActiveProfileIdState(id);
|
|
8395
|
+
setActiveProfileId(id);
|
|
8396
|
+
}, []);
|
|
7743
8397
|
const [profileSaved, setProfileSaved] = useState(false);
|
|
7744
8398
|
const [drawer, setDrawer] = useState(null);
|
|
7745
8399
|
const [profileDetail, setProfileDetail] = useState(null);
|
|
@@ -7878,6 +8532,69 @@ function PrimeStyleTryonInner({
|
|
|
7878
8532
|
return 1;
|
|
7879
8533
|
}
|
|
7880
8534
|
}, [view]);
|
|
8535
|
+
const persistResultToProfile = useCallback(
|
|
8536
|
+
(formData, recommendation) => {
|
|
8537
|
+
let targetId = activeProfileId;
|
|
8538
|
+
let target = profiles.find((p) => p.id === targetId);
|
|
8539
|
+
if (!target) {
|
|
8540
|
+
const newProfile = {
|
|
8541
|
+
id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
8542
|
+
name: formData.gender === "female" ? "My Profile" : "My Profile",
|
|
8543
|
+
gender: formData.gender,
|
|
8544
|
+
height: formData.height,
|
|
8545
|
+
weight: formData.weight,
|
|
8546
|
+
age: formData.age,
|
|
8547
|
+
heightUnit: formData.heightUnit,
|
|
8548
|
+
weightUnit: formData.weightUnit,
|
|
8549
|
+
chestProfile: formData.chestProfile,
|
|
8550
|
+
midsectionProfile: formData.midsectionProfile,
|
|
8551
|
+
hipProfile: formData.hipProfile,
|
|
8552
|
+
bandSize: formData.bandSize,
|
|
8553
|
+
cupSize: formData.cupSize,
|
|
8554
|
+
createdAt: Date.now(),
|
|
8555
|
+
lastUsedAt: Date.now()
|
|
8556
|
+
};
|
|
8557
|
+
setProfiles((prev) => [newProfile, ...prev]);
|
|
8558
|
+
setActiveProfileId$1(newProfile.id);
|
|
8559
|
+
targetId = newProfile.id;
|
|
8560
|
+
target = newProfile;
|
|
8561
|
+
}
|
|
8562
|
+
estimateFullMeasurements({
|
|
8563
|
+
apiUrl,
|
|
8564
|
+
height: formData.height,
|
|
8565
|
+
weight: formData.weight,
|
|
8566
|
+
heightUnit: formData.heightUnit,
|
|
8567
|
+
weightUnit: formData.weightUnit,
|
|
8568
|
+
gender: formData.gender,
|
|
8569
|
+
age: formData.age,
|
|
8570
|
+
chestProfile: formData.chestProfile,
|
|
8571
|
+
midsectionProfile: formData.midsectionProfile,
|
|
8572
|
+
hipProfile: formData.hipProfile,
|
|
8573
|
+
bodyImage: formData.bodyImage
|
|
8574
|
+
}).then((est) => {
|
|
8575
|
+
if (!est || !targetId) return;
|
|
8576
|
+
updateProfileMeasurements(targetId, est.estimates, est.unit);
|
|
8577
|
+
setProfiles(lsGet("profiles", []));
|
|
8578
|
+
}).catch(() => {
|
|
8579
|
+
}).finally(() => setEstimationDone(true));
|
|
8580
|
+
if (recommendation?.recommendedSize && targetId) {
|
|
8581
|
+
const sectionsMap = recommendation.sections ? Object.fromEntries(
|
|
8582
|
+
Object.entries(recommendation.sections).map(([name, sec]) => [name, sec.recommendedSize])
|
|
8583
|
+
) : void 0;
|
|
8584
|
+
addSizeToHistory(targetId, {
|
|
8585
|
+
productId: effectiveProductId,
|
|
8586
|
+
productTitle,
|
|
8587
|
+
productImage,
|
|
8588
|
+
recommendedSize: recommendation.recommendedSize,
|
|
8589
|
+
confidence: recommendation.confidence,
|
|
8590
|
+
sections: sectionsMap,
|
|
8591
|
+
savedAt: Date.now()
|
|
8592
|
+
});
|
|
8593
|
+
setProfiles(lsGet("profiles", []));
|
|
8594
|
+
}
|
|
8595
|
+
},
|
|
8596
|
+
[activeProfileId, profiles, apiUrl, productImage, productTitle, effectiveProductId, setActiveProfileId$1]
|
|
8597
|
+
);
|
|
7881
8598
|
const applyProfileRef = useRef(() => {
|
|
7882
8599
|
});
|
|
7883
8600
|
const handleOpen = useCallback(() => {
|
|
@@ -8084,6 +8801,7 @@ function PrimeStyleTryonInner({
|
|
|
8084
8801
|
if (formRef.current.hipProfile) qe.hipProfile = formRef.current.hipProfile;
|
|
8085
8802
|
payload.quickEstimate = qe;
|
|
8086
8803
|
}
|
|
8804
|
+
setEstimationDone(false);
|
|
8087
8805
|
try {
|
|
8088
8806
|
const res = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
|
|
8089
8807
|
method: "POST",
|
|
@@ -8095,20 +8813,39 @@ function PrimeStyleTryonInner({
|
|
|
8095
8813
|
console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
|
|
8096
8814
|
setSizingResult(data);
|
|
8097
8815
|
onComplete?.(data);
|
|
8816
|
+
const m = payload.measurements || {};
|
|
8817
|
+
const qe = payload.quickEstimate || {};
|
|
8818
|
+
const src = method === "exact" ? m : qe;
|
|
8819
|
+
persistResultToProfile(
|
|
8820
|
+
{
|
|
8821
|
+
gender: src.gender || "male",
|
|
8822
|
+
height: Number(src.height || src.heightCm || 0),
|
|
8823
|
+
weight: Number(src.weight || src.weightKg || 0),
|
|
8824
|
+
heightUnit: src.heightUnit || "cm",
|
|
8825
|
+
weightUnit: src.weightUnit || "kg",
|
|
8826
|
+
age: src.age != null ? Number(src.age) : void 0,
|
|
8827
|
+
chestProfile: src.chestProfile,
|
|
8828
|
+
midsectionProfile: src.midsectionProfile,
|
|
8829
|
+
hipProfile: src.hipProfile
|
|
8830
|
+
},
|
|
8831
|
+
data
|
|
8832
|
+
);
|
|
8098
8833
|
} else {
|
|
8099
8834
|
const errBody = await res.text().catch(() => "");
|
|
8100
8835
|
console.error("[PS-SDK] Sizing recommend failed:", res.status, errBody);
|
|
8101
8836
|
setErrorMessage(t("Unable to get size recommendation. Please try again."));
|
|
8102
8837
|
setView("error");
|
|
8838
|
+
setEstimationDone(true);
|
|
8103
8839
|
}
|
|
8104
8840
|
} catch (err) {
|
|
8105
8841
|
console.error("[PS-SDK] Sizing recommend network error:", err);
|
|
8106
8842
|
setErrorMessage(t("Unable to connect to sizing service. Please try again."));
|
|
8107
8843
|
setView("error");
|
|
8844
|
+
setEstimationDone(true);
|
|
8108
8845
|
} finally {
|
|
8109
8846
|
setSizingLoading(false);
|
|
8110
8847
|
}
|
|
8111
|
-
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
|
|
8848
|
+
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, persistResultToProfile]);
|
|
8112
8849
|
const handleQuickEstimate = useCallback(async (height, weight, heightUnit2, weightUnit2, gender, age, bodyType, chestProfile, midsectionProfile, hipProfile, bodyImage) => {
|
|
8113
8850
|
if (!apiRef.current) return;
|
|
8114
8851
|
getApiUrl(apiUrl);
|
|
@@ -8173,6 +8910,7 @@ function PrimeStyleTryonInner({
|
|
|
8173
8910
|
setSizingResult(null);
|
|
8174
8911
|
setResultImageUrl(null);
|
|
8175
8912
|
setSizingLoading(true);
|
|
8913
|
+
setEstimationDone(false);
|
|
8176
8914
|
setView("size-result");
|
|
8177
8915
|
modelPoseRef.current = null;
|
|
8178
8916
|
setBodyLandmarks(null);
|
|
@@ -8211,11 +8949,26 @@ function PrimeStyleTryonInner({
|
|
|
8211
8949
|
const recData = await recRes.json();
|
|
8212
8950
|
setSizingResult(recData);
|
|
8213
8951
|
onComplete?.(recData);
|
|
8952
|
+
persistResultToProfile(
|
|
8953
|
+
{
|
|
8954
|
+
gender: data.gender,
|
|
8955
|
+
height: data.height,
|
|
8956
|
+
weight: data.weight,
|
|
8957
|
+
heightUnit: data.heightUnit,
|
|
8958
|
+
weightUnit: data.weightUnit,
|
|
8959
|
+
age: data.age,
|
|
8960
|
+
bodyImage: data.photoBase64
|
|
8961
|
+
},
|
|
8962
|
+
recData
|
|
8963
|
+
);
|
|
8964
|
+
} else {
|
|
8965
|
+
setEstimationDone(true);
|
|
8214
8966
|
}
|
|
8215
8967
|
} catch {
|
|
8968
|
+
setEstimationDone(true);
|
|
8216
8969
|
}
|
|
8217
8970
|
setSizingLoading(false);
|
|
8218
|
-
}, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields]);
|
|
8971
|
+
}, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields, persistResultToProfile]);
|
|
8219
8972
|
const handleTryOnSubmit = useCallback(async () => {
|
|
8220
8973
|
const file = selectedFile || selectedFileRef.current;
|
|
8221
8974
|
if (!file || !apiRef.current || !sseRef.current) {
|
|
@@ -8407,7 +9160,7 @@ function PrimeStyleTryonInner({
|
|
|
8407
9160
|
const applyProfile = useCallback((id) => {
|
|
8408
9161
|
const p = profiles.find((pr) => pr.id === id);
|
|
8409
9162
|
if (!p) return;
|
|
8410
|
-
setActiveProfileId(id);
|
|
9163
|
+
setActiveProfileId$1(id);
|
|
8411
9164
|
setProfiles((prev) => prev.map((pr) => pr.id === id ? { ...pr, lastUsedAt: Date.now() } : pr));
|
|
8412
9165
|
const fd = { gender: p.gender || "male" };
|
|
8413
9166
|
if (p.height ?? p.heightCm) fd.height = String(p.height ?? p.heightCm);
|
|
@@ -8502,7 +9255,7 @@ function PrimeStyleTryonInner({
|
|
|
8502
9255
|
}
|
|
8503
9256
|
return [...prev, p].slice(-50);
|
|
8504
9257
|
});
|
|
8505
|
-
setActiveProfileId(id);
|
|
9258
|
+
setActiveProfileId$1(id);
|
|
8506
9259
|
setProfileSaved(true);
|
|
8507
9260
|
}, [activeProfileId, sizingCountry, sizingUnit, heightUnit, weightUnit]);
|
|
8508
9261
|
const saveHistoryEntry = useCallback(() => {
|
|
@@ -8717,6 +9470,7 @@ function PrimeStyleTryonInner({
|
|
|
8717
9470
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
8718
9471
|
SizeResultView,
|
|
8719
9472
|
{
|
|
9473
|
+
estimationDone,
|
|
8720
9474
|
sizingLoading,
|
|
8721
9475
|
sizingResult,
|
|
8722
9476
|
sizeGuide,
|
|
@@ -8771,6 +9525,28 @@ function PrimeStyleTryonInner({
|
|
|
8771
9525
|
) }, "v-proc");
|
|
8772
9526
|
case "result":
|
|
8773
9527
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(ResultView, { setView }) }, "v-result");
|
|
9528
|
+
case "profiles":
|
|
9529
|
+
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
9530
|
+
MySizingProfilesView,
|
|
9531
|
+
{
|
|
9532
|
+
profiles,
|
|
9533
|
+
activeProfileId,
|
|
9534
|
+
onSelectProfile: (id) => {
|
|
9535
|
+
setActiveProfileId$1(id);
|
|
9536
|
+
setView("body-profile");
|
|
9537
|
+
},
|
|
9538
|
+
onEditProfile: (p) => {
|
|
9539
|
+
setProfileDetail(p);
|
|
9540
|
+
},
|
|
9541
|
+
onCreateProfile: () => {
|
|
9542
|
+
setActiveProfileId$1(null);
|
|
9543
|
+
setView("body-profile");
|
|
9544
|
+
},
|
|
9545
|
+
onDeleteProfile: (id) => setProfiles((prev) => prev.filter((p) => p.id !== id)),
|
|
9546
|
+
onClose: () => setView("body-profile"),
|
|
9547
|
+
t
|
|
9548
|
+
}
|
|
9549
|
+
) }, "v-profiles");
|
|
8774
9550
|
case "error":
|
|
8775
9551
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
8776
9552
|
ErrorView,
|
|
@@ -8794,7 +9570,7 @@ function PrimeStyleTryonInner({
|
|
|
8794
9570
|
/* @__PURE__ */ jsx("div", { className: cx("ps-tryon-overlay", cn.overlay), style: cssVars, "data-ps-tryon-portal": true, children: /* @__PURE__ */ jsxs("div", { className: cx(`ps-tryon-modal${view === "result" && resultImageUrl && sizingResult || view === "size-result" || view === "estimation-review" || view === "body-profile" ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), children: [
|
|
8795
9571
|
/* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-header ps-tryon-header-minimal", cn.header), children: [
|
|
8796
9572
|
/* @__PURE__ */ jsx(LangSwitcher, { activeLocale, onSelect: setActiveLocale }),
|
|
8797
|
-
/* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("Profiles"), onClick: () =>
|
|
9573
|
+
/* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("Profiles"), onClick: () => setView(view === "profiles" ? "body-profile" : "profiles"), children: /* @__PURE__ */ jsx(UserIcon, {}) }),
|
|
8798
9574
|
/* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("History"), onClick: () => setDrawer(drawer === "history" ? null : "history"), children: /* @__PURE__ */ jsx(ClockIcon, {}) }),
|
|
8799
9575
|
/* @__PURE__ */ jsx("button", { onClick: handleClose, className: cx("ps-tryon-close", cn.closeButton), children: /* @__PURE__ */ jsx(XIcon, {}) })
|
|
8800
9576
|
] }),
|
|
@@ -8833,7 +9609,7 @@ function PrimeStyleTryonInner({
|
|
|
8833
9609
|
setProfileDetail,
|
|
8834
9610
|
setProfiles,
|
|
8835
9611
|
activeProfileId,
|
|
8836
|
-
setActiveProfileId,
|
|
9612
|
+
setActiveProfileId: setActiveProfileId$1,
|
|
8837
9613
|
t
|
|
8838
9614
|
}
|
|
8839
9615
|
)
|
|
@@ -8842,6 +9618,53 @@ function PrimeStyleTryonInner({
|
|
|
8842
9618
|
function PrimeStyleTryon(props) {
|
|
8843
9619
|
return /* @__PURE__ */ jsx(PrimeStyleTryonInner, { ...props });
|
|
8844
9620
|
}
|
|
9621
|
+
function usePrimeStyleSize(input) {
|
|
9622
|
+
const [result, setResult] = useState(null);
|
|
9623
|
+
const [loading, setLoading] = useState(true);
|
|
9624
|
+
const [noProfile, setNoProfile] = useState(false);
|
|
9625
|
+
const [tick, setTick] = useState(0);
|
|
9626
|
+
useEffect(() => {
|
|
9627
|
+
let cancelled = false;
|
|
9628
|
+
setLoading(true);
|
|
9629
|
+
setNoProfile(false);
|
|
9630
|
+
recommendForProduct(input).then((res) => {
|
|
9631
|
+
if (cancelled) return;
|
|
9632
|
+
if (res === null) {
|
|
9633
|
+
setNoProfile(true);
|
|
9634
|
+
setResult(null);
|
|
9635
|
+
} else {
|
|
9636
|
+
setResult(res);
|
|
9637
|
+
}
|
|
9638
|
+
}).catch(() => {
|
|
9639
|
+
if (!cancelled) setResult(null);
|
|
9640
|
+
}).finally(() => {
|
|
9641
|
+
if (!cancelled) setLoading(false);
|
|
9642
|
+
});
|
|
9643
|
+
return () => {
|
|
9644
|
+
cancelled = true;
|
|
9645
|
+
};
|
|
9646
|
+
}, [input.productId, input.apiUrl, input.apiKey, tick]);
|
|
9647
|
+
return {
|
|
9648
|
+
recommendedSize: result?.recommendedSize ?? null,
|
|
9649
|
+
sections: result?.sections ?? null,
|
|
9650
|
+
loading,
|
|
9651
|
+
noProfile,
|
|
9652
|
+
result,
|
|
9653
|
+
refetch: () => setTick((n) => n + 1)
|
|
9654
|
+
};
|
|
9655
|
+
}
|
|
8845
9656
|
export {
|
|
8846
|
-
PrimeStyleTryon
|
|
9657
|
+
PrimeStyleTryon,
|
|
9658
|
+
addSizeToHistory,
|
|
9659
|
+
estimateFullMeasurements,
|
|
9660
|
+
getActiveProfile,
|
|
9661
|
+
getActiveProfileId,
|
|
9662
|
+
getCachedSize,
|
|
9663
|
+
getProfiles,
|
|
9664
|
+
recommendForProduct,
|
|
9665
|
+
saveProfiles,
|
|
9666
|
+
setActiveProfileId,
|
|
9667
|
+
updateProfile,
|
|
9668
|
+
updateProfileMeasurements,
|
|
9669
|
+
usePrimeStyleSize
|
|
8847
9670
|
};
|