@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
@@ -9,6 +9,11 @@ import { Select } from './select';
9
9
 
10
10
  interface CurrencySelectProps {
11
11
  className?: string;
12
+ showIcon?: boolean;
13
+ /** Custom SVG icon content */
14
+ customIcon?: string;
15
+ /** Label format: 'full' (US Dollar), 'symbol' ($), 'code' (USD) */
16
+ labelFormat?: 'full' | 'symbol' | 'code';
12
17
  }
13
18
 
14
19
  export const CurrencySelect = (props: CurrencySelectProps) => {
@@ -17,6 +22,98 @@ export const CurrencySelect = (props: CurrencySelectProps) => {
17
22
  const { t, currency, setCurrency } = useLocalization();
18
23
  const { currencies } = settings.localization;
19
24
 
25
+ // Currency symbol map for common currencies
26
+ const currencySymbolMap: Record<string, string> = {
27
+ usd: '$',
28
+ eur: '€',
29
+ gbp: '£',
30
+ try: '₺',
31
+ jpy: '¥',
32
+ cny: '¥',
33
+ krw: '₩',
34
+ inr: '₹',
35
+ rub: '₽',
36
+ brl: 'R$',
37
+ aud: 'A$',
38
+ cad: 'C$',
39
+ chf: 'CHF',
40
+ sek: 'kr',
41
+ nok: 'kr',
42
+ dkk: 'kr',
43
+ pln: 'zł',
44
+ mxn: '$',
45
+ sgd: 'S$',
46
+ hkd: 'HK$',
47
+ nzd: 'NZ$',
48
+ zar: 'R',
49
+ aed: 'د.إ',
50
+ sar: '﷼',
51
+ thb: '฿',
52
+ myr: 'RM',
53
+ php: '₱',
54
+ idr: 'Rp',
55
+ vnd: '₫',
56
+ egp: 'E£',
57
+ ngn: '₦',
58
+ pkr: '₨',
59
+ bdt: '৳',
60
+ cop: '$',
61
+ ars: '$',
62
+ clp: '$',
63
+ pen: 'S/',
64
+ ils: '₪',
65
+ kwd: 'د.ك',
66
+ qar: '﷼',
67
+ bhd: '.د.ب',
68
+ omr: '﷼'
69
+ };
70
+
71
+ // Currency full name map for common currencies
72
+ const currencyNameMap: Record<string, string> = {
73
+ usd: 'US Dollar',
74
+ eur: 'Euro',
75
+ gbp: 'British Pound',
76
+ try: 'Turkish Lira',
77
+ jpy: 'Japanese Yen',
78
+ cny: 'Chinese Yuan',
79
+ krw: 'South Korean Won',
80
+ inr: 'Indian Rupee',
81
+ rub: 'Russian Ruble',
82
+ brl: 'Brazilian Real',
83
+ aud: 'Australian Dollar',
84
+ cad: 'Canadian Dollar',
85
+ chf: 'Swiss Franc',
86
+ sek: 'Swedish Krona',
87
+ nok: 'Norwegian Krone',
88
+ dkk: 'Danish Krone',
89
+ pln: 'Polish Zloty',
90
+ mxn: 'Mexican Peso',
91
+ sgd: 'Singapore Dollar',
92
+ hkd: 'Hong Kong Dollar',
93
+ nzd: 'New Zealand Dollar',
94
+ zar: 'South African Rand',
95
+ aed: 'UAE Dirham',
96
+ sar: 'Saudi Riyal',
97
+ thb: 'Thai Baht',
98
+ myr: 'Malaysian Ringgit',
99
+ php: 'Philippine Peso',
100
+ idr: 'Indonesian Rupiah',
101
+ vnd: 'Vietnamese Dong',
102
+ egp: 'Egyptian Pound',
103
+ ngn: 'Nigerian Naira',
104
+ pkr: 'Pakistani Rupee',
105
+ bdt: 'Bangladeshi Taka',
106
+ cop: 'Colombian Peso',
107
+ ars: 'Argentine Peso',
108
+ clp: 'Chilean Peso',
109
+ pen: 'Peruvian Sol',
110
+ ils: 'Israeli Shekel',
111
+ kwd: 'Kuwaiti Dinar',
112
+ qar: 'Qatari Riyal',
113
+ bhd: 'Bahraini Dinar',
114
+ omr: 'Omani Rial'
115
+ };
116
+
20
117
  const handleChange = async (e) => {
21
118
  setSelectedCurrency(e.currentTarget.value);
22
119
  setIsModalOpen(true);
@@ -26,16 +123,64 @@ export const CurrencySelect = (props: CurrencySelectProps) => {
26
123
  setCurrency(selectedCurrency);
27
124
  };
28
125
 
126
+ // Get currency symbol from map or use provided symbol
127
+ const getCurrencySymbol = (curr: { code: string; symbol?: string }) => {
128
+ if (curr.symbol) return curr.symbol;
129
+ return (
130
+ currencySymbolMap[curr.code.toLowerCase()] || curr.code.toUpperCase()
131
+ );
132
+ };
133
+
134
+ // Get currency full name from map or use provided label
135
+ const getCurrencyName = (curr: { code: string; label: string }) => {
136
+ const name = currencyNameMap[curr.code.toLowerCase()];
137
+ // If name exists in map, use it. Otherwise check if label looks like a full name (not just code)
138
+ if (name) return name;
139
+ // If label is different from code, assume it's a proper name
140
+ if (curr.label.toLowerCase() !== curr.code.toLowerCase()) return curr.label;
141
+ // Fallback to code uppercase
142
+ return curr.code.toUpperCase();
143
+ };
144
+
145
+ // Format label based on labelFormat prop
146
+ const formatLabel = (curr: {
147
+ code: string;
148
+ label: string;
149
+ symbol?: string;
150
+ }) => {
151
+ switch (props.labelFormat) {
152
+ case 'symbol':
153
+ // Return currency symbol ($, €, etc.)
154
+ return getCurrencySymbol(curr);
155
+ case 'code':
156
+ // Return currency code (USD, EUR, etc.)
157
+ return curr.code.toUpperCase();
158
+ case 'full':
159
+ default:
160
+ // Return full label (US Dollar, Euro, etc.)
161
+ return getCurrencyName(curr);
162
+ }
163
+ };
164
+
165
+ // Check if customIcon has actual SVG content
166
+ const hasValidCustomIcon =
167
+ props.customIcon &&
168
+ typeof props.customIcon === 'string' &&
169
+ props.customIcon.includes('<svg');
170
+
29
171
  return (
30
172
  <>
31
173
  <Select
32
174
  onChange={handleChange}
33
- options={currencies.map((currency) => ({
34
- value: currency.code,
35
- label: currency.label
175
+ options={currencies.map((curr) => ({
176
+ value: curr.code,
177
+ label: formatLabel(curr)
36
178
  }))}
37
179
  value={currency}
38
- icon="money"
180
+ icon={
181
+ props.showIcon !== false && !hasValidCustomIcon ? 'money' : undefined
182
+ }
183
+ customIcon={props.showIcon !== false ? props.customIcon : undefined}
39
184
  data-testid="currency"
40
185
  borderless
41
186
  className={props.className}
@@ -2,17 +2,16 @@ import { IconProps } from '@theme/components/types';
2
2
  import clsx from 'clsx';
3
3
 
4
4
  export const Icon = (props: IconProps) => {
5
- const { name, size, className, ...rest } = props;
5
+ const { name, size, className, style, ...rest } = props;
6
6
 
7
7
  return (
8
8
  <i
9
9
  className={clsx(`flex pz-icon-${name}`, className)}
10
10
  {...rest}
11
- style={
12
- size && {
13
- fontSize: `${size}px`
14
- }
15
- }
11
+ style={{
12
+ ...style,
13
+ ...(size && { fontSize: `${size}px` })
14
+ }}
16
15
  />
17
16
  );
18
17
  };
@@ -18,7 +18,8 @@ export * from './select';
18
18
  export * from './radio';
19
19
  export * from './checkbox';
20
20
  export * from './file-input';
21
-
21
+ export * from './widget-content';
22
+ export * from './action-tooltip';
22
23
  // Loaders
23
24
  export * from './loader-spinner';
24
25
  export * from './custom-loader';
@@ -39,3 +40,5 @@ export * from './skeleton-wrapper';
39
40
  // Head
40
41
  export * from './canonical-url';
41
42
  export * from './pwa-tags';
43
+ export * from './quantity-input';
44
+ export * from './quantity-selector';
@@ -1,30 +1,116 @@
1
1
  'use client';
2
2
 
3
3
  import { useLocalization } from '@akinon/next/hooks';
4
+ import { useEffect, useRef } from 'react';
4
5
  import { Select } from './select';
5
6
 
6
7
  interface LanguageSelectProps {
7
8
  className?: string;
9
+ showIcon?: boolean;
10
+ iconClassName?: string;
11
+ /** Custom SVG icon content */
12
+ customIcon?: string;
13
+ /** Label format: 'full' (English), 'short' (EN), 'code' (en) */
14
+ labelFormat?: 'full' | 'short' | 'code';
8
15
  }
9
16
 
10
17
  export const LanguageSelect = (props: LanguageSelectProps) => {
11
18
  const { locale, locales, setLocale } = useLocalization();
19
+ const isUpdatingFromParent = useRef(false);
20
+
21
+ useEffect(() => {
22
+ if (window.parent !== window && !isUpdatingFromParent.current) {
23
+ window.parent.postMessage(
24
+ {
25
+ type: 'LOCALE_UPDATE',
26
+ data: {
27
+ locale,
28
+ locales: locales.map((lang) => ({
29
+ value: lang.value,
30
+ label: lang.label
31
+ }))
32
+ }
33
+ },
34
+ '*'
35
+ );
36
+ }
37
+ }, [locale, locales]);
38
+
39
+ useEffect(() => {
40
+ const handleMessage = (event: MessageEvent) => {
41
+ if (event.data?.type === 'LOCALE_UPDATE') {
42
+ const { locale: newLocale } = event.data.data;
43
+
44
+ if (newLocale && newLocale !== locale) {
45
+ isUpdatingFromParent.current = true;
46
+ setLocale(newLocale);
47
+ setTimeout(() => {
48
+ isUpdatingFromParent.current = false;
49
+ }, 100);
50
+ }
51
+ }
52
+ };
53
+
54
+ window.addEventListener('message', handleMessage);
55
+ return () => window.removeEventListener('message', handleMessage);
56
+ }, [locale, setLocale]);
12
57
 
13
58
  const handleChange = async (e) => {
14
59
  const selectedLanguage = e.currentTarget.value;
15
60
 
16
61
  setLocale(selectedLanguage);
62
+
63
+ if (window.parent !== window) {
64
+ window.parent.postMessage(
65
+ {
66
+ type: 'LOCALE_UPDATE',
67
+ data: {
68
+ locale: selectedLanguage,
69
+ locales: locales.map((lang) => ({
70
+ value: lang.value,
71
+ label: lang.label
72
+ }))
73
+ }
74
+ },
75
+ '*'
76
+ );
77
+ }
78
+ };
79
+
80
+ // Format label based on labelFormat prop
81
+ const formatLabel = (lang: { value: string; label: string }) => {
82
+ switch (props.labelFormat) {
83
+ case 'short':
84
+ // Get first 2 characters uppercase (EN, TR, etc.)
85
+ return lang.value.slice(0, 2).toUpperCase();
86
+ case 'code':
87
+ // Return locale code as-is (en, tr, etc.)
88
+ return lang.value;
89
+ case 'full':
90
+ default:
91
+ // Return full label (English, Türkçe, etc.)
92
+ return lang.label;
93
+ }
17
94
  };
18
95
 
96
+ // Check if customIcon has actual SVG content
97
+ const hasValidCustomIcon =
98
+ props.customIcon &&
99
+ typeof props.customIcon === 'string' &&
100
+ props.customIcon.includes('<svg');
101
+
19
102
  return (
20
103
  <Select
21
104
  onChange={handleChange}
22
105
  options={locales.map((lang) => ({
23
106
  value: lang.value,
24
- label: lang.label
107
+ label: formatLabel(lang)
25
108
  }))}
26
109
  value={locale}
27
- icon="globe"
110
+ icon={
111
+ props.showIcon !== false && !hasValidCustomIcon ? 'globe' : undefined
112
+ }
113
+ customIcon={props.showIcon !== false ? props.customIcon : undefined}
28
114
  data-testid="language"
29
115
  borderless
30
116
  className={props.className}
@@ -8,6 +8,14 @@ import { useLocalization } from '@akinon/next/hooks';
8
8
  import { useRouter } from '@akinon/next/hooks';
9
9
  import { useInView } from 'react-intersection-observer';
10
10
 
11
+ const parseSafeUrl = (url: string) => {
12
+ try {
13
+ return new URL(url, window.location.origin);
14
+ } catch {
15
+ return null;
16
+ }
17
+ };
18
+
11
19
  export const Pagination = (props: PaginationProps) => {
12
20
  const { t } = useLocalization();
13
21
  const router = useRouter();
@@ -26,7 +34,8 @@ export const Pagination = (props: PaginationProps) => {
26
34
  onPageChange,
27
35
  direction,
28
36
  render,
29
- isLoading
37
+ isLoading,
38
+ customStyles
30
39
  } = props;
31
40
 
32
41
  const pagination = usePagination(total, limit, currentPage, numberOfPages);
@@ -96,7 +105,11 @@ export const Pagination = (props: PaginationProps) => {
96
105
  const handleClick = (e: MouseEvent<HTMLAnchorElement>, url: string) => {
97
106
  e.preventDefault();
98
107
 
99
- const newUrl = new URL(url, window.location.origin);
108
+ const newUrl = parseSafeUrl(url);
109
+ if (!newUrl) {
110
+ return;
111
+ }
112
+
100
113
  const page = newUrl.searchParams.get('page');
101
114
 
102
115
  if (page === '1') {
@@ -117,7 +130,21 @@ export const Pagination = (props: PaginationProps) => {
117
130
  setNextPage(changingPage);
118
131
  }
119
132
 
120
- onPageChange(changingPage);
133
+ // Navigate to new page using router if onPageChange is not provided
134
+ if (onPageChange) {
135
+ onPageChange(changingPage);
136
+ } else {
137
+ const currentUrl = parseSafeUrl(window.location.href);
138
+ if (!currentUrl) {
139
+ return;
140
+ }
141
+
142
+ currentUrl.searchParams.set('page', String(changingPage));
143
+ if (changingPage === 1) {
144
+ currentUrl.searchParams.delete('page');
145
+ }
146
+ router.push(currentUrl.pathname + currentUrl.search, undefined);
147
+ }
121
148
  };
122
149
 
123
150
  useEffect(() => {
@@ -156,11 +183,77 @@ export const Pagination = (props: PaginationProps) => {
156
183
  return <>{render(pagination)}</>;
157
184
  }
158
185
 
186
+ // Custom styles for pagination container
187
+ const containerStyles: React.CSSProperties = {
188
+ marginTop: customStyles?.marginTop || '32px',
189
+ marginBottom: customStyles?.marginBottom || '16px',
190
+ gap: customStyles?.pageGap || '8px'
191
+ };
192
+
193
+ // Custom styles for page links (button-like)
194
+ const pageStyles: React.CSSProperties = {
195
+ fontSize: customStyles?.pageFontSize || '14px',
196
+ color: customStyles?.pageColor || '#9ca3af',
197
+ backgroundColor: customStyles?.pageBgColor || 'transparent',
198
+ paddingLeft: customStyles?.pagePaddingX || '8px',
199
+ paddingRight: customStyles?.pagePaddingX || '8px',
200
+ paddingTop: customStyles?.pagePaddingY || '4px',
201
+ paddingBottom: customStyles?.pagePaddingY || '4px',
202
+ borderRadius: customStyles?.pageBorderRadius || '0px',
203
+ borderWidth: customStyles?.pageBorderWidth || '0px',
204
+ borderStyle: 'solid',
205
+ borderColor: customStyles?.pageBorderColor || '#e5e7eb',
206
+ display: 'flex',
207
+ alignItems: 'center',
208
+ justifyContent: 'center'
209
+ };
210
+
211
+ const activePageStyles: React.CSSProperties = {
212
+ fontSize: customStyles?.pageFontSize || '14px',
213
+ color: customStyles?.pageActiveColor || '#1a1a1a',
214
+ fontWeight: customStyles?.pageActiveFontWeight || '600',
215
+ backgroundColor: customStyles?.pageActiveBgColor || 'transparent',
216
+ paddingLeft: customStyles?.pagePaddingX || '8px',
217
+ paddingRight: customStyles?.pagePaddingX || '8px',
218
+ paddingTop: customStyles?.pagePaddingY || '4px',
219
+ paddingBottom: customStyles?.pagePaddingY || '4px',
220
+ borderRadius: customStyles?.pageBorderRadius || '0px',
221
+ borderWidth: customStyles?.pageBorderWidth || '0px',
222
+ borderStyle: 'solid',
223
+ borderColor: customStyles?.pageActiveBorderColor || '#1a1a1a',
224
+ display: 'flex',
225
+ alignItems: 'center',
226
+ justifyContent: 'center'
227
+ };
228
+
229
+ const arrowStyles: React.CSSProperties = {
230
+ color: customStyles?.arrowColor || '#1a1a1a'
231
+ };
232
+
233
+ // Custom styles for more button
234
+ const buttonStyles: React.CSSProperties = {
235
+ backgroundColor: customStyles?.buttonBgColor || '#000000',
236
+ color: customStyles?.buttonTextColor || '#ffffff',
237
+ borderRadius: customStyles?.buttonBorderRadius || '4px',
238
+ paddingLeft: customStyles?.buttonPaddingX || '20px',
239
+ paddingRight: customStyles?.buttonPaddingX || '20px',
240
+ paddingTop: customStyles?.buttonPaddingY || '12px',
241
+ paddingBottom: customStyles?.buttonPaddingY || '12px'
242
+ };
243
+
159
244
  return direction === 'prev' && type !== 'list' ? (
160
245
  <>
161
- <div className="flex items-center justify-center">
246
+ <div
247
+ className="flex items-center justify-center"
248
+ style={{
249
+ marginTop: customStyles?.marginTop,
250
+ marginBottom: customStyles?.marginBottom
251
+ }}
252
+ data-section-id="pagination-section"
253
+ >
162
254
  <Button
163
255
  className={twMerge('px-5', moreButtonClassName)}
256
+ style={buttonStyles}
164
257
  onClick={() => handlePageChange()}
165
258
  >
166
259
  {isLoading ? (
@@ -174,15 +267,26 @@ export const Pagination = (props: PaginationProps) => {
174
267
  ) : (
175
268
  <>
176
269
  {type === 'more' && (
177
- <div className="flex items-center justify-center">
270
+ <div
271
+ className="flex items-center justify-center"
272
+ style={{
273
+ marginTop: customStyles?.marginTop,
274
+ marginBottom: customStyles?.marginBottom
275
+ }}
276
+ data-section-id="pagination-section"
277
+ >
178
278
  <Button
179
279
  className={twMerge(
180
- 'px-5',
181
- Number(nextPage) === Number(last)
182
- ? 'bg-gray-600 border-gray-600 pointer-events-none'
183
- : 'bg-black',
280
+ Number(nextPage) === Number(last) ? 'pointer-events-none' : '',
184
281
  moreButtonClassName
185
282
  )}
283
+ style={{
284
+ ...buttonStyles,
285
+ backgroundColor:
286
+ Number(nextPage) === Number(last)
287
+ ? '#6b7280'
288
+ : customStyles?.buttonBgColor || '#000000'
289
+ }}
186
290
  onClick={() => handlePageChange()}
187
291
  disabled={Number(nextPage) === Number(last)}
188
292
  >
@@ -210,9 +314,11 @@ export const Pagination = (props: PaginationProps) => {
210
314
  {type === 'list' && (
211
315
  <ul
212
316
  className={twMerge(
213
- 'mb-4 mt-8 flex items-center justify-center',
317
+ 'flex items-center justify-center',
214
318
  containerClassName
215
319
  )}
320
+ style={containerStyles}
321
+ data-section-id="pagination-section"
216
322
  >
217
323
  {prev && currentPage !== 1 && (
218
324
  <li>
@@ -220,9 +326,10 @@ export const Pagination = (props: PaginationProps) => {
220
326
  onClick={(e) => handleClick(e, prev)}
221
327
  href={prev}
222
328
  className={twMerge(
223
- 'flex cursor-pointer px-2 text-sm items-center',
329
+ 'flex cursor-pointer px-2 items-center',
224
330
  prevClassName
225
331
  )}
332
+ style={arrowStyles}
226
333
  >
227
334
  <span>&lt;</span>
228
335
  <span className="ms-4 hidden lg:inline-block">
@@ -239,20 +346,24 @@ export const Pagination = (props: PaginationProps) => {
239
346
  onClick={(e) => handleClick(e, item.url)}
240
347
  href={item.url}
241
348
  className={twMerge(
242
- clsx(
243
- 'cursor-pointer px-2 text-xs items-center',
244
- { 'pointer-events-none': item.url === null },
245
- Number(page) === Number(item?.page)
246
- ? 'font-semibold text-black-800'
247
- : 'text-gray-400'
248
- ),
349
+ clsx('cursor-pointer px-2 items-center', {
350
+ 'pointer-events-none': item.url === null
351
+ }),
249
352
  pageClassName
250
353
  )}
354
+ style={
355
+ Number(page) === Number(item?.page)
356
+ ? activePageStyles
357
+ : pageStyles
358
+ }
251
359
  >
252
360
  {item?.page}
253
361
  </Link>
254
362
  ) : (
255
- <span className="flex cursor-default items-center justify-center text-xs">
363
+ <span
364
+ className="flex cursor-default items-center justify-center"
365
+ style={pageStyles}
366
+ >
256
367
  {item?.page}
257
368
  </span>
258
369
  )}
@@ -265,9 +376,10 @@ export const Pagination = (props: PaginationProps) => {
265
376
  onClick={(e) => handleClick(e, next)}
266
377
  href={next}
267
378
  className={twMerge(
268
- 'flex cursor-pointer px-2 text-xs items-center',
379
+ 'flex cursor-pointer px-2 items-center',
269
380
  nextClassName
270
381
  )}
382
+ style={arrowStyles}
271
383
  >
272
384
  <span className="me-4 hidden lg:inline-block">
273
385
  {t('category.pagination.next')}
@@ -0,0 +1,63 @@
1
+ import { Button, Icon, LoaderSpinner } from '@theme/components';
2
+
3
+ interface QuantityInputProps {
4
+ quantity: number;
5
+ onChange?: (newQuantity: number) => void;
6
+ min?: number;
7
+ max?: number;
8
+ isLoading?: boolean;
9
+ disabled?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ export const QuantityInput = ({
14
+ quantity,
15
+ onChange,
16
+ min = 1,
17
+ max = 999,
18
+ isLoading = false,
19
+ disabled = false,
20
+ className = ''
21
+ }: QuantityInputProps) => {
22
+ const handleDecrease = () => {
23
+ if (quantity > min && onChange) {
24
+ onChange(quantity - 1);
25
+ }
26
+ };
27
+
28
+ const handleIncrease = () => {
29
+ if (quantity < max && onChange) {
30
+ onChange(quantity + 1);
31
+ }
32
+ };
33
+
34
+ return (
35
+ <div
36
+ className={`w-[138px] h-11 flex items-center justify-between border p-4 ${className}`}
37
+ >
38
+ <Button
39
+ className="h-auto p-0 hover:bg-transparent hover:text-black"
40
+ appearance="ghost"
41
+ onClick={handleDecrease}
42
+ disabled={disabled || isLoading || quantity <= min}
43
+ >
44
+ <Icon name="minus" size={12} />
45
+ </Button>
46
+ <div>
47
+ {isLoading ? (
48
+ <LoaderSpinner className="w-4 h-4" />
49
+ ) : (
50
+ <span>{quantity}</span>
51
+ )}
52
+ </div>
53
+ <Button
54
+ className="h-auto p-0 hover:bg-transparent hover:text-black"
55
+ appearance="ghost"
56
+ onClick={handleIncrease}
57
+ disabled={disabled || isLoading || quantity >= max}
58
+ >
59
+ <Icon name="plus" size={12} />
60
+ </Button>
61
+ </div>
62
+ );
63
+ };