Browse Source

feat module 1,2,4 : change to dynamic navigation menu based on shopify collection

master
nadia 1 day ago
parent
commit
049eaf4c94
1 changed files with 219 additions and 24 deletions
  1. 219
    24
      src/config/menuCollections.js

+ 219
- 24
src/config/menuCollections.js View File

1
+export const MENU_GROUPS = {
2
+  casual: {
3
+    label: "Casual",
4
+    productType: "Apparel",
5
+  },
6
+  traditional: {
7
+    label: "Traditional",
8
+    productType: "Apparel",
9
+  },
10
+  essentials: {
11
+    label: "Essentials",
12
+    productType: "BEAUTY",
13
+    productTypes: ["BEAUTY", "HOME"],
14
+  },
15
+};
16
+
1
 export const NAV_MENU_STRUCTURE = [
17
 export const NAV_MENU_STRUCTURE = [
2
   {
18
   {
3
     productType: "Apparel",
19
     productType: "Apparel",
5
     groups: [
21
     groups: [
6
       {
22
       {
7
         label: "Casual",
23
         label: "Casual",
8
-        children: [
9
-          { label: "ND X Marii for Amber", titles: ["DENIM ND X MARII FOR AMBER", "ND X MARII FOR AMBER"] },
10
-          { label: "Somewhere Somehow Someone", titles: ["SOMEWHERE SOMEHOW SOMEONE"] },
11
-          { label: "Flower Power", titles: ["FLOWER POWER"] },
12
-        ],
24
+        menuGroup: "casual",
25
+        // children: [
26
+        //   { label: "ND X Marii for Amber", titles: ["DENIM ND X MARII FOR AMBER", "ND X MARII FOR AMBER"] },
27
+        //   { label: "Somewhere Somehow Someone", titles: ["SOMEWHERE SOMEHOW SOMEONE"] },
28
+        //   { label: "Flower Power", titles: ["FLOWER POWER"] },
29
+        // ],
30
+        children: [],
13
       },
31
       },
14
       {
32
       {
15
         label: "Traditional",
33
         label: "Traditional",
16
-        children: [
17
-          // label is the display name, titles are the collection names that will be matched to the product collections in shopify
18
-          { label: "Eid's Time For Love", titles: ["EID'S TIME FOR LOVE"] },
19
-          { label: "Raya Romantics", titles: ["RAYA ROMANTICS", "RAYA ROMANTICS COLLECTION 2025"] },
20
-          { label: "Atma Sari", titles: ["ATMA SARI"] },
21
-          { label: "Oasis abaya", titles: ["OASIS ABAYA COLLECTION", "MIRAGE COLLECTION"] },
22
-        ],
34
+        menuGroup: "traditional",
35
+        // children: [
36
+        //   { label: "Eid's Time For Love", titles: ["EID'S TIME FOR LOVE"] },
37
+        //   { label: "Raya Romantics", titles: ["RAYA ROMANTICS", "RAYA ROMANTICS COLLECTION 2025"] },
38
+        //   { label: "Atma Sari", titles: ["ATMA SARI"] },
39
+        //   { label: "Oasis abaya", titles: ["OASIS ABAYA COLLECTION", "MIRAGE COLLECTION"] },
40
+        // ],
41
+        children: [],
23
       },
42
       },
24
     ],
43
     ],
25
   },
44
   },
26
   {
45
   {
27
     productType: "BEAUTY",
46
     productType: "BEAUTY",
28
     label: "Essentials",
47
     label: "Essentials",
29
-    children: [
30
-      { label: "Cosmetics", titles: ["COSMETICS"] },
31
-      { label: "Hand & Body Lotion", titles: ["HAND & BODY LOTION"] },
32
-    ],
48
+    menuGroup: "essentials",
49
+    // children: [
50
+    //   { label: "Cosmetics", titles: ["COSMETICS"] },
51
+    //   { label: "Hand & Body Lotion", titles: ["HAND & BODY LOTION"] },
52
+    // ],
53
+    children: [],
33
   },
54
   },
34
 ];
55
 ];
35
 
56
 
