@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.
- package/core/component/general.image.tsx +86 -0
- package/core/context/cart.context.tsx +446 -0
- package/core/context/security.context.tsx +151 -0
- package/core/feature/address/api/places.api.ts +92 -0
- package/core/feature/address/form/address-search-input.tsx +125 -0
- package/core/feature/address/hook/use.addres.tsx +63 -0
- package/core/feature/address/hook/use.address-autocomplete.ts +116 -0
- package/core/feature/address/util/address.types.ts +38 -0
- package/core/feature/address/util/parse-google-place.ts +66 -0
- package/core/feature/analytic-event/analytic.event.api.ts +27 -0
- package/core/feature/analytic-event/analytic.event.context.tsx +180 -0
- package/core/feature/analytic-event/analytic.event.util.ts +42 -0
- package/core/feature/analytic-event/use.analytic.auto.tsx +114 -0
- package/core/feature/article/hook/use.article.tsx +33 -0
- package/core/feature/attribute/hook/use.attribute.tsx +24 -0
- package/core/feature/auth/hook/use.auth.tsx +141 -0
- package/core/feature/auth/modal/modal.auth.tsx +80 -0
- package/core/feature/auth/view/view.login.tsx +199 -0
- package/core/feature/auth/view/view.register.tsx +333 -0
- package/core/feature/bank-account/hook/use.bank.account.tsx +47 -0
- package/core/feature/brand/hook/use.brand.tsx +24 -0
- package/core/feature/cart/component/cart.order.summary.tsx +89 -0
- package/core/feature/cart/component/cart.promo.code.section.tsx +208 -0
- package/core/feature/cart/hook/use.cart.tsx +267 -0
- package/core/feature/cart/util/basket-pay.response.ts +67 -0
- package/core/feature/cart/util/cart-optimistic.ts +425 -0
- package/core/feature/cart/util/garanti-payment.ts +27 -0
- package/core/feature/collection/hook/use.collection.tsx +32 -0
- package/core/feature/delivery-method/hook/use.delivery.method.tsx +40 -0
- package/core/feature/delivery-method/util/checkout.delivery.method.ts +11 -0
- package/core/feature/faq/hook/use.faq.tsx +23 -0
- package/core/feature/favorite/hook/use.favorite.tsx +48 -0
- package/core/feature/form-submit/form/form.contact.tsx +118 -0
- package/core/feature/form-submit/hook/use.form.submit.tsx +16 -0
- package/core/feature/invoice/hook/use.invoice.tsx +51 -0
- package/core/feature/newsletter/hook/use.newsletter.tsx +124 -0
- package/core/feature/newsletter/modal/modal.newsletter.product.tsx +163 -0
- package/core/feature/order/hook/use.order.tsx +31 -0
- package/core/feature/payment-method/checkout.payment.options.ts +117 -0
- package/core/feature/payment-method/hook/use.payment.method.tsx +44 -0
- package/core/feature/product/hook/use.product.tsx +122 -0
- package/core/feature/profile/hook/use.profile.tsx +126 -0
- package/core/feature/promo-code/hook/use.promo.code.tsx +27 -0
- package/core/interface/basket.interface.ts +360 -0
- package/core/interface/bootstrap.interface.ts +39 -0
- package/core/interface/context.interface.ts +9 -0
- package/core/interface/inventory.interface.ts +88 -0
- package/core/interface/nexine.interface.ts +4 -0
- package/core/interface/prisma.interface.ts +8844 -0
- package/core/interface/product.interface.ts +111 -0
- package/core/raxon.context.tsx +256 -0
- package/core/schema/checkout.schema.ts +103 -0
- package/core/server/places.proxy.ts +35 -0
- package/core/server/raxon.bootstrap.route.ts +39 -0
- package/core/server/raxon.server.ts +80 -0
- package/core/util/basket.item.display.ts +19 -0
- package/core/util/category.nav.ts +46 -0
- package/core/util/client-ip.ts +35 -0
- package/core/util/collection.util.ts +433 -0
- package/core/util/fetch.bootstrap.ts +21 -0
- package/core/util/garanti-payment.ts +5 -0
- package/core/util/nexine.axios.tsx +104 -0
- package/core/util/no-cache.ts +6 -0
- package/core/util/util.ts +191 -0
- package/core/view/view.checkout.tsx +1964 -0
- package/dist/core/feature/address/api/places.api.d.ts.map +1 -1
- package/dist/core/feature/address/api/places.api.js +18 -4
- package/dist/core/server/places.proxy.d.ts +10 -0
- package/dist/core/server/places.proxy.d.ts.map +1 -0
- package/dist/core/server/places.proxy.js +24 -0
- package/dist/core/server/raxon.bootstrap.route.d.ts +7 -0
- package/dist/core/server/raxon.bootstrap.route.d.ts.map +1 -0
- package/dist/core/server/raxon.bootstrap.route.js +27 -0
- package/dist/core/server/raxon.server.d.ts +24 -0
- package/dist/core/server/raxon.server.d.ts.map +1 -0
- package/dist/core/server/raxon.server.js +59 -0
- package/dist/core/view/view.checkout.js +2 -2
- package/dist/middleware.d.ts +6 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +5 -0
- package/dist/server-bootstrap.d.ts +2 -0
- package/dist/server-bootstrap.d.ts.map +1 -0
- package/dist/server-bootstrap.js +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -3
- package/tailwind.css +11 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useCallback, useContext, useEffect, useRef } from 'react';
|
|
4
|
+
import { useSearchParams, useRouter } from 'next/navigation';
|
|
5
|
+
import { AnalyticEventType } from '@/core/interface/prisma.interface';
|
|
6
|
+
import { sendAnalyticEvents } from './analytic.event.api';
|
|
7
|
+
import { useAnalyticAutoTrack } from './use.analytic.auto';
|
|
8
|
+
|
|
9
|
+
interface ProductAnalyticEvent {
|
|
10
|
+
productId: string;
|
|
11
|
+
variantId?: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface EmailClickedEvent {
|
|
15
|
+
tcx: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface AnalyticEventContextType {
|
|
19
|
+
trackProductView: (event: ProductAnalyticEvent) => void;
|
|
20
|
+
trackProductClick: (event: ProductAnalyticEvent) => void;
|
|
21
|
+
trackEmailClicked: (event: EmailClickedEvent) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const AnalyticEventContext = createContext<AnalyticEventContextType | undefined>(undefined);
|
|
25
|
+
|
|
26
|
+
export interface AnalyticEventProviderProps {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
productPathPrefix?: string;
|
|
29
|
+
autoTrack?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const AnalyticEventProvider = ({
|
|
33
|
+
children,
|
|
34
|
+
productPathPrefix = '/urunler',
|
|
35
|
+
autoTrack = true,
|
|
36
|
+
}: AnalyticEventProviderProps) => {
|
|
37
|
+
const bufferRef = useRef<ProductAnalyticEvent[]>([]);
|
|
38
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
39
|
+
const searchParams = useSearchParams();
|
|
40
|
+
const router = useRouter();
|
|
41
|
+
const tcxProcessedRef = useRef(false);
|
|
42
|
+
|
|
43
|
+
const flush = useCallback(async () => {
|
|
44
|
+
const events = [...bufferRef.current];
|
|
45
|
+
if (events.length === 0) return;
|
|
46
|
+
|
|
47
|
+
bufferRef.current = [];
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await sendAnalyticEvents(
|
|
51
|
+
events.map((event) => ({
|
|
52
|
+
productId: event.productId,
|
|
53
|
+
variantId: event.variantId,
|
|
54
|
+
eventType: AnalyticEventType.VIEW,
|
|
55
|
+
}))
|
|
56
|
+
);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to send product view events', error);
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
const trackProductView = useCallback(
|
|
63
|
+
(event: ProductAnalyticEvent) => {
|
|
64
|
+
if (!event.productId) return;
|
|
65
|
+
|
|
66
|
+
const exists = bufferRef.current.some(
|
|
67
|
+
(item) => item.productId === event.productId && item.variantId === event.variantId
|
|
68
|
+
);
|
|
69
|
+
if (!exists) {
|
|
70
|
+
bufferRef.current.push(event);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!timeoutRef.current) {
|
|
74
|
+
timeoutRef.current = setTimeout(() => {
|
|
75
|
+
flush();
|
|
76
|
+
timeoutRef.current = null;
|
|
77
|
+
}, 2000);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
[flush]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const trackProductClick = useCallback((event: ProductAnalyticEvent) => {
|
|
84
|
+
if (!event.productId) return;
|
|
85
|
+
|
|
86
|
+
sendAnalyticEvents([
|
|
87
|
+
{
|
|
88
|
+
productId: event.productId,
|
|
89
|
+
variantId: event.variantId,
|
|
90
|
+
eventType: AnalyticEventType.CLICKED,
|
|
91
|
+
},
|
|
92
|
+
]).catch((error) => {
|
|
93
|
+
console.error('Failed to send product click event', error);
|
|
94
|
+
});
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const removeTcxFromUrl = useCallback(() => {
|
|
98
|
+
const currentUrl = new URL(window.location.href);
|
|
99
|
+
if (!currentUrl.searchParams.has('tcx')) return;
|
|
100
|
+
|
|
101
|
+
currentUrl.searchParams.delete('tcx');
|
|
102
|
+
router.replace(currentUrl.pathname + currentUrl.search, { scroll: false });
|
|
103
|
+
}, [router]);
|
|
104
|
+
|
|
105
|
+
const trackEmailClicked = useCallback(
|
|
106
|
+
(event: EmailClickedEvent) => {
|
|
107
|
+
if (!event.tcx) return;
|
|
108
|
+
|
|
109
|
+
sendAnalyticEvents([
|
|
110
|
+
{
|
|
111
|
+
trackingCode: event.tcx,
|
|
112
|
+
eventType: AnalyticEventType.EMAIL_CLICKED,
|
|
113
|
+
},
|
|
114
|
+
])
|
|
115
|
+
.then(() => removeTcxFromUrl())
|
|
116
|
+
.catch((error) => {
|
|
117
|
+
console.error('Failed to send email clicked event', error);
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
[removeTcxFromUrl]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
useAnalyticAutoTrack({
|
|
124
|
+
enabled: autoTrack,
|
|
125
|
+
productPathPrefix,
|
|
126
|
+
trackProductView,
|
|
127
|
+
trackProductClick,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (tcxProcessedRef.current) return;
|
|
132
|
+
|
|
133
|
+
const tcx = searchParams.get('tcx');
|
|
134
|
+
if (!tcx) return;
|
|
135
|
+
|
|
136
|
+
tcxProcessedRef.current = true;
|
|
137
|
+
trackEmailClicked({ tcx });
|
|
138
|
+
}, [searchParams, trackEmailClicked]);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const handleUnload = () => {
|
|
142
|
+
if (timeoutRef.current) {
|
|
143
|
+
clearTimeout(timeoutRef.current);
|
|
144
|
+
timeoutRef.current = null;
|
|
145
|
+
}
|
|
146
|
+
flush();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const handleVisibilityChange = () => {
|
|
150
|
+
if (document.visibilityState === 'hidden') {
|
|
151
|
+
handleUnload();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
window.addEventListener('beforeunload', handleUnload);
|
|
156
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
157
|
+
|
|
158
|
+
return () => {
|
|
159
|
+
window.removeEventListener('beforeunload', handleUnload);
|
|
160
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
161
|
+
if (timeoutRef.current) {
|
|
162
|
+
clearTimeout(timeoutRef.current);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}, [flush]);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<AnalyticEventContext.Provider value={{ trackProductView, trackProductClick, trackEmailClicked }}>
|
|
169
|
+
{children}
|
|
170
|
+
</AnalyticEventContext.Provider>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const useAnalyticEvent = () => {
|
|
175
|
+
const context = useContext(AnalyticEventContext);
|
|
176
|
+
if (!context) {
|
|
177
|
+
throw new Error('useAnalyticEvent must be used within an AnalyticEventProvider');
|
|
178
|
+
}
|
|
179
|
+
return context;
|
|
180
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function escapeRegExp(value: string) {
|
|
2
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function buildProductPathPattern(productPathPrefix: string) {
|
|
6
|
+
const prefix = productPathPrefix.replace(/\/$/, '');
|
|
7
|
+
return new RegExp(`${escapeRegExp(prefix)}/([^/?#]+)`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function extractProductIdFromHref(href: string, productPathPrefix: string) {
|
|
11
|
+
if (!href) return null;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const url = href.startsWith('http') ? new URL(href) : new URL(href, window.location.origin);
|
|
15
|
+
const match = url.pathname.match(buildProductPathPattern(productPathPrefix));
|
|
16
|
+
return match?.[1] ?? null;
|
|
17
|
+
} catch {
|
|
18
|
+
const match = href.match(buildProductPathPattern(productPathPrefix));
|
|
19
|
+
return match?.[1] ?? null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function readProductFromElement(element: Element, productPathPrefix: string) {
|
|
24
|
+
const trackedElement = element.closest('[data-raxon-product-id]');
|
|
25
|
+
if (trackedElement) {
|
|
26
|
+
const productId = trackedElement.getAttribute('data-raxon-product-id');
|
|
27
|
+
if (!productId) return null;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
productId,
|
|
31
|
+
variantId: trackedElement.getAttribute('data-raxon-variant-id'),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const link = element.closest('a[href]');
|
|
36
|
+
if (!link) return null;
|
|
37
|
+
|
|
38
|
+
const productId = extractProductIdFromHref(link.getAttribute('href') || '', productPathPrefix);
|
|
39
|
+
if (!productId) return null;
|
|
40
|
+
|
|
41
|
+
return { productId, variantId: null };
|
|
42
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
import { buildProductPathPattern, extractProductIdFromHref, readProductFromElement } from './analytic.event.util';
|
|
6
|
+
|
|
7
|
+
interface ProductAnalyticEvent {
|
|
8
|
+
productId: string;
|
|
9
|
+
variantId?: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface UseAnalyticAutoTrackOptions {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
productPathPrefix: string;
|
|
15
|
+
trackProductView: (event: ProductAnalyticEvent) => void;
|
|
16
|
+
trackProductClick: (event: ProductAnalyticEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useAnalyticAutoTrack({
|
|
20
|
+
enabled,
|
|
21
|
+
productPathPrefix,
|
|
22
|
+
trackProductView,
|
|
23
|
+
trackProductClick,
|
|
24
|
+
}: UseAnalyticAutoTrackOptions) {
|
|
25
|
+
const pathname = usePathname();
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!enabled) return;
|
|
29
|
+
|
|
30
|
+
const productId = pathname.match(buildProductPathPattern(productPathPrefix))?.[1];
|
|
31
|
+
if (productId) {
|
|
32
|
+
trackProductView({ productId });
|
|
33
|
+
}
|
|
34
|
+
}, [enabled, pathname, productPathPrefix, trackProductView]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!enabled || typeof window === 'undefined') return;
|
|
38
|
+
|
|
39
|
+
const viewedElements = new WeakSet<Element>();
|
|
40
|
+
|
|
41
|
+
const trackVisibleElement = (element: Element) => {
|
|
42
|
+
if (viewedElements.has(element)) return;
|
|
43
|
+
|
|
44
|
+
const productId = element.getAttribute('data-raxon-product-id');
|
|
45
|
+
if (productId) {
|
|
46
|
+
viewedElements.add(element);
|
|
47
|
+
trackProductView({
|
|
48
|
+
productId,
|
|
49
|
+
variantId: element.getAttribute('data-raxon-variant-id'),
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (element instanceof HTMLAnchorElement) {
|
|
55
|
+
const hrefProductId = extractProductIdFromHref(element.href, productPathPrefix);
|
|
56
|
+
if (hrefProductId) {
|
|
57
|
+
viewedElements.add(element);
|
|
58
|
+
trackProductView({ productId: hrefProductId });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const viewObserver = new IntersectionObserver(
|
|
64
|
+
(entries) => {
|
|
65
|
+
entries.forEach((entry) => {
|
|
66
|
+
if (entry.isIntersecting) {
|
|
67
|
+
trackVisibleElement(entry.target);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
{ threshold: 0.5 }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const observeCandidates = () => {
|
|
75
|
+
document
|
|
76
|
+
.querySelectorAll('[data-raxon-product-id], a[href]')
|
|
77
|
+
.forEach((element) => {
|
|
78
|
+
if (element.hasAttribute('data-raxon-product-id')) {
|
|
79
|
+
viewObserver.observe(element);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (element instanceof HTMLAnchorElement) {
|
|
84
|
+
const productId = extractProductIdFromHref(element.href, productPathPrefix);
|
|
85
|
+
if (productId) {
|
|
86
|
+
viewObserver.observe(element);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const onClick = (event: MouseEvent) => {
|
|
93
|
+
const target = event.target;
|
|
94
|
+
if (!(target instanceof Element)) return;
|
|
95
|
+
|
|
96
|
+
const productEvent = readProductFromElement(target, productPathPrefix);
|
|
97
|
+
if (productEvent) {
|
|
98
|
+
trackProductClick(productEvent);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const mutationObserver = new MutationObserver(observeCandidates);
|
|
103
|
+
|
|
104
|
+
document.addEventListener('click', onClick, true);
|
|
105
|
+
mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
106
|
+
observeCandidates();
|
|
107
|
+
|
|
108
|
+
return () => {
|
|
109
|
+
document.removeEventListener('click', onClick, true);
|
|
110
|
+
viewObserver.disconnect();
|
|
111
|
+
mutationObserver.disconnect();
|
|
112
|
+
};
|
|
113
|
+
}, [enabled, productPathPrefix, trackProductClick, trackProductView]);
|
|
114
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { nexineAxios } from "@/core/util/nexine.axios";
|
|
2
|
+
import { IData } from "@/core/interface/nexine.interface";
|
|
3
|
+
import { Article } from "@/core/interface/prisma.interface";
|
|
4
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
|
|
6
|
+
export const useArticle = () => {
|
|
7
|
+
|
|
8
|
+
const queryClient = useQueryClient();
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
fetch : (params?:any) => {
|
|
12
|
+
return useQuery({
|
|
13
|
+
queryKey: ['web','article',params],
|
|
14
|
+
queryFn: async () => {
|
|
15
|
+
var response = await nexineAxios.get<IData<Article>>('/customer/article',{
|
|
16
|
+
params
|
|
17
|
+
})
|
|
18
|
+
return response.data;
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
},
|
|
22
|
+
detail : (id:string) => {
|
|
23
|
+
return useQuery({
|
|
24
|
+
queryKey: ['web','article',id],
|
|
25
|
+
queryFn: async () => {
|
|
26
|
+
var response = await nexineAxios.get<Article>('/customer/article/'+id)
|
|
27
|
+
return response.data;
|
|
28
|
+
},
|
|
29
|
+
enabled: Boolean(id),
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { nexineAxios } from '@/core/util/nexine.axios';
|
|
2
|
+
import { IData } from '@/core/interface/nexine.interface';
|
|
3
|
+
import { Attribute, Brand, Category, Faq } from '@/core/interface/prisma.interface';
|
|
4
|
+
import { useQuery } from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
export const useAttribute = () => {
|
|
7
|
+
return {
|
|
8
|
+
fetch: (params: { categoryId?: string; enabled?: boolean, productId?: string }) => {
|
|
9
|
+
return useQuery({
|
|
10
|
+
queryKey: ['attribute', 'category', params.categoryId, params.productId],
|
|
11
|
+
enabled: (params.enabled ?? true) ,
|
|
12
|
+
queryFn: async () => {
|
|
13
|
+
var response = await nexineAxios.get<IData<Attribute>>('/customer/attribute', {
|
|
14
|
+
params: {
|
|
15
|
+
categoryId: params.categoryId,
|
|
16
|
+
productId: params.productId,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
return response.data;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { nexineAxios } from "@/core/util/nexine.axios";
|
|
2
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import toast from "react-hot-toast";
|
|
5
|
+
|
|
6
|
+
// Auth event'leri için custom eventler
|
|
7
|
+
export const LOGOUT_EVENT = 'intermarkt-logout';
|
|
8
|
+
export const LOGIN_SUCCESS_EVENT = 'intermarkt-login-success';
|
|
9
|
+
|
|
10
|
+
export const useAuth = () => {
|
|
11
|
+
|
|
12
|
+
const queryClient = useQueryClient();
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
|
|
18
|
+
profile : ({isEnabled = false}) => {
|
|
19
|
+
return useQuery({
|
|
20
|
+
queryKey: ["profile"],
|
|
21
|
+
enabled: isEnabled,
|
|
22
|
+
queryFn: async () => {
|
|
23
|
+
var response = await nexineAxios.get('/customer/profile')
|
|
24
|
+
return response.data.data
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
token : () => {
|
|
29
|
+
return useMutation({
|
|
30
|
+
mutationFn: async () => {
|
|
31
|
+
var response = await nexineAxios.get('/auth/token')
|
|
32
|
+
return response.data.data
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
loginEmail : () => {
|
|
38
|
+
return useMutation({
|
|
39
|
+
mutationFn: (data: any) => {
|
|
40
|
+
return nexineAxios.post('/auth/login/email', data)
|
|
41
|
+
},
|
|
42
|
+
onSuccess: (response: any) => {
|
|
43
|
+
localStorage.setItem("koksal-token", response.data.token);
|
|
44
|
+
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
45
|
+
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
46
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
47
|
+
|
|
48
|
+
// Login success event'ini tetikle
|
|
49
|
+
if (typeof window !== 'undefined') {
|
|
50
|
+
window.dispatchEvent(new Event(LOGIN_SUCCESS_EVENT));
|
|
51
|
+
window.dispatchEvent(new Event('koksal-token-changed'));
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
loginGuest : () => {
|
|
57
|
+
return useMutation({
|
|
58
|
+
mutationFn: () => {
|
|
59
|
+
return nexineAxios.post('/auth/login/guest')
|
|
60
|
+
},
|
|
61
|
+
onSuccess: (response: any) => {
|
|
62
|
+
localStorage.setItem("koksal-token", response.data.token);
|
|
63
|
+
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
64
|
+
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
65
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// Login success event'ini tetikle
|
|
69
|
+
if (typeof window !== 'undefined') {
|
|
70
|
+
window.dispatchEvent(new Event(LOGIN_SUCCESS_EVENT));
|
|
71
|
+
window.dispatchEvent(new Event('koksal-token-changed'));
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
},
|
|
76
|
+
loginSocial : () => {
|
|
77
|
+
return useMutation({
|
|
78
|
+
mutationFn: async (data: { platform: string, returnUrl?: string }) => {
|
|
79
|
+
var response = await nexineAxios.get(`/auth/login/social`, { params : { platform : data.platform,returnUrl : data.returnUrl } })
|
|
80
|
+
return response.data.url
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
},
|
|
84
|
+
register : () => {
|
|
85
|
+
return useMutation({
|
|
86
|
+
mutationFn: (data: any) => {
|
|
87
|
+
return nexineAxios.post('/auth/register', data)
|
|
88
|
+
},
|
|
89
|
+
onSuccess: () => {
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
},
|
|
94
|
+
// Şifremi unuttum - E-posta gönderme
|
|
95
|
+
codeSend: () => {
|
|
96
|
+
return useMutation({
|
|
97
|
+
mutationFn: (data: { email: string }) => {
|
|
98
|
+
return nexineAxios.post('/auth/code-send', data)
|
|
99
|
+
},
|
|
100
|
+
onSuccess: () => {
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
},
|
|
105
|
+
// Doğrulama kodu kontrol etme
|
|
106
|
+
verifyCode: () => {
|
|
107
|
+
return useMutation({
|
|
108
|
+
mutationFn: (data: { email: string, code: string }) => {
|
|
109
|
+
return nexineAxios.post('/auth/code-verify', data)
|
|
110
|
+
},
|
|
111
|
+
onSuccess: () => {
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
// Şifre sıfırlama
|
|
118
|
+
resetPassword: () => {
|
|
119
|
+
return useMutation({
|
|
120
|
+
mutationFn: (data: { email: string, code: string, password: string }) => {
|
|
121
|
+
return nexineAxios.post('/auth/reset-password', data)
|
|
122
|
+
},
|
|
123
|
+
onSuccess: () => {
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
logout: () => {
|
|
129
|
+
localStorage.removeItem("koksal-token");
|
|
130
|
+
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
131
|
+
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
132
|
+
|
|
133
|
+
// Logout event'ini tetikle
|
|
134
|
+
if (typeof window !== 'undefined') {
|
|
135
|
+
window.dispatchEvent(new Event(LOGOUT_EVENT));
|
|
136
|
+
window.dispatchEvent(new Event('koksal-token-changed'));
|
|
137
|
+
window.location.reload();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useImperativeHandle, useMemo, useState } from 'react';
|
|
2
|
+
import { Modal } from 'rizzui';
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import ViewLogin from '@/core/feature/auth/view/view.login';
|
|
5
|
+
import ViewRegister from '@/core/feature/auth/view/view.register';
|
|
6
|
+
import { useRaxon } from '@/core/raxon.context';
|
|
7
|
+
import { GeneralImage } from '@/core/component/general.image';
|
|
8
|
+
import { usePathname } from 'next/navigation';
|
|
9
|
+
import queryString from 'query-string';
|
|
10
|
+
|
|
11
|
+
export interface ModalAuthProps {}
|
|
12
|
+
export interface ModalAuthRef {
|
|
13
|
+
open: (defaultTab?: 'login' | 'register', queries?: any) => void;
|
|
14
|
+
close: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ModalAuth = React.forwardRef<ModalAuthRef, ModalAuthProps>((props, ref) => {
|
|
18
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
19
|
+
const [activeTab, setActiveTab] = useState<'login' | 'register'>('login');
|
|
20
|
+
const [queries, setQueries] = useState<any>({});
|
|
21
|
+
const { branch } = useRaxon();
|
|
22
|
+
|
|
23
|
+
const pathname = usePathname();
|
|
24
|
+
|
|
25
|
+
const returnUrl = useMemo(() => {
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
return queryString.stringify(queries);
|
|
29
|
+
}, [pathname, queries]);
|
|
30
|
+
|
|
31
|
+
useImperativeHandle(ref, () => ({
|
|
32
|
+
open: (defaultTab = 'login', queries?: any) => {
|
|
33
|
+
setActiveTab(defaultTab);
|
|
34
|
+
setQueries(queries ?? {});
|
|
35
|
+
setIsOpen(true);
|
|
36
|
+
},
|
|
37
|
+
close: () => {
|
|
38
|
+
setIsOpen(false);
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const handleClose = () => {
|
|
43
|
+
setIsOpen(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Modal isOpen={isOpen} onClose={handleClose} customSize={400} overlayClassName="backdrop-blur-sm">
|
|
48
|
+
<div className="relative bg-white rounded-lg shadow-2xl">
|
|
49
|
+
{/* Close Button */}
|
|
50
|
+
<button onClick={handleClose} className="absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 transition-colors z-10">
|
|
51
|
+
<X className="w-5 h-5" />
|
|
52
|
+
</button>
|
|
53
|
+
|
|
54
|
+
{/* Header */}
|
|
55
|
+
<div className="text-center pt-8 pb-6 px-8">
|
|
56
|
+
<div className="flex justify-center mb-4">
|
|
57
|
+
<GeneralImage quality={85} src={branch?.logoMedia?.relativePath ? `${process.env.NEXT_PUBLIC_STORAGE_URL}/${branch?.logoMedia?.relativePath}` : ''} alt="logo" width={180} height={40} className="h-8 w-auto object-contain" />
|
|
58
|
+
</div>
|
|
59
|
+
<p className="text-black/80 text-sm font-light uppercase tracking-wider">{activeTab === 'login' ? 'Hesabınıza giriş yapın' : 'Yeni hesap oluşturun'}</p>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Tab Navigation */}
|
|
63
|
+
<div className="flex border-b border-black/10 mx-8">
|
|
64
|
+
<button onClick={() => setActiveTab('login')} className={`flex-1 py-3 text-sm font-black uppercase tracking-wider transition-colors border-b-2 ${activeTab === 'login' ? 'border-black text-black' : 'border-transparent text-black/80 hover:text-black'}`}>
|
|
65
|
+
Giriş Yap
|
|
66
|
+
</button>
|
|
67
|
+
<button onClick={() => setActiveTab('register')} className={`flex-1 py-3 text-sm font-black uppercase tracking-wider transition-colors border-b-2 ${activeTab === 'register' ? 'border-black text-black' : 'border-transparent text-black/80 hover:text-black'}`}>
|
|
68
|
+
Kayıt Ol
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{/* Content */}
|
|
73
|
+
<div className="p-8">
|
|
74
|
+
{activeTab === 'login' && <ViewLogin onClose={handleClose} returnUrl={returnUrl} />}
|
|
75
|
+
{activeTab === 'register' && <ViewRegister onClose={handleClose} onSwitchToLogin={() => setActiveTab('login')} />}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</Modal>
|
|
79
|
+
);
|
|
80
|
+
});
|