@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.
- 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 +76 -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/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/view/view.checkout.js +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -3
- package/tailwind.css +11 -0
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { Eye, EyeOff, Lock, Mail, ArrowRight } from 'lucide-react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { useAuth } from '@/core/feature/auth/hook/use.auth';
|
|
6
|
+
import { useForm } from 'react-hook-form';
|
|
7
|
+
import { useRouter } from 'next/navigation';
|
|
8
|
+
import toast from 'react-hot-toast';
|
|
9
|
+
|
|
10
|
+
interface ViewLoginProps {
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
returnUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function ViewLogin({ onClose, returnUrl }: ViewLoginProps) {
|
|
16
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
17
|
+
const { mutate: login } = useAuth().loginEmail();
|
|
18
|
+
const { mutate: loginGuest } = useAuth().loginGuest();
|
|
19
|
+
const { mutate: loginSocial } = useAuth().loginSocial();
|
|
20
|
+
const form = useForm();
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const handleSubmitGuest = (e: React.FormEvent) => {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
loginGuest({} as any, {
|
|
26
|
+
onSuccess: () => {
|
|
27
|
+
onClose?.();
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleLoginSocial = (provider: string) => {
|
|
33
|
+
loginSocial({ platform: provider, returnUrl: returnUrl }, {
|
|
34
|
+
onSuccess: (url) => {
|
|
35
|
+
if (typeof window !== 'undefined') {
|
|
36
|
+
window.location.href = url;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
form.handleSubmit(data => {
|
|
48
|
+
login(data, {
|
|
49
|
+
onSuccess: () => {
|
|
50
|
+
|
|
51
|
+
onClose?.();
|
|
52
|
+
|
|
53
|
+
},
|
|
54
|
+
onError: (error: any) => {
|
|
55
|
+
toast.error(error?.response?.data?.info?.title || 'Giriş başarısız');
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
})();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
63
|
+
{/* Email Field */}
|
|
64
|
+
<div>
|
|
65
|
+
<input
|
|
66
|
+
type="email"
|
|
67
|
+
{...form.register('email')}
|
|
68
|
+
placeholder="E-posta adresinizi girin *"
|
|
69
|
+
className="w-full px-0 py-5 text-lg font-medium border-0 border-b-2 border-gray-300 bg-transparent focus:outline-none focus:border-black transition-colors placeholder-gray-500"
|
|
70
|
+
required
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Password Field */}
|
|
75
|
+
<div className="relative">
|
|
76
|
+
<input
|
|
77
|
+
type={showPassword ? 'text' : 'password'}
|
|
78
|
+
{...form.register('password')}
|
|
79
|
+
placeholder="Şifre *"
|
|
80
|
+
className="w-full px-0 py-5 pr-10 text-lg font-medium border-0 border-b-2 border-gray-300 bg-transparent focus:outline-none focus:border-black transition-colors placeholder-gray-500"
|
|
81
|
+
required
|
|
82
|
+
/>
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
className="absolute right-0 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
86
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
87
|
+
>
|
|
88
|
+
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Remember Me & Forgot Password */}
|
|
93
|
+
<div className="flex items-center justify-between py-4">
|
|
94
|
+
<div className="flex items-center">
|
|
95
|
+
<input
|
|
96
|
+
id="remember"
|
|
97
|
+
type="checkbox"
|
|
98
|
+
{...form.register('rememberMe')}
|
|
99
|
+
className="w-4 h-4 text-black border-gray-300 rounded focus:ring-0"
|
|
100
|
+
/>
|
|
101
|
+
<label htmlFor="remember" className="ml-2 text-base font-medium text-gray-700">Beni hatırla</label>
|
|
102
|
+
</div>
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
onClick={onClose}
|
|
106
|
+
className="text-base font-medium text-gray-600 hover:text-black transition-colors"
|
|
107
|
+
>
|
|
108
|
+
Şifrenizi mi unuttunuz?
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
{/* Social Login */}
|
|
115
|
+
<div className="py-6">
|
|
116
|
+
<div className="flex items-center mb-6">
|
|
117
|
+
<div className="flex-1 border-t border-gray-200"></div>
|
|
118
|
+
<span className="px-4 text-base font-medium text-gray-500">Veya sosyal hesapla devam edin</span>
|
|
119
|
+
<div className="flex-1 border-t border-gray-200"></div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div className="flex flex-col sm:grid sm:grid-cols-2 gap-4">
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
onClick={() => handleLoginSocial('google')}
|
|
126
|
+
className="py-4 text-base font-medium border border-gray-200 hover:border-black transition-colors flex items-center justify-center space-x-2"
|
|
127
|
+
>
|
|
128
|
+
<svg width="20" height="20" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
129
|
+
<g clipPath="url(#clip0_278_6045)">
|
|
130
|
+
<path d="M21.6696 9.08832L12.696 9.08789C12.2997 9.08789 11.9785 9.40904 11.9785 9.8053V12.672C11.9785 13.0681 12.2997 13.3894 12.6959 13.3894H17.7493C17.196 14.8254 16.1632 16.0281 14.8455 16.7922L17.0002 20.5223C20.4567 18.5233 22.5002 15.0158 22.5002 11.0894C22.5002 10.5303 22.459 10.1307 22.3766 9.68064C22.314 9.33874 22.0171 9.08832 21.6696 9.08832Z" fill="#167EE6"/>
|
|
131
|
+
<path d="M11.4999 17.6964C9.02689 17.6964 6.86797 16.3452 5.70846 14.3457L1.97852 16.4956C3.87666 19.7854 7.4325 22.0007 11.4999 22.0007C13.4953 22.0007 15.378 21.4635 16.9999 20.5272V20.5221L14.8452 16.792C13.8595 17.3637 12.719 17.6964 11.4999 17.6964Z" fill="#12B347"/>
|
|
132
|
+
<path d="M17 20.5262V20.5211L14.8452 16.791C13.8596 17.3626 12.7192 17.6954 11.5 17.6954V21.9997C13.4953 21.9997 15.3782 21.4625 17 20.5262Z" fill="#0F993E"/>
|
|
133
|
+
<path d="M4.80435 11.0007C4.80435 9.78177 5.13702 8.64133 5.70854 7.65576L1.9786 5.50586C1.0372 7.12264 0.5 9.00029 0.5 11.0007C0.5 13.0012 1.0372 14.8788 1.9786 16.4956L5.70854 14.3457C5.13702 13.3602 4.80435 12.2197 4.80435 11.0007Z" fill="#FFD500"/>
|
|
134
|
+
<path d="M11.4999 4.30435C13.1126 4.30435 14.5939 4.87738 15.7509 5.83056C16.0363 6.06568 16.4512 6.04871 16.7127 5.78725L18.7438 3.75611C19.0405 3.45946 19.0193 2.97387 18.7024 2.69895C16.7639 1.0172 14.2416 0 11.4999 0C7.4325 0 3.87666 2.21534 1.97852 5.50511L5.70846 7.65501C6.86797 5.65555 9.02689 4.30435 11.4999 4.30435Z" fill="#FF4B26"/>
|
|
135
|
+
<path d="M15.751 5.83056C16.0364 6.06568 16.4513 6.04871 16.7128 5.78725L18.7439 3.75611C19.0405 3.45946 19.0194 2.97387 18.7025 2.69895C16.764 1.01716 14.2417 0 11.5 0V4.30435C13.1126 4.30435 14.594 4.87738 15.751 5.83056Z" fill="#D93F21"/>
|
|
136
|
+
</g>
|
|
137
|
+
</svg>
|
|
138
|
+
<span className="text-sm sm:text-base">Google ile giriş yap</span>
|
|
139
|
+
</button>
|
|
140
|
+
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onClick={() => handleLoginSocial('facebook')}
|
|
144
|
+
className="py-4 text-base font-medium border border-gray-200 hover:border-black transition-colors flex items-center justify-center space-x-2"
|
|
145
|
+
>
|
|
146
|
+
<svg width="20" height="20" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
147
|
+
<g clipPath="url(#clip0_278_6055)">
|
|
148
|
+
<path d="M22.5 11C22.5 16.4905 18.4773 21.0414 13.2188 21.8664V14.1797H15.7818L16.2695 11H13.2188V8.93664C13.2188 8.06652 13.645 7.21875 15.0114 7.21875H16.3984V4.51172C16.3984 4.51172 15.1395 4.29688 13.9359 4.29688C11.4235 4.29688 9.78125 5.81969 9.78125 8.57656V11H6.98828V14.1797H9.78125V21.8664C4.52273 21.0414 0.5 16.4905 0.5 11C0.5 4.92508 5.42508 0 11.5 0C17.5749 0 22.5 4.92508 22.5 11Z" fill="#1877F2"/>
|
|
149
|
+
<path d="M15.7818 14.1797L16.2695 11H13.2188V8.9366C13.2188 8.0667 13.6449 7.21875 15.0114 7.21875H16.3984V4.51172C16.3984 4.51172 15.1396 4.29688 13.9361 4.29688C11.4235 4.29688 9.78125 5.81969 9.78125 8.57656V11H6.98828V14.1797H9.78125V21.8663C10.3413 21.9542 10.9153 22 11.5 22C12.0847 22 12.6587 21.9542 13.2188 21.8663V14.1797H15.7818Z" fill="white"/>
|
|
150
|
+
</g>
|
|
151
|
+
</svg>
|
|
152
|
+
<span className="text-sm sm:text-base">Facebook ile giriş yap</span>
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
{/* Misafir Login */}
|
|
158
|
+
<button
|
|
159
|
+
onClick={handleSubmitGuest}
|
|
160
|
+
type="button"
|
|
161
|
+
className="w-full py-4 text-base font-medium hover:text-black transition-colors"
|
|
162
|
+
>
|
|
163
|
+
Misafir Olarak Devam Et
|
|
164
|
+
</button>
|
|
165
|
+
|
|
166
|
+
{/* Login Button */}
|
|
167
|
+
<button
|
|
168
|
+
id="btnLogin"
|
|
169
|
+
type="submit"
|
|
170
|
+
className="w-full py-5 text-lg font-bold bg-black text-white hover:bg-gray-800 transition-colors"
|
|
171
|
+
>
|
|
172
|
+
Giriş Yap
|
|
173
|
+
</button>
|
|
174
|
+
|
|
175
|
+
{/* Terms */}
|
|
176
|
+
<div className="text-center">
|
|
177
|
+
<p className="text-sm font-medium text-gray-500 leading-relaxed">
|
|
178
|
+
Devam ederek{' '}
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
onClick={onClose}
|
|
182
|
+
className="hover:underline"
|
|
183
|
+
>
|
|
184
|
+
Kullanım Şartları
|
|
185
|
+
</button>{' '}
|
|
186
|
+
ve{' '}
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
onClick={onClose}
|
|
190
|
+
className="hover:underline"
|
|
191
|
+
>
|
|
192
|
+
Gizlilik Politikası
|
|
193
|
+
</button>
|
|
194
|
+
'nı kabul etmiş olursunuz.
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
</form>
|
|
198
|
+
);
|
|
199
|
+
}
|