@raxonltd/raxon-core 1.1.6 → 1.1.8

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 (66) hide show
  1. package/core/component/general.image.tsx +86 -0
  2. package/core/context/cart.context.tsx +446 -0
  3. package/core/context/security.context.tsx +151 -0
  4. package/core/feature/address/api/places.api.ts +76 -0
  5. package/core/feature/address/form/address-search-input.tsx +125 -0
  6. package/core/feature/address/hook/use.addres.tsx +63 -0
  7. package/core/feature/address/hook/use.address-autocomplete.ts +116 -0
  8. package/core/feature/address/util/address.types.ts +38 -0
  9. package/core/feature/address/util/parse-google-place.ts +66 -0
  10. package/core/feature/analytic-event/analytic.event.api.ts +27 -0
  11. package/core/feature/analytic-event/analytic.event.context.tsx +180 -0
  12. package/core/feature/analytic-event/analytic.event.util.ts +42 -0
  13. package/core/feature/analytic-event/use.analytic.auto.tsx +114 -0
  14. package/core/feature/article/hook/use.article.tsx +33 -0
  15. package/core/feature/attribute/hook/use.attribute.tsx +24 -0
  16. package/core/feature/auth/hook/use.auth.tsx +141 -0
  17. package/core/feature/auth/modal/modal.auth.tsx +80 -0
  18. package/core/feature/auth/view/view.login.tsx +199 -0
  19. package/core/feature/auth/view/view.register.tsx +333 -0
  20. package/core/feature/bank-account/hook/use.bank.account.tsx +47 -0
  21. package/core/feature/brand/hook/use.brand.tsx +24 -0
  22. package/core/feature/cart/component/cart.order.summary.tsx +89 -0
  23. package/core/feature/cart/component/cart.promo.code.section.tsx +208 -0
  24. package/core/feature/cart/hook/use.cart.tsx +267 -0
  25. package/core/feature/cart/util/basket-pay.response.ts +67 -0
  26. package/core/feature/cart/util/cart-optimistic.ts +425 -0
  27. package/core/feature/cart/util/garanti-payment.ts +27 -0
  28. package/core/feature/collection/hook/use.collection.tsx +32 -0
  29. package/core/feature/delivery-method/hook/use.delivery.method.tsx +40 -0
  30. package/core/feature/delivery-method/util/checkout.delivery.method.ts +11 -0
  31. package/core/feature/faq/hook/use.faq.tsx +23 -0
  32. package/core/feature/favorite/hook/use.favorite.tsx +48 -0
  33. package/core/feature/form-submit/form/form.contact.tsx +118 -0
  34. package/core/feature/form-submit/hook/use.form.submit.tsx +16 -0
  35. package/core/feature/invoice/hook/use.invoice.tsx +51 -0
  36. package/core/feature/newsletter/hook/use.newsletter.tsx +124 -0
  37. package/core/feature/newsletter/modal/modal.newsletter.product.tsx +163 -0
  38. package/core/feature/order/hook/use.order.tsx +31 -0
  39. package/core/feature/payment-method/checkout.payment.options.ts +117 -0
  40. package/core/feature/payment-method/hook/use.payment.method.tsx +44 -0
  41. package/core/feature/product/hook/use.product.tsx +122 -0
  42. package/core/feature/profile/hook/use.profile.tsx +126 -0
  43. package/core/feature/promo-code/hook/use.promo.code.tsx +27 -0
  44. package/core/interface/basket.interface.ts +360 -0
  45. package/core/interface/bootstrap.interface.ts +39 -0
  46. package/core/interface/context.interface.ts +9 -0
  47. package/core/interface/inventory.interface.ts +88 -0
  48. package/core/interface/nexine.interface.ts +4 -0
  49. package/core/interface/prisma.interface.ts +8844 -0
  50. package/core/interface/product.interface.ts +111 -0
  51. package/core/raxon.context.tsx +256 -0
  52. package/core/schema/checkout.schema.ts +103 -0
  53. package/core/util/basket.item.display.ts +19 -0
  54. package/core/util/category.nav.ts +46 -0
  55. package/core/util/client-ip.ts +35 -0
  56. package/core/util/collection.util.ts +433 -0
  57. package/core/util/fetch.bootstrap.ts +21 -0
  58. package/core/util/garanti-payment.ts +5 -0
  59. package/core/util/nexine.axios.tsx +104 -0
  60. package/core/util/no-cache.ts +6 -0
  61. package/core/util/util.ts +191 -0
  62. package/core/view/view.checkout.tsx +1964 -0
  63. package/dist/core/view/view.checkout.js +2 -2
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +12 -3
  66. package/tailwind.css +11 -0
