@akinon/projectzero 2.0.0-beta.19 → 2.0.0-beta.20

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 (223) hide show
  1. package/CHANGELOG.md +9 -7
  2. package/app-template/CHANGELOG.md +251 -204
  3. package/app-template/akinon.json +1 -1
  4. package/app-template/package.json +28 -28
  5. package/app-template/public/amex.svg +12 -0
  6. package/app-template/public/apple-pay.svg +16 -0
  7. package/app-template/public/assets/images/product-placeholder-1.jpg +0 -0
  8. package/app-template/public/assets/images/product-placeholder-2.jpg +0 -0
  9. package/app-template/public/assets/images/product-placeholder-3.jpg +0 -0
  10. package/app-template/public/assets/images/product-placeholder-4.jpg +0 -0
  11. package/app-template/public/google-pay.svg +16 -0
  12. package/app-template/public/locales/en/account.json +6 -3
  13. package/app-template/public/locales/en/auth.json +6 -7
  14. package/app-template/public/locales/en/basket.json +6 -6
  15. package/app-template/public/locales/en/blog.json +7 -0
  16. package/app-template/public/locales/en/category.json +3 -1
  17. package/app-template/public/locales/en/checkout.json +5 -4
  18. package/app-template/public/locales/en/common.json +11 -2
  19. package/app-template/public/locales/en/forgot_password.json +6 -7
  20. package/app-template/public/locales/en/product.json +4 -3
  21. package/app-template/public/locales/tr/account.json +6 -3
  22. package/app-template/public/locales/tr/auth.json +16 -17
  23. package/app-template/public/locales/tr/basket.json +4 -4
  24. package/app-template/public/locales/tr/blog.json +7 -0
  25. package/app-template/public/locales/tr/category.json +3 -1
  26. package/app-template/public/locales/tr/checkout.json +39 -38
  27. package/app-template/public/locales/tr/common.json +10 -1
  28. package/app-template/public/locales/tr/forgot_password.json +12 -13
  29. package/app-template/public/locales/tr/product.json +1 -0
  30. package/app-template/public/logo.svg +3 -27
  31. package/app-template/public/mastercard.svg +14 -0
  32. package/app-template/public/promotion-banner.jpg +0 -0
  33. package/app-template/public/shop-pay.svg +12 -0
  34. package/app-template/public/visa.svg +12 -0
  35. package/app-template/src/app/[commerce]/[locale]/[currency]/blog/[slug]/page.tsx +118 -0
  36. package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +15 -0
  37. package/app-template/src/app/api/theme-settings/route.ts +12 -0
  38. package/app-template/src/assets/fonts/pz-icon.css +211 -49
  39. package/app-template/src/assets/fonts/pz-icon.eot +0 -0
  40. package/app-template/src/assets/fonts/pz-icon.html +486 -0
  41. package/app-template/src/assets/fonts/pz-icon.scss +373 -49
  42. package/app-template/src/assets/fonts/pz-icon.svg +215 -53
  43. package/app-template/src/assets/fonts/pz-icon.ttf +0 -0
  44. package/app-template/src/assets/fonts/pz-icon.woff +0 -0
  45. package/app-template/src/assets/fonts/pz-icon.woff2 +0 -0
  46. package/app-template/src/assets/globals.scss +4 -0
  47. package/app-template/src/assets/icons/arrow-right.svg +3 -0
  48. package/app-template/src/assets/icons/cart.svg +4 -12
  49. package/app-template/src/assets/icons/check.svg +2 -18
  50. package/app-template/src/assets/icons/chevron-down.svg +2 -7
  51. package/app-template/src/assets/icons/delete.svg +3 -0
  52. package/app-template/src/assets/icons/facebook.svg +2 -8
  53. package/app-template/src/assets/icons/fav-off.svg +5 -0
  54. package/app-template/src/assets/icons/fav-on.svg +5 -0
  55. package/app-template/src/assets/icons/filter-and-sort.svg +3 -0
  56. package/app-template/src/assets/icons/heart.svg +3 -0
  57. package/app-template/src/assets/icons/instagram.svg +2 -13
  58. package/app-template/src/assets/icons/materials.svg +3 -0
  59. package/app-template/src/assets/icons/person.svg +4 -0
  60. package/app-template/src/assets/icons/pinterest.svg +5 -11
  61. package/app-template/src/assets/icons/ruler.svg +3 -0
  62. package/app-template/src/assets/icons/search.svg +8 -11
  63. package/app-template/src/assets/icons/share.svg +2 -9
  64. package/app-template/src/assets/icons/snapchat.svg +3 -0
  65. package/app-template/src/assets/icons/tiktok.svg +3 -0
  66. package/app-template/src/assets/icons/tumblr.svg +6 -0
  67. package/app-template/src/assets/icons/twitter.svg +2 -10
  68. package/app-template/src/assets/icons/vimeo.svg +3 -0
  69. package/app-template/src/assets/icons/youtube.svg +3 -0
  70. package/app-template/src/assets/icons/zoom.svg +8 -0
  71. package/app-template/src/components/accordion.tsx +33 -11
  72. package/app-template/src/components/action-tooltip.tsx +160 -0
  73. package/app-template/src/components/currency-select.tsx +149 -4
  74. package/app-template/src/components/icon.tsx +5 -6
  75. package/app-template/src/components/index.ts +4 -1
  76. package/app-template/src/components/language-select.tsx +88 -2
  77. package/app-template/src/components/pagination.tsx +132 -20
  78. package/app-template/src/components/quantity-input.tsx +63 -0
  79. package/app-template/src/components/quantity-selector.tsx +203 -0
  80. package/app-template/src/components/route-handler.tsx +50 -0
  81. package/app-template/src/components/select.tsx +89 -69
  82. package/app-template/src/components/types/index.ts +26 -0
  83. package/app-template/src/components/widget-content.tsx +323 -0
  84. package/app-template/src/data/server/theme.ts +70 -0
  85. package/app-template/src/hooks/use-fav-button.tsx +5 -2
  86. package/app-template/src/hooks/use-product-cart.ts +11 -8
  87. package/app-template/src/hooks/use-theme-settings.ts +42 -0
  88. package/app-template/src/lib/fonts.ts +149 -0
  89. package/app-template/src/settings.js +2 -2
  90. package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
  91. package/app-template/src/types/widget.ts +169 -0
  92. package/app-template/src/utils/formatDate.ts +48 -0
  93. package/app-template/src/utils/styles.ts +71 -0
  94. package/app-template/src/views/account/contact-form.tsx +147 -130
  95. package/app-template/src/views/basket/basket-item.tsx +691 -107
  96. package/app-template/src/views/basket/basket-summary-context.tsx +560 -0
  97. package/app-template/src/views/basket/designer-context.tsx +617 -0
  98. package/app-template/src/views/basket/index.ts +2 -0
  99. package/app-template/src/views/basket/summary.tsx +496 -75
  100. package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +190 -0
  101. package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +286 -0
  102. package/app-template/src/views/breadcrumb/constants.ts +15 -0
  103. package/app-template/src/views/breadcrumb/index.tsx +127 -0
  104. package/app-template/src/views/breadcrumb.tsx +13 -38
  105. package/app-template/src/views/category/category-banner.tsx +4 -23
  106. package/app-template/src/views/category/category-header.tsx +289 -66
  107. package/app-template/src/views/category/category-info.tsx +173 -24
  108. package/app-template/src/views/category/filters/filter-item.tsx +138 -42
  109. package/app-template/src/views/category/filters/index.tsx +208 -48
  110. package/app-template/src/views/category/layout.tsx +7 -4
  111. package/app-template/src/views/category/native-widget-context.tsx +257 -0
  112. package/app-template/src/views/category/product-list-registrar.tsx +665 -0
  113. package/app-template/src/views/checkout/auth.tsx +64 -40
  114. package/app-template/src/views/checkout/checkout-address-registrar.tsx +254 -0
  115. package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +183 -0
  116. package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +259 -0
  117. package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +253 -0
  118. package/app-template/src/views/checkout/checkout-summary-registrar.tsx +183 -0
  119. package/app-template/src/views/checkout/constants.ts +5 -0
  120. package/app-template/src/views/checkout/index.tsx +5 -0
  121. package/app-template/src/views/checkout/layout/header.tsx +9 -5
  122. package/app-template/src/views/checkout/steps/payment/index.tsx +5 -2
  123. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +72 -1
  124. package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
  125. package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
  126. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +171 -40
  127. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +74 -12
  128. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +128 -45
  129. package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
  130. package/app-template/src/views/checkout/summary.tsx +303 -29
  131. package/app-template/src/views/footer/footer-app-banner-context.tsx +326 -0
  132. package/app-template/src/views/footer/footer-bottom-context.tsx +215 -0
  133. package/app-template/src/views/footer/footer-bottom-wrapper.tsx +74 -0
  134. package/app-template/src/views/footer/footer-layout-constants.ts +35 -0
  135. package/app-template/src/views/footer/footer-layout-registrar.tsx +342 -0
  136. package/app-template/src/views/footer/footer-layout-switcher.tsx +110 -0
  137. package/app-template/src/views/footer/footer-menu-context.tsx +211 -0
  138. package/app-template/src/views/footer/footer-native-widgets.tsx +60 -0
  139. package/app-template/src/views/footer/footer-social-context.tsx +254 -0
  140. package/app-template/src/views/footer/footer-subscription-context.tsx +210 -0
  141. package/app-template/src/views/footer/footer-utils.ts +43 -0
  142. package/app-template/src/views/footer/footer-value-props-context.tsx +326 -0
  143. package/app-template/src/views/footer/logo-settings.ts +183 -0
  144. package/app-template/src/views/footer/native-widget-config.ts +262 -0
  145. package/app-template/src/views/footer/subscription-settings.ts +122 -0
  146. package/app-template/src/views/footer/use-footer-logo.ts +162 -0
  147. package/app-template/src/views/footer.tsx +415 -13
  148. package/app-template/src/views/guest-login/index.tsx +62 -58
  149. package/app-template/src/views/header/action-menu.tsx +277 -45
  150. package/app-template/src/views/header/band.tsx +6 -21
  151. package/app-template/src/views/header/designer-context.tsx +261 -0
  152. package/app-template/src/views/header/header-announcement-registrar.tsx +267 -0
  153. package/app-template/src/views/header/header-client-wrapper.tsx +496 -0
  154. package/app-template/src/views/header/header-content.tsx +1026 -0
  155. package/app-template/src/views/header/header-currency-registrar.tsx +348 -0
  156. package/app-template/src/views/header/header-icons-context.tsx +262 -0
  157. package/app-template/src/views/header/header-language-registrar.tsx +348 -0
  158. package/app-template/src/views/header/header-layout-context.tsx +143 -0
  159. package/app-template/src/views/header/header-layout-registrar.tsx +658 -0
  160. package/app-template/src/views/header/header-logo-context.tsx +228 -0
  161. package/app-template/src/views/header/header-logo.tsx +118 -0
  162. package/app-template/src/views/header/header-mini-basket-context.tsx +524 -0
  163. package/app-template/src/views/header/header-search-registrar.tsx +511 -0
  164. package/app-template/src/views/header/header-text-slider-registrar.tsx +382 -0
  165. package/app-template/src/views/header/index.tsx +109 -47
  166. package/app-template/src/views/header/inline-search.tsx +262 -0
  167. package/app-template/src/views/header/mini-basket.tsx +819 -44
  168. package/app-template/src/views/header/mobile-hamburger-button.tsx +5 -8
  169. package/app-template/src/views/header/mobile-menu.tsx +12 -0
  170. package/app-template/src/views/header/navbar-menu-context.tsx +219 -0
  171. package/app-template/src/views/header/navbar.tsx +178 -111
  172. package/app-template/src/views/header/search/index.tsx +71 -32
  173. package/app-template/src/views/header/search/results.tsx +127 -65
  174. package/app-template/src/views/header/search/search-input.tsx +61 -0
  175. package/app-template/src/views/header/server-settings-parser.ts +1105 -0
  176. package/app-template/src/views/header/use-header-icons.ts +241 -0
  177. package/app-template/src/views/header/use-header-logo.ts +213 -0
  178. package/app-template/src/views/header/use-navbar-menu.ts +179 -0
  179. package/app-template/src/views/login/index.tsx +54 -46
  180. package/app-template/src/views/product/accordion-section.tsx +61 -0
  181. package/app-template/src/views/product/accordion-wrapper.tsx +135 -43
  182. package/app-template/src/views/product/custom-button-group.tsx +69 -0
  183. package/app-template/src/views/product/favorites-button-section.tsx +69 -0
  184. package/app-template/src/views/product/find-in-store-section.tsx +60 -0
  185. package/app-template/src/views/product/index.ts +1 -0
  186. package/app-template/src/views/product/layout.tsx +6 -5
  187. package/app-template/src/views/product/misc-buttons.tsx +339 -25
  188. package/app-template/src/views/product/price-wrapper.tsx +3 -29
  189. package/app-template/src/views/product/product-actions.tsx +137 -8
  190. package/app-template/src/views/product/product-info-section.tsx +140 -0
  191. package/app-template/src/views/product/product-info.tsx +69 -31
  192. package/app-template/src/views/product/product-share.tsx +13 -8
  193. package/app-template/src/views/product/product-variants.tsx +2 -2
  194. package/app-template/src/views/product/quantity-section.tsx +73 -0
  195. package/app-template/src/views/product/sale-tag.tsx +10 -0
  196. package/app-template/src/views/product/share-section.tsx +357 -0
  197. package/app-template/src/views/product/slider.tsx +117 -79
  198. package/app-template/src/views/product/variant.tsx +69 -41
  199. package/app-template/src/views/product/variants-section.tsx +126 -0
  200. package/app-template/src/views/product-detail/constants.ts +272 -0
  201. package/app-template/src/views/product-detail/index.ts +10 -0
  202. package/app-template/src/views/product-detail/product-detail-registrar.tsx +616 -0
  203. package/app-template/src/views/product-item/index.tsx +119 -46
  204. package/app-template/src/views/register/index.tsx +14 -25
  205. package/app-template/src/views/share/index.tsx +9 -6
  206. package/app-template/src/views/widgets/home-hero-slider-content.tsx +41 -39
  207. package/app-template/src/widgets/flatpages/about-us/index.tsx +78 -0
  208. package/app-template/src/widgets/flatpages/blog-list/index.tsx +129 -0
  209. package/app-template/src/widgets/footer-app-banner.tsx +444 -0
  210. package/app-template/src/widgets/footer-bottom.tsx +127 -0
  211. package/app-template/src/widgets/footer-menu-compact.tsx +238 -0
  212. package/app-template/src/widgets/footer-menu-two.tsx +298 -0
  213. package/app-template/src/widgets/footer-social-client.tsx +251 -0
  214. package/app-template/src/widgets/footer-social.tsx +47 -16
  215. package/app-template/src/widgets/footer-subscription/footer-subscription-form.tsx +17 -14
  216. package/app-template/src/widgets/footer-subscription/index.tsx +183 -17
  217. package/app-template/src/widgets/footer-value-props.tsx +201 -0
  218. package/app-template/src/widgets/index.ts +7 -0
  219. package/app-template/src/widgets/schemas/about-us.json +46 -0
  220. package/app-template/src/widgets/schemas/blog-list.json +37 -0
  221. package/app-template/src/widgets/schemas/blog.json +29 -0
  222. package/app-template/tailwind.config.js +18 -2
  223. package/package.json +1 -1
