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,150 @@
1
+ /* global localStorage */
2
+ const STORAGE_KEY = 'help-centre'
3
+ const DEFAULT_LABELS = {
4
+ loading: 'Loading...',
5
+ error: 'Error loading data.',
6
+ home: 'Home',
7
+ searchHeading: 'Need assistance?',
8
+ searchDescription: 'Search for topics, questions or keywords.',
9
+ searchPlaceholder: 'Enter your search term',
10
+ searchButton: 'Submit',
11
+ searchBreadcrumb: 'Search results',
12
+ searchNoResults: 'No results.',
13
+ categoryListHeading: 'Find information by topic',
14
+ viewAllQuestions: 'View all [section] questions',
15
+ otherSectionsHeading: 'Other [category] sections',
16
+ voteIntro: 'Was this article helpful?',
17
+ voteYes: 'Yes',
18
+ voteNo: 'No',
19
+ voteHelpful: '[a] out of [b] found this helpful.',
20
+ votePositive: 'Thank you for your feedback.',
21
+ voteNegative: 'We apologise for the inconvenience. For further assistance, please don\'t hesitate to contact our customer service team.',
22
+ loadMore: 'Load more results'
23
+ }
24
+
25
+ export default class API {
26
+ constructor ({ apiUrl, voteUrl, voteCreds, dictionaryUrl, imageUrl, locale }) {
27
+ this.voteCreds = voteCreds
28
+ this.voteUrl = voteUrl
29
+ this.dictionaryUrl = dictionaryUrl
30
+ // Allow dictionary to be loaded before the main data to display the Loading label.
31
+ this.dictionaryPromise = new Promise(resolve => {
32
+ (this.dictionaryUrl ? this._fetch(this.dictionaryUrl) : Promise.resolve({})).then(labels => {
33
+ this.labels = { ...DEFAULT_LABELS, ...labels }
34
+ resolve(labels)
35
+ })
36
+ })
37
+ this.apiUrl = apiUrl
38
+ this.imageUrl = imageUrl
39
+ this.locale = locale
40
+ this.voteId = this._uuidv4()
41
+
42
+ if (!localStorage.getItem(STORAGE_KEY)) {
43
+ localStorage.setItem(STORAGE_KEY, '{}')
44
+ }
45
+ }
46
+
47
+ onDictionaryReady (callback) {
48
+ this.dictionaryPromise.then(callback)
49
+ }
50
+
51
+ async init () {
52
+ // Fetch all entities one by one.
53
+ await Promise.all(['categories', 'sections', 'articles'].map(this.fetchAllPages.bind(this)))
54
+ await this.dictionaryPromise
55
+ }
56
+
57
+ async fetchAllPages (key) {
58
+ const baseUrl = `${this.apiUrl}/${this.locale}`
59
+ this[key] = []
60
+ let page = 1
61
+ let pagesToFetch = 1
62
+
63
+ while (pagesToFetch > 0) {
64
+ const items = await this._fetch(`${baseUrl}/${key}?per_page=100&page=${page}`)
65
+ if (page === 1) {
66
+ pagesToFetch = items.page_count
67
+ }
68
+ this[key].push(...items[key] || [])
69
+ page++
70
+ pagesToFetch--
71
+ }
72
+ }
73
+
74
+ getCategories ({ categoryId }) {
75
+ return categoryId ? this.categories.filter(category => category.id === categoryId) : this.categories
76
+ }
77
+
78
+ getSections ({ categoryId, sectionId }) {
79
+ if (sectionId) {
80
+ return this.sections.filter(section => section.id === sectionId)
81
+ } else if (categoryId) {
82
+ return this.sections.filter(section => section.category_id === categoryId)
83
+ } else {
84
+ return this.sections
85
+ }
86
+ }
87
+
88
+ getArticles ({ sectionId, articleId }) {
89
+ if (articleId) {
90
+ return this.articles.filter(article => article.id === articleId)
91
+ } else if (sectionId) {
92
+ return this.articles.filter(article => article.section_id === sectionId)
93
+ } else {
94
+ return this.articles
95
+ }
96
+ }
97
+
98
+ async search (query, page = 1) {
99
+ const result = await this._fetch(`${this.apiUrl}/articles/search?query=${query}&per_page=20&page=${page}`)
100
+ const nextPage = page < result.page_count ? page + 1 : 0
101
+ return [result.results, nextPage]
102
+ }
103
+
104
+ hasVoted (articleId) {
105
+ return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}')[articleId]
106
+ }
107
+
108
+ async vote (articleId, isUp) {
109
+ const tokens = await this._fetch(`${this.voteUrl}/oauth/v1/mobility/token`, {
110
+ method: 'post',
111
+ headers: {
112
+ 'content-type': 'application/x-www-form-urlencoded',
113
+ accept: 'application/json',
114
+ authorization: `Basic ${btoa(this.voteCreds)}`
115
+ },
116
+ body: new URLSearchParams({ grant_type: 'client_credentials' })
117
+ })
118
+ const voteToken = tokens.access_token
119
+
120
+ this._saveVote(articleId)
121
+
122
+ return this._fetch(`${this.voteUrl}/zendesk/v1/vote/${this.locale}/articles/${articleId}/${isUp ? 'up' : 'down'}`, {
123
+ method: 'post',
124
+ headers: {
125
+ authorization: `Bearer ${voteToken}`,
126
+ requestId: this.voteId
127
+ }
128
+ })
129
+ }
130
+
131
+ _saveVote (articleId) {
132
+ const save = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}')
133
+ save[articleId] = true
134
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(save))
135
+ }
136
+
137
+ _uuidv4 () {
138
+ return '10000000-1000-4000-8000-100000000000'.replaceAll(/[018]/g, c =>
139
+ (+c ^ globalThis.crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
140
+ )
141
+ }
142
+
143
+ async _fetch (url, options) {
144
+ const res = await fetch(url, options)
145
+ if (!res.ok) {
146
+ throw new Error(`Failed to load resource: ${url}`)
147
+ }
148
+ return res.json()
149
+ }
150
+ }
@@ -0,0 +1,400 @@
1
+ /* global location, history */
2
+ import API from './api.js'
3
+ import ICON_NAVIGATION_SEARCH from './icon-search.svg'
4
+ import updateHead from '#acinguiux-preact/main/shared/update-head.js'
5
+ import idFromString from '#acinguiux-preact/main/shared/id-from-string.js'
6
+
7
+ const { h, preactHooks, matter, register } = globalThis.ami
8
+ const { useLayoutEffect, useState } = preactHooks
9
+ const {
10
+ AccordionItem, Asset, Box, Breadcrumb, Button,
11
+ Container,
12
+ Form, Heading, Input, Link,
13
+ RichText,
14
+ Tabs, Tags
15
+ } = matter
16
+
17
+ const CATEGORY_ARTICLE_COUNT = 3
18
+ const SUFFIX_PREFIX = '/help-centre/'
19
+ const DEFAULT_PROPS = {
20
+ locale: 'en-gb',
21
+ 'api-url': 'https://support.acinguiux.com/api/v2/help_center'
22
+ }
23
+
24
+ // -----------------------------------------------------------------------------
25
+ // Main web component.
26
+ // -----------------------------------------------------------------------------
27
+
28
+ function HelpCentre (props) {
29
+ const {
30
+ 'image-url': imageUrl,
31
+ locale,
32
+ 'api-url': apiUrl,
33
+ 'vote-url': voteUrl,
34
+ 'vote-creds': voteCreds,
35
+ 'dictionary-url': dictionaryUrl,
36
+ variant
37
+ } = { ...DEFAULT_PROPS, ...props }
38
+
39
+ // Initialise state.
40
+ const [state, setState] = useState(() => ({
41
+ view: null,
42
+ id: null,
43
+ api: new API({ apiUrl, voteUrl, voteCreds, dictionaryUrl, imageUrl, locale })
44
+ }))
45
+
46
+ // Main routing function.
47
+ const route = () => {
48
+ const [view, id] = getViewAndId(state.api)
49
+ setState({ ...state, view, id })
50
+ updateHead('link', { rel: 'canonical' }, { href: location.href.split('#')[0] })
51
+
52
+ return [view, id]
53
+ }
54
+
55
+ useLayoutEffect(() => {
56
+ // Display preloader as soon as the dictionary is ready.
57
+ state.api.onDictionaryReady(() => setState(currState => currState.view ? currState : { ...currState, view: 'loading' }))
58
+
59
+ ;(async () => {
60
+ // Initialise API.
61
+ await state.api.init()
62
+
63
+ // Initialise routing.
64
+ globalThis.addEventListener('popstate', route)
65
+
66
+ // Set initial route.
67
+ const [view] = route()
68
+
69
+ // Force default route if the current route is invalid.
70
+ if (!view) {
71
+ const categories = state.api.getCategories({})
72
+ const href = getSuffix('category', categories[0].id, categories[0].name)
73
+ navigate(href)
74
+ }
75
+ })()
76
+ }, [])
77
+
78
+ const dispatcher = {
79
+ loading: () => h(Box, { className: 'text-center' }, state.api.labels.loading),
80
+ category: () => h(CategoryView, { api: state.api, categoryId: state.id, isCompact: variant === 'compact' }),
81
+ section: () => h(SectionView, { api: state.api, sectionId: state.id }),
82
+ article: () => h(SectionView, { api: state.api, articleId: state.id }),
83
+ search: () => h(SearchView, { api: state.api, query: state.id })
84
+ }
85
+
86
+ return dispatcher[state.view]?.()
87
+ }
88
+
89
+ register(HelpCentre, 'help-centre')
90
+
91
+ // -----------------------------------------------------------------------------
92
+ // View components.
93
+ // -----------------------------------------------------------------------------
94
+
95
+ function CategoryView ({ api, categoryId, isCompact }) {
96
+ const tabLinks = api.getCategories({}).map(category => ({
97
+ name: category.name,
98
+ value: getSuffix('category', category.id, category.name),
99
+ selected: category.id === categoryId
100
+ }))
101
+
102
+ return h('div', { className: 'flex flex-col gap-5' },
103
+ h(SearchHeader, { api }),
104
+ h(Box, { className: 'text-center' },
105
+ h(Heading, { _level: 2 }, api.labels.categoryListHeading)
106
+ ),
107
+ h(Tabs, { _model: { links: tabLinks }, _variant: isCompact ? 'compact' : undefined }),
108
+ h(Grid, { className: 'grid-cols-3 md:grid-cols-2 sm:grid-cols-1 animate-fade' }, api.getSections({ categoryId }).map(section =>
109
+ h(Box, { className: 'bg-bga text-txa rounded-xl flex flex-col' },
110
+ h(Heading, { _level: 3 }, section.name),
111
+ h('ul', { className: 'grow' }, api.getArticles({ sectionId: section.id }).slice(0, CATEGORY_ARTICLE_COUNT).map(article =>
112
+ h('li',
113
+ h(Link, {
114
+ _variant: 'simple',
115
+ _model: {
116
+ name: article.name,
117
+ value: getSuffix('section', section.id, section.name, article.name)
118
+ }
119
+ })
120
+ )
121
+ )),
122
+ h(Link, {
123
+ _variant: 'full',
124
+ _model: {
125
+ name: api.labels.viewAllQuestions.replace('[section]', section.name),
126
+ value: getSuffix('section', section.id, section.name)
127
+ }
128
+ })
129
+ ))
130
+ )
131
+ )
132
+ }
133
+
134
+ function SectionView ({ api, sectionId, articleId }) {
135
+ if (articleId) {
136
+ sectionId = api.getArticles({ articleId }).section_id
137
+ }
138
+
139
+ const section = api.getSections({ sectionId })[0]
140
+ const category = api.getCategories({ categoryId: section.category_id })[0]
141
+ const articles = api.getArticles({ sectionId })
142
+ const firstCategory = api.getCategories({})[0]
143
+
144
+ return h(Grid,
145
+ h(Breadcrumb, {
146
+ _model: {
147
+ links: [{
148
+ name: api.labels.home,
149
+ value: getSuffix('category', firstCategory.id, firstCategory.name)
150
+ }, {
151
+ name: category.name,
152
+ value: getSuffix('category', category.id, category.name)
153
+ }, {
154
+ name: section.name,
155
+ value: getSuffix('section', section.id, section.name)
156
+ }]
157
+ }
158
+ }),
159
+ h(Box, { className: 'rounded-xl bg-bga text-txa' },
160
+ h(Heading, { _level: 2 }, section.name),
161
+ h(SearchForm, { api })
162
+ ),
163
+ h(Container, { _variant: 'list' }, articles.map(article =>
164
+ h(AccordionItem, {
165
+ _variant: 'faq',
166
+ _model: {
167
+ title: article.title
168
+ },
169
+ children: h(Box,
170
+ h(RichText, article.body),
171
+ h(Voting, { api, articleId: article.id })
172
+ )
173
+ })
174
+ )),
175
+ h(Box, { className: 'text-txa bg-bga rounded-xl' },
176
+ h(Heading, { _level: 3 }, api.labels.otherSectionsHeading.replace('[category]', category.name)),
177
+ h(Tags, {
178
+ _model: {
179
+ tags: api
180
+ .getSections({ categoryId: section.category_id })
181
+ .filter(s => s.id !== sectionId)
182
+ .map(s => ({
183
+ name: s.name,
184
+ value: getSuffix('section', s.id, s.name)
185
+ }))
186
+ }
187
+ })
188
+ )
189
+ )
190
+ }
191
+
192
+ function SearchView ({ api, query }) {
193
+ const [state, setState] = useState({
194
+ results: [],
195
+ nextPage: 0
196
+ })
197
+
198
+ useLayoutEffect(() => {
199
+ (async () => {
200
+ const [results, nextPage] = await api.search(query, 1)
201
+ setState({ results, nextPage })
202
+ })()
203
+ }, [query])
204
+
205
+ const firstCategory = api.getCategories({})[0]
206
+ return h(Grid,
207
+ h(Breadcrumb, {
208
+ _model: {
209
+ links: [{
210
+ name: api.labels.home,
211
+ value: getSuffix('category', firstCategory.id, firstCategory.name)
212
+ }, {
213
+ name: api.labels.searchBreadcrumb,
214
+ value: getSuffix('search', query)
215
+ }]
216
+ }
217
+ }),
218
+ h(SearchHeader, { api, query }),
219
+ // Search results.
220
+ state.results.length === 0 && h(Box, api.labels.searchNoResults),
221
+ h(Grid, { className: 'grid gap-5 lg:grid-cols-2' }, state.results.map(result =>
222
+ h(Box, { className: 'bg-bga text-txa rounded-xl animate-fade' },
223
+ h(Heading, { _level: 3 },
224
+ h(Link, {
225
+ _variant: 'full',
226
+ _model: {
227
+ name: result.name,
228
+ value: getSuffix('section', result.section_id, null, result.name)
229
+ }
230
+ })
231
+ ),
232
+ h(RichText, result.body)
233
+ ))
234
+ ),
235
+ // Load more.
236
+ state.nextPage > 0 && h('div', { className: 'flex items-center justify-center' },
237
+ h(Button, {
238
+ async onClick (event) {
239
+ event.preventDefault()
240
+ const [results, nextPage] = await api.search(query, state.nextPage)
241
+ state.results.push(...results)
242
+ setState({ results: state.results, nextPage })
243
+ },
244
+ _model: { name: api.labels.loadMore }
245
+ })
246
+ )
247
+ )
248
+ }
249
+
250
+ // -----------------------------------------------------------------------------
251
+ // Helper components.
252
+ // -----------------------------------------------------------------------------
253
+
254
+ function Grid ({ children, className }) {
255
+ return h('div', { className: `grid gap-5 ${className ?? ''}`, children })
256
+ }
257
+
258
+ function SearchHeader ({ api, query }) {
259
+ return h('div', { className: 'bg-bga text-txa rounded-xl grid lg:grid-cols-4 overflow-hidden' },
260
+ h(Box, { className: 'lg:col-span-3' },
261
+ h(Heading, { _level: 2 }, api.labels.searchHeading),
262
+ h('p', api.labels.searchDescription),
263
+ h(SearchForm, { api, query })
264
+ ),
265
+ api.imageUrl && h('div', { className: 'sm:hidden md:hidden' },
266
+ h(Asset, {
267
+ _tall: true,
268
+ _model: { image: { src: api.imageUrl, alt: api.labels.searchHeading, width: 1600, height: 900 } }
269
+ })
270
+ )
271
+ )
272
+ }
273
+
274
+ function onSubmit (event) {
275
+ event.preventDefault()
276
+ navigate(getSuffix('search', event.target[0].value))
277
+ }
278
+
279
+ function SearchForm ({ api, query }) {
280
+ return h(Form, { onSubmit },
281
+ h('div', { className: 'flex gap-5' },
282
+ h('div', { className: 'grow' },
283
+ h(Input, {
284
+ type: 'search',
285
+ value: query ? decodeURIComponent(query) : null,
286
+ placeholder: api.labels.searchPlaceholder
287
+ })
288
+ ),
289
+ h('div', { className: 'grow-0' },
290
+ h(Button, {
291
+ _textless: true,
292
+ _model: { name: 'submit', icon: ICON_NAVIGATION_SEARCH }
293
+ })
294
+ )
295
+ )
296
+ )
297
+ }
298
+
299
+ function Voting ({ api, articleId }) {
300
+ if (!api.voteUrl) {
301
+ return null
302
+ }
303
+
304
+ const article = api.getArticles({ articleId })[0]
305
+ const yesVotes = (article.vote_count + article.vote_sum) / 2
306
+ const [state, setState] = useState({
307
+ yesVotes,
308
+ voteCount: article.vote_count,
309
+ voted: api.hasVoted(articleId),
310
+ message: null
311
+ })
312
+
313
+ return h('div', { className: 'flex gap-5 flex-col' },
314
+ h('p', api.labels.voteHelpful.replace('[a]', state.yesVotes).replace('[b]', state.voteCount)),
315
+ state.voted || h('div', { className: 'flex gap-5 items-center' },
316
+ h('strong', api.labels.voteIntro),
317
+ h(Button, {
318
+ _size: 'xs',
319
+ _model: { name: api.labels.voteYes },
320
+ onClick: () => {
321
+ api.vote(articleId, true)
322
+ setState({
323
+ voted: true,
324
+ yesVotes: state.yesVotes + 1,
325
+ voteCount: state.voteCount + 1,
326
+ message: api.labels.votePositive
327
+ })
328
+ }
329
+ }),
330
+ h(Button, {
331
+ _size: 'xs',
332
+ _model: { name: api.labels.voteNo },
333
+ onClick: () => {
334
+ api.vote(articleId)
335
+ setState({
336
+ voted: true,
337
+ yesVotes,
338
+ voteCount: state.voteCount + 1,
339
+ message: api.labels.voteNegative
340
+ })
341
+ }
342
+ })
343
+ ),
344
+ state.message && h('p', state.message)
345
+ )
346
+ }
347
+
348
+ // -----------------------------------------------------------------------------
349
+ // Helper functions.
350
+ // -----------------------------------------------------------------------------
351
+
352
+ function getUrlFriendlyName (str) {
353
+ return encodeURIComponent(str.replaceAll(/[^a-zA-Z0-9\-_.!~*\s]*/g, '').trim().replaceAll(/\s+/g, '-'))
354
+ }
355
+
356
+ function getViewAndId (api) {
357
+ const suffix = location.pathname.split('.html')[1]
358
+
359
+ if (suffix?.startsWith?.(SUFFIX_PREFIX)) {
360
+ let [view, id] = suffix.split('/').slice(2)
361
+
362
+ if (view === 'search') {
363
+ return [view, id]
364
+ }
365
+
366
+ id = Number(id)
367
+
368
+ if (view === 'category' && api.getCategories({ categoryId: id }).length > 0) {
369
+ return [view, id]
370
+ }
371
+
372
+ if (view === 'section' && api.getSections({ sectionId: id }).length > 0) {
373
+ return [view, id]
374
+ }
375
+ }
376
+
377
+ return []
378
+ }
379
+
380
+ function getSuffix (view, id, name, question) {
381
+ if (name) {
382
+ name = getUrlFriendlyName(name)
383
+ }
384
+
385
+ const urlObj = new URL(location.href)
386
+ const fullPathname = urlObj.pathname.endsWith('/') ? `${urlObj.pathname}index.html` : urlObj.pathname
387
+ const currentSuffix = urlObj.suffix = fullPathname.split('.html')[1]
388
+ const pathnameWithoutSuffix = fullPathname.replace(currentSuffix, '')
389
+
390
+ const suffix = [view, id, name].filter(Boolean).join('/')
391
+ urlObj.pathname = pathnameWithoutSuffix + SUFFIX_PREFIX + suffix
392
+ urlObj.hash = question ? idFromString(question) : ''
393
+
394
+ return urlObj.href
395
+ }
396
+
397
+ function navigate (newUrl) {
398
+ history.replaceState({}, '', newUrl)
399
+ globalThis.dispatchEvent(new Event('popstate'))
400
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#404040"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.5 6a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm0-2a6.5 6.5 0 0 1 5.249 10.335l3.969 3.968a.5.5 0 0 1 0 .707l-.708.707a.5.5 0 0 1-.707 0l-3.968-3.968A6.5 6.5 0 1 1 10.5 4Z"></path></svg>