36
-export const getMenuCollectionTitlesByProductType = () => {
37
-  return NAV_MENU_STRUCTURE.reduce((allowedCollections, menu) => {
38
-    const titles = [];
57
+// Utility function to normalize product titles
58
+export const normalizeTitle = (value = "") => `${value || ""}`.trim().toUpperCase();
59
+
60
+// Utility function to normalize menu group names
61
+export const normalizeMenuGroup = (value = "") =>
62
+  `${value || ""}`.trim().toLowerCase().replace(/\s+/g, "_");
63
+
64
+// Utility function to check if a substring exists in an array of strings
65
+export const getCollectionMenuGroup = (collection = {}) => {
66
+  return normalizeMenuGroup(
67
+    collection?.menuGroup ||
68
+      collection?.metafield?.value ||
69
+      collection?.metafields?.menu_group?.value ||
70
+      collection?.metafields?.find?.(
71
+        (metafield) =>
72
+          metafield?.namespace === "custom" && metafield?.key === "menu_group"
73
+      )?.value
74
+  );
75
+};
76
+
77
+// Utility function to get the key for a collection, prioritizing handle over title
78
+const getCollectionKey = (collection = {}) =>
79
+  collection?.handle || normalizeTitle(collection?.title || "");
80
+
81
+// Utility function to check if a product type matches the allowed product types in a group configuration
82
+const getProductTypeMatches = (productType, groupConfig = {}) => {
83
+  const targetProductTypes = groupConfig.productTypes || [groupConfig.productType];
84
+
85
+  return targetProductTypes
86
+    .map(normalizeTitle)
87
+    .includes(normalizeTitle(productType));
88
+};
89
+
90
+// Utility function to get menu collections by product type
91
+export const getMenuCollectionsByProductType = (products = []) => {
92
+  return products.reduce((menuCollections, product) => {
93
+    if (!product?.productType || !Array.isArray(product?.collections)) {
94
+      return menuCollections;
95
+    }
96
+
97
+    product.collections.forEach((collection) => {
98
+      const menuGroup = getCollectionMenuGroup(collection);
99
+
100
+      if (!menuGroup) return;
101
+
102
+      const groupConfig = MENU_GROUPS[menuGroup];
103
+
104
+      if (!groupConfig) return;
105
+
106
+      if (!getProductTypeMatches(product.productType, groupConfig)) {
107
+        return;
108
+      }
109
+
110
+      const productType = groupConfig.productType;
111
+      const key = getCollectionKey(collection);
112
+
113
+      if (!menuCollections[productType]) {
114
+        menuCollections[productType] = {};
115
+      }
116
+
117
+      if (!menuCollections[productType][menuGroup]) {
118
+        menuCollections[productType][menuGroup] = new Map();
119
+      }
120
+
121
+      if (!menuCollections[productType][menuGroup].has(key)) {
122
+        menuCollections[productType][menuGroup].set(key, {
123
+          ...collection,
124
+          menuGroup,
125
+        });
126
+      }
127
+    });
128
+
129
+    return menuCollections;
130
+  }, {});
131
+};
132
+
133
+// Utility function to map a collection to a menu item structure
134
+const mapCollectionToMenuItem = (collection = {}) => ({
135
+  label: collection?.title || "",
136
+  titles: collection?.title ? [collection.title] : [],
137
+  handles: collection?.handle ? [collection.handle] : [],
138
+  collection,
139
+});
140
+
141
+// Utility function to build the navigation menu from products
142
+export const buildNavMenuFromProducts = (products = []) => {
143
+  const menuCollections = getMenuCollectionsByProductType(products);
144
+
145
+  return NAV_MENU_STRUCTURE.map((menu) => {
146
+    if (menu.groups) {
147
+      return {
148
+        ...menu,
149
+        groups: menu.groups
150
+          .map((group) => ({
151
+            ...group,
152
+            children: Array.from(
153
+              menuCollections?.[menu.productType]?.[group.menuGroup]?.values?.() || []
154
+            ).map(mapCollectionToMenuItem),
155
+          }))
156
+          .filter((group) => group.children.length > 0),
157
+      };
158
+    }
159
+
160
+    if (menu.menuGroup) {
161
+      return {
162
+        ...menu,
163
+        children: Array.from(
164
+          menuCollections?.[menu.productType]?.[menu.menuGroup]?.values?.() || []
165
+        ).map(mapCollectionToMenuItem),
166
+      };
167
+    }
168
+
169
+    return menu;
170
+  }).filter((menu) => {
171
+    if (menu.groups) return menu.groups.length > 0;
172
+    if (menu.menuGroup) return (menu.children || []).length > 0;
173
+    return true;
174
+  });
175
+};
176
+
177
+// Utility function to get menu collection titles by product type
178
+export const getMenuCollectionTitlesByProductType = (products = []) => {
179
+  const menuSource = products.length > 0 ? buildNavMenuFromProducts(products) : NAV_MENU_STRUCTURE;
180
+
181
+  return menuSource.reduce((allowedCollections, menu) => {
182
+    const addTitles = (productTypes = [], titles = []) => {
183
+      productTypes.filter(Boolean).forEach((productType) => {
184
+        allowedCollections[productType] = [
185
+          ...(allowedCollections[productType] || []),
186
+          ...titles,
187
+        ];
188
+      });
189
+    };
39
 
190
 
40
     if (menu.groups) {
191
     if (menu.groups) {
41
       menu.groups.forEach((group) => {
192
       menu.groups.forEach((group) => {
42
-        group.children.forEach((item) => {
193
+        const titles = [];
194
+
195
+        (group.children || []).forEach((item) => {
43
           titles.push(...(item.titles || []));
196
           titles.push(...(item.titles || []));
44
         });
197
         });
198
+
199
+        const groupConfig = MENU_GROUPS[group.menuGroup];
200
+        addTitles(
201
+          groupConfig?.productTypes || [groupConfig?.productType || menu.productType],
202
+          titles
203
+        );
45
       });
204
       });
46
     }
205
     }
47
 
206
 
48
     if (menu.children) {
207
     if (menu.children) {
208
+      const titles = [];
209
+
49
       menu.children.forEach((item) => {
210
       menu.children.forEach((item) => {
50
         titles.push(...(item.titles || []));
211
         titles.push(...(item.titles || []));
51
       });
212
       });
213
+
214
+      const groupConfig = MENU_GROUPS[menu.menuGroup];
215
+      addTitles(
216
+        groupConfig?.productTypes || [groupConfig?.productType || menu.productType],
217
+        titles
218
+      );
52
     }
219
     }
53
 
220
 
54
-    return {
55
-      ...allowedCollections,
56
-      [menu.productType]: titles,
57
-    };
221
+    return allowedCollections;
58
   }, {});
222
   }, {});
59
 };
223
 };
