@primestyleai/tryon 5.5.26 → 5.6.1
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 +844 -24
- 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,295 @@ 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-accent);
|
|
3840
|
+
box-shadow: 0 4px 12px rgba(33, 84, 239, 0.08);
|
|
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
|
+
.ps-msp-avatar { color: var(--ps-accent); border-color: var(--ps-accent); }
|
|
3864
|
+
.ps-msp-avatar-tag { background: var(--ps-accent); }
|
|
3865
|
+
|
|
3866
|
+
.ps-msp-card-thumb {
|
|
3867
|
+
height: 90px;
|
|
3868
|
+
background: var(--ps-bg-secondary);
|
|
3869
|
+
border-radius: 8px;
|
|
3870
|
+
display: flex; align-items: center; justify-content: center;
|
|
3871
|
+
margin-bottom: 12px;
|
|
3872
|
+
color: var(--ps-text-secondary);
|
|
3873
|
+
}
|
|
3874
|
+
.ps-msp-avatar {
|
|
3875
|
+
position: relative;
|
|
3876
|
+
display: flex; align-items: center; justify-content: center;
|
|
3877
|
+
width: 56px; height: 56px;
|
|
3878
|
+
border-radius: 50%;
|
|
3879
|
+
background: var(--ps-bg-primary);
|
|
3880
|
+
color: var(--ps-text-secondary);
|
|
3881
|
+
border: 1.5px solid var(--ps-border-subtle);
|
|
3882
|
+
}
|
|
3883
|
+
.ps-msp-avatar-tag {
|
|
3884
|
+
position: absolute; bottom: -2px; right: -2px;
|
|
3885
|
+
width: 18px; height: 18px; border-radius: 50%;
|
|
3886
|
+
background: var(--ps-accent); color: #FFFFFF;
|
|
3887
|
+
font-size: 10px; font-weight: 700;
|
|
3888
|
+
display: flex; align-items: center; justify-content: center;
|
|
3889
|
+
border: 2px solid var(--ps-bg-primary);
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
.ps-msp-card-name {
|
|
3893
|
+
font-size: 16px; font-weight: 700;
|
|
3894
|
+
color: var(--ps-text-primary);
|
|
3895
|
+
margin-bottom: 8px;
|
|
3896
|
+
}
|
|
3897
|
+
.ps-msp-card-meta {
|
|
3898
|
+
display: flex; flex-direction: column;
|
|
3899
|
+
gap: 4px;
|
|
3900
|
+
margin-bottom: 12px;
|
|
3901
|
+
}
|
|
3902
|
+
.ps-msp-meta-row {
|
|
3903
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3904
|
+
padding: 4px 0;
|
|
3905
|
+
border-bottom: 1px solid var(--ps-border-subtle);
|
|
3906
|
+
}
|
|
3907
|
+
.ps-msp-meta-row:last-child { border-bottom: none; }
|
|
3908
|
+
.ps-msp-meta-label {
|
|
3909
|
+
font-size: 9px; font-weight: 600;
|
|
3910
|
+
letter-spacing: 0.1em; text-transform: uppercase;
|
|
3911
|
+
color: var(--ps-text-muted);
|
|
3912
|
+
}
|
|
3913
|
+
.ps-msp-meta-value {
|
|
3914
|
+
font-size: 12px; font-weight: 600;
|
|
3915
|
+
color: var(--ps-text-primary);
|
|
3916
|
+
font-feature-settings: "tnum" 1;
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
.ps-msp-card-actions {
|
|
3920
|
+
display: flex; gap: 8px;
|
|
3921
|
+
margin-top: auto;
|
|
3922
|
+
}
|
|
3923
|
+
.ps-msp-card-select {
|
|
3924
|
+
flex: 1;
|
|
3925
|
+
background: var(--ps-accent); color: #FFFFFF;
|
|
3926
|
+
border: none; border-radius: 6px;
|
|
3927
|
+
padding: 10px;
|
|
3928
|
+
font-family: inherit; font-size: 11px; font-weight: 700;
|
|
3929
|
+
letter-spacing: 0.12em; text-transform: uppercase;
|
|
3930
|
+
cursor: pointer;
|
|
3931
|
+
transition: opacity 0.15s, transform 0.15s;
|
|
3932
|
+
}
|
|
3933
|
+
.ps-msp-card-select:hover { opacity: 0.85; }
|
|
3934
|
+
.ps-msp-card-select:active { transform: scale(0.97); }
|
|
3935
|
+
.ps-msp-card-select.ps-active {
|
|
3936
|
+
background: var(--ps-accent);
|
|
3937
|
+
box-shadow: 0 0 0 2px rgba(33, 84, 239, 0.18);
|
|
3938
|
+
}
|
|
3939
|
+
.ps-msp-card-edit {
|
|
3940
|
+
width: 36px; height: 36px;
|
|
3941
|
+
background: var(--ps-bg-primary);
|
|
3942
|
+
border: 1px solid var(--ps-border-subtle);
|
|
3943
|
+
border-radius: 6px;
|
|
3944
|
+
display: flex; align-items: center; justify-content: center;
|
|
3945
|
+
color: var(--ps-text-secondary); cursor: pointer;
|
|
3946
|
+
transition: border-color 0.15s, color 0.15s;
|
|
3947
|
+
}
|
|
3948
|
+
.ps-msp-card-edit:hover {
|
|
3949
|
+
border-color: var(--ps-accent);
|
|
3950
|
+
color: var(--ps-accent);
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
/* "Create New Profile" empty card */
|
|
3954
|
+
.ps-msp-card-create {
|
|
3955
|
+
background: var(--ps-bg-secondary);
|
|
3956
|
+
border: 2px dashed var(--ps-border-color);
|
|
3957
|
+
align-items: center; justify-content: center;
|
|
3958
|
+
text-align: center;
|
|
3959
|
+
cursor: pointer;
|
|
3960
|
+
min-height: 280px;
|
|
3961
|
+
transition: border-color 0.15s, background 0.15s;
|
|
3962
|
+
}
|
|
3963
|
+
.ps-msp-card-create:hover {
|
|
3964
|
+
border-color: var(--ps-accent);
|
|
3965
|
+
background: rgba(33,84,239,0.04);
|
|
3966
|
+
}
|
|
3967
|
+
.ps-msp-create-icon {
|
|
3968
|
+
width: 50px; height: 50px;
|
|
3969
|
+
background: var(--ps-accent); color: #FFFFFF;
|
|
3970
|
+
border-radius: 8px;
|
|
3971
|
+
display: flex; align-items: center; justify-content: center;
|
|
3972
|
+
margin-bottom: 12px;
|
|
3973
|
+
}
|
|
3974
|
+
.ps-msp-create-title {
|
|
3975
|
+
font-size: 15px; font-weight: 700;
|
|
3976
|
+
color: var(--ps-text-primary);
|
|
3977
|
+
margin-bottom: 4px;
|
|
3978
|
+
}
|
|
3979
|
+
.ps-msp-create-sub {
|
|
3980
|
+
font-size: 9px; font-weight: 700;
|
|
3981
|
+
letter-spacing: 0.14em; text-transform: uppercase;
|
|
3982
|
+
color: var(--ps-text-muted);
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
/* Recent size calculations section */
|
|
3986
|
+
.ps-msp-history-section {
|
|
3987
|
+
margin-top: 20px;
|
|
3988
|
+
padding-top: 20px;
|
|
3989
|
+
border-top: 1px solid var(--ps-border-subtle);
|
|
3990
|
+
}
|
|
3991
|
+
.ps-msp-history-title {
|
|
3992
|
+
font-size: 13px; font-weight: 700;
|
|
3993
|
+
letter-spacing: 0.1em; text-transform: uppercase;
|
|
3994
|
+
color: var(--ps-text-primary);
|
|
3995
|
+
margin: 0 0 14px;
|
|
3996
|
+
}
|
|
3997
|
+
.ps-msp-history-list {
|
|
3998
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
3999
|
+
}
|
|
4000
|
+
.ps-msp-history-card {
|
|
4001
|
+
display: flex; align-items: center; gap: 12px;
|
|
4002
|
+
padding: 12px;
|
|
4003
|
+
background: var(--ps-bg-primary);
|
|
4004
|
+
border: 1px solid var(--ps-border-subtle);
|
|
4005
|
+
border-radius: 8px;
|
|
4006
|
+
}
|
|
4007
|
+
.ps-msp-history-thumb {
|
|
4008
|
+
width: 48px; height: 48px;
|
|
4009
|
+
border-radius: 6px;
|
|
4010
|
+
background: var(--ps-bg-secondary);
|
|
4011
|
+
overflow: hidden;
|
|
4012
|
+
flex-shrink: 0;
|
|
4013
|
+
display: flex; align-items: center; justify-content: center;
|
|
4014
|
+
}
|
|
4015
|
+
.ps-msp-history-thumb img {
|
|
4016
|
+
max-width: 100%; max-height: 100%; object-fit: contain;
|
|
4017
|
+
}
|
|
4018
|
+
.ps-msp-history-info {
|
|
4019
|
+
flex: 1; min-width: 0;
|
|
4020
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
4021
|
+
}
|
|
4022
|
+
.ps-msp-history-name {
|
|
4023
|
+
font-size: 13px; font-weight: 600;
|
|
4024
|
+
color: var(--ps-text-primary);
|
|
4025
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
4026
|
+
}
|
|
4027
|
+
.ps-msp-history-profile {
|
|
4028
|
+
font-size: 10px;
|
|
4029
|
+
color: var(--ps-text-muted);
|
|
4030
|
+
}
|
|
4031
|
+
.ps-msp-history-meta {
|
|
4032
|
+
font-size: 10px;
|
|
4033
|
+
color: var(--ps-text-muted);
|
|
4034
|
+
display: flex; align-items: center; gap: 4px;
|
|
4035
|
+
}
|
|
4036
|
+
.ps-msp-history-meta svg { color: var(--ps-text-muted); }
|
|
4037
|
+
.ps-msp-history-size {
|
|
4038
|
+
display: flex; flex-direction: column; align-items: flex-end;
|
|
4039
|
+
flex-shrink: 0;
|
|
4040
|
+
}
|
|
4041
|
+
.ps-msp-history-size-label {
|
|
4042
|
+
font-size: 9px; font-weight: 600;
|
|
4043
|
+
letter-spacing: 0.1em; text-transform: uppercase;
|
|
4044
|
+
color: var(--ps-text-muted);
|
|
4045
|
+
}
|
|
4046
|
+
.ps-msp-history-size-value {
|
|
4047
|
+
font-size: 22px; font-weight: 700;
|
|
4048
|
+
color: var(--ps-text-primary);
|
|
4049
|
+
font-feature-settings: "tnum" 1;
|
|
4050
|
+
line-height: 1;
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
@media (max-width: 768px) {
|
|
4054
|
+
.ps-msp-grid { grid-template-columns: 1fr; }
|
|
4055
|
+
.ps-msp-card-create { min-height: 200px; }
|
|
4056
|
+
.ps-msp-title { font-size: 22px; }
|
|
4057
|
+
.ps-msp-subtitle { font-size: 12px; }
|
|
4058
|
+
}
|
|
4059
|
+
|
|
3560
4060
|
/* Big product image */
|
|
3561
4061
|
.ps-msd-image {
|
|
3562
4062
|
width: 100%; height: 240px;
|
|
@@ -4550,7 +5050,7 @@ function ProfileDetailModal({
|
|
|
4550
5050
|
setProfileDetail,
|
|
4551
5051
|
setProfiles,
|
|
4552
5052
|
activeProfileId,
|
|
4553
|
-
setActiveProfileId,
|
|
5053
|
+
setActiveProfileId: setActiveProfileId2,
|
|
4554
5054
|
t
|
|
4555
5055
|
}) {
|
|
4556
5056
|
if (!profileDetail) return null;
|
|
@@ -4598,7 +5098,7 @@ function ProfileDetailModal({
|
|
|
4598
5098
|
] }),
|
|
4599
5099
|
/* @__PURE__ */ jsxs("button", { className: "ps-tryon-detail-delete", onClick: () => {
|
|
4600
5100
|
setProfiles((prev) => prev.filter((x) => x.id !== p.id));
|
|
4601
|
-
if (activeProfileId === p.id)
|
|
5101
|
+
if (activeProfileId === p.id) setActiveProfileId2(null);
|
|
4602
5102
|
setProfileDetail(null);
|
|
4603
5103
|
}, children: [
|
|
4604
5104
|
/* @__PURE__ */ jsx(TrashIcon, {}),
|
|
@@ -5715,6 +6215,7 @@ function SizeResultView({
|
|
|
5715
6215
|
handleTryOnSubmit,
|
|
5716
6216
|
tryOnProcessing,
|
|
5717
6217
|
bodyLandmarks,
|
|
6218
|
+
estimationDone = false,
|
|
5718
6219
|
activeSection,
|
|
5719
6220
|
setActiveSection,
|
|
5720
6221
|
t
|
|
@@ -5791,15 +6292,7 @@ function SizeResultView({
|
|
|
5791
6292
|
const [poseLines, setPoseLines] = useState(null);
|
|
5792
6293
|
const [poseReady, setPoseReady] = useState(false);
|
|
5793
6294
|
const [imgDims, setImgDims] = useState({ w: 800, h: 1200 });
|
|
5794
|
-
const
|
|
5795
|
-
useEffect(() => {
|
|
5796
|
-
if (!bodyLandmarks) {
|
|
5797
|
-
setAnalyzingDone(false);
|
|
5798
|
-
return;
|
|
5799
|
-
}
|
|
5800
|
-
const id = setTimeout(() => setAnalyzingDone(true), 1800);
|
|
5801
|
-
return () => clearTimeout(id);
|
|
5802
|
-
}, [bodyLandmarks]);
|
|
6295
|
+
const analyzingDone = estimationDone;
|
|
5803
6296
|
const handleImgLoad = useCallback((e) => {
|
|
5804
6297
|
const el = e.currentTarget;
|
|
5805
6298
|
if (el.naturalWidth && el.naturalHeight) {
|
|
@@ -6574,6 +7067,154 @@ function ErrorView({
|
|
|
6574
7067
|
/* @__PURE__ */ jsx("button", { onClick: handleRetry, className: cx("ps-tryon-submit", cn.submitButton), children: t("Try Again") })
|
|
6575
7068
|
] });
|
|
6576
7069
|
}
|
|
7070
|
+
function ProfileAvatar({ gender }) {
|
|
7071
|
+
return /* @__PURE__ */ jsxs("div", { className: "ps-msp-avatar", children: [
|
|
7072
|
+
/* @__PURE__ */ jsx(UserIcon, { size: 28 }),
|
|
7073
|
+
gender && /* @__PURE__ */ jsx("span", { className: `ps-msp-avatar-tag ps-${gender}`, children: gender === "female" ? "♀" : "♂" })
|
|
7074
|
+
] });
|
|
7075
|
+
}
|
|
7076
|
+
function ProfileCard({
|
|
7077
|
+
profile,
|
|
7078
|
+
isActive,
|
|
7079
|
+
onSelect,
|
|
7080
|
+
onEdit,
|
|
7081
|
+
t
|
|
7082
|
+
}) {
|
|
7083
|
+
const heightDisplay = (() => {
|
|
7084
|
+
const h = profile.height ?? profile.heightCm;
|
|
7085
|
+
if (!h) return null;
|
|
7086
|
+
if (profile.heightUnit === "in" || profile.heightUnit === "ft") {
|
|
7087
|
+
const ft = Math.floor(h / 12);
|
|
7088
|
+
const inches = Math.round(h % 12);
|
|
7089
|
+
return `${ft}'${inches}"`;
|
|
7090
|
+
}
|
|
7091
|
+
return `${Math.round(h)} cm`;
|
|
7092
|
+
})();
|
|
7093
|
+
const weightDisplay = (() => {
|
|
7094
|
+
const w = profile.weight ?? profile.weightKg;
|
|
7095
|
+
if (!w) return null;
|
|
7096
|
+
return `${Math.round(w)} ${profile.weightUnit || "kg"}`;
|
|
7097
|
+
})();
|
|
7098
|
+
const updatedDisplay = (() => {
|
|
7099
|
+
const ts = profile.lastEditedAt || profile.lastUsedAt || profile.createdAt;
|
|
7100
|
+
if (!ts) return null;
|
|
7101
|
+
return new Date(ts).toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
7102
|
+
})();
|
|
7103
|
+
return /* @__PURE__ */ jsxs("div", { className: `ps-msp-card${isActive ? " ps-active" : ""}`, children: [
|
|
7104
|
+
/* @__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") }) }),
|
|
7105
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-card-thumb", children: /* @__PURE__ */ jsx(ProfileAvatar, { gender: profile.gender }) }),
|
|
7106
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-card-name", children: profile.name }),
|
|
7107
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-card-meta", children: [
|
|
7108
|
+
heightDisplay && /* @__PURE__ */ jsxs("div", { className: "ps-msp-meta-row", children: [
|
|
7109
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-label", children: t("HEIGHT") }),
|
|
7110
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-value", children: heightDisplay })
|
|
7111
|
+
] }),
|
|
7112
|
+
weightDisplay && /* @__PURE__ */ jsxs("div", { className: "ps-msp-meta-row", children: [
|
|
7113
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-label", children: t("WEIGHT") }),
|
|
7114
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-value", children: weightDisplay })
|
|
7115
|
+
] }),
|
|
7116
|
+
updatedDisplay && /* @__PURE__ */ jsxs("div", { className: "ps-msp-meta-row", children: [
|
|
7117
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-label", children: t("LAST UPDATE") }),
|
|
7118
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-meta-value", children: updatedDisplay })
|
|
7119
|
+
] })
|
|
7120
|
+
] }),
|
|
7121
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-card-actions", children: [
|
|
7122
|
+
/* @__PURE__ */ jsx(
|
|
7123
|
+
"button",
|
|
7124
|
+
{
|
|
7125
|
+
type: "button",
|
|
7126
|
+
className: `ps-msp-card-select${isActive ? " ps-active" : ""}`,
|
|
7127
|
+
onClick: onSelect,
|
|
7128
|
+
children: isActive ? t("SELECTED") : t("SELECT")
|
|
7129
|
+
}
|
|
7130
|
+
),
|
|
7131
|
+
/* @__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: [
|
|
7132
|
+
/* @__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" }),
|
|
7133
|
+
/* @__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" })
|
|
7134
|
+
] }) })
|
|
7135
|
+
] })
|
|
7136
|
+
] });
|
|
7137
|
+
}
|
|
7138
|
+
function CreateProfileCard({ onClick, t }) {
|
|
7139
|
+
return /* @__PURE__ */ jsxs("button", { type: "button", className: "ps-msp-card ps-msp-card-create", onClick, children: [
|
|
7140
|
+
/* @__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: [
|
|
7141
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
7142
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
|
|
7143
|
+
] }) }),
|
|
7144
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-create-title", children: t("Create New Profile") }),
|
|
7145
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-create-sub", children: t("ADD MEASUREMENTS & PHOTO") })
|
|
7146
|
+
] });
|
|
7147
|
+
}
|
|
7148
|
+
function SizeHistoryCard({ entry, profileName, t }) {
|
|
7149
|
+
const date = new Date(entry.savedAt).toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
7150
|
+
return /* @__PURE__ */ jsxs("div", { className: "ps-msp-history-card", children: [
|
|
7151
|
+
entry.productImage && /* @__PURE__ */ jsx("div", { className: "ps-msp-history-thumb", children: /* @__PURE__ */ jsx("img", { src: entry.productImage, alt: entry.productTitle }) }),
|
|
7152
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-history-info", children: [
|
|
7153
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-history-name", children: entry.productTitle }),
|
|
7154
|
+
profileName && /* @__PURE__ */ jsx("div", { className: "ps-msp-history-profile", children: profileName }),
|
|
7155
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-history-meta", children: [
|
|
7156
|
+
/* @__PURE__ */ jsx(ClockIcon, { size: 11 }),
|
|
7157
|
+
" ",
|
|
7158
|
+
date
|
|
7159
|
+
] })
|
|
7160
|
+
] }),
|
|
7161
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-history-size", children: [
|
|
7162
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-history-size-label", children: t("SIZE") }),
|
|
7163
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-history-size-value", children: entry.recommendedSize })
|
|
7164
|
+
] })
|
|
7165
|
+
] });
|
|
7166
|
+
}
|
|
7167
|
+
function MySizingProfilesView({
|
|
7168
|
+
profiles,
|
|
7169
|
+
activeProfileId,
|
|
7170
|
+
onSelectProfile,
|
|
7171
|
+
onEditProfile,
|
|
7172
|
+
onCreateProfile,
|
|
7173
|
+
onDeleteProfile,
|
|
7174
|
+
onClose,
|
|
7175
|
+
t
|
|
7176
|
+
}) {
|
|
7177
|
+
const allHistory = useMemo(() => {
|
|
7178
|
+
const items = [];
|
|
7179
|
+
for (const p of profiles) {
|
|
7180
|
+
for (const h of p.sizeHistory || []) {
|
|
7181
|
+
items.push({ entry: h, profileName: p.name });
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
return items.sort((a, b) => b.entry.savedAt - a.entry.savedAt).slice(0, 12);
|
|
7185
|
+
}, [profiles]);
|
|
7186
|
+
return /* @__PURE__ */ jsxs("div", { className: "ps-msp-root", children: [
|
|
7187
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-topbar", children: [
|
|
7188
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ps-msp-back", onClick: onClose, "aria-label": t("Back"), children: /* @__PURE__ */ jsx(ArrowLeftIcon, {}) }),
|
|
7189
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-topbar-title", children: t("MY SIZING PROFILES") }),
|
|
7190
|
+
/* @__PURE__ */ jsx("span", { className: "ps-msp-topbar-spacer" })
|
|
7191
|
+
] }),
|
|
7192
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-scroll", children: [
|
|
7193
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-header", children: [
|
|
7194
|
+
/* @__PURE__ */ jsx("h2", { className: "ps-msp-title", children: t("My Sizing Profiles") }),
|
|
7195
|
+
/* @__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.") })
|
|
7196
|
+
] }),
|
|
7197
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-msp-grid", children: [
|
|
7198
|
+
/* @__PURE__ */ jsx(CreateProfileCard, { onClick: onCreateProfile, t }),
|
|
7199
|
+
profiles.map((p) => /* @__PURE__ */ jsx(
|
|
7200
|
+
ProfileCard,
|
|
7201
|
+
{
|
|
7202
|
+
profile: p,
|
|
7203
|
+
isActive: p.id === activeProfileId,
|
|
7204
|
+
onSelect: () => onSelectProfile(p.id),
|
|
7205
|
+
onEdit: () => onEditProfile(p),
|
|
7206
|
+
t
|
|
7207
|
+
},
|
|
7208
|
+
p.id
|
|
7209
|
+
))
|
|
7210
|
+
] }),
|
|
7211
|
+
allHistory.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-msp-history-section", children: [
|
|
7212
|
+
/* @__PURE__ */ jsx("h3", { className: "ps-msp-history-title", children: t("Recent Size Calculations") }),
|
|
7213
|
+
/* @__PURE__ */ jsx("div", { className: "ps-msp-history-list", children: allHistory.map(({ entry, profileName }) => /* @__PURE__ */ jsx(SizeHistoryCard, { entry, profileName, t }, `${entry.productId}-${entry.savedAt}`)) })
|
|
7214
|
+
] })
|
|
7215
|
+
] })
|
|
7216
|
+
] });
|
|
7217
|
+
}
|
|
6577
7218
|
function ResultView({ setView }) {
|
|
6578
7219
|
useEffect(() => {
|
|
6579
7220
|
setView("size-result");
|
|
@@ -7696,6 +8337,7 @@ if (typeof document !== "undefined") {
|
|
|
7696
8337
|
function PrimeStyleTryonInner({
|
|
7697
8338
|
productImage,
|
|
7698
8339
|
productTitle = "Product",
|
|
8340
|
+
productId,
|
|
7699
8341
|
buttonText,
|
|
7700
8342
|
apiUrl,
|
|
7701
8343
|
showPoweredBy = true,
|
|
@@ -7715,6 +8357,7 @@ function PrimeStyleTryonInner({
|
|
|
7715
8357
|
onError,
|
|
7716
8358
|
sizeGuideData
|
|
7717
8359
|
}) {
|
|
8360
|
+
const effectiveProductId = productId || productImage;
|
|
7718
8361
|
const [activeLocale, setActiveLocale] = useState(() => locale || "");
|
|
7719
8362
|
useEffect(() => {
|
|
7720
8363
|
if (locale !== void 0) setActiveLocale(locale);
|
|
@@ -7732,6 +8375,7 @@ function PrimeStyleTryonInner({
|
|
|
7732
8375
|
const [sizingMethod, setSizingMethod] = useState(null);
|
|
7733
8376
|
const [sizingResult, setSizingResult] = useState(null);
|
|
7734
8377
|
const [sizingLoading, setSizingLoading] = useState(false);
|
|
8378
|
+
const [estimationDone, setEstimationDone] = useState(false);
|
|
7735
8379
|
const [tryOnProcessing, setTryOnProcessing] = useState(false);
|
|
7736
8380
|
const [sizeGuide, setSizeGuide] = useState(null);
|
|
7737
8381
|
const [sizeGuideFetching, setSizeGuideFetching] = useState(false);
|
|
@@ -7748,7 +8392,11 @@ function PrimeStyleTryonInner({
|
|
|
7748
8392
|
const [estimationLoading, setEstimationLoading] = useState(false);
|
|
7749
8393
|
const [profiles, setProfiles] = useState(() => lsGet("profiles", []));
|
|
7750
8394
|
const [history, setHistory] = useState(() => lsGet("history", []));
|
|
7751
|
-
const [activeProfileId,
|
|
8395
|
+
const [activeProfileId, setActiveProfileIdState] = useState(() => getActiveProfileId());
|
|
8396
|
+
const setActiveProfileId$1 = useCallback((id) => {
|
|
8397
|
+
setActiveProfileIdState(id);
|
|
8398
|
+
setActiveProfileId(id);
|
|
8399
|
+
}, []);
|
|
7752
8400
|
const [profileSaved, setProfileSaved] = useState(false);
|
|
7753
8401
|
const [drawer, setDrawer] = useState(null);
|
|
7754
8402
|
const [profileDetail, setProfileDetail] = useState(null);
|
|
@@ -7887,6 +8535,69 @@ function PrimeStyleTryonInner({
|
|
|
7887
8535
|
return 1;
|
|
7888
8536
|
}
|
|
7889
8537
|
}, [view]);
|
|
8538
|
+
const persistResultToProfile = useCallback(
|
|
8539
|
+
(formData, recommendation) => {
|
|
8540
|
+
let targetId = activeProfileId;
|
|
8541
|
+
let target = profiles.find((p) => p.id === targetId);
|
|
8542
|
+
if (!target) {
|
|
8543
|
+
const newProfile = {
|
|
8544
|
+
id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
8545
|
+
name: formData.gender === "female" ? "My Profile" : "My Profile",
|
|
8546
|
+
gender: formData.gender,
|
|
8547
|
+
height: formData.height,
|
|
8548
|
+
weight: formData.weight,
|
|
8549
|
+
age: formData.age,
|
|
8550
|
+
heightUnit: formData.heightUnit,
|
|
8551
|
+
weightUnit: formData.weightUnit,
|
|
8552
|
+
chestProfile: formData.chestProfile,
|
|
8553
|
+
midsectionProfile: formData.midsectionProfile,
|
|
8554
|
+
hipProfile: formData.hipProfile,
|
|
8555
|
+
bandSize: formData.bandSize,
|
|
8556
|
+
cupSize: formData.cupSize,
|
|
8557
|
+
createdAt: Date.now(),
|
|
8558
|
+
lastUsedAt: Date.now()
|
|
8559
|
+
};
|
|
8560
|
+
setProfiles((prev) => [newProfile, ...prev]);
|
|
8561
|
+
setActiveProfileId$1(newProfile.id);
|
|
8562
|
+
targetId = newProfile.id;
|
|
8563
|
+
target = newProfile;
|
|
8564
|
+
}
|
|
8565
|
+
estimateFullMeasurements({
|
|
8566
|
+
apiUrl,
|
|
8567
|
+
height: formData.height,
|
|
8568
|
+
weight: formData.weight,
|
|
8569
|
+
heightUnit: formData.heightUnit,
|
|
8570
|
+
weightUnit: formData.weightUnit,
|
|
8571
|
+
gender: formData.gender,
|
|
8572
|
+
age: formData.age,
|
|
8573
|
+
chestProfile: formData.chestProfile,
|
|
8574
|
+
midsectionProfile: formData.midsectionProfile,
|
|
8575
|
+
hipProfile: formData.hipProfile,
|
|
8576
|
+
bodyImage: formData.bodyImage
|
|
8577
|
+
}).then((est) => {
|
|
8578
|
+
if (!est || !targetId) return;
|
|
8579
|
+
updateProfileMeasurements(targetId, est.estimates, est.unit);
|
|
8580
|
+
setProfiles(lsGet("profiles", []));
|
|
8581
|
+
}).catch(() => {
|
|
8582
|
+
}).finally(() => setEstimationDone(true));
|
|
8583
|
+
if (recommendation?.recommendedSize && targetId) {
|
|
8584
|
+
const sectionsMap = recommendation.sections ? Object.fromEntries(
|
|
8585
|
+
Object.entries(recommendation.sections).map(([name, sec]) => [name, sec.recommendedSize])
|
|
8586
|
+
) : void 0;
|
|
8587
|
+
addSizeToHistory(targetId, {
|
|
8588
|
+
productId: effectiveProductId,
|
|
8589
|
+
productTitle,
|
|
8590
|
+
productImage,
|
|
8591
|
+
recommendedSize: recommendation.recommendedSize,
|
|
8592
|
+
confidence: recommendation.confidence,
|
|
8593
|
+
sections: sectionsMap,
|
|
8594
|
+
savedAt: Date.now()
|
|
8595
|
+
});
|
|
8596
|
+
setProfiles(lsGet("profiles", []));
|
|
8597
|
+
}
|
|
8598
|
+
},
|
|
8599
|
+
[activeProfileId, profiles, apiUrl, productImage, productTitle, effectiveProductId, setActiveProfileId$1]
|
|
8600
|
+
);
|
|
7890
8601
|
const applyProfileRef = useRef(() => {
|
|
7891
8602
|
});
|
|
7892
8603
|
const handleOpen = useCallback(() => {
|
|
@@ -8093,6 +8804,7 @@ function PrimeStyleTryonInner({
|
|
|
8093
8804
|
if (formRef.current.hipProfile) qe.hipProfile = formRef.current.hipProfile;
|
|
8094
8805
|
payload.quickEstimate = qe;
|
|
8095
8806
|
}
|
|
8807
|
+
setEstimationDone(false);
|
|
8096
8808
|
try {
|
|
8097
8809
|
const res = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
|
|
8098
8810
|
method: "POST",
|
|
@@ -8104,20 +8816,39 @@ function PrimeStyleTryonInner({
|
|
|
8104
8816
|
console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
|
|
8105
8817
|
setSizingResult(data);
|
|
8106
8818
|
onComplete?.(data);
|
|
8819
|
+
const m = payload.measurements || {};
|
|
8820
|
+
const qe = payload.quickEstimate || {};
|
|
8821
|
+
const src = method === "exact" ? m : qe;
|
|
8822
|
+
persistResultToProfile(
|
|
8823
|
+
{
|
|
8824
|
+
gender: src.gender || "male",
|
|
8825
|
+
height: Number(src.height || src.heightCm || 0),
|
|
8826
|
+
weight: Number(src.weight || src.weightKg || 0),
|
|
8827
|
+
heightUnit: src.heightUnit || "cm",
|
|
8828
|
+
weightUnit: src.weightUnit || "kg",
|
|
8829
|
+
age: src.age != null ? Number(src.age) : void 0,
|
|
8830
|
+
chestProfile: src.chestProfile,
|
|
8831
|
+
midsectionProfile: src.midsectionProfile,
|
|
8832
|
+
hipProfile: src.hipProfile
|
|
8833
|
+
},
|
|
8834
|
+
data
|
|
8835
|
+
);
|
|
8107
8836
|
} else {
|
|
8108
8837
|
const errBody = await res.text().catch(() => "");
|
|
8109
8838
|
console.error("[PS-SDK] Sizing recommend failed:", res.status, errBody);
|
|
8110
8839
|
setErrorMessage(t("Unable to get size recommendation. Please try again."));
|
|
8111
8840
|
setView("error");
|
|
8841
|
+
setEstimationDone(true);
|
|
8112
8842
|
}
|
|
8113
8843
|
} catch (err) {
|
|
8114
8844
|
console.error("[PS-SDK] Sizing recommend network error:", err);
|
|
8115
8845
|
setErrorMessage(t("Unable to connect to sizing service. Please try again."));
|
|
8116
8846
|
setView("error");
|
|
8847
|
+
setEstimationDone(true);
|
|
8117
8848
|
} finally {
|
|
8118
8849
|
setSizingLoading(false);
|
|
8119
8850
|
}
|
|
8120
|
-
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
|
|
8851
|
+
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, persistResultToProfile]);
|
|
8121
8852
|
const handleQuickEstimate = useCallback(async (height, weight, heightUnit2, weightUnit2, gender, age, bodyType, chestProfile, midsectionProfile, hipProfile, bodyImage) => {
|
|
8122
8853
|
if (!apiRef.current) return;
|
|
8123
8854
|
getApiUrl(apiUrl);
|
|
@@ -8182,6 +8913,7 @@ function PrimeStyleTryonInner({
|
|
|
8182
8913
|
setSizingResult(null);
|
|
8183
8914
|
setResultImageUrl(null);
|
|
8184
8915
|
setSizingLoading(true);
|
|
8916
|
+
setEstimationDone(false);
|
|
8185
8917
|
setView("size-result");
|
|
8186
8918
|
modelPoseRef.current = null;
|
|
8187
8919
|
setBodyLandmarks(null);
|
|
@@ -8220,11 +8952,26 @@ function PrimeStyleTryonInner({
|
|
|
8220
8952
|
const recData = await recRes.json();
|
|
8221
8953
|
setSizingResult(recData);
|
|
8222
8954
|
onComplete?.(recData);
|
|
8955
|
+
persistResultToProfile(
|
|
8956
|
+
{
|
|
8957
|
+
gender: data.gender,
|
|
8958
|
+
height: data.height,
|
|
8959
|
+
weight: data.weight,
|
|
8960
|
+
heightUnit: data.heightUnit,
|
|
8961
|
+
weightUnit: data.weightUnit,
|
|
8962
|
+
age: data.age,
|
|
8963
|
+
bodyImage: data.photoBase64
|
|
8964
|
+
},
|
|
8965
|
+
recData
|
|
8966
|
+
);
|
|
8967
|
+
} else {
|
|
8968
|
+
setEstimationDone(true);
|
|
8223
8969
|
}
|
|
8224
8970
|
} catch {
|
|
8971
|
+
setEstimationDone(true);
|
|
8225
8972
|
}
|
|
8226
8973
|
setSizingLoading(false);
|
|
8227
|
-
}, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields]);
|
|
8974
|
+
}, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields, persistResultToProfile]);
|
|
8228
8975
|
const handleTryOnSubmit = useCallback(async () => {
|
|
8229
8976
|
const file = selectedFile || selectedFileRef.current;
|
|
8230
8977
|
if (!file || !apiRef.current || !sseRef.current) {
|
|
@@ -8416,7 +9163,7 @@ function PrimeStyleTryonInner({
|
|
|
8416
9163
|
const applyProfile = useCallback((id) => {
|
|
8417
9164
|
const p = profiles.find((pr) => pr.id === id);
|
|
8418
9165
|
if (!p) return;
|
|
8419
|
-
setActiveProfileId(id);
|
|
9166
|
+
setActiveProfileId$1(id);
|
|
8420
9167
|
setProfiles((prev) => prev.map((pr) => pr.id === id ? { ...pr, lastUsedAt: Date.now() } : pr));
|
|
8421
9168
|
const fd = { gender: p.gender || "male" };
|
|
8422
9169
|
if (p.height ?? p.heightCm) fd.height = String(p.height ?? p.heightCm);
|
|
@@ -8511,7 +9258,7 @@ function PrimeStyleTryonInner({
|
|
|
8511
9258
|
}
|
|
8512
9259
|
return [...prev, p].slice(-50);
|
|
8513
9260
|
});
|
|
8514
|
-
setActiveProfileId(id);
|
|
9261
|
+
setActiveProfileId$1(id);
|
|
8515
9262
|
setProfileSaved(true);
|
|
8516
9263
|
}, [activeProfileId, sizingCountry, sizingUnit, heightUnit, weightUnit]);
|
|
8517
9264
|
const saveHistoryEntry = useCallback(() => {
|
|
@@ -8726,6 +9473,7 @@ function PrimeStyleTryonInner({
|
|
|
8726
9473
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
8727
9474
|
SizeResultView,
|
|
8728
9475
|
{
|
|
9476
|
+
estimationDone,
|
|
8729
9477
|
sizingLoading,
|
|
8730
9478
|
sizingResult,
|
|
8731
9479
|
sizeGuide,
|
|
@@ -8780,6 +9528,31 @@ function PrimeStyleTryonInner({
|
|
|
8780
9528
|
) }, "v-proc");
|
|
8781
9529
|
case "result":
|
|
8782
9530
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(ResultView, { setView }) }, "v-result");
|
|
9531
|
+
case "profiles":
|
|
9532
|
+
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
9533
|
+
MySizingProfilesView,
|
|
9534
|
+
{
|
|
9535
|
+
profiles,
|
|
9536
|
+
activeProfileId,
|
|
9537
|
+
onSelectProfile: (id) => {
|
|
9538
|
+
setActiveProfileId$1(id);
|
|
9539
|
+
setView("body-profile");
|
|
9540
|
+
},
|
|
9541
|
+
onEditProfile: (p) => {
|
|
9542
|
+
setProfileDetail(p);
|
|
9543
|
+
},
|
|
9544
|
+
onCreateProfile: () => {
|
|
9545
|
+
setActiveProfileId$1(null);
|
|
9546
|
+
formRef.current = {};
|
|
9547
|
+
setFormGender("male");
|
|
9548
|
+
setFormKey((k) => k + 1);
|
|
9549
|
+
setView("body-profile");
|
|
9550
|
+
},
|
|
9551
|
+
onDeleteProfile: (id) => setProfiles((prev) => prev.filter((p) => p.id !== id)),
|
|
9552
|
+
onClose: () => setView("body-profile"),
|
|
9553
|
+
t
|
|
9554
|
+
}
|
|
9555
|
+
) }, "v-profiles");
|
|
8783
9556
|
case "error":
|
|
8784
9557
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
8785
9558
|
ErrorView,
|
|
@@ -8800,10 +9573,10 @@ function PrimeStyleTryonInner({
|
|
|
8800
9573
|
/* @__PURE__ */ jsx("span", { children: resolvedButtonText })
|
|
8801
9574
|
] }),
|
|
8802
9575
|
view !== "idle" && typeof document !== "undefined" && createPortal(
|
|
8803
|
-
/* @__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: [
|
|
9576
|
+
/* @__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" || view === "profiles" ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), children: [
|
|
8804
9577
|
/* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-header ps-tryon-header-minimal", cn.header), children: [
|
|
8805
9578
|
/* @__PURE__ */ jsx(LangSwitcher, { activeLocale, onSelect: setActiveLocale }),
|
|
8806
|
-
/* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("Profiles"), onClick: () =>
|
|
9579
|
+
/* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("Profiles"), onClick: () => setView(view === "profiles" ? "body-profile" : "profiles"), children: /* @__PURE__ */ jsx(UserIcon, {}) }),
|
|
8807
9580
|
/* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("History"), onClick: () => setDrawer(drawer === "history" ? null : "history"), children: /* @__PURE__ */ jsx(ClockIcon, {}) }),
|
|
8808
9581
|
/* @__PURE__ */ jsx("button", { onClick: handleClose, className: cx("ps-tryon-close", cn.closeButton), children: /* @__PURE__ */ jsx(XIcon, {}) })
|
|
8809
9582
|
] }),
|
|
@@ -8842,7 +9615,7 @@ function PrimeStyleTryonInner({
|
|
|
8842
9615
|
setProfileDetail,
|
|
8843
9616
|
setProfiles,
|
|
8844
9617
|
activeProfileId,
|
|
8845
|
-
setActiveProfileId,
|
|
9618
|
+
setActiveProfileId: setActiveProfileId$1,
|
|
8846
9619
|
t
|
|
8847
9620
|
}
|
|
8848
9621
|
)
|
|
@@ -8851,6 +9624,53 @@ function PrimeStyleTryonInner({
|
|
|
8851
9624
|
function PrimeStyleTryon(props) {
|
|
8852
9625
|
return /* @__PURE__ */ jsx(PrimeStyleTryonInner, { ...props });
|
|
8853
9626
|
}
|
|
9627
|
+
function usePrimeStyleSize(input) {
|
|
9628
|
+
const [result, setResult] = useState(null);
|
|
9629
|
+
const [loading, setLoading] = useState(true);
|
|
9630
|
+
const [noProfile, setNoProfile] = useState(false);
|
|
9631
|
+
const [tick, setTick] = useState(0);
|
|
9632
|
+
useEffect(() => {
|
|
9633
|
+
let cancelled = false;
|
|
9634
|
+
setLoading(true);
|
|
9635
|
+
setNoProfile(false);
|
|
9636
|
+
recommendForProduct(input).then((res) => {
|
|
9637
|
+
if (cancelled) return;
|
|
9638
|
+
if (res === null) {
|
|
9639
|
+
setNoProfile(true);
|
|
9640
|
+
setResult(null);
|
|
9641
|
+
} else {
|
|
9642
|
+
setResult(res);
|
|
9643
|
+
}
|
|
9644
|
+
}).catch(() => {
|
|
9645
|
+
if (!cancelled) setResult(null);
|
|
9646
|
+
}).finally(() => {
|
|
9647
|
+
if (!cancelled) setLoading(false);
|
|
9648
|
+
});
|
|
9649
|
+
return () => {
|
|
9650
|
+
cancelled = true;
|
|
9651
|
+
};
|
|
9652
|
+
}, [input.productId, input.apiUrl, input.apiKey, tick]);
|
|
9653
|
+
return {
|
|
9654
|
+
recommendedSize: result?.recommendedSize ?? null,
|
|
9655
|
+
sections: result?.sections ?? null,
|
|
9656
|
+
loading,
|
|
9657
|
+
noProfile,
|
|
9658
|
+
result,
|
|
9659
|
+
refetch: () => setTick((n) => n + 1)
|
|
9660
|
+
};
|
|
9661
|
+
}
|
|
8854
9662
|
export {
|
|
8855
|
-
PrimeStyleTryon
|
|
9663
|
+
PrimeStyleTryon,
|
|
9664
|
+
addSizeToHistory,
|
|
9665
|
+
estimateFullMeasurements,
|
|
9666
|
+
getActiveProfile,
|
|
9667
|
+
getActiveProfileId,
|
|
9668
|
+
getCachedSize,
|
|
9669
|
+
getProfiles,
|
|
9670
|
+
recommendForProduct,
|
|
9671
|
+
saveProfiles,
|
|
9672
|
+
setActiveProfileId,
|
|
9673
|
+
updateProfile,
|
|
9674
|
+
updateProfileMeasurements,
|
|
9675
|
+
usePrimeStyleSize
|
|
8856
9676
|
};
|