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.
- package/package.json +56 -0
- package/src/content/themes/theme-acinguiux-amg/theme-acinguiux-amg.css +23 -0
- package/src/content/themes/theme-acinguiux-cafe/theme-acinguiux-cafe.css +47 -0
- package/src/content/themes/theme-acinguiux-energy/theme-acinguiux-energy.css +45 -0
- package/src/content/themes/theme-acinguiux-livewire/theme-acinguiux-livewire.css +22 -0
- package/src/content/themes/theme-acinguiux-livewire-italy/theme-acinguiux-livewire-italy.css +22 -0
- package/src/content/themes/theme-acinguiux-recharge/theme-acinguiux-recharge.css +49 -0
- package/src/content/themes/theme-allon/theme-allon.css +25 -0
- package/src/content/themes/theme-atlas/theme-atlas.css +31 -0
- package/src/content/themes/theme-aurvana/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon.svg +13 -0
- package/src/content/themes/theme-aurvana/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-aurvana/resources/favicon/site.webmanifest +21 -0
- package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-aurvana/theme-aurvana.css +49 -0
- package/src/content/themes/theme-base/theme-base.css +49 -0
- package/src/content/themes/theme-base2/resources/favicon/android-chrome-192x192.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/android-chrome-512x512.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon-16x16.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon-32x32.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon.svg +9 -0
- package/src/content/themes/theme-base2/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-base2/resources/favicon/site.webmanifest +1 -0
- package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-base2/resources/fonts/acinguiux-typeface-la-heavy-221208.woff2 +0 -0
- package/src/content/themes/theme-base2/theme-base2.css +47 -0
- package/src/content/themes/theme-eco-marathon/theme-eco-marathon.css +22 -0
- package/src/content/themes/theme-energy-transition-campus-amsterdam/theme-energy-transition-campus-amsterdam.css +26 -0
- package/src/content/themes/theme-evpass/theme-evpass.css +46 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon.svg +9 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/site.webmanifest +21 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-nam-2025/theme-nam-2025.css +47 -0
- package/src/content/themes/theme-pennzoil/theme-pennzoil.css +36 -0
- package/src/content/themes/theme-quaker-state/theme-quaker-state.css +63 -0
- package/src/content/themes/theme-tafawoq/theme-tafawoq.css +26 -0
- package/src/content/themes/theme-vegetable/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon.svg +13 -0
- package/src/content/themes/theme-vegetable/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-vegetable/resources/favicon/site.webmanifest +21 -0
- package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-vegetable/theme-vegetable.css +49 -0
- package/src/content/themes/theme-zeolyst/resources/fonts/type-ar-medium.woff2 +0 -0
- package/src/content/themes/theme-zeolyst/theme-zeolyst.css +29 -0
- package/src/main/atoms/audio.js +16 -0
- package/src/main/atoms/box.js +5 -0
- package/src/main/atoms/button.js +40 -0
- package/src/main/atoms/card.js +22 -0
- package/src/main/atoms/form.js +30 -0
- package/src/main/atoms/heading.js +17 -0
- package/src/main/atoms/icon.js +24 -0
- package/src/main/atoms/img.js +131 -0
- package/src/main/atoms/input.js +55 -0
- package/src/main/atoms/link-text.js +21 -0
- package/src/main/atoms/link.js +60 -0
- package/src/main/atoms/list.js +12 -0
- package/src/main/atoms/logo.js +9 -0
- package/src/main/atoms/menu.js +10 -0
- package/src/main/atoms/message.js +5 -0
- package/src/main/atoms/nav-link.js +49 -0
- package/src/main/atoms/popup.js +47 -0
- package/src/main/atoms/rich-text.js +128 -0
- package/src/main/atoms/scroller.js +224 -0
- package/src/main/atoms/svg.js +65 -0
- package/src/main/atoms/table.js +32 -0
- package/src/main/atoms/textarea.js +10 -0
- package/src/main/atoms/time.js +12 -0
- package/src/main/atoms/video.js +100 -0
- package/src/main/export-main.js +12 -0
- package/src/main/export-matter.js +86 -0
- package/src/main/export-preact-hooks.js +1 -0
- package/src/main/export-preact.js +1 -0
- package/src/main/index.js +13 -0
- package/src/main/molecules/asset.js +23 -0
- package/src/main/molecules/glossary.js +44 -0
- package/src/main/molecules/links.js +23 -0
- package/src/main/molecules/promo-text.js +27 -0
- package/src/main/molecules/tags.js +15 -0
- package/src/main/molecules/tree.js +51 -0
- package/src/main/organisms/accordion-item.js +106 -0
- package/src/main/organisms/author.js +29 -0
- package/src/main/organisms/breadcrumb.js +69 -0
- package/src/main/organisms/call-to-action.js +24 -0
- package/src/main/organisms/carousel.js +178 -0
- package/src/main/organisms/cart-item.js +156 -0
- package/src/main/organisms/cart.js +162 -0
- package/src/main/organisms/contact-form.js +141 -0
- package/src/main/organisms/container/ab-test.js +47 -0
- package/src/main/organisms/container/default.js +6 -0
- package/src/main/organisms/container/filtered-section.js +293 -0
- package/src/main/organisms/container/footer.js +12 -0
- package/src/main/organisms/container/grid.js +44 -0
- package/src/main/organisms/container/header.js +13 -0
- package/src/main/organisms/container/list.js +7 -0
- package/src/main/organisms/container/main.js +6 -0
- package/src/main/organisms/container/raw.js +7 -0
- package/src/main/organisms/container/section.js +28 -0
- package/src/main/organisms/container.js +29 -0
- package/src/main/organisms/content-owner.js +15 -0
- package/src/main/organisms/date-entry.js +56 -0
- package/src/main/organisms/external-search.js +73 -0
- package/src/main/organisms/filtered-item.js +163 -0
- package/src/main/organisms/footer-item.js +17 -0
- package/src/main/organisms/image-gallery.js +164 -0
- package/src/main/organisms/last-modified.js +20 -0
- package/src/main/organisms/legal-footer.js +16 -0
- package/src/main/organisms/list-item.js +48 -0
- package/src/main/organisms/metadata.js +11 -0
- package/src/main/organisms/navigation.js +232 -0
- package/src/main/organisms/notification.js +87 -0
- package/src/main/organisms/order-tracker.js +203 -0
- package/src/main/organisms/page-header-banner.js +26 -0
- package/src/main/organisms/page-header.js +33 -0
- package/src/main/organisms/page-tags.js +14 -0
- package/src/main/organisms/page.js +260 -0
- package/src/main/organisms/press-release.js +24 -0
- package/src/main/organisms/product-admin.js +204 -0
- package/src/main/organisms/promo-banner.js +28 -0
- package/src/main/organisms/promo-bottom.js +23 -0
- package/src/main/organisms/promo-button.js +8 -0
- package/src/main/organisms/promo-card-cover.js +35 -0
- package/src/main/organisms/promo-card.js +33 -0
- package/src/main/organisms/promo-full.js +20 -0
- package/src/main/organisms/promo-image.js +22 -0
- package/src/main/organisms/promo-lure.js +22 -0
- package/src/main/organisms/promo-product-card.js +187 -0
- package/src/main/organisms/promo-product-full.js +293 -0
- package/src/main/organisms/promo-simple.js +23 -0
- package/src/main/organisms/quote.js +21 -0
- package/src/main/organisms/search-form.js +42 -0
- package/src/main/organisms/search-nav.js +66 -0
- package/src/main/organisms/search-result.js +53 -0
- package/src/main/organisms/slider.js +26 -0
- package/src/main/organisms/standalone-asset.js +22 -0
- package/src/main/organisms/tabs.js +277 -0
- package/src/main/organisms/topbar.js +83 -0
- package/src/main/organisms/web-component.js +53 -0
- package/src/main/routing/annotation.js +9 -0
- package/src/main/routing/component.js +138 -0
- package/src/main/routing/empty.js +5 -0
- package/src/main/routing/error-handler.js +64 -0
- package/src/main/routing/placeholder-image.svg +5 -0
- package/src/main/routing/router.js +219 -0
- package/src/main/shared/analytics.js +677 -0
- package/src/main/shared/bubble-event.js +11 -0
- package/src/main/shared/custom-element.js +21 -0
- package/src/main/shared/deep-selector.js +28 -0
- package/src/main/shared/disable-transparency.js +10 -0
- package/src/main/shared/format-time.js +8 -0
- package/src/main/shared/get-id.js +5 -0
- package/src/main/shared/get-meta.js +3 -0
- package/src/main/shared/get-size-class.js +3 -0
- package/src/main/shared/get-size.js +11 -0
- package/src/main/shared/h.js +88 -0
- package/src/main/shared/hash-jump.js +33 -0
- package/src/main/shared/icons/arrow-back.svg +1 -0
- package/src/main/shared/icons/arrow-down.svg +1 -0
- package/src/main/shared/icons/arrow-next.svg +1 -0
- package/src/main/shared/icons/arrow-tail-right.svg +1 -0
- package/src/main/shared/icons/arrow-tail-up.svg +1 -0
- package/src/main/shared/icons/arrow-up.svg +1 -0
- package/src/main/shared/icons/asset-download.svg +1 -0
- package/src/main/shared/icons/logo.svg +5 -0
- package/src/main/shared/icons/low-carbon-placeholder.svg +9 -0
- package/src/main/shared/icons/media-pause.svg +1 -0
- package/src/main/shared/icons/media-play.svg +1 -0
- package/src/main/shared/icons/navigation-burger.svg +1 -0
- package/src/main/shared/icons/navigation-close.svg +1 -0
- package/src/main/shared/icons/navigation-link.svg +1 -0
- package/src/main/shared/icons/navigation-refresh.svg +1 -0
- package/src/main/shared/icons/navigation-search.svg +1 -0
- package/src/main/shared/icons/navigation-share.svg +1 -0
- package/src/main/shared/icons/toggle-newwindow.svg +1 -0
- package/src/main/shared/icons.js +18 -0
- package/src/main/shared/id-from-string.js +5 -0
- package/src/main/shared/mark-selection.js +19 -0
- package/src/main/shared/register.js +26 -0
- package/src/main/shared/renderer.js +43 -0
- package/src/main/shared/simple-consent-api.js +70 -0
- package/src/main/shared/split-links.js +11 -0
- package/src/main/shared/t.js +60 -0
- package/src/main/shared/twind.js +837 -0
- package/src/main/shared/update-head.js +34 -0
- package/src/main/shared/update-scrollbar-width.js +30 -0
- package/src/main/shared/use-link.js +151 -0
- package/src/main/shared/use-persistent-state.js +42 -0
- package/src/main/shared/wait-for-dom-ready.js +6 -0
- package/src/main/shared/wcm-mode.js +4 -0
- package/src/wcs/components/acinguiux-preact-doc/acinguiux-preact-doc.js +207 -0
- package/src/wcs/components/admin-dashboard/admin-dashboard.js +487 -0
- package/src/wcs/components/admin-login/admin-login.js +91 -0
- package/src/wcs/components/bazaar-voice/bazaar-voice.js +56 -0
- package/src/wcs/components/chatbot-koreai/chatbot-koreai.js +176 -0
- package/src/wcs/components/chatbot-koreai/koreai-transport.js +217 -0
- package/src/wcs/components/chatbot-ms/chatbot-ms.js +210 -0
- package/src/wcs/components/chatbot-test/chatbot-test.js +44 -0
- package/src/wcs/components/comparison-chart/comparison-chart.js +111 -0
- package/src/wcs/components/consent-banner/consent-banner.js +248 -0
- package/src/wcs/components/consent-banner/icons/ccpa.svg +6 -0
- package/src/wcs/components/consent-banner/icons/info.svg +1 -0
- package/src/wcs/components/consent-banner/provider-onetrust.js +131 -0
- package/src/wcs/components/decision-tree/arrow-back.svg +3 -0
- package/src/wcs/components/decision-tree/badges.js +37 -0
- package/src/wcs/components/decision-tree/decision-tree.js +162 -0
- package/src/wcs/components/dynamic-contact-details/dynamic-contact-details.js +111 -0
- package/src/wcs/components/example-accordion/example-accordion.js +10 -0
- package/src/wcs/components/example-asset/example-asset.js +12 -0
- package/src/wcs/components/example-form/example-form.js +59 -0
- package/src/wcs/components/example-nested/example-nested.js +10 -0
- package/src/wcs/components/example-routing/example-routing.js +51 -0
- package/src/wcs/components/example-rtl/example-rtl.js +28 -0
- package/src/wcs/components/example-tabs/example-tabs.js +12 -0
- package/src/wcs/components/example-web-component/example-web-component.js +34 -0
- package/src/wcs/components/floating-button/floating-button.js +17 -0
- package/src/wcs/components/formstack-form/fields/address.js +38 -0
- package/src/wcs/components/formstack-form/fields/checkbox.js +42 -0
- package/src/wcs/components/formstack-form/fields/date.js +22 -0
- package/src/wcs/components/formstack-form/fields/description.js +8 -0
- package/src/wcs/components/formstack-form/fields/input.js +8 -0
- package/src/wcs/components/formstack-form/fields/name.js +39 -0
- package/src/wcs/components/formstack-form/fields/radio.js +24 -0
- package/src/wcs/components/formstack-form/fields/rating.js +53 -0
- package/src/wcs/components/formstack-form/fields/section.js +8 -0
- package/src/wcs/components/formstack-form/fields/select.js +10 -0
- package/src/wcs/components/formstack-form/fields/textarea.js +8 -0
- package/src/wcs/components/formstack-form/fields/wrapper.js +11 -0
- package/src/wcs/components/formstack-form/formstack-form.js +280 -0
- package/src/wcs/components/fuel-prices/fuel-prices.js +45 -0
- package/src/wcs/components/furniture-overview/furniture-overview.js +115 -0
- package/src/wcs/components/gauge-value/gauge-value.js +65 -0
- package/src/wcs/components/help-centre/api.js +150 -0
- package/src/wcs/components/help-centre/help-centre.js +400 -0
- package/src/wcs/components/help-centre/icon-search.svg +1 -0
- package/src/wcs/components/image-gen/admin-panel.js +248 -0
- package/src/wcs/components/image-gen/image-gen.js +385 -0
- package/src/wcs/components/image-gen/labels.js +37 -0
- package/src/wcs/components/image-gen/use-api.js +392 -0
- package/src/wcs/components/inspired-gallery/inspired-gallery.js +118 -0
- package/src/wcs/components/launch-container/launch-container.js +95 -0
- package/src/wcs/components/launch-container/ledger.js +140 -0
- package/src/wcs/components/lng-map/lng-map.js +44 -0
- package/src/wcs/components/mouseflow-analytics/mouseflow-analytics.js +39 -0
- package/src/wcs/components/msds-search/msds-search.js +127 -0
- package/src/wcs/components/msds-search/navigation-search.svg +3 -0
- package/src/wcs/components/product-catalogue/icon-back.svg +3 -0
- package/src/wcs/components/product-catalogue/icon-cart.svg +3 -0
- package/src/wcs/components/product-catalogue/icon-close.svg +3 -0
- package/src/wcs/components/product-catalogue/product-catalogue.js +215 -0
- package/src/wcs/components/product-links/icon-cart.svg +3 -0
- package/src/wcs/components/product-links/product-links.js +43 -0
- package/src/wcs/components/rio-iframe/rio-iframe.js +137 -0
- package/src/wcs/components/salsify-products/filter-tools.js +60 -0
- package/src/wcs/components/salsify-products/icon-cart.svg +3 -0
- package/src/wcs/components/salsify-products/process-products.js +53 -0
- package/src/wcs/components/salsify-products/route-tools.js +54 -0
- package/src/wcs/components/salsify-products/salsify-products.js +281 -0
- package/src/wcs/components/shout-out/shout-out.js +51 -0
- package/src/wcs/components/simple-chart/simple-chart.js +53 -0
- package/src/wcs/components/single-stat/single-stat.js +85 -0
- package/src/wcs/components/site-feedback/site-feedback.js +56 -0
- package/src/wcs/components/skds-search/navigation-search.svg +3 -0
- package/src/wcs/components/skds-search/skds-search.js +103 -0
- package/src/wcs/components/smart-banner/smart-banner.js +104 -0
- package/src/wcs/components/standalone-table/arrow-up-down.svg +3 -0
- package/src/wcs/components/standalone-table/arrow-up.svg +3 -0
- package/src/wcs/components/standalone-table/standalone-table.js +440 -0
- package/src/wcs/components/station-locator/station-locator.js +49 -0
- package/src/wcs/components/store-badges/badges.js +60 -0
- package/src/wcs/components/store-badges/store-badges.js +93 -0
- package/src/wcs/components/topbar-button/person.svg +1 -0
- package/src/wcs/components/topbar-button/topbar-button.js +22 -0
- package/src/wcs/components/universal-gallery/universal-gallery.js +308 -0
- package/src/wcs/components/zendesk-chat/zendesk-chat.js +133 -0
- package/src/wcs/shared/chat-bot/README.md +61 -0
- package/src/wcs/shared/chat-bot/chat-bot.js +216 -0
- package/src/wcs/shared/chat-bot/resources/arrow-next.svg +1 -0
- package/src/wcs/shared/chat-bot/resources/navigation-close.svg +1 -0
- package/src/wcs/shared/chat-bot/resources/person.svg +1 -0
- package/src/wcs/shared/chat-bot/resources/upload.svg +1 -0
- package/src/wcs/shared/filtered-data/README.md +52 -0
- package/src/wcs/shared/filtered-data/fetch-data.js +33 -0
- package/src/wcs/shared/filtered-data/filtered-data.js +337 -0
- package/src/wcs/shared/promo-with-popup/icon-close.svg +3 -0
- package/src/wcs/shared/promo-with-popup/icon-next.svg +3 -0
- package/src/wcs/shared/promo-with-popup/icon-prev.svg +3 -0
- 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')
|