@primestyleai/tryon 5.5.26 → 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 +837 -23
- 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,15 +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 });
|
|
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]);
|
|
6292
|
+
const analyzingDone = estimationDone;
|
|
5803
6293
|
const handleImgLoad = useCallback((e) => {
|
|
5804
6294
|
const el = e.currentTarget;
|
|
5805
6295
|
if (el.naturalWidth && el.naturalHeight) {
|
|
@@ -6574,6 +7064,154 @@ function ErrorView({
|
|
|
6574
7064
|
/* @__PURE__ */ jsx("button", { onClick: handleRetry, className: cx("ps-tryon-submit", cn.submitButton), children: t("Try Again") })
|
|
6575
7065
|
] });
|
|
6576
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
|
+
}
|
|
6577
7215
|
function ResultView({ setView }) {
|
|
6578
7216
|
useEffect(() => {
|
|
6579
7217
|
setView("size-result");
|
|
@@ -7696,6 +8334,7 @@ if (typeof document !== "undefined") {
|
|
|
7696
8334
|
function PrimeStyleTryonInner({
|
|
7697
8335
|
productImage,
|
|
7698
8336
|
productTitle = "Product",
|
|
8337
|
+
productId,
|
|
7699
8338
|
buttonText,
|
|
7700
8339
|
apiUrl,
|
|
7701
8340
|
showPoweredBy = true,
|
|
@@ -7715,6 +8354,7 @@ function PrimeStyleTryonInner({
|
|
|
7715
8354
|
onError,
|
|
7716
8355
|
sizeGuideData
|
|
7717
8356
|
}) {
|
|
8357
|
+
const effectiveProductId = productId || productImage;
|
|
7718
8358
|
const [activeLocale, setActiveLocale] = useState(() => locale || "");
|
|
7719
8359
|
useEffect(() => {
|
|
7720
8360
|
if (locale !== void 0) setActiveLocale(locale);
|
|
@@ -7732,6 +8372,7 @@ function PrimeStyleTryonInner({
|
|
|
7732
8372
|
const [sizingMethod, setSizingMethod] = useState(null);
|
|
7733
8373
|
const [sizingResult, setSizingResult] = useState(null);
|
|
7734
8374
|
const [sizingLoading, setSizingLoading] = useState(false);
|
|
8375
|
+
const [estimationDone, setEstimationDone] = useState(false);
|
|
7735
8376
|
const [tryOnProcessing, setTryOnProcessing] = useState(false);
|
|
7736
8377
|
const [sizeGuide, setSizeGuide] = useState(null);
|
|
7737
8378
|
const [sizeGuideFetching, setSizeGuideFetching] = useState(false);
|
|
@@ -7748,7 +8389,11 @@ function PrimeStyleTryonInner({
|
|
|
7748
8389
|
const [estimationLoading, setEstimationLoading] = useState(false);
|
|
7749
8390
|
const [profiles, setProfiles] = useState(() => lsGet("profiles", []));
|
|
7750
8391
|
const [history, setHistory] = useState(() => lsGet("history", []));
|
|
7751
|
-
const [activeProfileId,
|
|
8392
|
+
const [activeProfileId, setActiveProfileIdState] = useState(() => getActiveProfileId());
|
|
8393
|
+
const setActiveProfileId$1 = useCallback((id) => {
|
|
8394
|
+
setActiveProfileIdState(id);
|
|
8395
|
+
setActiveProfileId(id);
|
|
8396
|
+
}, []);
|
|
7752
8397
|
const [profileSaved, setProfileSaved] = useState(false);
|
|
7753
8398
|
const [drawer, setDrawer] = useState(null);
|
|
7754
8399
|
const [profileDetail, setProfileDetail] = useState(null);
|
|
@@ -7887,6 +8532,69 @@ function PrimeStyleTryonInner({
|
|
|
7887
8532
|
return 1;
|
|
7888
8533
|
}
|
|
7889
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
|
+
);
|
|
7890
8598
|
const applyProfileRef = useRef(() => {
|
|
7891
8599
|
});
|
|
7892
8600
|
const handleOpen = useCallback(() => {
|
|
@@ -8093,6 +8801,7 @@ function PrimeStyleTryonInner({
|
|
|
8093
8801
|
if (formRef.current.hipProfile) qe.hipProfile = formRef.current.hipProfile;
|
|
8094
8802
|
payload.quickEstimate = qe;
|
|
8095
8803
|
}
|
|
8804
|
+
setEstimationDone(false);
|
|
8096
8805
|
try {
|
|
8097
8806
|
const res = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
|
|
8098
8807
|
method: "POST",
|
|
@@ -8104,20 +8813,39 @@ function PrimeStyleTryonInner({
|
|
|
8104
8813
|
console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
|
|
8105
8814
|
setSizingResult(data);
|
|
8106
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
|
+
);
|
|
8107
8833
|
} else {
|
|
8108
8834
|
const errBody = await res.text().catch(() => "");
|
|
8109
8835
|
console.error("[PS-SDK] Sizing recommend failed:", res.status, errBody);
|
|
8110
8836
|
setErrorMessage(t("Unable to get size recommendation. Please try again."));
|
|
8111
8837
|
setView("error");
|
|
8838
|
+
setEstimationDone(true);
|
|
8112
8839
|
}
|
|
8113
8840
|
} catch (err) {
|
|
8114
8841
|
console.error("[PS-SDK] Sizing recommend network error:", err);
|
|
8115
8842
|
setErrorMessage(t("Unable to connect to sizing service. Please try again."));
|
|
8116
8843
|
setView("error");
|
|
8844
|
+
setEstimationDone(true);
|
|
8117
8845
|
} finally {
|
|
8118
8846
|
setSizingLoading(false);
|
|
8119
8847
|
}
|
|
8120
|
-
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
|
|
8848
|
+
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, persistResultToProfile]);
|
|
8121
8849
|
const handleQuickEstimate = useCallback(async (height, weight, heightUnit2, weightUnit2, gender, age, bodyType, chestProfile, midsectionProfile, hipProfile, bodyImage) => {
|
|
8122
8850
|
if (!apiRef.current) return;
|
|
8123
8851
|
getApiUrl(apiUrl);
|
|
@@ -8182,6 +8910,7 @@ function PrimeStyleTryonInner({
|
|
|
8182
8910
|
setSizingResult(null);
|
|
8183
8911
|
setResultImageUrl(null);
|
|
8184
8912
|
setSizingLoading(true);
|
|
8913
|
+
setEstimationDone(false);
|
|
8185
8914
|
setView("size-result");
|
|
8186
8915
|
modelPoseRef.current = null;
|
|
8187
8916
|
setBodyLandmarks(null);
|
|
@@ -8220,11 +8949,26 @@ function PrimeStyleTryonInner({
|
|
|
8220
8949
|
const recData = await recRes.json();
|
|
8221
8950
|
setSizingResult(recData);
|
|
8222
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);
|
|
8223
8966
|
}
|
|
8224
8967
|
} catch {
|
|
8968
|
+
setEstimationDone(true);
|
|
8225
8969
|
}
|
|
8226
8970
|
setSizingLoading(false);
|
|
8227
|
-
}, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields]);
|
|
8971
|
+
}, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields, persistResultToProfile]);
|
|
8228
8972
|
const handleTryOnSubmit = useCallback(async () => {
|
|
8229
8973
|
const file = selectedFile || selectedFileRef.current;
|
|
8230
8974
|
if (!file || !apiRef.current || !sseRef.current) {
|
|
@@ -8416,7 +9160,7 @@ function PrimeStyleTryonInner({
|
|
|
8416
9160
|
const applyProfile = useCallback((id) => {
|
|
8417
9161
|
const p = profiles.find((pr) => pr.id === id);
|
|
8418
9162
|
if (!p) return;
|
|
8419
|
-
setActiveProfileId(id);
|
|
9163
|
+
setActiveProfileId$1(id);
|
|
8420
9164
|
setProfiles((prev) => prev.map((pr) => pr.id === id ? { ...pr, lastUsedAt: Date.now() } : pr));
|
|
8421
9165
|
const fd = { gender: p.gender || "male" };
|
|
8422
9166
|
if (p.height ?? p.heightCm) fd.height = String(p.height ?? p.heightCm);
|
|
@@ -8511,7 +9255,7 @@ function PrimeStyleTryonInner({
|
|
|
8511
9255
|
}
|
|
8512
9256
|
return [...prev, p].slice(-50);
|
|
8513
9257
|
});
|
|
8514
|
-
setActiveProfileId(id);
|
|
9258
|
+
setActiveProfileId$1(id);
|
|
8515
9259
|
setProfileSaved(true);
|
|
8516
9260
|
}, [activeProfileId, sizingCountry, sizingUnit, heightUnit, weightUnit]);
|
|
8517
9261
|
const saveHistoryEntry = useCallback(() => {
|
|
@@ -8726,6 +9470,7 @@ function PrimeStyleTryonInner({
|
|
|
8726
9470
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
8727
9471
|
SizeResultView,
|
|
8728
9472
|
{
|
|
9473
|
+
estimationDone,
|
|
8729
9474
|
sizingLoading,
|
|
8730
9475
|
sizingResult,
|
|
8731
9476
|
sizeGuide,
|
|
@@ -8780,6 +9525,28 @@ function PrimeStyleTryonInner({
|
|
|
8780
9525
|
) }, "v-proc");
|
|
8781
9526
|
case "result":
|
|
8782
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");
|
|
8783
9550
|
case "error":
|
|
8784
9551
|
return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(
|
|
8785
9552
|
ErrorView,
|
|
@@ -8803,7 +9570,7 @@ function PrimeStyleTryonInner({
|
|
|
8803
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: [
|
|
8804
9571
|
/* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-header ps-tryon-header-minimal", cn.header), children: [
|
|
8805
9572
|
/* @__PURE__ */ jsx(LangSwitcher, { activeLocale, onSelect: setActiveLocale }),
|
|
8806
|
-
/* @__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, {}) }),
|
|
8807
9574
|
/* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("History"), onClick: () => setDrawer(drawer === "history" ? null : "history"), children: /* @__PURE__ */ jsx(ClockIcon, {}) }),
|
|
8808
9575
|
/* @__PURE__ */ jsx("button", { onClick: handleClose, className: cx("ps-tryon-close", cn.closeButton), children: /* @__PURE__ */ jsx(XIcon, {}) })
|
|
8809
9576
|
] }),
|
|
@@ -8842,7 +9609,7 @@ function PrimeStyleTryonInner({
|
|
|
8842
9609
|
setProfileDetail,
|
|
8843
9610
|
setProfiles,
|
|
8844
9611
|
activeProfileId,
|
|
8845
|
-
setActiveProfileId,
|
|
9612
|
+
setActiveProfileId: setActiveProfileId$1,
|
|
8846
9613
|
t
|
|
8847
9614
|
}
|
|
8848
9615
|
)
|
|
@@ -8851,6 +9618,53 @@ function PrimeStyleTryonInner({
|
|
|
8851
9618
|
function PrimeStyleTryon(props) {
|
|
8852
9619
|
return /* @__PURE__ */ jsx(PrimeStyleTryonInner, { ...props });
|
|
8853
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
|
+
}
|
|
8854
9656
|
export {
|
|
8855
|
-
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
|
|
8856
9670
|
};
|