@raxonltd/raxon-core 1.1.7 → 1.1.13

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 (89) 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 +92 -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/server/places.proxy.ts +35 -0
  54. package/core/server/raxon.bootstrap.route.ts +39 -0
  55. package/core/server/raxon.server.ts +80 -0
  56. package/core/util/basket.item.display.ts +19 -0
  57. package/core/util/category.nav.ts +46 -0
  58. package/core/util/client-ip.ts +35 -0
  59. package/core/util/collection.util.ts +433 -0
  60. package/core/util/fetch.bootstrap.ts +21 -0
  61. package/core/util/garanti-payment.ts +5 -0
  62. package/core/util/nexine.axios.tsx +104 -0
  63. package/core/util/no-cache.ts +6 -0
  64. package/core/util/util.ts +191 -0
  65. package/core/view/view.checkout.tsx +1964 -0
  66. package/dist/core/feature/address/api/places.api.d.ts.map +1 -1
  67. package/dist/core/feature/address/api/places.api.js +18 -4
  68. package/dist/core/server/places.proxy.d.ts +10 -0
  69. package/dist/core/server/places.proxy.d.ts.map +1 -0
  70. package/dist/core/server/places.proxy.js +24 -0
  71. package/dist/core/server/raxon.bootstrap.route.d.ts +7 -0
  72. package/dist/core/server/raxon.bootstrap.route.d.ts.map +1 -0
  73. package/dist/core/server/raxon.bootstrap.route.js +27 -0
  74. package/dist/core/server/raxon.server.d.ts +24 -0
  75. package/dist/core/server/raxon.server.d.ts.map +1 -0
  76. package/dist/core/server/raxon.server.js +59 -0
  77. package/dist/core/view/view.checkout.js +2 -2
  78. package/dist/middleware.d.ts +6 -0
  79. package/dist/middleware.d.ts.map +1 -0
  80. package/dist/middleware.js +5 -0
  81. package/dist/server-bootstrap.d.ts +2 -0
  82. package/dist/server-bootstrap.d.ts.map +1 -0
  83. package/dist/server-bootstrap.js +1 -0
  84. package/dist/server.d.ts +3 -0
  85. package/dist/server.d.ts.map +1 -0
  86. package/dist/server.js +1 -0
  87. package/dist/tsconfig.tsbuildinfo +1 -1
  88. package/package.json +22 -3
  89. package/tailwind.css +11 -0
