acinguiux-preact-components 0.0.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.
Files changed (313) hide show
  1. package/package.json +56 -0
  2. package/src/content/themes/theme-acinguiux-amg/theme-acinguiux-amg.css +23 -0
  3. package/src/content/themes/theme-acinguiux-cafe/theme-acinguiux-cafe.css +47 -0
  4. package/src/content/themes/theme-acinguiux-energy/theme-acinguiux-energy.css +45 -0
  5. package/src/content/themes/theme-acinguiux-livewire/theme-acinguiux-livewire.css +22 -0
  6. package/src/content/themes/theme-acinguiux-livewire-italy/theme-acinguiux-livewire-italy.css +22 -0
  7. package/src/content/themes/theme-acinguiux-recharge/theme-acinguiux-recharge.css +49 -0
  8. package/src/content/themes/theme-allon/theme-allon.css +25 -0
  9. package/src/content/themes/theme-atlas/theme-atlas.css +31 -0
  10. package/src/content/themes/theme-aurvana/resources/favicon/apple-touch-icon.png +0 -0
  11. package/src/content/themes/theme-aurvana/resources/favicon/favico.ico +0 -0
  12. package/src/content/themes/theme-aurvana/resources/favicon/favicon-96x96.png +0 -0
  13. package/src/content/themes/theme-aurvana/resources/favicon/favicon.ico +0 -0
  14. package/src/content/themes/theme-aurvana/resources/favicon/favicon.png +0 -0
  15. package/src/content/themes/theme-aurvana/resources/favicon/favicon.svg +13 -0
  16. package/src/content/themes/theme-aurvana/resources/favicon/google-touch-icon.png +0 -0
  17. package/src/content/themes/theme-aurvana/resources/favicon/manifest.json +14 -0
  18. package/src/content/themes/theme-aurvana/resources/favicon/site.webmanifest +21 -0
  19. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-192x192.png +0 -0
  20. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-512x512.png +0 -0
  21. package/src/content/themes/theme-aurvana/theme-aurvana.css +49 -0
  22. package/src/content/themes/theme-base/theme-base.css +49 -0
  23. package/src/content/themes/theme-base2/resources/favicon/android-chrome-192x192.png +0 -0
  24. package/src/content/themes/theme-base2/resources/favicon/android-chrome-512x512.png +0 -0
  25. package/src/content/themes/theme-base2/resources/favicon/apple-touch-icon.png +0 -0
  26. package/src/content/themes/theme-base2/resources/favicon/favico.ico +0 -0
  27. package/src/content/themes/theme-base2/resources/favicon/favicon-16x16.png +0 -0
  28. package/src/content/themes/theme-base2/resources/favicon/favicon-32x32.png +0 -0
  29. package/src/content/themes/theme-base2/resources/favicon/favicon-96x96.png +0 -0
  30. package/src/content/themes/theme-base2/resources/favicon/favicon.ico +0 -0
  31. package/src/content/themes/theme-base2/resources/favicon/favicon.png +0 -0
  32. package/src/content/themes/theme-base2/resources/favicon/favicon.svg +9 -0
  33. package/src/content/themes/theme-base2/resources/favicon/google-touch-icon.png +0 -0
  34. package/src/content/themes/theme-base2/resources/favicon/manifest.json +14 -0
  35. package/src/content/themes/theme-base2/resources/favicon/site.webmanifest +1 -0
  36. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-192x192.png +0 -0
  37. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-512x512.png +0 -0
  38. package/src/content/themes/theme-base2/resources/fonts/acinguiux-typeface-la-heavy-221208.woff2 +0 -0
  39. package/src/content/themes/theme-base2/theme-base2.css +47 -0
  40. package/src/content/themes/theme-eco-marathon/theme-eco-marathon.css +22 -0
  41. package/src/content/themes/theme-energy-transition-campus-amsterdam/theme-energy-transition-campus-amsterdam.css +26 -0
  42. package/src/content/themes/theme-evpass/theme-evpass.css +46 -0
  43. package/src/content/themes/theme-nam-2025/resources/favicon/apple-touch-icon.png +0 -0
  44. package/src/content/themes/theme-nam-2025/resources/favicon/favico.ico +0 -0
  45. package/src/content/themes/theme-nam-2025/resources/favicon/favicon-96x96.png +0 -0
  46. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.ico +0 -0
  47. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.png +0 -0
  48. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.svg +9 -0
  49. package/src/content/themes/theme-nam-2025/resources/favicon/google-touch-icon.png +0 -0
  50. package/src/content/themes/theme-nam-2025/resources/favicon/manifest.json +14 -0
  51. package/src/content/themes/theme-nam-2025/resources/favicon/site.webmanifest +21 -0
  52. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-192x192.png +0 -0
  53. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-512x512.png +0 -0
  54. package/src/content/themes/theme-nam-2025/theme-nam-2025.css +47 -0
  55. package/src/content/themes/theme-pennzoil/theme-pennzoil.css +36 -0
  56. package/src/content/themes/theme-quaker-state/theme-quaker-state.css +63 -0
  57. package/src/content/themes/theme-tafawoq/theme-tafawoq.css +26 -0
  58. package/src/content/themes/theme-vegetable/resources/favicon/apple-touch-icon.png +0 -0
  59. package/src/content/themes/theme-vegetable/resources/favicon/favico.ico +0 -0
  60. package/src/content/themes/theme-vegetable/resources/favicon/favicon-96x96.png +0 -0
  61. package/src/content/themes/theme-vegetable/resources/favicon/favicon.ico +0 -0
  62. package/src/content/themes/theme-vegetable/resources/favicon/favicon.png +0 -0
  63. package/src/content/themes/theme-vegetable/resources/favicon/favicon.svg +13 -0
  64. package/src/content/themes/theme-vegetable/resources/favicon/google-touch-icon.png +0 -0
  65. package/src/content/themes/theme-vegetable/resources/favicon/manifest.json +14 -0
  66. package/src/content/themes/theme-vegetable/resources/favicon/site.webmanifest +21 -0
  67. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-192x192.png +0 -0
  68. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-512x512.png +0 -0
  69. package/src/content/themes/theme-vegetable/theme-vegetable.css +49 -0
  70. package/src/content/themes/theme-zeolyst/resources/fonts/type-ar-medium.woff2 +0 -0
  71. package/src/content/themes/theme-zeolyst/theme-zeolyst.css +29 -0
  72. package/src/main/atoms/audio.js +16 -0
  73. package/src/main/atoms/box.js +5 -0
  74. package/src/main/atoms/button.js +40 -0
  75. package/src/main/atoms/card.js +22 -0
  76. package/src/main/atoms/form.js +30 -0
  77. package/src/main/atoms/heading.js +17 -0
  78. package/src/main/atoms/icon.js +24 -0
  79. package/src/main/atoms/img.js +131 -0
  80. package/src/main/atoms/input.js +55 -0
  81. package/src/main/atoms/link-text.js +21 -0
  82. package/src/main/atoms/link.js +60 -0
  83. package/src/main/atoms/list.js +12 -0
  84. package/src/main/atoms/logo.js +9 -0
  85. package/src/main/atoms/menu.js +10 -0
  86. package/src/main/atoms/message.js +5 -0
  87. package/src/main/atoms/nav-link.js +49 -0
  88. package/src/main/atoms/popup.js +47 -0
  89. package/src/main/atoms/rich-text.js +128 -0
  90. package/src/main/atoms/scroller.js +224 -0
  91. package/src/main/atoms/svg.js +65 -0
  92. package/src/main/atoms/table.js +32 -0
  93. package/src/main/atoms/textarea.js +10 -0
  94. package/src/main/atoms/time.js +12 -0
  95. package/src/main/atoms/video.js +100 -0
  96. package/src/main/export-main.js +12 -0
  97. package/src/main/export-matter.js +86 -0
  98. package/src/main/export-preact-hooks.js +1 -0
  99. package/src/main/export-preact.js +1 -0
  100. package/src/main/index.js +13 -0
  101. package/src/main/molecules/asset.js +23 -0
  102. package/src/main/molecules/glossary.js +44 -0
  103. package/src/main/molecules/links.js +23 -0
  104. package/src/main/molecules/promo-text.js +27 -0
  105. package/src/main/molecules/tags.js +15 -0
  106. package/src/main/molecules/tree.js +51 -0
  107. package/src/main/organisms/accordion-item.js +106 -0
  108. package/src/main/organisms/author.js +29 -0
  109. package/src/main/organisms/breadcrumb.js +69 -0
  110. package/src/main/organisms/call-to-action.js +24 -0
  111. package/src/main/organisms/carousel.js +178 -0
  112. package/src/main/organisms/cart-item.js +156 -0
  113. package/src/main/organisms/cart.js +162 -0
  114. package/src/main/organisms/contact-form.js +141 -0
  115. package/src/main/organisms/container/ab-test.js +47 -0
  116. package/src/main/organisms/container/default.js +6 -0
  117. package/src/main/organisms/container/filtered-section.js +293 -0
  118. package/src/main/organisms/container/footer.js +12 -0
  119. package/src/main/organisms/container/grid.js +44 -0
  120. package/src/main/organisms/container/header.js +13 -0
  121. package/src/main/organisms/container/list.js +7 -0
  122. package/src/main/organisms/container/main.js +6 -0
  123. package/src/main/organisms/container/raw.js +7 -0
  124. package/src/main/organisms/container/section.js +28 -0
  125. package/src/main/organisms/container.js +29 -0
  126. package/src/main/organisms/content-owner.js +15 -0
  127. package/src/main/organisms/date-entry.js +56 -0
  128. package/src/main/organisms/external-search.js +73 -0
  129. package/src/main/organisms/filtered-item.js +163 -0
  130. package/src/main/organisms/footer-item.js +17 -0
  131. package/src/main/organisms/image-gallery.js +164 -0
  132. package/src/main/organisms/last-modified.js +20 -0
  133. package/src/main/organisms/legal-footer.js +16 -0
  134. package/src/main/organisms/list-item.js +48 -0
  135. package/src/main/organisms/metadata.js +11 -0
  136. package/src/main/organisms/navigation.js +232 -0
  137. package/src/main/organisms/notification.js +87 -0
  138. package/src/main/organisms/order-tracker.js +203 -0
  139. package/src/main/organisms/page-header-banner.js +26 -0
  140. package/src/main/organisms/page-header.js +33 -0
  141. package/src/main/organisms/page-tags.js +14 -0
  142. package/src/main/organisms/page.js +260 -0
  143. package/src/main/organisms/press-release.js +24 -0
  144. package/src/main/organisms/product-admin.js +204 -0
  145. package/src/main/organisms/promo-banner.js +28 -0
  146. package/src/main/organisms/promo-bottom.js +23 -0
  147. package/src/main/organisms/promo-button.js +8 -0
  148. package/src/main/organisms/promo-card-cover.js +35 -0
  149. package/src/main/organisms/promo-card.js +33 -0
  150. package/src/main/organisms/promo-full.js +20 -0
  151. package/src/main/organisms/promo-image.js +22 -0
  152. package/src/main/organisms/promo-lure.js +22 -0
  153. package/src/main/organisms/promo-product-card.js +187 -0
  154. package/src/main/organisms/promo-product-full.js +293 -0
  155. package/src/main/organisms/promo-simple.js +23 -0
  156. package/src/main/organisms/quote.js +21 -0
  157. package/src/main/organisms/search-form.js +42 -0
  158. package/src/main/organisms/search-nav.js +66 -0
  159. package/src/main/organisms/search-result.js +53 -0
  160. package/src/main/organisms/slider.js +26 -0
  161. package/src/main/organisms/standalone-asset.js +22 -0
  162. package/src/main/organisms/tabs.js +277 -0
  163. package/src/main/organisms/topbar.js +83 -0
  164. package/src/main/organisms/web-component.js +53 -0
  165. package/src/main/routing/annotation.js +9 -0
  166. package/src/main/routing/component.js +138 -0
  167. package/src/main/routing/empty.js +5 -0
  168. package/src/main/routing/error-handler.js +64 -0
  169. package/src/main/routing/placeholder-image.svg +5 -0
  170. package/src/main/routing/router.js +219 -0
  171. package/src/main/shared/analytics.js +677 -0
  172. package/src/main/shared/bubble-event.js +11 -0
  173. package/src/main/shared/custom-element.js +21 -0
  174. package/src/main/shared/deep-selector.js +28 -0
  175. package/src/main/shared/disable-transparency.js +10 -0
  176. package/src/main/shared/format-time.js +8 -0
  177. package/src/main/shared/get-id.js +5 -0
  178. package/src/main/shared/get-meta.js +3 -0
  179. package/src/main/shared/get-size-class.js +3 -0
  180. package/src/main/shared/get-size.js +11 -0
  181. package/src/main/shared/h.js +88 -0
  182. package/src/main/shared/hash-jump.js +33 -0
  183. package/src/main/shared/icons/arrow-back.svg +1 -0
  184. package/src/main/shared/icons/arrow-down.svg +1 -0
  185. package/src/main/shared/icons/arrow-next.svg +1 -0
  186. package/src/main/shared/icons/arrow-tail-right.svg +1 -0
  187. package/src/main/shared/icons/arrow-tail-up.svg +1 -0
  188. package/src/main/shared/icons/arrow-up.svg +1 -0
  189. package/src/main/shared/icons/asset-download.svg +1 -0
  190. package/src/main/shared/icons/logo.svg +5 -0
  191. package/src/main/shared/icons/low-carbon-placeholder.svg +9 -0
  192. package/src/main/shared/icons/media-pause.svg +1 -0
  193. package/src/main/shared/icons/media-play.svg +1 -0
  194. package/src/main/shared/icons/navigation-burger.svg +1 -0
  195. package/src/main/shared/icons/navigation-close.svg +1 -0
  196. package/src/main/shared/icons/navigation-link.svg +1 -0
  197. package/src/main/shared/icons/navigation-refresh.svg +1 -0
  198. package/src/main/shared/icons/navigation-search.svg +1 -0
  199. package/src/main/shared/icons/navigation-share.svg +1 -0
  200. package/src/main/shared/icons/toggle-newwindow.svg +1 -0
  201. package/src/main/shared/icons.js +18 -0
  202. package/src/main/shared/id-from-string.js +5 -0
  203. package/src/main/shared/mark-selection.js +19 -0
  204. package/src/main/shared/register.js +26 -0
  205. package/src/main/shared/renderer.js +43 -0
  206. package/src/main/shared/simple-consent-api.js +70 -0
  207. package/src/main/shared/split-links.js +11 -0
  208. package/src/main/shared/t.js +60 -0
  209. package/src/main/shared/twind.js +837 -0
  210. package/src/main/shared/update-head.js +34 -0
  211. package/src/main/shared/update-scrollbar-width.js +30 -0
  212. package/src/main/shared/use-link.js +151 -0
  213. package/src/main/shared/use-persistent-state.js +42 -0
  214. package/src/main/shared/wait-for-dom-ready.js +6 -0
  215. package/src/main/shared/wcm-mode.js +4 -0
  216. package/src/wcs/components/acinguiux-preact-doc/acinguiux-preact-doc.js +207 -0
  217. package/src/wcs/components/admin-dashboard/admin-dashboard.js +487 -0
  218. package/src/wcs/components/admin-login/admin-login.js +91 -0
  219. package/src/wcs/components/bazaar-voice/bazaar-voice.js +56 -0
  220. package/src/wcs/components/chatbot-koreai/chatbot-koreai.js +176 -0
  221. package/src/wcs/components/chatbot-koreai/koreai-transport.js +217 -0
  222. package/src/wcs/components/chatbot-ms/chatbot-ms.js +210 -0
  223. package/src/wcs/components/chatbot-test/chatbot-test.js +44 -0
  224. package/src/wcs/components/comparison-chart/comparison-chart.js +111 -0
  225. package/src/wcs/components/consent-banner/consent-banner.js +248 -0
  226. package/src/wcs/components/consent-banner/icons/ccpa.svg +6 -0
  227. package/src/wcs/components/consent-banner/icons/info.svg +1 -0
  228. package/src/wcs/components/consent-banner/provider-onetrust.js +131 -0
  229. package/src/wcs/components/decision-tree/arrow-back.svg +3 -0
  230. package/src/wcs/components/decision-tree/badges.js +37 -0
  231. package/src/wcs/components/decision-tree/decision-tree.js +162 -0
  232. package/src/wcs/components/dynamic-contact-details/dynamic-contact-details.js +111 -0
  233. package/src/wcs/components/example-accordion/example-accordion.js +10 -0
  234. package/src/wcs/components/example-asset/example-asset.js +12 -0
  235. package/src/wcs/components/example-form/example-form.js +59 -0
  236. package/src/wcs/components/example-nested/example-nested.js +10 -0
  237. package/src/wcs/components/example-routing/example-routing.js +51 -0
  238. package/src/wcs/components/example-rtl/example-rtl.js +28 -0
  239. package/src/wcs/components/example-tabs/example-tabs.js +12 -0
  240. package/src/wcs/components/example-web-component/example-web-component.js +34 -0
  241. package/src/wcs/components/floating-button/floating-button.js +17 -0
  242. package/src/wcs/components/formstack-form/fields/address.js +38 -0
  243. package/src/wcs/components/formstack-form/fields/checkbox.js +42 -0
  244. package/src/wcs/components/formstack-form/fields/date.js +22 -0
  245. package/src/wcs/components/formstack-form/fields/description.js +8 -0
  246. package/src/wcs/components/formstack-form/fields/input.js +8 -0
  247. package/src/wcs/components/formstack-form/fields/name.js +39 -0
  248. package/src/wcs/components/formstack-form/fields/radio.js +24 -0
  249. package/src/wcs/components/formstack-form/fields/rating.js +53 -0
  250. package/src/wcs/components/formstack-form/fields/section.js +8 -0
  251. package/src/wcs/components/formstack-form/fields/select.js +10 -0
  252. package/src/wcs/components/formstack-form/fields/textarea.js +8 -0
  253. package/src/wcs/components/formstack-form/fields/wrapper.js +11 -0
  254. package/src/wcs/components/formstack-form/formstack-form.js +280 -0
  255. package/src/wcs/components/fuel-prices/fuel-prices.js +45 -0
  256. package/src/wcs/components/furniture-overview/furniture-overview.js +115 -0
  257. package/src/wcs/components/gauge-value/gauge-value.js +65 -0
  258. package/src/wcs/components/help-centre/api.js +150 -0
  259. package/src/wcs/components/help-centre/help-centre.js +400 -0
  260. package/src/wcs/components/help-centre/icon-search.svg +1 -0
  261. package/src/wcs/components/image-gen/admin-panel.js +248 -0
  262. package/src/wcs/components/image-gen/image-gen.js +385 -0
  263. package/src/wcs/components/image-gen/labels.js +37 -0
  264. package/src/wcs/components/image-gen/use-api.js +392 -0
  265. package/src/wcs/components/inspired-gallery/inspired-gallery.js +118 -0
  266. package/src/wcs/components/launch-container/launch-container.js +95 -0
  267. package/src/wcs/components/launch-container/ledger.js +140 -0
  268. package/src/wcs/components/lng-map/lng-map.js +44 -0
  269. package/src/wcs/components/mouseflow-analytics/mouseflow-analytics.js +39 -0
  270. package/src/wcs/components/msds-search/msds-search.js +127 -0
  271. package/src/wcs/components/msds-search/navigation-search.svg +3 -0
  272. package/src/wcs/components/product-catalogue/icon-back.svg +3 -0
  273. package/src/wcs/components/product-catalogue/icon-cart.svg +3 -0
  274. package/src/wcs/components/product-catalogue/icon-close.svg +3 -0
  275. package/src/wcs/components/product-catalogue/product-catalogue.js +215 -0
  276. package/src/wcs/components/product-links/icon-cart.svg +3 -0
  277. package/src/wcs/components/product-links/product-links.js +43 -0
  278. package/src/wcs/components/rio-iframe/rio-iframe.js +137 -0
  279. package/src/wcs/components/salsify-products/filter-tools.js +60 -0
  280. package/src/wcs/components/salsify-products/icon-cart.svg +3 -0
  281. package/src/wcs/components/salsify-products/process-products.js +53 -0
  282. package/src/wcs/components/salsify-products/route-tools.js +54 -0
  283. package/src/wcs/components/salsify-products/salsify-products.js +281 -0
  284. package/src/wcs/components/shout-out/shout-out.js +51 -0
  285. package/src/wcs/components/simple-chart/simple-chart.js +53 -0
  286. package/src/wcs/components/single-stat/single-stat.js +85 -0
  287. package/src/wcs/components/site-feedback/site-feedback.js +56 -0
  288. package/src/wcs/components/skds-search/navigation-search.svg +3 -0
  289. package/src/wcs/components/skds-search/skds-search.js +103 -0
  290. package/src/wcs/components/smart-banner/smart-banner.js +104 -0
  291. package/src/wcs/components/standalone-table/arrow-up-down.svg +3 -0
  292. package/src/wcs/components/standalone-table/arrow-up.svg +3 -0
  293. package/src/wcs/components/standalone-table/standalone-table.js +440 -0
  294. package/src/wcs/components/station-locator/station-locator.js +49 -0
  295. package/src/wcs/components/store-badges/badges.js +60 -0
  296. package/src/wcs/components/store-badges/store-badges.js +93 -0
  297. package/src/wcs/components/topbar-button/person.svg +1 -0
  298. package/src/wcs/components/topbar-button/topbar-button.js +22 -0
  299. package/src/wcs/components/universal-gallery/universal-gallery.js +308 -0
  300. package/src/wcs/components/zendesk-chat/zendesk-chat.js +133 -0
  301. package/src/wcs/shared/chat-bot/README.md +61 -0
  302. package/src/wcs/shared/chat-bot/chat-bot.js +216 -0
  303. package/src/wcs/shared/chat-bot/resources/arrow-next.svg +1 -0
  304. package/src/wcs/shared/chat-bot/resources/navigation-close.svg +1 -0
  305. package/src/wcs/shared/chat-bot/resources/person.svg +1 -0
  306. package/src/wcs/shared/chat-bot/resources/upload.svg +1 -0
  307. package/src/wcs/shared/filtered-data/README.md +52 -0
  308. package/src/wcs/shared/filtered-data/fetch-data.js +33 -0
  309. package/src/wcs/shared/filtered-data/filtered-data.js +337 -0
  310. package/src/wcs/shared/promo-with-popup/icon-close.svg +3 -0
  311. package/src/wcs/shared/promo-with-popup/icon-next.svg +3 -0
  312. package/src/wcs/shared/promo-with-popup/icon-prev.svg +3 -0
  313. package/src/wcs/shared/promo-with-popup/promo-with-popup.js +93 -0
