@thefittingroom/shop-ui 2.0.2 → 3.0.0-alpha-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.
@@ -2,6 +2,7 @@ export type RecommendedSize = {
2
2
  recommended: string;
3
3
  sizes: {
4
4
  size: string;
5
+ size_id: number;
5
6
  locations: {
6
7
  fit: string;
7
8
  isPerfect: boolean;
@@ -13,7 +14,9 @@ export declare class SizeRecComponent {
13
14
  private readonly onSignInClick;
14
15
  private readonly onSignOutClick;
15
16
  private readonly onFitInfoClick;
17
+ private readonly onTryOnClick;
16
18
  private _sku;
19
+ private _styleId;
17
20
  private isLoggedIn;
18
21
  private tfrInfoIcon;
19
22
  private tfrLoginToView;
@@ -35,9 +38,11 @@ export declare class SizeRecComponent {
35
38
  private tfrToggleClosedElements;
36
39
  private isCollapsed;
37
40
  private redraw;
38
- constructor(sizeRecMainDivId: string, onSignInClick: () => void, onSignOutClick: () => void, onFitInfoClick: () => void);
41
+ constructor(sizeRecMainDivId: string, onSignInClick: () => void, onSignOutClick: () => void, onFitInfoClick: () => void, onTryOnClick: (styleId: number, sizeId: number) => void);
39
42
  get sku(): string;
40
43
  setSku(sku: string): void;
44
+ get styleId(): number;
45
+ setStyleId(styleId: number): void;
41
46
  setIsLoggedIn(isLoggedIn: boolean): void;
42
47
  setLoading(isLoading: boolean): void;
43
48
  setGarmentLocations(locations: string[]): void;
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * thefittingroom v2.0.2 (2024-10-23T17:24:53.220Z)
2
+ * thefittingroom v3.0.0-alpha-0 (2025-01-17T19:58:29.656Z)
3
3
  * Copyright 2022-present, TheFittingRoom, Inc. All rights reserved.
4
4
  */
5
5
  function loadImageRecursive(imageURL, imageURLs) {
@@ -47,7 +47,7 @@ const InitImageSlider = (sliderID, onChange) => {
47
47
  };
48
48
 
49
49
  /*!
50
- * thefittingroom v1.5.2 (2024-09-03T15:43:56.403Z)
50
+ * thefittingroom v2.0.0-alpha-0 (2025-01-17T19:54:05.370Z)
51
51
  * Copyright 2022-present, TheFittingRoom, Inc. All rights reserved.
52
52
  */
53
53
 
@@ -25696,6 +25696,24 @@ class NoColorwaySizeAssetsFoundError extends Error {
25696
25696
  this.name = 'NoColorwaySizeAssetsFoundError';
25697
25697
  }
25698
25698
  }
25699
+ class BrandUserIdNotSetError extends Error {
25700
+ constructor() {
25701
+ super('brand user id not set');
25702
+ this.name = 'BrandUserIdNotSetError';
25703
+ }
25704
+ }
25705
+ class NoFramesFoundError extends Error {
25706
+ constructor() {
25707
+ super('no frames found');
25708
+ this.name = 'NoFramesFoundError';
25709
+ }
25710
+ }
25711
+ class NoStylesFoundError extends Error {
25712
+ constructor() {
25713
+ super('no styles found');
25714
+ this.name = 'NoStylesFoundError';
25715
+ }
25716
+ }
25699
25717
  // Backend responses
25700
25718
  const AvatarNotCreated = 'avatar not created';
25701
25719
 
@@ -25713,12 +25731,15 @@ class FirebaseUser {
25713
25731
  async onInit(brandId) {
25714
25732
  this.auth.onAuthStateChanged((user) => {
25715
25733
  this.setUser(user);
25716
- if (user)
25717
- this.logUserLogin(brandId, user);
25734
+ if (!user)
25735
+ return;
25736
+ this.logUserLogin(brandId, user);
25737
+ this.setBrandUserId(user.uid);
25718
25738
  });
25719
25739
  await this.auth.authStateReady();
25720
25740
  const user = this.auth.currentUser;
25721
25741
  this.setUser(user);
25742
+ this.setBrandUserId(user.uid);
25722
25743
  return Boolean(user);
25723
25744
  }
25724
25745
  setUser(user) {
@@ -25764,6 +25785,12 @@ class FirebaseUser {
25764
25785
  const token = await this.user.getIdToken();
25765
25786
  return token;
25766
25787
  }
25788
+ get userId() {
25789
+ var _a;
25790
+ if (!((_a = this.user) === null || _a === void 0 ? void 0 : _a.uid))
25791
+ throw new UserNotLoggedInError();
25792
+ return this.user.uid;
25793
+ }
25767
25794
  async getUserProfile() {
25768
25795
  var _a;
25769
25796
  if (!((_a = this.user) === null || _a === void 0 ? void 0 : _a.uid))
@@ -25777,6 +25804,18 @@ class FirebaseUser {
25777
25804
  unsub = jl(q, (snapshot) => callback(snapshot.docs[0].data()));
25778
25805
  return () => unsub();
25779
25806
  }
25807
+ watchUserProfileForFrames(predicate) {
25808
+ let unsub;
25809
+ const q = sl(Ta(this.firestore, 'users'), rl(Eh(), '==', this.id));
25810
+ return new Promise((resolve) => {
25811
+ unsub = jl(q, async (snapshot) => {
25812
+ if (!(await predicate(snapshot)))
25813
+ return;
25814
+ unsub();
25815
+ resolve(snapshot.docs[0].data());
25816
+ });
25817
+ });
25818
+ }
25780
25819
  async login(username, password) {
25781
25820
  if (this.auth.currentUser)
25782
25821
  await this.auth.signOut();
@@ -25840,6 +25879,8 @@ const getFirebaseError = (e) => {
25840
25879
  }
25841
25880
  };
25842
25881
 
25882
+ const asyncTry = (promise) => promise.then((data) => [null, data]).catch((error) => [error]);
25883
+
25843
25884
  class Fetcher {
25844
25885
  static get endpoint() {
25845
25886
  const api = Config.getInstance().api;
@@ -25888,6 +25929,15 @@ class Fetcher {
25888
25929
  }
25889
25930
  }
25890
25931
 
25932
+ const testImage = (url) => {
25933
+ const img = new Image();
25934
+ img.src = url;
25935
+ return new Promise((resolve) => {
25936
+ img.onerror = () => resolve(false);
25937
+ img.onload = () => resolve(true);
25938
+ });
25939
+ };
25940
+
25891
25941
  class TfrShop {
25892
25942
  constructor(brandId, firebase) {
25893
25943
  this.brandId = brandId;
@@ -25939,6 +25989,7 @@ class TfrShop {
25939
25989
  return Array.from(assets.values())[0];
25940
25990
  }
25941
25991
  async getMeasurementLocationsFromSku(sku, filledLocations = []) {
25992
+ console.log({ sku });
25942
25993
  const asset = await this.getColorwaySizeAssetFromSku(sku);
25943
25994
  if (!asset)
25944
25995
  throw new Error('No colorway size asset found for sku');
@@ -26028,6 +26079,33 @@ class TfrShop {
26028
26079
  getMeasurementLocationSortOrder(location) {
26029
26080
  return this.measurementLocations.has(location) ? this.measurementLocations.get(location).sort_order : Infinity;
26030
26081
  }
26082
+ async tryOn(styleId, sizeId) {
26083
+ if (!this.isLoggedIn)
26084
+ throw new UserNotLoggedInError();
26085
+ const colorwaySizeAssetSku = await this.getColorwaySizeAssetSkuFromStyleIdAndSizeId(styleId, sizeId);
26086
+ try {
26087
+ const frames = await this.getColorwaySizeAssetFrames(colorwaySizeAssetSku);
26088
+ return frames;
26089
+ }
26090
+ catch (error) {
26091
+ if (!(error instanceof NoFramesFoundError))
26092
+ throw error;
26093
+ return this.requestThenGetColorwaySizeAssetFrames(colorwaySizeAssetSku);
26094
+ }
26095
+ }
26096
+ async getColorwaySizeAssetSkuFromStyleIdAndSizeId(styleId, sizeId) {
26097
+ var _a, _b, _c;
26098
+ try {
26099
+ const constraints = [rl('brand_id', '==', this.brandId)];
26100
+ constraints.push(rl('style_id', '==', styleId));
26101
+ constraints.push(rl('size_id', '==', sizeId));
26102
+ const querySnapshot = await this.firebase.getDocs('colorway_size_assets', constraints);
26103
+ return (_c = (_b = (_a = querySnapshot.docs) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.data()) === null || _c === void 0 ? void 0 : _c.sku;
26104
+ }
26105
+ catch (error) {
26106
+ return getFirebaseError(error);
26107
+ }
26108
+ }
26031
26109
  async getGetTaxonomy(styleId) {
26032
26110
  try {
26033
26111
  const doc = await this.firebase.getDoc('style_garment_categories', String(styleId));
@@ -26052,6 +26130,56 @@ class TfrShop {
26052
26130
  return getFirebaseError(error);
26053
26131
  }
26054
26132
  }
26133
+ async requestThenGetColorwaySizeAssetFrames(colorwaySizeAssetSku) {
26134
+ const [error, colorwaySizeAsset] = await asyncTry(this.getColorwaySizeAssetFromSku(colorwaySizeAssetSku));
26135
+ if (error)
26136
+ throw error;
26137
+ try {
26138
+ await this.requestColorwaySizeAssetFrames(colorwaySizeAsset.id);
26139
+ return this.awaitColorwaySizeAssetFrames(colorwaySizeAssetSku);
26140
+ }
26141
+ catch (error) {
26142
+ if ((error === null || error === void 0 ? void 0 : error.error) === AvatarNotCreated)
26143
+ throw new AvatarNotCreatedError();
26144
+ throw new NoStylesFoundError();
26145
+ }
26146
+ }
26147
+ async awaitColorwaySizeAssetFrames(colorwaySizeAssetSKU) {
26148
+ var _a, _b, _c, _d;
26149
+ if (!this.isLoggedIn)
26150
+ throw new UserNotLoggedInError();
26151
+ const callback = async (data) => {
26152
+ var _a, _b, _c, _d;
26153
+ const frames = (_d = (_c = (_b = (_a = data.docs[0].data()) === null || _a === void 0 ? void 0 : _a.vto) === null || _b === void 0 ? void 0 : _b[this.brandId]) === null || _c === void 0 ? void 0 : _c[colorwaySizeAssetSKU]) === null || _d === void 0 ? void 0 : _d.frames;
26154
+ if (!(frames === null || frames === void 0 ? void 0 : frames.length))
26155
+ return false;
26156
+ return testImage(frames[0]);
26157
+ };
26158
+ const userProfile = (await this.user.watchUserProfileForFrames(callback));
26159
+ if (!((_d = (_c = (_b = (_a = userProfile === null || userProfile === void 0 ? void 0 : userProfile.vto) === null || _a === void 0 ? void 0 : _a[this.brandId]) === null || _b === void 0 ? void 0 : _b[colorwaySizeAssetSKU]) === null || _c === void 0 ? void 0 : _c.frames) === null || _d === void 0 ? void 0 : _d.length))
26160
+ throw new NoFramesFoundError();
26161
+ return userProfile.vto[this.brandId][colorwaySizeAssetSKU].frames;
26162
+ }
26163
+ async requestColorwaySizeAssetFrames(colorwaySizeAssetId) {
26164
+ if (!this.isLoggedIn)
26165
+ throw new UserNotLoggedInError();
26166
+ if (!this.user.brandUserId)
26167
+ throw new BrandUserIdNotSetError();
26168
+ await Fetcher.Post(this.user, `/colorway-size-assets/${colorwaySizeAssetId}/frames`, {
26169
+ brand_user_id: String(this.user.brandUserId),
26170
+ });
26171
+ }
26172
+ async getColorwaySizeAssetFrames(colorwaySizeAssetSKU) {
26173
+ var _a, _b, _c;
26174
+ const userProfile = await this.user.getUserProfile();
26175
+ const frames = ((_c = (_b = (_a = userProfile === null || userProfile === void 0 ? void 0 : userProfile.vto) === null || _a === void 0 ? void 0 : _a[this.brandId]) === null || _b === void 0 ? void 0 : _b[colorwaySizeAssetSKU]) === null || _c === void 0 ? void 0 : _c.frames) || [];
26176
+ if (!frames.length)
26177
+ throw new NoFramesFoundError();
26178
+ const testedImage = await testImage(frames[0]);
26179
+ if (!testedImage)
26180
+ throw new NoFramesFoundError();
26181
+ return frames;
26182
+ }
26055
26183
  }
26056
26184
  const initShop = (brandId, env = 'dev') => {
26057
26185
  if (env === 'dev' || env === 'development')
@@ -27206,10 +27334,6 @@ const TryOnModal = (props) => {
27206
27334
  <img id="tfr-tryon-image" src="" />
27207
27335
  <input type="range" id="tfr-slider" />
27208
27336
  </div>
27209
- <div class="tfr-t-a-center">
27210
- <span id="tfr-back" tfr-element="true" class="tfr-body-font tfr-16-default tfr-c-black-o5 tfr-underline tfr-cursor tfr-mr-20">${L.ReturnToCatalogPage }</span>
27211
- <span id="tfr-close" tfr-element="true" class="tfr-body-font tfr-16-default tfr-c-black-o5 tfr-underline tfr-cursor" id="returnToSite">${L.ReturnToProductPage }</span>
27212
- </div>
27213
27337
  `;
27214
27338
  };
27215
27339
  return {
@@ -27297,11 +27421,13 @@ class TfrModal {
27297
27421
  }
27298
27422
 
27299
27423
  class SizeRecComponent {
27300
- constructor(sizeRecMainDivId, onSignInClick, onSignOutClick, onFitInfoClick) {
27424
+ constructor(sizeRecMainDivId, onSignInClick, onSignOutClick, onFitInfoClick, onTryOnClick) {
27301
27425
  this.onSignInClick = onSignInClick;
27302
27426
  this.onSignOutClick = onSignOutClick;
27303
27427
  this.onFitInfoClick = onFitInfoClick;
27428
+ this.onTryOnClick = onTryOnClick;
27304
27429
  this._sku = '';
27430
+ this._styleId = null;
27305
27431
  this.isLoggedIn = false;
27306
27432
  this.tfrLoggedInElements = [];
27307
27433
  this.tfrLoggedOutElements = [];
@@ -27317,12 +27443,19 @@ class SizeRecComponent {
27317
27443
  setSku(sku) {
27318
27444
  this._sku = sku;
27319
27445
  }
27446
+ get styleId() {
27447
+ return this._styleId;
27448
+ }
27449
+ setStyleId(styleId) {
27450
+ this._styleId = styleId;
27451
+ }
27320
27452
  setIsLoggedIn(isLoggedIn) {
27321
27453
  this.isLoggedIn = isLoggedIn;
27322
27454
  this.tfrSizeRecSelectContainer.style.display = 'flex';
27323
27455
  this.tfrSizeRecSelect.style.display = 'flex';
27324
27456
  this.tfrSizeHowItFits.style.display = 'block';
27325
27457
  if (isLoggedIn) {
27458
+ this.tfrSizeHowItFits.style.opacity = '1';
27326
27459
  this.tfrSizeRecSelect.style.opacity = '1';
27327
27460
  this.tfrLoggedInElements.forEach((element) => (element.style.display = 'block'));
27328
27461
  this.tfrLoggedOutElements.forEach((element) => (element.style.display = 'none'));
@@ -27334,6 +27467,7 @@ class SizeRecComponent {
27334
27467
  this.tfrSizeRecTitleToggle.classList.remove('tfr-chevron-down');
27335
27468
  }
27336
27469
  else {
27470
+ this.tfrSizeHowItFits.style.opacity = '0.4';
27337
27471
  this.tfrSizeRecSelect.style.opacity = '0.4';
27338
27472
  this.tfrLoggedInElements.forEach((element) => (element.style.display = 'none'));
27339
27473
  this.tfrLoggedOutElements.forEach((element) => (element.style.display = 'block'));
@@ -27420,6 +27554,10 @@ class SizeRecComponent {
27420
27554
  allButtons.forEach((button) => button.classList.remove('active'));
27421
27555
  allButtons.item(selectedIndex).classList.add('active');
27422
27556
  this.redraw(selectedIndex);
27557
+ const selectedSizeId = Number(target.getAttribute('data-size-id'));
27558
+ if (Number.isNaN(selectedSizeId))
27559
+ return;
27560
+ this.onTryOnClick(this.styleId, selectedSizeId);
27423
27561
  }
27424
27562
  renderSizeRec(recommended, sizes) {
27425
27563
  this.tfrSizeRecSize.innerHTML = `&nbsp;${recommended}`;
@@ -27427,6 +27565,8 @@ class SizeRecComponent {
27427
27565
  this.redraw = (index) => this.renderSizeRecTable(sizes, index);
27428
27566
  this.redraw(selectedSizeIndex);
27429
27567
  this.renderSizeRecSelect(sizes, selectedSizeIndex);
27568
+ const selectedSizeId = sizes[selectedSizeIndex].size_id;
27569
+ this.onTryOnClick(this.styleId, selectedSizeId);
27430
27570
  }
27431
27571
  renderSizeRecTable(sizes, index) {
27432
27572
  const { locations } = sizes[index];
@@ -27438,7 +27578,7 @@ class SizeRecComponent {
27438
27578
  renderSizeRecSelect(sizes, index) {
27439
27579
  const sizeNames = sizes.map(({ size }) => size);
27440
27580
  const html = sizeNames
27441
- .map((name, i) => `<div class="tfr-size-rec-select-button ${i === index ? 'active' : ''}" data-index="${i}">${name}</div>`)
27581
+ .map((name, i) => `<div class="tfr-size-rec-select-button ${i === index ? 'active' : ''}" data-index="${i}" data-size-id="${sizes[i].size_id}">${name}</div>`)
27442
27582
  .join('');
27443
27583
  this.tfrSizeRecSelect.innerHTML = html;
27444
27584
  }
@@ -27571,14 +27711,15 @@ class SizeRecComponent {
27571
27711
  }
27572
27712
 
27573
27713
  class TfrSizeRec {
27574
- constructor(sizeRecMainDivId, cssVariables, tfrShop, onSignInClick, onSignOutClick, onFitInfoClick) {
27714
+ constructor(sizeRecMainDivId, cssVariables, tfrShop, onSignInClick, onSignOutClick, onFitInfoClick, onTryOnClick) {
27575
27715
  this.tfrShop = tfrShop;
27576
27716
  this.onSignInClick = onSignInClick;
27577
27717
  this.onSignOutClick = onSignOutClick;
27578
27718
  this.onFitInfoClick = onFitInfoClick;
27719
+ this.onTryOnClick = onTryOnClick;
27579
27720
  this.perfectFits = [index.Fit.PERFECT_FIT, index.Fit.SLIGHTLY_LOOSE, index.Fit.SLIGHTLY_TIGHT];
27580
27721
  this.setCssVariables(cssVariables);
27581
- this.sizeRecComponent = new SizeRecComponent(sizeRecMainDivId, this.onSignInClick, this.onSignOutClick, this.onFitInfoClick);
27722
+ this.sizeRecComponent = new SizeRecComponent(sizeRecMainDivId, this.onSignInClick, this.onSignOutClick, this.onFitInfoClick, this.onTryOnClick);
27582
27723
  }
27583
27724
  get sku() {
27584
27725
  return this.sizeRecComponent.sku;
@@ -27586,6 +27727,12 @@ class TfrSizeRec {
27586
27727
  setSku(sku) {
27587
27728
  this.sizeRecComponent.setSku(sku);
27588
27729
  }
27730
+ get styleId() {
27731
+ return this.sizeRecComponent.styleId;
27732
+ }
27733
+ setStyleId(styleId) {
27734
+ this.sizeRecComponent.setStyleId(styleId);
27735
+ }
27589
27736
  setIsLoggedIn(isLoggedIn) {
27590
27737
  this.sizeRecComponent.setIsLoggedIn(isLoggedIn);
27591
27738
  }
@@ -27631,12 +27778,14 @@ class TfrSizeRec {
27631
27778
  try {
27632
27779
  const colorwaySizeAsset = await this.tfrShop.getColorwaySizeAssetFromSku(this.sku);
27633
27780
  const sizes = await this.getRecommendedSizes(String(colorwaySizeAsset.style_id));
27781
+ this.setStyleId(colorwaySizeAsset.style_id);
27634
27782
  return sizes;
27635
27783
  }
27636
27784
  catch (error) {
27637
27785
  try {
27638
27786
  const style = await this.tfrShop.getStyleByBrandStyleId(this.sku);
27639
27787
  const sizes = await this.getRecommendedSizes(String(style.id));
27788
+ this.setStyleId(style.id);
27640
27789
  return sizes;
27641
27790
  }
27642
27791
  catch (error) {
@@ -27655,6 +27804,7 @@ class TfrSizeRec {
27655
27804
  sizes: sizeRec.fits.map((fit) => {
27656
27805
  return {
27657
27806
  size: sizeRec.available_sizes.find((size) => size.id === fit.size_id).label,
27807
+ size_id: fit.size_id,
27658
27808
  locations: fit.measurement_location_fits
27659
27809
  .map((locationFit) => {
27660
27810
  const fitLabel = typeof locationFit.fit_label === 'string' && locationFit.fit_label
@@ -27768,7 +27918,7 @@ class FittingRoom {
27768
27918
  console.log('tfr-env', env);
27769
27919
  this.tfrModal = new TfrModal(modalDivId, this.signIn.bind(this), this.forgotPassword.bind(this), this.submitTel.bind(this));
27770
27920
  this.tfrShop = initShop(Number(this.shopId), env);
27771
- this.tfrSizeRec = new TfrSizeRec(sizeRecMainDivId, cssVariables, this.tfrShop, this.onSignInClick.bind(this), this.signOut.bind(this), this.onFitInfoClick.bind(this));
27921
+ this.tfrSizeRec = new TfrSizeRec(sizeRecMainDivId, cssVariables, this.tfrShop, this.onSignInClick.bind(this), this.signOut.bind(this), this.onFitInfoClick.bind(this), this.onTryOnClick.bind(this));
27772
27922
  }
27773
27923
  get shop() {
27774
27924
  return this.tfrShop;
@@ -27867,6 +28017,10 @@ class FittingRoom {
27867
28017
  onFitInfoClick() {
27868
28018
  this.tfrModal.toFitInfo();
27869
28019
  }
28020
+ async onTryOnClick(styleId, sizeId) {
28021
+ const frames = await this.shop.tryOn(styleId, sizeId);
28022
+ this.tfrModal.onTryOn(frames);
28023
+ }
27870
28024
  onUserProfileChange(userProfile) {
27871
28025
  var _a, _b, _c, _d;
27872
28026
  switch (userProfile.avatar_status) {