224
+
225
+// Utility function to get a set of allowed collection titles for a given product type
226
+export const getAllowedCollectionTitleSet = (products = [], productType = "") => {
227
+  const allowedCollections = getMenuCollectionTitlesByProductType(products)[productType] || [];
228
+
229
+  return new Set(allowedCollections.map(normalizeTitle));
230
+};
231
+
232
+// Utility function to get related product types based on a given product type
233
+export const getRelatedProductTypes = (productType = "") => {
234
+  const normalizedProductType = normalizeTitle(productType);
235
+  const relatedProductTypes = Object.values(MENU_GROUPS)
236
+    .filter((group) => normalizeTitle(group.productType) === normalizedProductType)
237
+    .flatMap((group) => group.productTypes || [group.productType])
238
+    .map(normalizeTitle);
239
+
240
+  if (relatedProductTypes.length === 0) return [normalizedProductType];
241
+
242
+  return [...new Set(relatedProductTypes)];
243
+};
244
+
245
+// Utility function to check if a product is in the visible menu based on its collections and product type
246
+export const isProductInVisibleMenu = (product, products = []) => {
247
+  const allowedCollectionSet = getAllowedCollectionTitleSet(products, product?.productType);
248
+
249
+  if (allowedCollectionSet.size === 0) return false;
250
+
251
+  return product?.collections?.some((collection) =>
252
+    allowedCollectionSet.has(normalizeTitle(collection?.title || ""))
253
+  );
254
+};

Loading…
Cancel
Save