@raxonltd/raxon-core 1.1.7 → 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,333 @@
1
+ 'use client';
2
+ import React, { useState } from 'react';
3
+ import { Eye, EyeOff, Lock, Mail, User, Phone, ArrowRight } from 'lucide-react';
4
+ import { useAuth } from '@/core/feature/auth/hook/use.auth';
5
+ import { useRouter } from 'next/navigation';
6
+ import { useForm } from 'react-hook-form';
7
+ import { zodResolver } from '@hookform/resolvers/zod';
8
+ import { z } from 'zod';
9
+ import toast from 'react-hot-toast';
10
+
11
+ // Türk telefon numarası regex'i - 0 ile başlayan 11 haneli veya 5 ile başlayan 10 haneli
12
+ const turkishPhoneRegex = /^(\+90[5-9][0-9]{9}|0[5-9][0-9]{9}|[5-9][0-9]{8})$/;
13
+
14
+ // Zod validation şeması
15
+ const registerSchema = z.object({
16
+ firstName: z.string()
17
+ .min(1, 'Ad alanı zorunludur')
18
+ .min(2, 'Ad en az 2 karakter olmalıdır')
19
+ .max(50, 'Ad en fazla 50 karakter olabilir')
20
+ .regex(/^[a-zA-ZçğıöşüÇĞIİÖŞÜ\s]+$/, 'Ad sadece harf içerebilir'),
21
+
22
+ lastName: z.string()
23
+ .min(1, 'Soyad alanı zorunludur')
24
+ .min(2, 'Soyad en az 2 karakter olmalıdır')
25
+ .max(50, 'Soyad en fazla 50 karakter olabilir')
26
+ .regex(/^[a-zA-ZçğıöşüÇĞIİÖŞÜ\s]+$/, 'Soyad sadece harf içerebilir'),
27
+
28
+ email: z.string()
29
+ .min(1, 'E-posta alanı zorunludur')
30
+ .email('Geçerli bir e-posta adresi giriniz')
31
+ .max(100, 'E-posta adresi çok uzun'),
32
+
33
+ phone: z.string()
34
+ .min(1, 'Telefon numarası zorunludur')
35
+ .regex(turkishPhoneRegex, 'Geçerli bir Türk telefon numarası giriniz (örn: +905551234567)')
36
+ .transform((val) => val.replace(/\s/g, '')), // Boşlukları temizle
37
+
38
+ password: z.string()
39
+ .min(1, 'Şifre alanı zorunludur')
40
+ .min(6, 'Şifre en az 6 karakter olmalıdır')
41
+ .max(100, 'Şifre çok uzun'),
42
+
43
+ confirmPassword: z.string()
44
+ .min(1, 'Şifre tekrarı zorunludur'),
45
+
46
+ acceptTerms: z.boolean()
47
+ .refine((val) => val === true, 'Kullanım şartlarını kabul etmelisiniz'),
48
+
49
+ acceptMarketing: z.boolean().optional()
50
+ }).refine((data) => data.password === data.confirmPassword, {
51
+ message: 'Şifreler eşleşmiyor',
52
+ path: ['confirmPassword']
53
+ });
54
+
55
+ type RegisterFormData = z.infer<typeof registerSchema>;
56
+
57
+ interface ViewRegisterProps {
58
+ onClose?: () => void;
59
+ onSwitchToLogin?: () => void;
60
+ }
61
+
62
+ export default function ViewRegister({ onClose, onSwitchToLogin }: ViewRegisterProps) {
63
+ const router = useRouter();
64
+ const { register: authRegister } = useAuth();
65
+ const registerMutation = authRegister();
66
+
67
+ const [showPassword, setShowPassword] = useState(false);
68
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
69
+
70
+ const {
71
+ register,
72
+ handleSubmit,
73
+ formState: { errors, isSubmitting },
74
+ } = useForm<RegisterFormData>({
75
+ resolver: zodResolver(registerSchema),
76
+ defaultValues: {
77
+ firstName: '',
78
+ lastName: '',
79
+ email: '',
80
+ phone: '',
81
+ password: '',
82
+ confirmPassword: '',
83
+ acceptTerms: false,
84
+ acceptMarketing: false
85
+ }
86
+ });
87
+
88
+ const onSubmit = async (data: RegisterFormData) => {
89
+ registerMutation.mutate({
90
+ firstName: data.firstName,
91
+ lastName: data.lastName,
92
+ email: data.email,
93
+ phone: data.phone,
94
+ password: data.password,
95
+ acceptMarketing: data.acceptMarketing || false
96
+ }, {
97
+ onSuccess: () => {
98
+
99
+ onSwitchToLogin?.();
100
+ },
101
+ onError: (e: any) => {
102
+ toast.error(e.response?.data?.info?.title || 'Kayıt başarısız');
103
+ }
104
+ });
105
+ };
106
+
107
+ return (
108
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
109
+ {/* Name Fields */}
110
+ <div className="grid grid-cols-2 gap-4">
111
+ <div className="relative">
112
+ <div className="relative">
113
+ <User className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
114
+ <input
115
+ type="text"
116
+ {...register('firstName')}
117
+ placeholder="Adınız"
118
+ className={`w-full pl-10 pr-4 py-3 text-sm border rounded-sm focus:outline-none transition-colors ${
119
+ errors.firstName ? 'border-red-500' : 'border-gray-200 focus:border-black'
120
+ }`}
121
+ />
122
+ </div>
123
+ {errors.firstName && (
124
+ <p className="mt-1 text-xs text-red-500">{errors.firstName.message}</p>
125
+ )}
126
+ </div>
127
+
128
+ <div className="relative">
129
+ <div className="relative">
130
+ <User className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
131
+ <input
132
+ type="text"
133
+ {...register('lastName')}
134
+ placeholder="Soyadınız"
135
+ className={`w-full pl-10 pr-4 py-3 text-sm border rounded-sm focus:outline-none transition-colors ${
136
+ errors.lastName ? 'border-red-500' : 'border-gray-200 focus:border-black'
137
+ }`}
138
+ />
139
+ </div>
140
+ {errors.lastName && (
141
+ <p className="mt-1 text-xs text-red-500">{errors.lastName.message}</p>
142
+ )}
143
+ </div>
144
+ </div>
145
+
146
+ {/* Email Field */}
147
+ <div className="relative">
148
+ <div className="relative">
149
+ <Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
150
+ <input
151
+ type="email"
152
+ {...register('email')}
153
+ placeholder="E-posta adresiniz"
154
+ className={`w-full pl-10 pr-4 py-3 text-sm border rounded-sm focus:outline-none transition-colors ${
155
+ errors.email ? 'border-red-500' : 'border-gray-200 focus:border-black'
156
+ }`}
157
+ />
158
+ </div>
159
+ {errors.email && (
160
+ <p className="mt-1 text-xs text-red-500">{errors.email.message}</p>
161
+ )}
162
+ </div>
163
+
164
+ {/* Phone Field */}
165
+ <div className="relative">
166
+ <div className="relative">
167
+ <Phone className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
168
+ <input
169
+ type="tel"
170
+ {...register('phone')}
171
+ placeholder="Telefon numaranız (örn: 05551234567)"
172
+ className={`w-full pl-10 pr-4 py-3 text-sm border rounded-sm focus:outline-none transition-colors ${
173
+ errors.phone ? 'border-red-500' : 'border-gray-200 focus:border-black'
174
+ }`}
175
+ />
176
+ </div>
177
+ {errors.phone && (
178
+ <p className="mt-1 text-xs text-red-500">{errors.phone.message}</p>
179
+ )}
180
+ </div>
181
+
182
+ {/* Password Field */}
183
+ <div className="relative">
184
+ <div className="relative">
185
+ <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
186
+ <input
187
+ type={showPassword ? "text" : "password"}
188
+ {...register('password')}
189
+ placeholder="Şifreniz (en az 6 karakter, büyük/küçük harf ve rakam)"
190
+ className={`w-full pl-10 pr-12 py-3 text-sm border rounded-sm focus:outline-none transition-colors ${
191
+ errors.password ? 'border-red-500' : 'border-gray-200 focus:border-black'
192
+ }`}
193
+ />
194
+ <button
195
+ type="button"
196
+ onClick={() => setShowPassword(!showPassword)}
197
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
198
+ >
199
+ {showPassword ? (
200
+ <EyeOff className="w-4 h-4" />
201
+ ) : (
202
+ <Eye className="w-4 h-4" />
203
+ )}
204
+ </button>
205
+ </div>
206
+ {errors.password && (
207
+ <p className="mt-1 text-xs text-red-500">{errors.password.message}</p>
208
+ )}
209
+ </div>
210
+
211
+ {/* Confirm Password Field */}
212
+ <div className="relative">
213
+ <div className="relative">
214
+ <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
215
+ <input
216
+ type={showConfirmPassword ? "text" : "password"}
217
+ {...register('confirmPassword')}
218
+ placeholder="Şifrenizi tekrar giriniz"
219
+ className={`w-full pl-10 pr-12 py-3 text-sm border rounded-sm focus:outline-none transition-colors ${
220
+ errors.confirmPassword ? 'border-red-500' : 'border-gray-200 focus:border-black'
221
+ }`}
222
+ />
223
+ <button
224
+ type="button"
225
+ onClick={() => setShowConfirmPassword(!showConfirmPassword)}
226
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
227
+ >
228
+ {showConfirmPassword ? (
229
+ <EyeOff className="w-4 h-4" />
230
+ ) : (
231
+ <Eye className="w-4 h-4" />
232
+ )}
233
+ </button>
234
+ </div>
235
+ {errors.confirmPassword && (
236
+ <p className="mt-1 text-xs text-red-500">{errors.confirmPassword.message}</p>
237
+ )}
238
+ </div>
239
+
240
+ {/* Terms & Marketing Checkboxes */}
241
+ <div className="space-y-3">
242
+ <div className="flex items-start">
243
+ <input
244
+ type="checkbox"
245
+ {...register('acceptTerms')}
246
+ className="w-4 h-4 text-black border-gray-300 rounded focus:ring-0 focus:ring-offset-0 mt-0.5"
247
+ />
248
+ <label className="ml-2 text-sm text-gray-600 font-light">
249
+ <button
250
+ type="button"
251
+ onClick={onClose}
252
+ className="text-black hover:underline"
253
+ >
254
+ Kullanım Şartları
255
+ </button>
256
+ {' '}ve{' '}
257
+ <button
258
+ type="button"
259
+ onClick={onClose}
260
+ className="text-black hover:underline"
261
+ >
262
+ Gizlilik Politikası
263
+ </button>
264
+ 'nı okudum ve kabul ediyorum. *
265
+ </label>
266
+ </div>
267
+ {errors.acceptTerms && (
268
+ <p className="text-xs text-red-500 ml-6">{errors.acceptTerms.message}</p>
269
+ )}
270
+
271
+ <div className="flex items-start">
272
+ <input
273
+ type="checkbox"
274
+ {...register('acceptMarketing')}
275
+ className="w-4 h-4 text-black border-gray-300 rounded focus:ring-0 focus:ring-offset-0 mt-0.5"
276
+ />
277
+ <label className="ml-2 text-sm text-gray-600 font-light">
278
+ Kampanya ve promosyon bilgilerini e-posta ile almak istiyorum.
279
+ </label>
280
+ </div>
281
+ </div>
282
+
283
+ {/* Register Button */}
284
+ <button
285
+ type="submit"
286
+ disabled={isSubmitting || registerMutation.isPending}
287
+ className="w-full bg-black text-white py-3 rounded-sm hover:bg-gray-800 transition-colors duration-200 flex items-center justify-center space-x-2 group disabled:opacity-50 disabled:cursor-not-allowed"
288
+ >
289
+ <span className="text-sm font-light uppercase tracking-wider">
290
+ {isSubmitting || registerMutation.isPending ? 'Kayıt Oluşturuluyor...' : 'Kayıt Ol'}
291
+ </span>
292
+ {!isSubmitting && !registerMutation.isPending && (
293
+ <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
294
+ )}
295
+ </button>
296
+
297
+ {/* Divider */}
298
+ <div className="my-8 flex items-center">
299
+ <div className="flex-1 border-t border-gray-200"></div>
300
+ <span className="px-4 text-xs text-gray-500 uppercase tracking-wider">
301
+ veya
302
+ </span>
303
+ <div className="flex-1 border-t border-gray-200"></div>
304
+ </div>
305
+
306
+ {/* Social Register */}
307
+ <div className="space-y-3 hidden">
308
+ <button
309
+ type="button"
310
+ className="w-full border border-gray-200 py-3 rounded-sm hover:bg-gray-50 transition-colors duration-200 flex items-center justify-center space-x-2"
311
+ >
312
+ <svg className="w-5 h-5" viewBox="0 0 24 24">
313
+ <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
314
+ <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
315
+ <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
316
+ <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
317
+ </svg>
318
+ <span className="text-sm font-light">Google ile kayıt ol</span>
319
+ </button>
320
+
321
+ <button
322
+ type="button"
323
+ className="w-full border border-gray-200 py-3 rounded-sm hover:bg-gray-50 transition-colors duration-200 flex items-center justify-center space-x-2"
324
+ >
325
+ <svg className="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
326
+ <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
327
+ </svg>
328
+ <span className="text-sm font-light">Facebook ile kayıt ol</span>
329
+ </button>
330
+ </div>
331
+ </form>
332
+ );
333
+ }
@@ -0,0 +1,47 @@
1
+ import { nexineAxios } from '@/core/util/nexine.axios';
2
+ import { IData } from '@/core/interface/nexine.interface';
3
+ import { BankAccount, BankTransferCode } from '@/core/interface/prisma.interface';
4
+ import { useMutation, useQuery } from '@tanstack/react-query';
5
+
6
+ async function fetchBankAccounts() {
7
+ const withTags = await nexineAxios.get<IData<BankAccount>>('/customer/bank-account', {
8
+ params: {
9
+ isCompanySpecificVisible: true,
10
+ tags: ['WEB'],
11
+ },
12
+ });
13
+
14
+ const tagged = withTags.data?.data ?? [];
15
+ if (tagged.length > 0) return withTags.data;
16
+
17
+ const fallback = await nexineAxios.get<IData<BankAccount>>('/customer/bank-account', {
18
+ params: {
19
+ isCompanySpecificVisible: true,
20
+ },
21
+ });
22
+
23
+ return fallback.data;
24
+ }
25
+
26
+ export const useBankAccount = () => {
27
+ return {
28
+ fetch: (opts?: { enabled?: boolean }) => {
29
+ return useQuery({
30
+ queryKey: ['bank-account', 'web'],
31
+ queryFn: fetchBankAccounts,
32
+ enabled: opts?.enabled !== false,
33
+ });
34
+ },
35
+ createTransferCode: () => {
36
+ return useMutation({
37
+ mutationFn: async (data: { amount: number; bankAccountId: string }) => {
38
+ const response = await nexineAxios.post<{ bankTransferCode: BankTransferCode }>(
39
+ '/customer/payment/bank-transfer',
40
+ data
41
+ );
42
+ return response.data.bankTransferCode;
43
+ },
44
+ });
45
+ },
46
+ };
47
+ };
@@ -0,0 +1,24 @@
1
+ import { nexineAxios } from '@/core/util/nexine.axios';
2
+ import { IData } from '@/core/interface/nexine.interface';
3
+ import { Brand } from '@/core/interface/prisma.interface';
4
+ import { useQuery } from '@tanstack/react-query';
5
+
6
+ export const useBrand = () => {
7
+ return {
8
+ fetch: (params?: { enabled?: boolean; page?: number; amount?: number }) => {
9
+ return useQuery({
10
+ queryKey: ['brand', params?.page, params?.amount],
11
+ enabled: params?.enabled ?? true,
12
+ queryFn: async () => {
13
+ const response = await nexineAxios.get<IData<Brand>>('/customer/brand', {
14
+ params: {
15
+ page: params?.page,
16
+ amount: params?.amount,
17
+ },
18
+ });
19
+ return response.data;
20
+ },
21
+ });
22
+ },
23
+ };
24
+ };
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import type { BasketSummaryInterface } from '@/core/interface/basket.interface';
5
+ import { CartPromoCodeSection } from '@/core/feature/cart/component/cart.promo.code.section';
6
+
7
+ const ShieldCheck = (props: React.SVGProps<SVGSVGElement>) => (
8
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}>
9
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
10
+ <polyline points="9 12 11 14 15 10" />
11
+ </svg>
12
+ );
13
+
14
+ type CartOrderSummaryProps = {
15
+ cart: BasketSummaryInterface | null;
16
+ itemCount: number;
17
+ };
18
+
19
+ export function CartOrderSummary({ cart, itemCount }: CartOrderSummaryProps) {
20
+ return (
21
+ <aside className="lg:col-span-4 lg:sticky lg:top-28 self-start w-full">
22
+ <div className="p-4 bg-white border border-gray-100 rounded-2xl space-y-4">
23
+ <div className="pb-1">
24
+ <h2 className="text-sm font-medium text-gray-900 uppercase tracking-[0.1em]">Sipariş Özeti</h2>
25
+ <p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mt-0.5">{itemCount} Ürün</p>
26
+ </div>
27
+
28
+ <div className="space-y-2.5">
29
+ <div className="flex justify-between items-center text-sm">
30
+ <span className="text-gray-500">Ara Toplam</span>
31
+ <span className="font-bold text-gray-900">
32
+ ₺{(Number(cart?.info?.payPrice?.total) || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 })}
33
+ </span>
34
+ </div>
35
+
36
+ {Number(cart?.info?.discount?.pay || 0) > 0 && (
37
+ <div className="flex justify-between items-center text-sm">
38
+ <span className="text-gray-500">İndirim</span>
39
+ <span className="font-bold text-green-600">
40
+ -₺{Number(cart?.info?.discount?.pay || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 })}
41
+ </span>
42
+ </div>
43
+ )}
44
+
45
+ {Number(cart?.info?.promotion?.pay || 0) > 0 && (
46
+ <div className="flex justify-between items-center text-sm">
47
+ <span className="text-gray-500">Promosyon</span>
48
+ <span className="font-bold text-green-600">
49
+ -₺{Number(cart?.info?.promotion?.pay || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 })}
50
+ </span>
51
+ </div>
52
+ )}
53
+
54
+ <div className="flex justify-between items-center text-sm">
55
+ <span className="text-gray-500">KDV Dahil</span>
56
+ <span className="font-bold text-gray-900">
57
+ ₺{(Number(cart?.info?.payPrice?.tax) || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 })}
58
+ </span>
59
+ </div>
60
+ </div>
61
+
62
+ <div className="pt-3 border-t border-gray-100">
63
+ <CartPromoCodeSection variant="cart" />
64
+ </div>
65
+
66
+ <div className="pt-3 border-t border-gray-100 space-y-4">
67
+ <div className="flex justify-between items-end gap-3">
68
+ <span className="text-sm font-bold text-gray-900">Toplam</span>
69
+ <span className="text-xl font-light text-gray-900">
70
+ ₺{(Number(cart?.info?.payPrice?.pay) || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 })}
71
+ </span>
72
+ </div>
73
+
74
+ <Link
75
+ href="/odeme"
76
+ className="block w-full bg-black text-white py-4 rounded-full text-center text-[11px] font-black uppercase tracking-[0.2em] hover:bg-[#CF0A2C] transition-all duration-300"
77
+ >
78
+ Ödemeye Geç
79
+ </Link>
80
+
81
+ <div className="flex items-center justify-center gap-2 text-[9px] font-bold text-gray-400 uppercase tracking-widest">
82
+ <ShieldCheck className="w-3.5 h-3.5 shrink-0" />
83
+ Güvenli Ödeme
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </aside>
88
+ );
89
+ }