@@ -0,0 +1,118 @@
1
+ import { Send } from 'lucide-react';
2
+
3
+ const labelClass = 'mb-2 block text-[11px] font-bold uppercase tracking-[0.18em] text-gray-500';
4
+ const fieldOk =
5
+ 'w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-gray-900 transition-all focus:border-black focus:outline-none focus:ring-2 focus:ring-black/10';
6
+ const fieldErr =
7
+ 'w-full rounded-xl border border-red-500 bg-white px-4 py-3 text-gray-900 focus:border-red-500 focus:outline-none focus:ring-2 focus:ring-red-500/20';
8
+
9
+ export const FormContact = ({ form, onSubmit, isPending }: { form: any; onSubmit: any; isPending: boolean }) => {
10
+ return (
11
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
12
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
13
+ <div>
14
+ <label htmlFor="firstName" className={labelClass}>
15
+ Ad *
16
+ </label>
17
+ <input
18
+ type="text"
19
+ id="firstName"
20
+ {...form.register('firstName')}
21
+ className={form.formState.errors.firstName ? fieldErr : fieldOk}
22
+ placeholder="Adınız"
23
+ />
24
+ {form.formState.errors.firstName && <p className="mt-1 text-sm text-red-600">{form.formState.errors.firstName.message}</p>}
25
+ </div>
26
+ <div>
27
+ <label htmlFor="lastName" className={labelClass}>
28
+ Soyad *
29
+ </label>
30
+ <input
31
+ type="text"
32
+ id="lastName"
33
+ {...form.register('lastName')}
34
+ className={form.formState.errors.lastName ? fieldErr : fieldOk}
35
+ placeholder="Soyadınız"
36
+ />
37
+ {form.formState.errors.lastName && <p className="mt-1 text-sm text-red-600">{form.formState.errors.lastName.message}</p>}
38
+ </div>
39
+ <div className="md:col-span-2">
40
+ <label htmlFor="email" className={labelClass}>
41
+ E-posta *
42
+ </label>
43
+ <input
44
+ type="email"
45
+ id="email"
46
+ {...form.register('email')}
47
+ className={form.formState.errors.email ? fieldErr : fieldOk}
48
+ placeholder="ornek@email.com"
49
+ />
50
+ {form.formState.errors.email && <p className="mt-1 text-sm text-red-600">{form.formState.errors.email.message}</p>}
51
+ </div>
52
+ </div>
53
+
54
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
55
+ <div>
56
+ <label htmlFor="phoneNumber" className={labelClass}>
57
+ Telefon
58
+ </label>
59
+ <input
60
+ type="tel"
61
+ id="phoneNumber"
62
+ {...form.register('phoneNumber')}
63
+ className={form.formState.errors.phoneNumber ? fieldErr : fieldOk}
64
+ placeholder="Telefon numaranız"
65
+ />
66
+ {form.formState.errors.phoneNumber && <p className="mt-1 text-sm text-red-600">{form.formState.errors.phoneNumber.message}</p>}
67
+ </div>
68
+ <div>
69
+ <label htmlFor="subject" className={labelClass}>
70
+ Konu *
71
+ </label>
72
+ <select id="subject" {...form.register('subject')} className={form.formState.errors.subject ? fieldErr : fieldOk}>
73
+ <option value="">Konu seçin</option>
74
+ <option value="genel">Genel bilgi</option>
75
+ <option value="siparis">Sipariş</option>
76
+ <option value="iade">İade / değişim</option>
77
+ <option value="urun">Ürün bilgisi</option>
78
+ <option value="oneri">Öneri / şikayet</option>
79
+ <option value="diger">Diğer</option>
80
+ </select>
81
+ {form.formState.errors.subject && <p className="mt-1 text-sm text-red-600">{form.formState.errors.subject.message}</p>}
82
+ </div>
83
+ </div>
84
+
85
+ <div>
86
+ <label htmlFor="message" className={labelClass}>
87
+ Mesajınız *
88
+ </label>
89
+ <textarea
90
+ id="message"
91
+ rows={6}
92
+ {...form.register('message')}
93
+ className={`resize-none ${form.formState.errors.message ? fieldErr : fieldOk}`}
94
+ placeholder="Mesajınızı buraya yazın..."
95
+ />
96
+ {form.formState.errors.message && <p className="mt-1 text-sm text-red-600">{form.formState.errors.message.message}</p>}
97
+ </div>
98
+
99
+ <button
100
+ type="submit"
101
+ disabled={isPending || form.formState.isSubmitting}
102
+ className="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-black bg-black px-8 py-4 text-sm font-black uppercase tracking-widest text-white shadow-sm transition hover:bg-white hover:text-black focus:outline-none focus:ring-2 focus:ring-black/20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
103
+ >
104
+ {isPending || form.formState.isSubmitting ? (
105
+ <>
106
+ <div className="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent" />
107
+ <span>Gönderiliyor…</span>
108
+ </>
109
+ ) : (
110
+ <>
111
+ <Send className="h-5 w-5" strokeWidth={1.5} />
112
+ <span>Mesaj gönder</span>
113
+ </>
114
+ )}
115
+ </button>
116
+ </form>
117
+ );
118
+ };
@@ -0,0 +1,16 @@
1
+ import { nexineAxios } from "@/core/util/nexine.axios";
2
+ import { IData } from "@/core/interface/nexine.interface";
3
+ import { Brand, Category, Faq } from "@/core/interface/prisma.interface";
4
+ import { useMutation, useQuery } from "@tanstack/react-query";
5
+
6
+ export const useFormSubmit = () => {
7
+ return {
8
+ create: ( ) => {
9
+ return useMutation({
10
+ mutationFn: async (data: any) => {
11
+ return await nexineAxios.post("/customer/form/submit", data);
12
+ },
13
+ });
14
+ },
15
+ };
16
+ };
@@ -0,0 +1,51 @@
1
+ import { nexineAxios } from "@/core/util/nexine.axios";
2
+ import { IData } from "@/core/interface/nexine.interface";
3
+ import { Invoice } from "@/core/interface/prisma.interface";
4
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
+
6
+ export const useInvoice = () => {
7
+
8
+ const queryClient = useQueryClient();
9
+
10
+ return {
11
+ fetch: () => {
12
+ return useQuery({
13
+ queryKey: ['web', 'invoice', 'list'],
14
+ queryFn: async () => {
15
+ const response = await nexineAxios.get<IData<Invoice>>('/customer/invoice');
16
+ return response.data;
17
+ },
18
+ });
19
+ },
20
+ detail: (id: string) => {
21
+ return useQuery({
22
+ queryKey: ['web', 'invoice', 'detail', id],
23
+ enabled: !!id && id !== "" && id !== "create",
24
+ queryFn: async () => {
25
+ const response = await nexineAxios.get<Invoice>(`/customer/invoice/${id}`);
26
+ return response.data;
27
+ },
28
+ });
29
+ },
30
+ download: () => {
31
+ return useMutation({
32
+ mutationFn: async (id: string) => {
33
+ const response = await nexineAxios.get(`/customer/invoice/${id}/download`, {
34
+ responseType: 'blob',
35
+ });
36
+ return response.data;
37
+ },
38
+ });
39
+ },
40
+ downloadByOrder: () => {
41
+ return useMutation({
42
+ mutationFn: async (orderId: string) => {
43
+ const response = await nexineAxios.get(`/customer/invoice/order/${orderId}/download`, {
44
+ responseType: 'blob',
45
+ });
46
+ return response.data;
47
+ },
48
+ });
49
+ },
50
+ };
51
+ };
@@ -0,0 +1,124 @@
1
+ import { nexineAxios } from '@/core/util/nexine.axios';
2
+ import { Newsletter } from '@/core/interface/prisma.interface';
3
+ import { useMutation } from '@tanstack/react-query';
4
+
5
+ export type NewsletterPromoCode = {
6
+ code: string;
7
+ amount: number | null;
8
+ type: string;
9
+ endDate?: string | null;
10
+ };
11
+
12
+ export type NewsletterVerifyResult = Newsletter & {
13
+ promoCode?: NewsletterPromoCode | null;
14
+ };
15
+
16
+ export type NewsletterVerifyResponse = {
17
+ info?: {
18
+ title?: string;
19
+ message?: string;
20
+ statusCode?: number;
21
+ };
22
+ data?: NewsletterVerifyResult;
23
+ };
24
+
25
+ export const getNewsletterVerifyCacheKey = (id: string, email: string) =>
26
+ `kuatto:newsletter-verify:${id}:${email.trim().toLowerCase()}`;
27
+
28
+ export const readNewsletterVerifyCache = (id: string, email: string): NewsletterVerifyResponse | null => {
29
+ if (typeof window === 'undefined' || !id || !email) {
30
+ return null;
31
+ }
32
+
33
+ try {
34
+ const cached = sessionStorage.getItem(getNewsletterVerifyCacheKey(id, email));
35
+ return cached ? (JSON.parse(cached) as NewsletterVerifyResponse) : null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ };
40
+
41
+ export const writeNewsletterVerifyCache = (id: string, email: string, payload: NewsletterVerifyResponse) => {
42
+ if (typeof window === 'undefined' || !id || !email) {
43
+ return;
44
+ }
45
+
46
+ try {
47
+ sessionStorage.setItem(getNewsletterVerifyCacheKey(id, email), JSON.stringify(payload));
48
+ } catch {
49
+ // sessionStorage dolu veya devre dışı olabilir
50
+ }
51
+ };
52
+
53
+ const inflightVerifyRequests = new Map<string, Promise<NewsletterVerifyResponse>>();
54
+
55
+ export const verifyNewsletterOnce = async (id: string, email: string) => {
56
+ const normalizedEmail = email.trim().toLowerCase();
57
+ const cached = readNewsletterVerifyCache(id, normalizedEmail);
58
+ if (cached?.data) {
59
+ return cached;
60
+ }
61
+
62
+ const inflightKey = `${id}:${normalizedEmail}`;
63
+ const inflight = inflightVerifyRequests.get(inflightKey);
64
+ if (inflight) {
65
+ return inflight;
66
+ }
67
+
68
+ const request = nexineAxios
69
+ .post<NewsletterVerifyResponse>('/global/newsletter/verify', {
70
+ id,
71
+ email: normalizedEmail,
72
+ })
73
+ .then((response) => {
74
+ const payload = response.data;
75
+
76
+ if (payload.data?.promoCode?.code) {
77
+ writeNewsletterVerifyCache(id, normalizedEmail, payload);
78
+ }
79
+
80
+ return payload;
81
+ })
82
+ .finally(() => {
83
+ inflightVerifyRequests.delete(inflightKey);
84
+ });
85
+
86
+ inflightVerifyRequests.set(inflightKey, request);
87
+ return request;
88
+ };
89
+
90
+ export const useNewsletter = () => {
91
+ const subscribe = useMutation({
92
+ mutationFn: async (data: { email: string; userId?: string }) => {
93
+ const response = await nexineAxios.post<Newsletter>('/customer/newsletter/subscribe', data);
94
+ return response.data;
95
+ },
96
+ });
97
+
98
+ const subscribeByVariant = useMutation({
99
+ mutationFn: async (data: { variantId?: string; productId?: string; email: string }) => {
100
+ const response = await nexineAxios.post<Newsletter>('/customer/newsletter/subscribe/variant', {
101
+ variantId: data.variantId,
102
+ productId: data.productId,
103
+ email: data.email,
104
+ });
105
+ return response.data;
106
+ },
107
+ });
108
+
109
+ const verify = useMutation({
110
+ mutationFn: async (data: { id: string; email: string }) => {
111
+ const response = await nexineAxios.post<NewsletterVerifyResponse>('/global/newsletter/verify', data);
112
+ return response.data;
113
+ },
114
+ });
115
+
116
+ const unsubscribe = useMutation({
117
+ mutationFn: async (data: { id: string; email: string }) => {
118
+ const response = await nexineAxios.post<Newsletter>('/global/newsletter/unsubscribe', data);
119
+ return response.data;
120
+ },
121
+ });
122
+
123
+ return { subscribe, subscribeByVariant, verify, unsubscribe };
124
+ };
@@ -0,0 +1,163 @@
1
+ import { useRaxon } from '@/core/raxon.context';
2
+ import { ProductDetail } from '@/core/interface/product.interface';
3
+ import React, { useImperativeHandle, useRef, useState } from 'react';
4
+ import { Modal } from 'rizzui/modal';
5
+ import { Button } from 'rizzui/button';
6
+ import { Input } from 'rizzui/input';
7
+ import { useNewsletter } from '@/core/feature/newsletter/hook/use.newsletter';
8
+
9
+ export interface ModalVariantArg {
10
+ id: string;
11
+ attributeOption1?: { id?: string; label?: string; name?: string } | null;
12
+ attributeOption2?: { id?: string; label?: string; name?: string } | null;
13
+ price?: { mainPrice?: number; discountPrice?: number } | null;
14
+ }
15
+
16
+ export interface ModalNewsletterVariantProductRef {
17
+ open: (product: ProductDetail, variant: ModalVariantArg | null) => void;
18
+ close: () => void;
19
+ }
20
+
21
+ export interface ModalNewsletterVariantProductProps {}
22
+
23
+ export const ModalNewsletterVariantProduct = React.forwardRef<ModalNewsletterVariantProductRef, ModalNewsletterVariantProductProps>((props, ref) => {
24
+ const callbackRef = useRef<any>(null);
25
+
26
+ const { mutate: subscribeByVariant } = useNewsletter().subscribeByVariant;
27
+ const { profile } = useRaxon();
28
+ const [product, setProduct] = useState<ProductDetail | null>(null);
29
+ const [variant, setVariant] = useState<ModalVariantArg | null>(null);
30
+ const [isOpen, setIsOpen] = useState(false);
31
+ const [email, setEmail] = useState('');
32
+ const [isLoading, setIsLoading] = useState(false);
33
+
34
+ const onHandleSubscripeProduct = async () => {
35
+ if(!product) return;
36
+
37
+ const emailToUse = profile?.email || email;
38
+ if(!emailToUse) return;
39
+
40
+ setIsLoading(true);
41
+ try {
42
+ // Varyant varsa variantId, yoksa productId ile abone ol
43
+ if (variant?.id) {
44
+ await subscribeByVariant({
45
+ variantId: variant.id,
46
+ email: emailToUse,
47
+ });
48
+ } else {
49
+ await subscribeByVariant({
50
+ productId: product.id,
51
+ email: emailToUse,
52
+ });
53
+ }
54
+ setIsOpen(false);
55
+ setEmail('');
56
+ } catch (error) {
57
+ console.error('Newsletter subscription error:', error);
58
+ } finally {
59
+ setIsLoading(false);
60
+ }
61
+ }
62
+ useImperativeHandle(ref, () => ({
63
+ open: (product: ProductDetail, variant: ModalVariantArg | null) => {
64
+ setProduct(product);
65
+ setVariant(variant);
66
+ setIsOpen(true);
67
+ setEmail('');
68
+ return new Promise((resolve, reject) => {
69
+ callbackRef.current = { resolve, reject };
70
+ resolve(true);
71
+ });
72
+ },
73
+ close: () => {
74
+ setIsOpen(false);
75
+ setProduct(null);
76
+ setVariant(null);
77
+ setEmail('');
78
+ },
79
+ }));
80
+
81
+ return (
82
+ <Modal
83
+ isOpen={isOpen}
84
+ onClose={() => {
85
+ setIsOpen(false);
86
+ setProduct(null);
87
+ setVariant(null);
88
+ setEmail('');
89
+ }}
90
+ size="md"
91
+ >
92
+ <div className="p-6">
93
+ <div className="mb-6">
94
+ <h2 className="text-xl font-semibold mb-2">
95
+ Ürün Gelince Haber Ver
96
+ </h2>
97
+ <p className="text-gray-600">
98
+ Bu ürün stoğa geldiğinde size haber verelim
99
+ </p>
100
+ </div>
101
+
102
+ {product && (
103
+ <div className="mb-6 p-4 bg-gray-50 rounded-lg">
104
+ <p className="font-medium mb-2">{product.name}</p>
105
+ {variant && (
106
+ <p className="text-sm text-gray-600 mb-1">
107
+ Varyant: {variant.attributeOption1?.label ?? variant.attributeOption1?.name} {variant.attributeOption2?.label ?? variant.attributeOption2?.name}
108
+ </p>
109
+ )}
110
+ {(variant?.price || product.variant?.[0]?.price) && (
111
+ <p className="text-sm font-medium text-green-600">
112
+ ₺{(variant?.price?.discountPrice ?? variant?.price?.mainPrice ?? product.variant?.[0]?.price?.discountPrice ?? product.variant?.[0]?.price?.mainPrice ?? 0).toFixed(2)}
113
+ </p>
114
+ )}
115
+ </div>
116
+ )}
117
+
118
+ {!profile?.email && (
119
+ <div className="mb-4">
120
+ <Input
121
+ label="E-posta Adresiniz"
122
+ type="email"
123
+ value={email}
124
+ onChange={(e) => setEmail(e.target.value)}
125
+ placeholder="ornek@email.com"
126
+ required
127
+ />
128
+ </div>
129
+ )}
130
+
131
+ {profile?.email && (
132
+ <div className="mb-4">
133
+ <p className="text-sm text-gray-600">
134
+ Bildirim gönderilecek e-posta: <span className="font-medium">{profile.email}</span>
135
+ </p>
136
+ </div>
137
+ )}
138
+
139
+ <div className="flex gap-3 justify-end">
140
+ <Button
141
+ variant="outline"
142
+ onClick={() => {
143
+ setIsOpen(false);
144
+ setProduct(null);
145
+ setVariant(null);
146
+ setEmail('');
147
+ }}
148
+ disabled={isLoading}
149
+ >
150
+ İptal
151
+ </Button>
152
+ <Button
153
+ onClick={onHandleSubscripeProduct}
154
+ disabled={isLoading || (!profile?.email && !email)}
155
+ isLoading={isLoading}
156
+ >
157
+ Haber Ver
158
+ </Button>
159
+ </div>
160
+ </div>
161
+ </Modal>
162
+ );
163
+ });
@@ -0,0 +1,31 @@
1
+ import { nexineAxios } from "@/core/util/nexine.axios";
2
+ import { IData } from "@/core/interface/nexine.interface";
3
+ import { Address, Order } from "@/core/interface/prisma.interface";
4
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
+
6
+ export const useOrder = () => {
7
+
8
+ const queryClient = useQueryClient();
9
+
10
+ return {
11
+ fetch : () => {
12
+ return useQuery({
13
+ queryKey: ['web','order','list'],
14
+ queryFn: async () => {
15
+ var response = await nexineAxios.get<IData<Order>>(`/customer/order`)
16
+ return response.data;
17
+ },
18
+ })
19
+ },
20
+ detail : (id : string) => {
21
+ return useQuery({
22
+ queryKey: ['web','order','detail',id],
23
+ enabled : !!id && id !== "" && id != "create",
24
+ queryFn: async () => {
25
+ var response = await nexineAxios.get<Order>(`/customer/order/${id}`)
26
+ return response.data;
27
+ },
28
+ })
29
+ },
30
+ }
31
+ };
@@ -0,0 +1,117 @@
1
+ import { PaymentMethod, PaymentProvider } from '../../interface/prisma.interface';
2
+ import { PaymentUiType, getPaymentUiType } from '@/core/schema/checkout.schema';
3
+
4
+ export interface CheckoutPaymentOption {
5
+ key: PaymentUiType;
6
+ label: string;
7
+ paymentMethodId: string | null;
8
+ method?: PaymentMethod;
9
+ /** Checkout UI'da seçilebilir (her zaman true) */
10
+ available: boolean;
11
+ /** Backend'de karşılık gelen ödeme yöntemi tanımlı mı */
12
+ configured: boolean;
13
+ }
14
+
15
+ export function findGarantiPaymentMethod(methods: PaymentMethod[]): PaymentMethod | undefined {
16
+ return methods.find(
17
+ (m) =>
18
+ m.provider === PaymentProvider.GARANTI ||
19
+ /garanti/i.test(m.name) ||
20
+ /garanti/i.test(m.provider || '')
21
+ );
22
+ }
23
+
24
+ const CHECKOUT_SLOTS: { key: PaymentUiType; label: string }[] = [
25
+ { key: 'credit_card', label: 'Kredi Kartı' },
26
+ { key: 'bank_transfer', label: 'Havale' },
27
+ { key: 'garanti', label: 'GarantiPay' },
28
+ ];
29
+
30
+ function resolveMethodForType(methods: PaymentMethod[], type: PaymentUiType): PaymentMethod | undefined {
31
+ const byUi = methods.find((m) => getPaymentUiType(m.name, m.provider) === type);
32
+ if (byUi) return byUi;
33
+
34
+ if (type === 'credit_card') {
35
+ return methods.find(
36
+ (m) =>
37
+ m.provider === PaymentProvider.PAYTR ||
38
+ m.provider === PaymentProvider.STRIPE ||
39
+ /kredi|kart|card/i.test(m.name)
40
+ );
41
+ }
42
+
43
+ if (type === 'bank_transfer') {
44
+ return methods.find(
45
+ (m) =>
46
+ m.provider === PaymentProvider.BANK_TRANSFER ||
47
+ /havale|eft|peşin|pesin|transfer/i.test(m.name)
48
+ );
49
+ }
50
+
51
+ if (type === 'garanti') {
52
+ return findGarantiPaymentMethod(methods);
53
+ }
54
+
55
+ return undefined;
56
+ }
57
+
58
+ /** Checkout'ta gösterilecek ödeme seçenekleri (Kredi Kartı, Havale, GarantiPay). */
59
+ export function buildCheckoutPaymentOptions(methods: PaymentMethod[]): CheckoutPaymentOption[] {
60
+ const slots = CHECKOUT_SLOTS;
61
+ const usedIds = new Set<string>();
62
+
63
+ const options = slots.map((slot) => {
64
+ const method = resolveMethodForType(
65
+ methods.filter((m) => !usedIds.has(m.id)),
66
+ slot.key
67
+ );
68
+ if (method) usedIds.add(method.id);
69
+
70
+ const configured =
71
+ slot.key === 'garanti'
72
+ ? Boolean(method && method.provider === PaymentProvider.GARANTI)
73
+ : Boolean(method);
74
+
75
+ return {
76
+ key: slot.key,
77
+ label: slot.label,
78
+ paymentMethodId: method?.id ?? null,
79
+ method,
80
+ available: true,
81
+ configured,
82
+ };
83
+ });
84
+
85
+ // Eşleşmeyen yöntemleri yalnızca uyumlu boş slotlara ata (Garanti slotuna Cari vb. gitmesin)
86
+ const unused = methods.filter((m) => !usedIds.has(m.id));
87
+ unused.forEach((method) => {
88
+ const slotType = getPaymentUiType(method.name, method.provider);
89
+ const matchingSlot = options.find(
90
+ (o) => !o.paymentMethodId && o.key === slotType && slotType !== 'other'
91
+ );
92
+ if (!matchingSlot) return;
93
+
94
+ matchingSlot.paymentMethodId = method.id;
95
+ matchingSlot.method = method;
96
+ matchingSlot.configured =
97
+ matchingSlot.key === 'garanti'
98
+ ? method.provider === PaymentProvider.GARANTI
99
+ : true;
100
+ });
101
+
102
+ return options;
103
+ }
104
+
105
+ export function resolveCheckoutPaymentMethodId(
106
+ options: CheckoutPaymentOption[],
107
+ selectedKey: PaymentUiType,
108
+ allMethods: PaymentMethod[]
109
+ ): string | null {
110
+ const selected = options.find((o) => o.key === selectedKey);
111
+ if (selected?.paymentMethodId) return selected.paymentMethodId;
112
+
113
+ const fallback = resolveMethodForType(allMethods, selectedKey);
114
+ return fallback?.id ?? null;
115
+ }
116
+
117
+ export const DEFAULT_CHECKOUT_PAYMENT_KEY: PaymentUiType = 'credit_card';
@@ -0,0 +1,44 @@
1
+ import { nexineAxios } from '@/core/util/nexine.axios';
2
+ import { IData } from '@/core/interface/nexine.interface';
3
+ import { PaymentMethod } from '@/core/interface/prisma.interface';
4
+ import { useQuery } from '@tanstack/react-query';
5
+
6
+ async function fetchPaymentMethods() {
7
+ const baseParams = { isCompanySpecificVisible: true };
8
+
9
+ const [withTags, withoutTags] = await Promise.all([
10
+ nexineAxios.get<IData<PaymentMethod>>('/customer/payment/method', {
11
+ params: { ...baseParams, tags: ['WEB'] },
12
+ }),
13
+ nexineAxios.get<IData<PaymentMethod>>('/customer/payment/method', {
14
+ params: baseParams,
15
+ }),
16
+ ]);
17
+
18
+ const tagged = withTags.data?.data ?? [];
19
+ const all = withoutTags.data?.data ?? [];
20
+
21
+ if (tagged.length === 0) return withoutTags.data;
22
+
23
+ // WEB etiketli GarantiPay ile etiketsiz diğer yöntemleri birleştir
24
+ const merged = new Map<string, PaymentMethod>();
25
+ all.forEach((method) => merged.set(method.id, method));
26
+ tagged.forEach((method) => merged.set(method.id, method));
27
+
28
+ return {
29
+ ...withoutTags.data,
30
+ data: Array.from(merged.values()),
31
+ };
32
+ }
33
+
34
+ export const usePaymentMethod = () => {
35
+ return {
36
+ fetch: (opts?: { enabled?: boolean }) => {
37
+ return useQuery({
38
+ queryKey: ['payment-method'],
39
+ queryFn: fetchPaymentMethods,
40
+ enabled: opts?.enabled !== false,
41
+ });
42
+ },
43
+ };
44
+ };