@@ -0,0 +1,487 @@
1
+ const { h, preactHooks, matter } = globalThis.ami
2
+ const { useState, useEffect } = preactHooks
3
+ const { Popup } = matter
4
+
5
+ const SUBCATEGORIES = {
6
+ Sanitaryware: [
7
+ 'Showers',
8
+ 'Mixer & Diverters',
9
+ 'Flushing System',
10
+ 'Faucets',
11
+ 'Wash Basin',
12
+ 'Sinks',
13
+ 'Mirror',
14
+ 'Bath Tub',
15
+ 'Kamod',
16
+ 'Toilet Paper Holder',
17
+ 'Towel Stand',
18
+ 'Shelf & Corner',
19
+ 'Hooks',
20
+ 'Soap Dispenser',
21
+ 'Water Heaters',
22
+ 'Washing Machine'
23
+ ],
24
+ Hardware: [
25
+ 'Tandem Box',
26
+ 'Hinges',
27
+ 'Drawer Channel (Normal & Softclose)',
28
+ 'Sliding Channel',
29
+ 'Handles',
30
+ 'Locks (Digital Locks & Normal Locks)',
31
+ 'Lockers',
32
+ 'Doorclosers',
33
+ 'Doorprofiles',
34
+ 'Liftup system',
35
+ 'Kitchen corners',
36
+ 'Pantery Unit',
37
+ 'Wardrobe',
38
+ 'Door chain',
39
+ 'Door Stoper',
40
+ 'Door Eyes'
41
+ ],
42
+ 'Kitchen Appliances': [
43
+ 'Hob & Induction Hob',
44
+ 'Chimney',
45
+ 'Oven Microwave',
46
+ 'Refrigerator',
47
+ 'Deep Fryer',
48
+ 'Barbeque Grill',
49
+ 'Toaster',
50
+ 'Electric Kettle',
51
+ 'Dishwasher',
52
+ 'Juicer Mixer Grinder',
53
+ 'Coffee Maker',
54
+ 'Warmer Drawer',
55
+ 'Cooking Range',
56
+ 'Cold Pressed Juicer',
57
+ 'Food Waste Disposer'
58
+ ],
59
+ Plywood: [
60
+ 'Commercial',
61
+ 'Waterproof',
62
+ 'Marine',
63
+ 'Fire Retardant',
64
+ 'Flexible',
65
+ 'MDF',
66
+ 'Particle Board',
67
+ 'Laminates',
68
+ 'Veneers',
69
+ 'Adhesives',
70
+ ]
71
+ }
72
+
73
+ function AdminDashboard () {
74
+ const [products, setProducts] = useState([])
75
+ const [filteredProducts, setFilteredProducts] = useState([])
76
+ const [loading, setLoading] = useState(true)
77
+ const [showModal, setShowModal] = useState(false)
78
+ const [editingProduct, setEditingProduct] = useState(null)
79
+ const [searchTerm, setSearchTerm] = useState('')
80
+ const [filterBrand, setFilterBrand] = useState('')
81
+ const [filterMainCategory, setFilterMainCategory] = useState('')
82
+ const [filterSubcategory, setFilterSubcategory] = useState('')
83
+
84
+ // Check authentication
85
+ useEffect(() => {
86
+ const token = localStorage.getItem('adminToken')
87
+ if (!token) {
88
+ globalThis.location.href = '/admin/login'
89
+ return
90
+ }
91
+ loadProducts()
92
+ }, [])
93
+
94
+ // Filter products
95
+ useEffect(() => {
96
+ let filtered = products
97
+ if (searchTerm) {
98
+ filtered = filtered.filter(p =>
99
+ p.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
100
+ p.brand?.toLowerCase().includes(searchTerm.toLowerCase())
101
+ )
102
+ }
103
+ if (filterMainCategory) {
104
+ filtered = filtered.filter(p => p.mainCategory === filterMainCategory)
105
+ }
106
+ if (filterSubcategory) {
107
+ filtered = filtered.filter(p => p.subcategory === filterSubcategory)
108
+ }
109
+ if (filterBrand) {
110
+ filtered = filtered.filter(p => p.brand === filterBrand)
111
+ }
112
+ setFilteredProducts(filtered)
113
+ }, [products, searchTerm, filterBrand, filterMainCategory, filterSubcategory])
114
+
115
+ const loadProducts = async () => {
116
+ try {
117
+ // First try to fetch from API (Vercel KV or updated source)
118
+ let data = []
119
+ try {
120
+ const apiResponse = await fetch('/api/admin/products')
121
+ if (apiResponse.ok) {
122
+ data = await apiResponse.json()
123
+ }
124
+ } catch (error) {
125
+ // Ignore API fetch error, fallback to static file
126
+ }
127
+
128
+ // If no data from API, load from static JSON file (default/initial data)
129
+ if (!data || data.length === 0) {
130
+ const response = await fetch('/assets/products.json')
131
+ data = await response.json()
132
+ }
133
+
134
+ setProducts(data)
135
+ setFilteredProducts(data)
136
+ } catch (error) {
137
+ console.error('Failed to load products:', error)
138
+ } finally {
139
+ setLoading(false)
140
+ }
141
+ }
142
+
143
+ const handleLogout = async () => {
144
+ localStorage.removeItem('adminToken')
145
+ localStorage.removeItem('adminUser')
146
+
147
+ try {
148
+ // Optional: Call logout API to clear server-side session if exists
149
+ await fetch('/api/admin/logout', {
150
+ method: 'POST',
151
+ headers: { Authorization: `Bearer ${localStorage.getItem('adminToken')}` }
152
+ })
153
+ } catch (error) {
154
+ // Ignore errors on logout
155
+ }
156
+
157
+ globalThis.location.href = '/admin/login'
158
+ }
159
+
160
+ const handleAddProduct = () => {
161
+ setEditingProduct({
162
+ title: '',
163
+ text: '',
164
+ subcategory: '',
165
+ brand: '',
166
+ mainCategory: '',
167
+ image: { src: '', alt: '', width: 295, height: 295 },
168
+ links: [{ name: 'View product', value: '/all_products.html' }]
169
+ })
170
+ setShowModal(true)
171
+ }
172
+
173
+ const handleEditProduct = product => {
174
+ setEditingProduct({ ...product })
175
+ setShowModal(true)
176
+ }
177
+
178
+ const handleDeleteProduct = async index => {
179
+ if (!confirm('Are you sure you want to delete this product?')) { return }
180
+
181
+ const newProducts = products.filter((_, i) => i !== index)
182
+ await saveProducts(newProducts)
183
+ }
184
+
185
+ const handleSaveProduct = async e => {
186
+ e.preventDefault()
187
+ let newProducts
188
+
189
+ if (editingProduct.index === undefined) {
190
+ // Add new
191
+ newProducts = [...products, editingProduct]
192
+ } else {
193
+ // Edit existing
194
+ newProducts = [...products]
195
+ newProducts[editingProduct.index] = editingProduct
196
+ }
197
+
198
+ await saveProducts(newProducts)
199
+ setShowModal(false)
200
+ setEditingProduct(null)
201
+ }
202
+
203
+ const saveProducts = async newProducts => {
204
+ try {
205
+ const response = await fetch('/api/admin/products', {
206
+ method: 'POST',
207
+ headers: {
208
+ 'Content-Type': 'application/json',
209
+ Authorization: `Bearer ${localStorage.getItem('adminToken')}`
210
+ },
211
+ body: JSON.stringify({ products: newProducts })
212
+ })
213
+
214
+ if (response.ok) {
215
+ setProducts(newProducts)
216
+ alert('Products updated successfully!')
217
+ setShowModal(false)
218
+ } else {
219
+ alert('Failed to save products')
220
+ }
221
+ } catch (error) {
222
+ console.error('Save failed:', error)
223
+ alert('Failed to save products')
224
+ }
225
+ }
226
+
227
+ const brands = [...new Set(products.map(p => p.brand).filter(Boolean))].sort()
228
+ const mainCategories = [...new Set(products.map(p => p.mainCategory).filter(Boolean))].sort()
229
+ const subcategories = filterMainCategory ? (SUBCATEGORIES[filterMainCategory] || []) : []
230
+
231
+ if (loading) {
232
+ return h('div', { className: 'flex items-center justify-center min-h-screen' },
233
+ h('div', { className: 'text-xl' }, 'Loading...')
234
+ )
235
+ }
236
+
237
+ return h('div', { className: 'min-h-screen bg-gray-50' },
238
+ // Header
239
+ h('div', { className: 'bg-white shadow' },
240
+ h('div', { className: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4' },
241
+ h('div', { className: 'flex justify-between items-center' },
242
+ h('div', {},
243
+ h('h1', { className: 'text-2xl font-bold text-gray-900' }, 'Product Management'),
244
+ h('p', { className: 'text-sm text-gray-600' }, `Total Products: ${products.length}`)
245
+ ),
246
+ h('button', {
247
+ onClick: handleLogout,
248
+ className: 'px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors'
249
+ }, 'Logout')
250
+ )
251
+ )
252
+ ),
253
+
254
+ // Main Content
255
+ h('div', { className: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8' },
256
+ // Filters and Add Button
257
+ h('div', { className: 'bg-white rounded-lg shadow p-6 mb-6' },
258
+ h('div', { className: 'grid grid-cols-1 md:grid-cols-6 gap-4 mb-4' },
259
+ h('input', {
260
+ type: 'text',
261
+ placeholder: 'Search products...',
262
+ className: 'px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500',
263
+ value: searchTerm,
264
+ onInput: e => setSearchTerm(e.target.value)
265
+ }),
266
+ h('select', {
267
+ className: 'px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500',
268
+ value: filterMainCategory,
269
+ onChange: e => {
270
+ setFilterMainCategory(e.target.value)
271
+ setFilterSubcategory('')
272
+ }
273
+ },
274
+ h('option', { value: '' }, 'All Main Categories'),
275
+ mainCategories.map(mainCat => h('option', { value: mainCat, key: mainCat }, mainCat))
276
+ ),
277
+ h('select', {
278
+ className: 'px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500',
279
+ value: filterSubcategory,
280
+ onChange: e => setFilterSubcategory(e.target.value),
281
+ disabled: !filterMainCategory
282
+ },
283
+ h('option', { value: '' }, 'All Subcategories'),
284
+ subcategories.map(subcat => h('option', { value: subcat, key: subcat }, subcat))
285
+ ),
286
+ h('select', {
287
+ className: 'px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500',
288
+ value: filterBrand,
289
+ onChange: e => setFilterBrand(e.target.value)
290
+ },
291
+ h('option', { value: '' }, 'All Brands'),
292
+ brands.map(brand => h('option', { value: brand, key: brand }, brand))
293
+ ),
294
+ h('button', {
295
+ onClick: handleAddProduct,
296
+ className: 'px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium'
297
+ }, '+ Add Product')
298
+ ),
299
+ h('p', { className: 'text-sm text-gray-600' }, `Showing ${filteredProducts.length} products`)
300
+ ),
301
+
302
+ // Products Table
303
+ h('div', { className: 'bg-white rounded-lg shadow overflow-hidden' },
304
+ h('div', { className: 'overflow-x-auto' },
305
+ h('table', { className: 'min-w-full divide-y divide-gray-200' },
306
+ h('thead', { className: 'bg-gray-50' },
307
+ h('tr', {},
308
+ h('th', { className: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider' }, 'Image'),
309
+ h('th', { className: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider' }, 'Title'),
310
+ h('th', { className: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider' }, 'Brand'),
311
+ h('th', { className: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider' }, 'Category'),
312
+ h('th', { className: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider' }, 'Description'),
313
+ h('th', { className: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider' }, 'Actions')
314
+ )
315
+ ),
316
+ h('tbody', { className: 'bg-white divide-y divide-gray-200' },
317
+ filteredProducts.map((product, index) =>
318
+ h('tr', { key: index, className: 'hover:bg-gray-50' },
319
+ h('td', { className: 'px-6 py-4 whitespace-nowrap' },
320
+ h('img', { src: product.image.src, alt: product.image.alt, className: 'h-12 w-12 object-cover rounded' })
321
+ ),
322
+ h('td', { className: 'px-6 py-4' },
323
+ h('div', { className: 'text-sm font-medium text-gray-900' }, product.title)
324
+ ),
325
+ h('td', { className: 'px-6 py-4 whitespace-nowrap' },
326
+ h('span', { className: 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800' }, product.brand || 'N/A')
327
+ ),
328
+ h('td', { className: 'px-6 py-4 whitespace-nowrap text-sm text-gray-500' }, product.subcategory),
329
+ h('td', { className: 'px-6 py-4 whitespace-nowrap text-sm text-gray-900' }, product.text),
330
+ h('td', { className: 'px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2' },
331
+ h('button', {
332
+ onClick: () => {
333
+ const productIndex = products.findIndex(p => p.title === product.title)
334
+ handleEditProduct({ ...product, index: productIndex })
335
+ },
336
+ className: 'text-indigo-600 hover:text-indigo-900'
337
+ }, 'Edit'),
338
+ h('button', {
339
+ onClick: () => {
340
+ const productIndex = products.findIndex(p => p.title === product.title)
341
+ handleDeleteProduct(productIndex)
342
+ },
343
+ className: 'text-red-600 hover:text-red-900'
344
+ }, 'Delete')
345
+ )
346
+ )
347
+ )
348
+ )
349
+ )
350
+ )
351
+ )
352
+ ),
353
+
354
+ // Modal popup for Add/Edit using Popup component
355
+ h(Popup, {
356
+ _show: showModal,
357
+ _layout: 'toast',
358
+ onKeyDown: event => event.key === 'Escape' && setShowModal(false)
359
+ },
360
+ h('div', { className: 'flex flex-col h-full bg-white rounded-t-xl' },
361
+ h('div', { className: 'flex justify-between items-center p-4 border-b' },
362
+ h('h2', { className: 'text-xl font-bold' }, editingProduct?.index === undefined ? 'Add New Product' : 'Edit Product'),
363
+ h('button', {
364
+ onClick: () => {
365
+ setShowModal(false)
366
+ setEditingProduct(null)
367
+ },
368
+ className: 'text-gray-500 hover:text-gray-700'
369
+ }, '✕')
370
+ ),
371
+ h('div', { className: 'flex-1 overflow-y-auto p-4' },
372
+ h('form', { onSubmit: handleSaveProduct, className: 'space-y-4' },
373
+ h('div', {},
374
+ h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Title *'),
375
+ h('input', {
376
+ type: 'text',
377
+ required: true,
378
+ className: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500',
379
+ value: editingProduct?.title || '',
380
+ onInput: e => setEditingProduct({ ...editingProduct, title: e.target.value })
381
+ })
382
+ ),
383
+ h('div', { className: 'grid grid-cols-2 gap-4' },
384
+ h('div', {},
385
+ h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Brand *'),
386
+ h('input', {
387
+ type: 'text',
388
+ required: true,
389
+ className: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500',
390
+ value: editingProduct?.brand || '',
391
+ onInput: e => setEditingProduct({ ...editingProduct, brand: e.target.value })
392
+ })
393
+ ),
394
+ h('div', {},
395
+ h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Main Category *'),
396
+ h('select', {
397
+ required: true,
398
+ className: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500',
399
+ value: editingProduct?.mainCategory || '',
400
+ onChange: e => setEditingProduct({
401
+ ...editingProduct,
402
+ mainCategory: e.target.value,
403
+ subcategory: ''
404
+ })
405
+ },
406
+ h('option', { value: '' }, 'Select Main Category'),
407
+ Object.keys(SUBCATEGORIES).map(mainCat => h('option', { value: mainCat, key: mainCat }, mainCat))
408
+ )
409
+ )
410
+ ),
411
+ h('div', {},
412
+ h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Subcategory *'),
413
+ h('select', {
414
+ required: true,
415
+ className: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500',
416
+ value: editingProduct?.subcategory || '',
417
+ onChange: e => setEditingProduct({ ...editingProduct, subcategory: e.target.value }),
418
+ disabled: !editingProduct?.mainCategory
419
+ },
420
+ h('option', { value: '' }, 'Select Subcategory'),
421
+ (editingProduct?.mainCategory ? SUBCATEGORIES[editingProduct.mainCategory] || [] : []).map(subcat =>
422
+ h('option', { value: subcat, key: subcat }, subcat)
423
+ )
424
+ )
425
+ ),
426
+ h('div', {},
427
+ h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Description (3-4 words) *'),
428
+ h('input', {
429
+ type: 'text',
430
+ required: true,
431
+ placeholder: 'e.g. Premium Soft Close',
432
+ className: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500',
433
+ value: editingProduct?.text || '',
434
+ onInput: e => setEditingProduct({ ...editingProduct, text: e.target.value })
435
+ })
436
+ ),
437
+ h('div', {},
438
+ h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Image URL *'),
439
+ h('input', {
440
+ type: 'text',
441
+ required: true,
442
+ placeholder: '/assets/images/hardware/product.jpg',
443
+ className: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500',
444
+ value: editingProduct?.image?.src || '',
445
+ onInput: e => setEditingProduct({
446
+ ...editingProduct,
447
+ image: { ...editingProduct.image, src: e.target.value }
448
+ })
449
+ })
450
+ ),
451
+ h('div', {},
452
+ h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Image Alt Text *'),
453
+ h('input', {
454
+ type: 'text',
455
+ required: true,
456
+ placeholder: 'e.g. Soft Close Drawer Slide | Best hardware in Bangalore',
457
+ className: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500',
458
+ value: editingProduct?.image?.alt || '',
459
+ onInput: e => setEditingProduct({
460
+ ...editingProduct,
461
+ image: { ...editingProduct.image, alt: e.target.value }
462
+ })
463
+ })
464
+ ),
465
+ h('div', { className: 'flex justify-end space-x-3 pt-4' },
466
+ h('button', {
467
+ type: 'button',
468
+ onClick: () => {
469
+ setShowModal(false)
470
+ setEditingProduct(null)
471
+ },
472
+ className: 'px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50'
473
+ }, 'Cancel'),
474
+ h('button', {
475
+ type: 'submit',
476
+ className: 'px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700'
477
+ }, 'Save Product')
478
+ )
479
+ )
480
+ )
481
+ )
482
+
483
+ )
484
+ )
485
+ }
486
+
487
+ globalThis.ami.register(AdminDashboard, 'admin-dashboard')
@@ -0,0 +1,91 @@
1
+ const { h, preactHooks } = globalThis.ami
2
+ const { useState } = preactHooks
3
+
4
+ function AdminLogin() {
5
+ const [username, setUsername] = useState('')
6
+ const [password, setPassword] = useState('')
7
+ const [error, setError] = useState('')
8
+ const [loading, setLoading] = useState(false)
9
+
10
+ const handleLogin = async (e) => {
11
+ e.preventDefault()
12
+ setError('')
13
+ setLoading(true)
14
+
15
+ try {
16
+ const response = await fetch('/api/admin/login', {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ body: JSON.stringify({ username, password })
20
+ })
21
+
22
+ const data = await response.json()
23
+
24
+ if (response.ok) {
25
+ // Store token in localStorage
26
+ localStorage.setItem('adminToken', data.token)
27
+ localStorage.setItem('adminUser', username)
28
+ // Redirect to dashboard
29
+ window.location.href = '/admin/dashboard'
30
+ } else {
31
+ setError(data.message || 'Invalid credentials')
32
+ }
33
+ } catch (err) {
34
+ setError('Login failed. Please try again.')
35
+ } finally {
36
+ setLoading(false)
37
+ }
38
+ }
39
+
40
+ return h('div', { className: 'min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4 sm:px-6 lg:px-8' },
41
+ h('div', { className: 'max-w-md w-full space-y-8 bg-white p-10 rounded-2xl shadow-2xl' },
42
+ h('div', {},
43
+ h('h2', { className: 'text-center text-3xl font-extrabold text-gray-900' }, 'Admin Login'),
44
+ h('p', { className: 'mt-2 text-center text-sm text-gray-600' }, 'Shiv Shakti Hardware - Product Management')
45
+ ),
46
+ h('form', { className: 'mt-8 space-y-6', onSubmit: handleLogin },
47
+ error && h('div', { className: 'bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg text-sm' }, error),
48
+ h('div', { className: 'space-y-4' },
49
+ h('div', {},
50
+ h('label', { htmlFor: 'username', className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Username'),
51
+ h('input', {
52
+ id: 'username',
53
+ name: 'username',
54
+ type: 'text',
55
+ required: true,
56
+ className: 'appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm',
57
+ placeholder: 'Enter username',
58
+ value: username,
59
+ onInput: (e) => setUsername(e.target.value)
60
+ })
61
+ ),
62
+ h('div', {},
63
+ h('label', { htmlFor: 'password', className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Password'),
64
+ h('input', {
65
+ id: 'password',
66
+ name: 'password',
67
+ type: 'password',
68
+ required: true,
69
+ className: 'appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm',
70
+ placeholder: 'Enter password',
71
+ value: password,
72
+ onInput: (e) => setPassword(e.target.value)
73
+ })
74
+ )
75
+ ),
76
+ h('div', {},
77
+ h('button', {
78
+ type: 'submit',
79
+ disabled: loading,
80
+ className: `group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-lg text-white ${loading ? 'bg-indigo-400 cursor-not-allowed' : 'bg-indigo-600 hover:bg-indigo-700'} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors`
81
+ }, loading ? 'Logging in...' : 'Sign in')
82
+ )
83
+ ),
84
+ h('div', { className: 'text-center text-xs text-gray-500 mt-4' },
85
+ ''
86
+ )
87
+ )
88
+ )
89
+ }
90
+
91
+ globalThis.ami.register(AdminLogin, 'admin-login')
@@ -0,0 +1,56 @@
1
+ const { h, register } = globalThis.ami
2
+
3
+ const PADDING = 14
4
+
5
+ function BazaarVoice ({ src, product = '', features = '' }) {
6
+ if (!src) {
7
+ console.error('Missing data source.')
8
+ return null
9
+ }
10
+
11
+ return h('iframe', {
12
+ // Set temporary source to spoof Bazaarvoice as it needs an origin.
13
+ // - Using current origin allows to interact with the iframe without CORS issues.
14
+ // - The do-not-render parameter prevents acinguiux-preact from rendering - see router.js.
15
+ src: `${location.origin}${location.pathname}?do-not-render`,
16
+ height: 0,
17
+ style: 'background: white',
18
+ className: 'block border box-content w-full hidden rounded-2xl animate-fade border-txa/20',
19
+ onLoad: event => createWidget(event.target, src, product, features)
20
+ })
21
+ }
22
+
23
+ function createWidget (iframe, src, product, features) {
24
+ const doc = iframe.contentWindow.document
25
+ const body = doc.body
26
+
27
+ body.innerHTML = ''
28
+ body.style = `margin: 0; padding: ${PADDING}px; overflow: hidden`
29
+ iframe.classList.remove('hidden')
30
+
31
+ const wrapper = doc.createElement('div')
32
+ body.appendChild(wrapper)
33
+
34
+ const featureArr = features.split(/ +/)
35
+ for (const feature of featureArr) {
36
+ const div = document.createElement('div')
37
+ div.dataset.bvShow = feature
38
+ div.dataset.bvProductId = product
39
+ wrapper.appendChild(div)
40
+ }
41
+
42
+ // Watch for changes.
43
+ const observer = new MutationObserver(() => {
44
+ iframe.height = `${wrapper.scrollHeight + 2 * PADDING}px`
45
+ })
46
+
47
+ // Start observing body.
48
+ observer.observe(body, { attributes: true, childList: true, subtree: true })
49
+
50
+ // Add Bazaarvoice script.
51
+ const script = doc.createElement('script')
52
+ script.src = src
53
+ body.appendChild(script)
54
+ }
55
+
56
+ register(BazaarVoice, 'bazaar-voice')