@@ -0,0 +1,433 @@
1
+ export function getCollectionLabel(tags?: string[]) {
2
+
3
+ const ignored = new Set(['banner', 'subhero', 'web', 'homepage', 'anasayfa']);
4
+
5
+ const tag = tags?.find((t) => t && !ignored.has(String(t).toLowerCase()));
6
+
7
+ return tag ? String(tag).toUpperCase() : 'KOLEKSİYON';
8
+
9
+ }
10
+
11
+
12
+
13
+ function normalizeText(value: string) {
14
+
15
+ return value
16
+
17
+ .toLowerCase()
18
+
19
+ .normalize('NFD')
20
+
21
+ .replace(/[\u0300-\u036f]/g, '')
22
+
23
+ .replace(/ı/g, 'i')
24
+
25
+ .replace(/ğ/g, 'g')
26
+
27
+ .replace(/ü/g, 'u')
28
+
29
+ .replace(/ş/g, 's')
30
+
31
+ .replace(/ö/g, 'o')
32
+
33
+ .replace(/ç/g, 'c')
34
+
35
+ .replace(/[^a-z0-9]+/g, ' ')
36
+
37
+ .trim();
38
+
39
+ }
40
+
41
+
42
+
43
+ function tokensSimilar(a: string, b: string) {
44
+
45
+ if (a.length < 4 || b.length < 4) return false;
46
+
47
+ const prefixLength = Math.min(6, a.length, b.length);
48
+
49
+ const prefixA = a.slice(0, prefixLength);
50
+
51
+ const prefixB = b.slice(0, prefixLength);
52
+
53
+ return prefixA === prefixB || a.includes(b) || b.includes(a);
54
+
55
+ }
56
+
57
+
58
+
59
+ export function isBrandCollectionTitle(title: string | undefined) {
60
+
61
+ if (!title) return false;
62
+
63
+ const normalizedTitle = normalizeText(title);
64
+
65
+ return /kesfet|markasinin|marka urun/.test(normalizedTitle);
66
+
67
+ }
68
+
69
+
70
+
71
+ export function matchDirectBrandIdFromCollectionTitle(
72
+
73
+ title: string | undefined,
74
+
75
+ brands: Array<{ id: string; name?: string | null }> | undefined
76
+
77
+ ) {
78
+
79
+ if (!title || !brands?.length) return null;
80
+
81
+
82
+
83
+ const normalizedTitle = normalizeText(title);
84
+
85
+ if (!normalizedTitle) return null;
86
+
87
+
88
+
89
+ for (const brand of brands) {
90
+
91
+ const brandName = brand.name?.trim();
92
+
93
+ if (!brandName) continue;
94
+
95
+
96
+
97
+ const normalizedBrand = normalizeText(brandName);
98
+
99
+ if (normalizedBrand && normalizedTitle === normalizedBrand) {
100
+
101
+ return brand.id;
102
+
103
+ }
104
+
105
+ }
106
+
107
+
108
+
109
+ return null;
110
+
111
+ }
112
+
113
+
114
+
115
+ export function matchBrandIdFromCollectionTitle(
116
+
117
+ title: string | undefined,
118
+
119
+ brands: Array<{ id: string; name?: string | null }> | undefined
120
+
121
+ ) {
122
+
123
+ if (!title || !brands?.length) return null;
124
+
125
+
126
+
127
+ const directBrandId = matchDirectBrandIdFromCollectionTitle(title, brands);
128
+
129
+ if (directBrandId) return directBrandId;
130
+
131
+
132
+
133
+ if (!isBrandCollectionTitle(title)) return null;
134
+
135
+
136
+
137
+ const normalizedTitle = normalizeText(title);
138
+
139
+ if (!normalizedTitle) return null;
140
+
141
+
142
+
143
+ const titleTokens = normalizedTitle.split(' ').filter((token) => token.length >= 4);
144
+
145
+ let bestMatch: { id: string; score: number } | null = null;
146
+
147
+
148
+
149
+ for (const brand of brands) {
150
+
151
+ const brandName = brand.name?.trim();
152
+
153
+ if (!brandName) continue;
154
+
155
+
156
+
157
+ const normalizedBrand = normalizeText(brandName);
158
+
159
+ if (!normalizedBrand || normalizedBrand.length < 3) continue;
160
+
161
+
162
+
163
+ const isMatch =
164
+
165
+ tokensSimilar(normalizedTitle, normalizedBrand) ||
166
+
167
+ titleTokens.some((token) => tokensSimilar(token, normalizedBrand));
168
+
169
+
170
+
171
+ if (!isMatch) continue;
172
+
173
+
174
+
175
+ const score = normalizedBrand.length;
176
+
177
+ if (!bestMatch || score > bestMatch.score) {
178
+
179
+ bestMatch = { id: brand.id, score };
180
+
181
+ }
182
+
183
+ }
184
+
185
+
186
+
187
+ return bestMatch?.id ?? null;
188
+
189
+ }
190
+
191
+
192
+
193
+ function getCategoryName(category: { name?: unknown } | null | undefined) {
194
+
195
+ if (!category?.name) return '';
196
+
197
+ if (Array.isArray(category.name)) {
198
+
199
+ const first = category.name[0] as { value?: string } | undefined;
200
+
201
+ return first?.value || '';
202
+
203
+ }
204
+
205
+ return String(category.name);
206
+
207
+ }
208
+
209
+
210
+
211
+ const COLLECTION_CATEGORY_HINTS: Array<{ titlePattern: RegExp; categoryPattern: RegExp }> = [
212
+
213
+ { titlePattern: /saclar|sacini|sac bakim/, categoryPattern: /sac bakim urunleri/ },
214
+
215
+ { titlePattern: /gunes|tatilde/, categoryPattern: /gunes/ },
216
+
217
+ { titlePattern: /makyaj/, categoryPattern: /makyaj urunleri/ },
218
+
219
+ ];
220
+
221
+
222
+
223
+ const COLLECTION_SEARCH_HINTS: Array<{ titlePattern: RegExp; searchQuery: string }> = [
224
+
225
+ { titlePattern: /saclar|sacini|sac bakim/, searchQuery: 'sac bakim' },
226
+
227
+ ];
228
+
229
+
230
+
231
+ export function matchSearchQueryFromCollectionTitle(title: string | undefined) {
232
+
233
+ if (!title) return null;
234
+
235
+
236
+
237
+ const normalizedTitle = normalizeText(title);
238
+
239
+ if (!normalizedTitle) return null;
240
+
241
+
242
+
243
+ for (const hint of COLLECTION_SEARCH_HINTS) {
244
+
245
+ if (hint.titlePattern.test(normalizedTitle)) {
246
+
247
+ return hint.searchQuery;
248
+
249
+ }
250
+
251
+ }
252
+
253
+
254
+
255
+ return null;
256
+
257
+ }
258
+
259
+
260
+
261
+ export function shouldPreferThematicCategoryFallback(title: string | undefined) {
262
+
263
+ if (!title) return false;
264
+
265
+ const normalizedTitle = normalizeText(title);
266
+
267
+ return COLLECTION_CATEGORY_HINTS.some((hint) => hint.titlePattern.test(normalizedTitle));
268
+
269
+ }
270
+
271
+
272
+
273
+ export function matchCategoryIdFromCollectionTitle(
274
+
275
+ title: string | undefined,
276
+
277
+ categories: Array<{ id: string; name?: unknown }> | undefined
278
+
279
+ ) {
280
+
281
+ if (!title || !categories?.length || !shouldPreferThematicCategoryFallback(title)) return null;
282
+
283
+
284
+
285
+ const normalizedTitle = normalizeText(title);
286
+
287
+ if (!normalizedTitle) return null;
288
+
289
+
290
+
291
+ for (const hint of COLLECTION_CATEGORY_HINTS) {
292
+
293
+ if (!hint.titlePattern.test(normalizedTitle)) continue;
294
+
295
+ const match = categories.find((category) =>
296
+
297
+ hint.categoryPattern.test(normalizeText(getCategoryName(category)))
298
+
299
+ );
300
+
301
+ if (match) return match.id;
302
+
303
+ }
304
+
305
+
306
+
307
+ return null;
308
+
309
+ }
310
+
311
+
312
+
313
+ export type CollectionProductSource = 'collection' | 'detail' | 'brand' | 'category' | 'search' | 'empty';
314
+
315
+
316
+
317
+ const MAX_SANE_COLLECTION_COUNT = 200;
318
+
319
+ const MIN_COMPLETE_BRAND_COLLECTION_PRODUCTS = 10;
320
+
321
+
322
+
323
+ export function resolveCollectionProductSource(params: {
324
+
325
+ apiCount: number;
326
+
327
+ detailCount: number;
328
+
329
+ title?: string;
330
+
331
+ brandId?: string | null;
332
+
333
+ categoryId?: string | null;
334
+
335
+ searchQuery?: string | null;
336
+
337
+ isDirectBrandCollection?: boolean;
338
+
339
+ isCollectionProductsSettled: boolean;
340
+
341
+ }): CollectionProductSource | null {
342
+
343
+ const {
344
+
345
+ apiCount,
346
+
347
+ detailCount,
348
+
349
+ title,
350
+
351
+ brandId,
352
+
353
+ categoryId,
354
+
355
+ searchQuery,
356
+
357
+ isDirectBrandCollection,
358
+
359
+ isCollectionProductsSettled,
360
+
361
+ } = params;
362
+
363
+
364
+
365
+ if (!isCollectionProductsSettled) return null;
366
+
367
+
368
+
369
+ if (apiCount > 0 && apiCount <= MAX_SANE_COLLECTION_COUNT) {
370
+
371
+ if (apiCount < 3 && shouldPreferThematicCategoryFallback(title)) {
372
+
373
+ if (searchQuery) return 'search';
374
+
375
+ if (categoryId) return 'category';
376
+
377
+ }
378
+
379
+
380
+
381
+ if (
382
+
383
+ isDirectBrandCollection &&
384
+
385
+ brandId &&
386
+
387
+ apiCount < MIN_COMPLETE_BRAND_COLLECTION_PRODUCTS
388
+
389
+ ) {
390
+
391
+ return 'brand';
392
+
393
+ }
394
+
395
+
396
+
397
+ return 'collection';
398
+
399
+ }
400
+
401
+
402
+
403
+ if (detailCount > 0) return 'detail';
404
+
405
+ if (brandId) return 'brand';
406
+
407
+ if (searchQuery) return 'search';
408
+
409
+ if (categoryId) return 'category';
410
+
411
+
412
+
413
+ return 'empty';
414
+
415
+ }
416
+
417
+
418
+
419
+ export function filterProductsByCollectionAllowlist<T extends { id?: string | null }>(
420
+
421
+ products: T[],
422
+
423
+ allowlistIds: Set<string>
424
+
425
+ ) {
426
+
427
+ if (allowlistIds.size === 0) return products;
428
+
429
+ return products.filter((product) => product.id && allowlistIds.has(product.id));
430
+
431
+ }
432
+
433
+
@@ -0,0 +1,21 @@
1
+ import { RaxonBootstrapPayload } from '@/core/interface/bootstrap.interface';
2
+
3
+ export async function fetchRaxonBootstrap(
4
+ apiUrl: string,
5
+ apiKey: string,
6
+ init?: RequestInit,
7
+ ): Promise<RaxonBootstrapPayload> {
8
+ const response = await fetch(`${apiUrl}/customer/bootstrap`, {
9
+ ...init,
10
+ headers: {
11
+ 'x-api-key': apiKey,
12
+ ...init?.headers,
13
+ },
14
+ });
15
+
16
+ if (!response.ok) {
17
+ throw new Error(`[RAXON] Bootstrap failed: ${response.status}`);
18
+ }
19
+
20
+ return response.json() as Promise<RaxonBootstrapPayload>;
21
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ storeGarantiPaymentHtml,
3
+ consumeGarantiPaymentHtml,
4
+ submitGarantiPaymentHtml,
5
+ } from '@/core/feature/cart/util/garanti-payment';
@@ -0,0 +1,104 @@
1
+ import axios from 'axios';
2
+ import { toast } from 'react-hot-toast';
3
+
4
+ export const nexineAxios = axios.create({
5
+ baseURL: typeof window !== 'undefined' ? (window as any).__RAXON_API_URL__ : process.env.NEXT_PUBLIC_API_URL,
6
+ headers: {
7
+ 'Content-Type': 'application/json',
8
+ 'x-api-key': typeof window !== 'undefined' ? (window as any).__RAXON_API_KEY__ : (process.env.NEXT_PUBLIC_API_KEY || ''),
9
+ },
10
+ });
11
+
12
+ const resolveApiKey = (): string => {
13
+ if (typeof window !== 'undefined') {
14
+ return (
15
+ (window as any).__RAXON_API_KEY__ ||
16
+ (nexineAxios.defaults.headers.common['x-api-key'] as string) ||
17
+ ''
18
+ );
19
+ }
20
+ return process.env.NEXT_PUBLIC_API_KEY || '';
21
+ };
22
+
23
+ const resolveApiUrl = (): string | undefined => {
24
+ if (typeof window !== 'undefined') {
25
+ return (window as any).__RAXON_API_URL__ || nexineAxios.defaults.baseURL;
26
+ }
27
+ return process.env.NEXT_PUBLIC_API_URL;
28
+ };
29
+
30
+ nexineAxios.interceptors.request.use(config => {
31
+ const apiKey = resolveApiKey();
32
+ if (apiKey) {
33
+ config.headers['x-api-key'] = apiKey;
34
+ }
35
+
36
+ const apiUrl = resolveApiUrl();
37
+ if (apiUrl) {
38
+ config.baseURL = apiUrl;
39
+ }
40
+
41
+ if (typeof window !== 'undefined') {
42
+ const token = localStorage.getItem('koksal-token');
43
+ if (token) {
44
+ config.headers.Authorization = `Bearer ${token}`;
45
+ }
46
+
47
+ console.log(document.cookie
48
+ .split('; ').find(row => row.startsWith('_fbp='))?.split('=')[1])
49
+
50
+ const fbc = document.cookie
51
+ .split('; ')
52
+ .find(row => row.startsWith('_fbc='))
53
+ ?.split('=')[1];
54
+ const fbp = document.cookie
55
+ .split('; ')
56
+ .find(row => row.startsWith('_fbp='))
57
+ ?.split('=')[1];
58
+
59
+ if (fbc) {
60
+ config.headers['x-fbc'] = fbc;
61
+ }
62
+ if (fbp) {
63
+ config.headers['x-fbp'] = fbp;
64
+ }
65
+ }
66
+
67
+ return config;
68
+ });
69
+
70
+ nexineAxios.interceptors.response.use(
71
+ response => {
72
+ var excludeArray = ['/auth/register'];
73
+ if (response.config.method != 'get' && response.data.info && response.data.info.message && !excludeArray.includes(response.config.url || '')) {
74
+ toast.success(response.data.info.message);
75
+ }
76
+ return response;
77
+ },
78
+ error => {
79
+ // 401 Unauthorized hatası yakalandığında sadece token'ı temizle
80
+ if (error.response?.status === 401) {
81
+ const requestUrl = error.config?.url || '';
82
+ const requestMethod = error.config?.method || '';
83
+
84
+ // Token'ı temizle
85
+ if (typeof window !== 'undefined') {
86
+ localStorage.removeItem('koksal-token');
87
+ }
88
+
89
+ // /auth/me endpoint'inde toast gösterme (SecurityContext yönetiyor)
90
+ if (!requestUrl.includes('/auth/me')) {
91
+ // Sadece POST, PUT, DELETE işlemlerinde toast göster
92
+ if (requestMethod !== 'get') {
93
+ toast.error('Oturum süreniz dolmuş. Lütfen tekrar giriş yapın.');
94
+ }
95
+ }
96
+ }
97
+
98
+ if (error.response?.data.info && error.response?.data.info.message) {
99
+ toast.error(error.response?.data.info.message ?? error.response?.data.info.title );
100
+ }
101
+
102
+ return Promise.reject(error);
103
+ }
104
+ );
@@ -0,0 +1,6 @@
1
+ // API yanıtları için no-cache header'ları
2
+ export const NO_CACHE_HEADERS = {
3
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
4
+ 'Pragma': 'no-cache',
5
+ 'Expires': '0',
6
+ };