@@ -1,16 +1,20 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useRef, useState } from 'react';
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
5
5
  import { closeSearch } from '@akinon/next/redux/reducers/header';
6
6
  import clsx from 'clsx';
7
7
 
8
8
  import { Icon } from '@theme/components';
9
+ import { SearchInput } from './search-input';
9
10
  import Results from './results';
10
11
  import { ROUTES } from '@theme/routes';
11
- import { useLocalization, useRouter } from '@akinon/next/hooks';
12
+ import { useDebounce, useLocalization, useRouter } from '@akinon/next/hooks';
13
+ import { useAutocompleteQuery } from '@akinon/next/data/client/misc';
12
14
  import PluginModule, { Component } from '@akinon/next/components/plugin-module';
13
15
 
16
+ const MINIMUM_SEARCH_LENGTH = 3;
17
+
14
18
  export default function Search() {
15
19
  const { t } = useLocalization();
16
20
  const router = useRouter();
@@ -19,6 +23,24 @@ export default function Search() {
19
23
  const [searchText, setSearchText] = useState('');
20
24
  const inputRef = useRef<HTMLInputElement>(null);
21
25
 
26
+ const debouncedSearchText = useDebounce(searchText, 400);
27
+ const { currentData } = useAutocompleteQuery(debouncedSearchText, {
28
+ refetchOnMountOrArgChange: true,
29
+ skip: debouncedSearchText.length < MINIMUM_SEARCH_LENGTH
30
+ });
31
+
32
+ const categoryLabel = useMemo(() => {
33
+ const firstCategory = currentData?.groups.find(
34
+ (group) => group.suggestion_type === 'Category'
35
+ )?.entries?.[0];
36
+
37
+ if (firstCategory?.label) {
38
+ return firstCategory.label;
39
+ }
40
+
41
+ return t('common.search.all_products');
42
+ }, [currentData, t]);
43
+
22
44
  useEffect(() => {
23
45
  if (isSearchOpen) {
24
46
  inputRef.current?.focus();
@@ -42,11 +64,19 @@ export default function Search() {
42
64
  };
43
65
  }, [isSearchOpen, dispatch]);
44
66
 
67
+ const handleSearch = () => {
68
+ if (searchText.trim() !== '') {
69
+ router.push(
70
+ `${ROUTES.LIST}/?search_text=${encodeURIComponent(searchText)}`
71
+ );
72
+ dispatch(closeSearch());
73
+ }
74
+ };
75
+
45
76
  return (
46
77
  <>
47
78
  <div
48
79
  className={clsx(
49
- // 177px is the height of the header
50
80
  'absolute bg-black opacity-75 w-screen h-screen transition duration-500 left-0 bottom-0 translate-y-full z-30',
51
81
  isSearchOpen && searchText
52
82
  ? 'visible opacity-100'
@@ -55,44 +85,53 @@ export default function Search() {
55
85
  role="button"
56
86
  onClick={() => dispatch(closeSearch())}
57
87
  />
88
+
58
89
  <div
59
90
  className={clsx(
60
- 'absolute overflow-auto max-h-screen md:max-h-[calc(100vh-177px)] bg-white p-6 left-0 lg:bottom-0 lg:translate-y-full z-40 w-screen transition duration-500',
91
+ 'fixed inset-0 lg:absolute lg:inset-auto lg:left-0 lg:bottom-0 lg:translate-y-full lg:w-screen lg:h-auto',
92
+ 'overflow-auto h-screen lg:max-h-[calc(100vh-177px)] bg-white p-6 z-40 transition duration-500',
61
93
  isSearchOpen ? 'visible opacity-100' : 'invisible opacity-0'
62
94
  )}
63
95
  >
64
- <div className="max-w-screen-2xl mx-auto flex flex-col gap-12">
65
- <div className="border-b border-gray-400 flex flex-col py-1.5 gap-2 self-center items-center md:flex-row">
66
- <span className="text-xl lg:text-2xl">
67
- {t('common.search.results_for')}
68
- </span>
69
- <div className="flex items-center">
70
- <input
96
+ <button
97
+ className="absolute top-4 right-4 p-2 lg:hidden z-50"
98
+ onClick={() => dispatch(closeSearch())}
99
+ aria-label="Close search"
100
+ >
101
+ <Icon name="close" size={16} className="text-black" />
102
+ </button>
103
+
104
+ <div className="max-w-screen-2xl mx-auto flex flex-col gap-12 pt-8 lg:pt-0">
105
+ <div className="flex flex-col items-center gap-8">
106
+ <h1 className="text-2xl text-black-750 font-normal text-center">
107
+ {t('common.search.results_title') || 'Search results'}
108
+ </h1>
109
+
110
+ <div className="w-full max-w-[468px]">
111
+ <SearchInput
112
+ startAdornment={
113
+ <PluginModule
114
+ component={Component.HeaderImageSearchFeature}
115
+ props={{
116
+ enableTextSearch: true,
117
+ isEnabled: false,
118
+ settings: {
119
+ iconNames: {
120
+ imageSearchButton: 'hamburger'
121
+ }
122
+ }
123
+ }}
124
+ />
125
+ }
126
+ ref={inputRef}
127
+ label={categoryLabel}
71
128
  value={searchText}
72
129
  onChange={(e) => setSearchText(e.target.value)}
73
130
  onKeyDown={(e) => {
74
- if (e.key === 'Enter' && searchText.trim() !== '') {
75
- router.push(`${ROUTES.LIST}/?search_text=${searchText}`);
76
- }
131
+ if (e.key === 'Enter') handleSearch();
77
132
  }}
78
- className="border-0 text-2xl outline-none text-secondary placeholder:text-xl placeholder:lg:text-2xl"
79
- placeholder={t('common.search.placeholder')}
80
- ref={inputRef}
81
- />
82
-
83
- <PluginModule
84
- component={Component.HeaderImageSearchFeature}
85
- props={{
86
- enableTextSearch: true,
87
- isEnabled: true
88
- }}
89
- />
90
-
91
- <Icon
92
- name="close"
93
- size={14}
94
- onClick={() => dispatch(closeSearch())}
95
- className="cursor-pointer"
133
+ onSearchClick={handleSearch}
134
+ placeholder={t('common.search.placeholder') || 'Search...'}
96
135
  />
97
136
  </div>
98
137
  </div>
@@ -1,18 +1,77 @@
1
1
  'use client';
2
2
 
3
3
  import { useMemo } from 'react';
4
- import { useAutocompleteQuery } from '@akinon/next/data/client/misc';
4
+ import {
5
+ useAutocompleteQuery,
6
+ AutocompleteResponse
7
+ } from '@akinon/next/data/client/misc';
5
8
  import { ROUTES } from '@theme/routes';
6
9
 
7
- import { LoaderSpinner, Price, Link } from '@theme/components';
10
+ import { LoaderSpinner, Price, Link, Button } from '@theme/components';
8
11
  import { useDebounce, useLocalization } from '@akinon/next/hooks';
9
12
  import { Image } from '@akinon/next/components/image';
13
+ import { SaleTag } from '@theme/views/product/sale-tag';
14
+
10
15
  interface ResultsProps {
11
16
  searchText: string;
12
17
  }
13
18
 
14
19
  const MINIMUM_SEARCH_LENGTH = 3;
15
20
 
21
+ type AutocompleteProduct = AutocompleteResponse['groups'][0]['entries'][0];
22
+
23
+ function ProductCard({ product }: { product: AutocompleteProduct }) {
24
+ const productPk = (product?.extra as unknown as { pk?: number })?.pk || 0;
25
+
26
+ const isOnSale =
27
+ product?.extra?.retail_price &&
28
+ product?.extra?.price &&
29
+ parseFloat(String(product.extra.retail_price)) >
30
+ parseFloat(String(product.extra.price));
31
+ return (
32
+ <div className="flex flex-col group w-full">
33
+ <div className="relative aspect-square mb-2">
34
+ <Link href={product?.url || '#'} className="block w-full h-full">
35
+ <Image
36
+ src={product?.extra?.image || '/noimage.jpg'}
37
+ alt={product?.label || 'Product'}
38
+ fill
39
+ aspectRatio={200 / 250}
40
+ sizes="(max-width: 768px) 170px, 260px"
41
+ imageClassName="object-cover transition-transform duration-300"
42
+ />
43
+ </Link>
44
+
45
+ {isOnSale && (
46
+ <div className="absolute bottom-4 left-2 z-10">
47
+ <SaleTag />
48
+ </div>
49
+ )}
50
+ </div>
51
+
52
+ <Link
53
+ href={product?.url || '#'}
54
+ className="text-sm text-black-750 tracking-[0.28px] leading-[21px] hover:underline"
55
+ >
56
+ {product?.label}
57
+ </Link>
58
+
59
+ <div className="flex items-center gap-2">
60
+ {isOnSale && (
61
+ <Price
62
+ value={product?.extra?.retail_price}
63
+ className="text-sm text-black-750 tracking-[0.28px] leading-[21px] line-through"
64
+ />
65
+ )}
66
+ <Price
67
+ value={product?.extra?.price}
68
+ className="text-base text-black-750 tracking-[0.64px] leading-[27.52px]"
69
+ />
70
+ </div>
71
+ </div>
72
+ );
73
+ }
74
+
16
75
  export default function Results(props: ResultsProps) {
17
76
  const { searchText } = props;
18
77
  const { t } = useLocalization();
@@ -47,77 +106,80 @@ export default function Results(props: ResultsProps) {
47
106
  }
48
107
 
49
108
  if (isLoading || isFetching) {
50
- return <LoaderSpinner />;
109
+ return (
110
+ <div className="flex justify-center py-12">
111
+ <LoaderSpinner />
112
+ </div>
113
+ );
51
114
  }
52
115
 
53
116
  if (categories.length === 0 && products.length === 0) {
54
- return <p className="text-center">{t('common.search.not_found')}</p>;
117
+ return (
118
+ <p className="text-center text-black-750 py-12">
119
+ {t('common.search.not_found')}
120
+ </p>
121
+ );
55
122
  }
56
123
 
57
124
  return (
58
- <div className="w-full flex flex-wrap gap-4 md:gap-0">
59
- {categories.length > 0 && (
60
- <div className="flex flex-col w-44">
61
- <h6 className="mb-6 font-semibold">
62
- {t('common.search.categories')}
63
- </h6>
64
- <ul className="flex flex-col gap-3">
65
- {categories.map((category, index) => (
66
- <li key={index} className="text-sm">
67
- <Link href={category.url}>{category.label}</Link>
68
- </li>
69
- ))}
70
- </ul>
71
- </div>
72
- )}
73
- <div className="flex-1 flex flex-col gap-6">
74
- <h6 className="font-semibold">
75
- {t('common.search.products_for')}{' '}
76
- <span className="text-secondary uppercase">
77
- {debouncedSearchText}
78
- </span>
79
- </h6>
80
- <div className="grid grid-cols-2 sm:grid-cols-4 gap-8">
81
- {products.map((product, index) => (
82
- <Link href={product?.url} key={index} className="flex flex-col">
83
- <div className="relative aspect-[315/448]">
84
- {product.extra.image ? (
85
- <Image
86
- src={product.extra.image}
87
- alt={product?.label}
88
- aspectRatio={274.5 / 390.39}
89
- fill
90
- sizes="(min-width: 320px) 164px,
91
- (min-width: 640px) 50vw,
92
- (min-width: 1160px) 315px"
93
- />
94
- ) : (
95
- <Image
96
- className="h-full object-cover"
97
- src="/noimage.jpg"
98
- alt={product?.label}
99
- aspectRatio={274.5 / 390.39}
100
- fill
101
- sizes="100vw"
102
- />
103
- )}
104
- </div>
105
- <span className="text-sm mt-2">{product?.label}</span>
106
- <Price
107
- value={product?.extra?.price}
108
- className="font-semibold text-sm"
109
- />
110
- </Link>
111
- ))}
125
+ <div className="w-full md:px-16 pb-12">
126
+ <p className="text-center text-base text-black-750 tracking-[0.64px] leading-[15px]">
127
+ {products.length}{' '}
128
+ {t('common.search.results_found') || 'results found for'} &ldquo;
129
+ {debouncedSearchText}&rdquo;
130
+ </p>
131
+
132
+ <div className="mt-10 md:mt-16 flex justify-center">
133
+ <div className="flex flex-col lg:flex-row gap-8 max-w-[1300px]">
134
+ {categories.length > 0 && (
135
+ <div className="lg:w-44 shrink-0">
136
+ <h6 className="mb-4 text-lg font-normal text-black-750">
137
+ {t('common.search.categories')}
138
+ </h6>
139
+ <ul className="flex flex-col gap-3">
140
+ {categories.map((category, index) => (
141
+ <li key={index} className="text-sm text-black-750/75">
142
+ <Link
143
+ href={category.url}
144
+ className="hover:text-black-750 hover:underline"
145
+ >
146
+ {category.label}
147
+ </Link>
148
+ </li>
149
+ ))}
150
+ </ul>
151
+ </div>
152
+ )}
153
+
154
+ <div className="flex-1">
155
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-[10px]">
156
+ {products.slice(0, 8).map((product, index) => (
157
+ <ProductCard
158
+ key={
159
+ (product?.extra as unknown as { pk?: number })?.pk || index
160
+ }
161
+ product={product as unknown as AutocompleteProduct}
162
+ />
163
+ ))}
164
+ </div>
165
+ </div>
112
166
  </div>
113
- <Link
114
- href={`${ROUTES.LIST}/?search_text=${debouncedSearchText}`}
115
- data-testid="search-view-all"
116
- className="w-80 py-3 px-10 border border-primary text-center text-xs font-semibold hover:bg-primary hover:text-white transition-all"
117
- >
118
- {t('common.search.view_all')} {debouncedSearchText.toUpperCase()}
119
- </Link>
120
167
  </div>
168
+
169
+ {products.length > 0 && (
170
+ <div className="flex justify-center mt-8">
171
+ <Button
172
+ appearance="outlined"
173
+ href={`${ROUTES.LIST}/?search_text=${encodeURIComponent(
174
+ debouncedSearchText
175
+ )}`}
176
+ data-testid="search-view-all"
177
+ className="px-10 py-3 border-black text-black hover:bg-black hover:text-white"
178
+ >
179
+ {t('common.search.view_all')} {debouncedSearchText.toUpperCase()}
180
+ </Button>
181
+ </div>
182
+ )}
121
183
  </div>
122
184
  );
123
185
  }
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, InputHTMLAttributes } from 'react';
4
+ import clsx from 'clsx';
5
+ import { Icon } from '@theme/components';
6
+
7
+ interface SearchInputProps extends InputHTMLAttributes<HTMLInputElement> {
8
+ label?: string;
9
+ onSearchClick?: () => void;
10
+ error?: string;
11
+ startAdornment?: React.ReactNode;
12
+ }
13
+
14
+ export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
15
+ (props, ref) => {
16
+ const { label, onSearchClick, error, className, startAdornment, ...rest } =
17
+ props;
18
+
19
+ return (
20
+ <div className={clsx('flex flex-col', className)}>
21
+ <div
22
+ className={clsx(
23
+ 'relative flex items-center border h-12 px-4',
24
+ error ? 'border-error' : 'border-black'
25
+ )}
26
+ >
27
+ {startAdornment}
28
+ <div className="flex-1 flex flex-col justify-center">
29
+ {label && (
30
+ <span className="text-[10px] text-black-750 leading-none tracking-wider">
31
+ {label}
32
+ </span>
33
+ )}
34
+ <input
35
+ ref={ref}
36
+ type="text"
37
+ className={clsx(
38
+ 'w-full text-base leading-[15px] tracking-[0.64px] text-black-750',
39
+ 'bg-transparent border-none outline-none',
40
+ 'placeholder:text-black-750/75 font-jost',
41
+ label ? 'mt-0.5' : ''
42
+ )}
43
+ {...rest}
44
+ />
45
+ </div>
46
+ <button
47
+ type="button"
48
+ onClick={onSearchClick}
49
+ className="flex items-center justify-center ml-2"
50
+ aria-label="Search"
51
+ >
52
+ <Icon name="search" size={20} className="text-black" />
53
+ </button>
54
+ </div>
55
+ {error && <span className="mt-1 text-sm text-error">{error}</span>}
56
+ </div>
57
+ );
58
+ }
59
+ );
60
+
61
+ SearchInput.displayName = 'SearchInput';