@raxonltd/raxon-core 1.1.8 → 1.1.14
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/context/security.context.tsx +9 -5
- package/core/feature/address/api/places.api.ts +20 -4
- package/core/feature/auth/hook/use.auth.tsx +7 -6
- package/core/feature/cart/hook/use.cart.tsx +3 -0
- package/core/raxon.context.tsx +7 -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/nexine.axios.tsx +3 -2
- package/core/util/storage.keys.ts +39 -0
- package/dist/core/context/security.context.d.ts.map +1 -1
- package/dist/core/context/security.context.js +8 -5
- 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/feature/auth/hook/use.auth.d.ts.map +1 -1
- package/dist/core/feature/auth/hook/use.auth.js +7 -6
- package/dist/core/raxon.context.d.ts +3 -1
- package/dist/core/raxon.context.d.ts.map +1 -1
- package/dist/core/raxon.context.js +5 -3
- 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/util/nexine.axios.d.ts.map +1 -1
- package/dist/core/util/nexine.axios.js +3 -2
- package/dist/core/util/storage.keys.d.ts +9 -0
- package/dist/core/util/storage.keys.d.ts.map +1 -0
- package/dist/core/util/storage.keys.js +35 -0
- 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 +11 -1
|
@@ -5,6 +5,7 @@ import { useRouter, usePathname } from 'next/navigation';
|
|
|
5
5
|
import { LoginType, User } from '@/core/interface/prisma.interface';
|
|
6
6
|
import { useAuth } from '@/core/feature/auth/hook/use.auth';
|
|
7
7
|
import { useProfile } from '@/core/feature/profile/hook/use.profile';
|
|
8
|
+
import { getToken, getTokenChangedEventName, getTokenStorageKey, removeToken } from '@/core/util/storage.keys';
|
|
8
9
|
|
|
9
10
|
export interface SecurityState {
|
|
10
11
|
profile: User | null | undefined;
|
|
@@ -58,7 +59,7 @@ export const useSecurityState = (): SecurityState => {
|
|
|
58
59
|
|
|
59
60
|
const checkToken = useCallback(() => {
|
|
60
61
|
if (typeof window !== 'undefined') {
|
|
61
|
-
const token =
|
|
62
|
+
const token = getToken();
|
|
62
63
|
if (token && token.trim() !== '' && token.length > 3) {
|
|
63
64
|
setAuthLoading(true);
|
|
64
65
|
tokenCheck(undefined, {
|
|
@@ -76,7 +77,7 @@ export const useSecurityState = (): SecurityState => {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
if (isInvalidTokenError(error)) {
|
|
79
|
-
|
|
80
|
+
removeToken();
|
|
80
81
|
ensureGuestLogin();
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
@@ -97,8 +98,11 @@ export const useSecurityState = (): SecurityState => {
|
|
|
97
98
|
|
|
98
99
|
useEffect(() => {
|
|
99
100
|
if (typeof window !== 'undefined') {
|
|
101
|
+
const tokenStorageKey = getTokenStorageKey();
|
|
102
|
+
const tokenChangedEvent = getTokenChangedEventName();
|
|
103
|
+
|
|
100
104
|
const handleStorageChange = (e: StorageEvent) => {
|
|
101
|
-
if (e.key ===
|
|
105
|
+
if (e.key === tokenStorageKey) {
|
|
102
106
|
checkToken();
|
|
103
107
|
}
|
|
104
108
|
};
|
|
@@ -108,11 +112,11 @@ export const useSecurityState = (): SecurityState => {
|
|
|
108
112
|
};
|
|
109
113
|
|
|
110
114
|
window.addEventListener('storage', handleStorageChange);
|
|
111
|
-
window.addEventListener(
|
|
115
|
+
window.addEventListener(tokenChangedEvent, handleCustomStorageChange);
|
|
112
116
|
|
|
113
117
|
return () => {
|
|
114
118
|
window.removeEventListener('storage', handleStorageChange);
|
|
115
|
-
window.removeEventListener(
|
|
119
|
+
window.removeEventListener(tokenChangedEvent, handleCustomStorageChange);
|
|
116
120
|
};
|
|
117
121
|
}
|
|
118
122
|
}, [checkToken]);
|
|
@@ -24,11 +24,19 @@ export async function fetchPlaceAutocomplete(
|
|
|
24
24
|
throw new Error('Google Maps API key not configured');
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const isBrowser = typeof window !== 'undefined';
|
|
28
|
+
let url: URL;
|
|
29
|
+
|
|
30
|
+
if (isBrowser) {
|
|
31
|
+
url = new URL('/api/places/autocomplete', window.location.origin);
|
|
32
|
+
} else {
|
|
33
|
+
url = new URL('https://maps.googleapis.com/maps/api/place/autocomplete/json');
|
|
34
|
+
url.searchParams.set('key', apiKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
url.searchParams.set('input', input);
|
|
29
38
|
url.searchParams.set('components', components);
|
|
30
39
|
url.searchParams.set('language', language);
|
|
31
|
-
url.searchParams.set('key', apiKey);
|
|
32
40
|
|
|
33
41
|
const response = await fetch(url.toString(), { signal });
|
|
34
42
|
|
|
@@ -54,11 +62,19 @@ export async function fetchPlaceDetails(
|
|
|
54
62
|
throw new Error('Google Maps API key not configured');
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
const
|
|
65
|
+
const isBrowser = typeof window !== 'undefined';
|
|
66
|
+
let url: URL;
|
|
67
|
+
|
|
68
|
+
if (isBrowser) {
|
|
69
|
+
url = new URL('/api/places/details', window.location.origin);
|
|
70
|
+
} else {
|
|
71
|
+
url = new URL('https://maps.googleapis.com/maps/api/place/details/json');
|
|
72
|
+
url.searchParams.set('key', apiKey);
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
url.searchParams.set('place_id', placeId);
|
|
59
76
|
url.searchParams.set('fields', 'geometry,address_components,formatted_address');
|
|
60
77
|
url.searchParams.set('language', language);
|
|
61
|
-
url.searchParams.set('key', apiKey);
|
|
62
78
|
|
|
63
79
|
const response = await fetch(url.toString(), { signal });
|
|
64
80
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nexineAxios } from "@/core/util/nexine.axios";
|
|
2
|
+
import { dispatchTokenChanged, removeToken, setToken } from "@/core/util/storage.keys";
|
|
2
3
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
4
|
import { useEffect, useState } from "react";
|
|
4
5
|
import toast from "react-hot-toast";
|
|
@@ -40,7 +41,7 @@ export const useAuth = () => {
|
|
|
40
41
|
return nexineAxios.post('/auth/login/email', data)
|
|
41
42
|
},
|
|
42
43
|
onSuccess: (response: any) => {
|
|
43
|
-
|
|
44
|
+
setToken(response.data.token);
|
|
44
45
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
45
46
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
46
47
|
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
@@ -48,7 +49,7 @@ export const useAuth = () => {
|
|
|
48
49
|
// Login success event'ini tetikle
|
|
49
50
|
if (typeof window !== 'undefined') {
|
|
50
51
|
window.dispatchEvent(new Event(LOGIN_SUCCESS_EVENT));
|
|
51
|
-
|
|
52
|
+
dispatchTokenChanged();
|
|
52
53
|
}
|
|
53
54
|
},
|
|
54
55
|
})
|
|
@@ -59,7 +60,7 @@ export const useAuth = () => {
|
|
|
59
60
|
return nexineAxios.post('/auth/login/guest')
|
|
60
61
|
},
|
|
61
62
|
onSuccess: (response: any) => {
|
|
62
|
-
|
|
63
|
+
setToken(response.data.token);
|
|
63
64
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
64
65
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
65
66
|
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
@@ -68,7 +69,7 @@ export const useAuth = () => {
|
|
|
68
69
|
// Login success event'ini tetikle
|
|
69
70
|
if (typeof window !== 'undefined') {
|
|
70
71
|
window.dispatchEvent(new Event(LOGIN_SUCCESS_EVENT));
|
|
71
|
-
|
|
72
|
+
dispatchTokenChanged();
|
|
72
73
|
}
|
|
73
74
|
},
|
|
74
75
|
})
|
|
@@ -126,14 +127,14 @@ export const useAuth = () => {
|
|
|
126
127
|
})
|
|
127
128
|
},
|
|
128
129
|
logout: () => {
|
|
129
|
-
|
|
130
|
+
removeToken();
|
|
130
131
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
131
132
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
132
133
|
|
|
133
134
|
// Logout event'ini tetikle
|
|
134
135
|
if (typeof window !== 'undefined') {
|
|
135
136
|
window.dispatchEvent(new Event(LOGOUT_EVENT));
|
|
136
|
-
|
|
137
|
+
dispatchTokenChanged();
|
|
137
138
|
window.location.reload();
|
|
138
139
|
}
|
|
139
140
|
}
|
|
@@ -79,6 +79,9 @@ export const useCart = () => {
|
|
|
79
79
|
const response = await nexineAxios.post('/customer/basket/me/item', data);
|
|
80
80
|
return response.data;
|
|
81
81
|
},
|
|
82
|
+
onSuccess: () => {
|
|
83
|
+
queryClient.invalidateQueries({ queryKey: CART_QUERY_KEY });
|
|
84
|
+
},
|
|
82
85
|
onError: (error, _newData, context: any) => {
|
|
83
86
|
if (context?.previousCarts) {
|
|
84
87
|
context.previousCarts.forEach(([key, value]: any) => {
|
package/core/raxon.context.tsx
CHANGED
|
@@ -29,6 +29,7 @@ import { useCartState, CartState } from "@/core/context/cart.context";
|
|
|
29
29
|
import { AnalyticEventProvider } from "@/core/feature/analytic-event/analytic.event.context";
|
|
30
30
|
import { RaxonContextBrand } from "./interface/context.interface";
|
|
31
31
|
import { RaxonBootstrapPayload } from "./interface/bootstrap.interface";
|
|
32
|
+
import { DEFAULT_STORAGE_PREFIX } from "./util/storage.keys";
|
|
32
33
|
|
|
33
34
|
export const RaxonContext = createContext<RaxonContextType | undefined>(undefined);
|
|
34
35
|
|
|
@@ -72,6 +73,8 @@ export interface RaxonProviderProps {
|
|
|
72
73
|
children: React.ReactNode;
|
|
73
74
|
apiKey: string;
|
|
74
75
|
apiUrl: string;
|
|
76
|
+
/** localStorage anahtarları için önek. Varsayılan: `raxon` (ör. `raxon-token`) */
|
|
77
|
+
storagePrefix?: string;
|
|
75
78
|
productPathPrefix?: string;
|
|
76
79
|
analyticAutoTrack?: boolean;
|
|
77
80
|
/** Varsayılan: `/api/bootstrap` — Next.js BFF veya özel proxy */
|
|
@@ -120,6 +123,7 @@ const RaxonProviderInner = ({
|
|
|
120
123
|
children,
|
|
121
124
|
apiKey,
|
|
122
125
|
apiUrl,
|
|
126
|
+
storagePrefix = DEFAULT_STORAGE_PREFIX,
|
|
123
127
|
productPathPrefix,
|
|
124
128
|
analyticAutoTrack,
|
|
125
129
|
bootstrapUrl = "/api/bootstrap",
|
|
@@ -142,6 +146,7 @@ const RaxonProviderInner = ({
|
|
|
142
146
|
if (typeof window !== "undefined") {
|
|
143
147
|
(window as any).__RAXON_API_KEY__ = apiKey;
|
|
144
148
|
(window as any).__RAXON_API_URL__ = apiUrl;
|
|
149
|
+
(window as any).__RAXON_STORAGE_PREFIX__ = storagePrefix;
|
|
145
150
|
nexineAxios.defaults.baseURL = apiUrl;
|
|
146
151
|
nexineAxios.defaults.headers.common["x-api-key"] = apiKey;
|
|
147
152
|
}
|
|
@@ -224,6 +229,7 @@ export const RaxonProvider = ({
|
|
|
224
229
|
children,
|
|
225
230
|
apiKey,
|
|
226
231
|
apiUrl,
|
|
232
|
+
storagePrefix,
|
|
227
233
|
productPathPrefix,
|
|
228
234
|
analyticAutoTrack,
|
|
229
235
|
bootstrapUrl,
|
|
@@ -236,6 +242,7 @@ export const RaxonProvider = ({
|
|
|
236
242
|
<RaxonProviderInner
|
|
237
243
|
apiKey={apiKey}
|
|
238
244
|
apiUrl={apiUrl}
|
|
245
|
+
storagePrefix={storagePrefix}
|
|
239
246
|
productPathPrefix={productPathPrefix}
|
|
240
247
|
analyticAutoTrack={analyticAutoTrack}
|
|
241
248
|
bootstrapUrl={bootstrapUrl}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import type { NextRequest } from 'next/server';
|
|
3
|
+
|
|
4
|
+
const GOOGLE_PLACES_URL = {
|
|
5
|
+
autocomplete: 'https://maps.googleapis.com/maps/api/place/autocomplete/json',
|
|
6
|
+
details: 'https://maps.googleapis.com/maps/api/place/details/json',
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type GooglePlacesEndpoint = keyof typeof GOOGLE_PLACES_URL;
|
|
10
|
+
|
|
11
|
+
export async function proxyGooglePlaces(
|
|
12
|
+
request: NextRequest,
|
|
13
|
+
endpoint: GooglePlacesEndpoint,
|
|
14
|
+
googleMapsApiKey?: string,
|
|
15
|
+
) {
|
|
16
|
+
const googleUrl = new URL(GOOGLE_PLACES_URL[endpoint]);
|
|
17
|
+
|
|
18
|
+
request.nextUrl.searchParams.forEach((value, key) => {
|
|
19
|
+
googleUrl.searchParams.set(key, value);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const apiKey = googleMapsApiKey ?? process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? '';
|
|
23
|
+
if (!googleUrl.searchParams.has('key') && apiKey) {
|
|
24
|
+
googleUrl.searchParams.set('key', apiKey);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(googleUrl.toString());
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
return NextResponse.json(data);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('[raxonServer/places]', error);
|
|
33
|
+
return NextResponse.json({ error: 'Google Places API isteği başarısız' }, { status: 500 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { unstable_cache } from 'next/cache';
|
|
3
|
+
import { fetchRaxonBootstrap } from '@/core/util/fetch.bootstrap';
|
|
4
|
+
import {
|
|
5
|
+
BOOTSTRAP_REVALIDATE_SECONDS,
|
|
6
|
+
bootstrapCacheHeaders,
|
|
7
|
+
} from '@/core/server/raxon.server';
|
|
8
|
+
|
|
9
|
+
async function fetchBootstrapData() {
|
|
10
|
+
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
11
|
+
const apiKey = process.env.NEXT_PUBLIC_API_KEY;
|
|
12
|
+
|
|
13
|
+
if (!apiUrl || !apiKey) {
|
|
14
|
+
throw new Error('[raxonServer] NEXT_PUBLIC_API_URL veya NEXT_PUBLIC_API_KEY tanımlı değil');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return fetchRaxonBootstrap(apiUrl, apiKey);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getCachedBootstrap = unstable_cache(
|
|
21
|
+
fetchBootstrapData,
|
|
22
|
+
['raxon-bootstrap'],
|
|
23
|
+
{ revalidate: BOOTSTRAP_REVALIDATE_SECONDS },
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
/** API route kullanmak isteyenler için: export { GET, revalidate } from '@raxonltd/raxon-core/server/bootstrap' */
|
|
27
|
+
export const revalidate = BOOTSTRAP_REVALIDATE_SECONDS;
|
|
28
|
+
|
|
29
|
+
export async function GET() {
|
|
30
|
+
try {
|
|
31
|
+
const data = await getCachedBootstrap();
|
|
32
|
+
return NextResponse.json(data, {
|
|
33
|
+
headers: bootstrapCacheHeaders(revalidate),
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('[raxonServer]', error);
|
|
37
|
+
return NextResponse.json({ error: 'Bootstrap verisi alınamadı' }, { status: 502 });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import type { NextRequest } from 'next/server';
|
|
3
|
+
import { fetchRaxonBootstrap } from '@/core/util/fetch.bootstrap';
|
|
4
|
+
import { proxyGooglePlaces } from '@/core/server/places.proxy';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_REVALIDATE_SECONDS = 300;
|
|
7
|
+
|
|
8
|
+
const RAXON_SERVER_PATHS = {
|
|
9
|
+
bootstrap: '/api/bootstrap',
|
|
10
|
+
placesAutocomplete: '/api/places/autocomplete',
|
|
11
|
+
placesDetails: '/api/places/details',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export interface RaxonServerOptions {
|
|
15
|
+
apiUrl?: string;
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
googleMapsApiKey?: string;
|
|
18
|
+
revalidate?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveCredentials(options: RaxonServerOptions = {}) {
|
|
22
|
+
const apiUrl = options.apiUrl ?? process.env.NEXT_PUBLIC_API_URL;
|
|
23
|
+
const apiKey = options.apiKey ?? process.env.NEXT_PUBLIC_API_KEY;
|
|
24
|
+
return { apiUrl, apiKey };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function fetchBootstrapData(options: RaxonServerOptions = {}) {
|
|
28
|
+
const { apiUrl, apiKey } = resolveCredentials(options);
|
|
29
|
+
|
|
30
|
+
if (!apiUrl || !apiKey) {
|
|
31
|
+
throw new Error('[raxonServer] NEXT_PUBLIC_API_URL veya NEXT_PUBLIC_API_KEY tanımlı değil');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return fetchRaxonBootstrap(apiUrl, apiKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function bootstrapCacheHeaders(revalidate = DEFAULT_REVALIDATE_SECONDS) {
|
|
38
|
+
return {
|
|
39
|
+
'Cache-Control': `public, s-maxage=${revalidate}, stale-while-revalidate=${revalidate * 2}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Next.js middleware — bootstrap ve Google Places BFF isteklerini karşılar.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // proxy.ts — config Next.js tarafından statik parse edilmeli, import edilemez
|
|
48
|
+
* import { raxonServer } from '@raxonltd/raxon-core/server';
|
|
49
|
+
* export default raxonServer();
|
|
50
|
+
* export const config = { matcher: ['/api/bootstrap', '/api/places/:path'] };
|
|
51
|
+
*/
|
|
52
|
+
export function raxonServer(options: RaxonServerOptions = {}) {
|
|
53
|
+
const revalidate = options.revalidate ?? DEFAULT_REVALIDATE_SECONDS;
|
|
54
|
+
|
|
55
|
+
return async function raxonMiddleware(request: NextRequest) {
|
|
56
|
+
const { pathname } = request.nextUrl;
|
|
57
|
+
|
|
58
|
+
if (pathname === RAXON_SERVER_PATHS.bootstrap) {
|
|
59
|
+
try {
|
|
60
|
+
const data = await fetchBootstrapData(options);
|
|
61
|
+
return NextResponse.json(data, { headers: bootstrapCacheHeaders(revalidate) });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('[raxonServer/bootstrap]', error);
|
|
64
|
+
return NextResponse.json({ error: 'Bootstrap verisi alınamadı' }, { status: 502 });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (pathname === RAXON_SERVER_PATHS.placesAutocomplete) {
|
|
69
|
+
return proxyGooglePlaces(request, 'autocomplete', options.googleMapsApiKey);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (pathname === RAXON_SERVER_PATHS.placesDetails) {
|
|
73
|
+
return proxyGooglePlaces(request, 'details', options.googleMapsApiKey);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return NextResponse.next();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { DEFAULT_REVALIDATE_SECONDS as BOOTSTRAP_REVALIDATE_SECONDS, bootstrapCacheHeaders };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { toast } from 'react-hot-toast';
|
|
3
|
+
import { getToken, removeToken } from '@/core/util/storage.keys';
|
|
3
4
|
|
|
4
5
|
export const nexineAxios = axios.create({
|
|
5
6
|
baseURL: typeof window !== 'undefined' ? (window as any).__RAXON_API_URL__ : process.env.NEXT_PUBLIC_API_URL,
|
|
@@ -39,7 +40,7 @@ nexineAxios.interceptors.request.use(config => {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
if (typeof window !== 'undefined') {
|
|
42
|
-
const token =
|
|
43
|
+
const token = getToken();
|
|
43
44
|
if (token) {
|
|
44
45
|
config.headers.Authorization = `Bearer ${token}`;
|
|
45
46
|
}
|
|
@@ -83,7 +84,7 @@ nexineAxios.interceptors.response.use(
|
|
|
83
84
|
|
|
84
85
|
// Token'ı temizle
|
|
85
86
|
if (typeof window !== 'undefined') {
|
|
86
|
-
|
|
87
|
+
removeToken();
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// /auth/me endpoint'inde toast gösterme (SecurityContext yönetiyor)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const DEFAULT_STORAGE_PREFIX = 'raxon';
|
|
2
|
+
|
|
3
|
+
const TOKEN_SUFFIX = '-token';
|
|
4
|
+
const TOKEN_CHANGED_SUFFIX = '-token-changed';
|
|
5
|
+
|
|
6
|
+
export const getStoragePrefix = (): string => {
|
|
7
|
+
if (typeof window !== 'undefined') {
|
|
8
|
+
return (window as any).__RAXON_STORAGE_PREFIX__ || DEFAULT_STORAGE_PREFIX;
|
|
9
|
+
}
|
|
10
|
+
return DEFAULT_STORAGE_PREFIX;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getTokenStorageKey = (): string => {
|
|
14
|
+
return `${getStoragePrefix()}${TOKEN_SUFFIX}`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const getTokenChangedEventName = (): string => {
|
|
18
|
+
return `${getStoragePrefix()}${TOKEN_CHANGED_SUFFIX}`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const getToken = (): string | null => {
|
|
22
|
+
if (typeof window === 'undefined') return null;
|
|
23
|
+
return localStorage.getItem(getTokenStorageKey());
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const setToken = (token: string): void => {
|
|
27
|
+
if (typeof window === 'undefined') return;
|
|
28
|
+
localStorage.setItem(getTokenStorageKey(), token);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const removeToken = (): void => {
|
|
32
|
+
if (typeof window === 'undefined') return;
|
|
33
|
+
localStorage.removeItem(getTokenStorageKey());
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const dispatchTokenChanged = (): void => {
|
|
37
|
+
if (typeof window === 'undefined') return;
|
|
38
|
+
window.dispatchEvent(new Event(getTokenChangedEventName()));
|
|
39
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.context.d.ts","sourceRoot":"","sources":["../../../core/context/security.context.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAa,IAAI,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"security.context.d.ts","sourceRoot":"","sources":["../../../core/context/security.context.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAa,IAAI,EAAE,MAAM,mCAAmC,CAAC;AAKpE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,gBAAgB,QAAO,aA0InC,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { useRouter, usePathname } from 'next/navigation';
|
|
|
4
4
|
import { LoginType } from '../interface/prisma.interface';
|
|
5
5
|
import { useAuth } from '../feature/auth/hook/use.auth';
|
|
6
6
|
import { useProfile } from '../feature/profile/hook/use.profile';
|
|
7
|
+
import { getToken, getTokenChangedEventName, getTokenStorageKey, removeToken } from '../util/storage.keys';
|
|
7
8
|
export const useSecurityState = () => {
|
|
8
9
|
const router = useRouter();
|
|
9
10
|
const pathname = usePathname();
|
|
@@ -41,7 +42,7 @@ export const useSecurityState = () => {
|
|
|
41
42
|
}, [loginGuest]);
|
|
42
43
|
const checkToken = useCallback(() => {
|
|
43
44
|
if (typeof window !== 'undefined') {
|
|
44
|
-
const token =
|
|
45
|
+
const token = getToken();
|
|
45
46
|
if (token && token.trim() !== '' && token.length > 3) {
|
|
46
47
|
setAuthLoading(true);
|
|
47
48
|
tokenCheck(undefined, {
|
|
@@ -58,7 +59,7 @@ export const useSecurityState = () => {
|
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
60
61
|
if (isInvalidTokenError(error)) {
|
|
61
|
-
|
|
62
|
+
removeToken();
|
|
62
63
|
ensureGuestLogin();
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
@@ -77,8 +78,10 @@ export const useSecurityState = () => {
|
|
|
77
78
|
}, [checkToken]);
|
|
78
79
|
useEffect(() => {
|
|
79
80
|
if (typeof window !== 'undefined') {
|
|
81
|
+
const tokenStorageKey = getTokenStorageKey();
|
|
82
|
+
const tokenChangedEvent = getTokenChangedEventName();
|
|
80
83
|
const handleStorageChange = (e) => {
|
|
81
|
-
if (e.key ===
|
|
84
|
+
if (e.key === tokenStorageKey) {
|
|
82
85
|
checkToken();
|
|
83
86
|
}
|
|
84
87
|
};
|
|
@@ -86,10 +89,10 @@ export const useSecurityState = () => {
|
|
|
86
89
|
checkToken();
|
|
87
90
|
};
|
|
88
91
|
window.addEventListener('storage', handleStorageChange);
|
|
89
|
-
window.addEventListener(
|
|
92
|
+
window.addEventListener(tokenChangedEvent, handleCustomStorageChange);
|
|
90
93
|
return () => {
|
|
91
94
|
window.removeEventListener('storage', handleStorageChange);
|
|
92
|
-
window.removeEventListener(
|
|
95
|
+
window.removeEventListener(tokenChangedEvent, handleCustomStorageChange);
|
|
93
96
|
};
|
|
94
97
|
}
|
|
95
98
|
}, [checkToken]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"places.api.d.ts","sourceRoot":"","sources":["../../../../../core/feature/address/api/places.api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAMtG,UAAU,yBAAyB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,EAAE,UAAyB,EAAE,QAAe,EAAE,MAAM,EAAE,GAAE,yBAA8B,GACrF,OAAO,CAAC,qBAAqB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"places.api.d.ts","sourceRoot":"","sources":["../../../../../core/feature/address/api/places.api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAMtG,UAAU,yBAAyB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,EAAE,UAAyB,EAAE,QAAe,EAAE,MAAM,EAAE,GAAE,yBAA8B,GACrF,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAiClC;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,EAAE,QAAe,EAAE,MAAM,EAAE,GAAE,oBAAyB,GACrD,OAAO,CAAC,kBAAkB,CAAC,CAiC7B"}
|
|
@@ -6,11 +6,18 @@ export async function fetchPlaceAutocomplete(input, { components = 'country:tr',
|
|
|
6
6
|
if (!apiKey) {
|
|
7
7
|
throw new Error('Google Maps API key not configured');
|
|
8
8
|
}
|
|
9
|
-
const
|
|
9
|
+
const isBrowser = typeof window !== 'undefined';
|
|
10
|
+
let url;
|
|
11
|
+
if (isBrowser) {
|
|
12
|
+
url = new URL('/api/places/autocomplete', window.location.origin);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
url = new URL('https://maps.googleapis.com/maps/api/place/autocomplete/json');
|
|
16
|
+
url.searchParams.set('key', apiKey);
|
|
17
|
+
}
|
|
10
18
|
url.searchParams.set('input', input);
|
|
11
19
|
url.searchParams.set('components', components);
|
|
12
20
|
url.searchParams.set('language', language);
|
|
13
|
-
url.searchParams.set('key', apiKey);
|
|
14
21
|
const response = await fetch(url.toString(), { signal });
|
|
15
22
|
if (!response.ok) {
|
|
16
23
|
throw new Error(`Google Places API error: ${response.status}`);
|
|
@@ -26,11 +33,18 @@ export async function fetchPlaceDetails(placeId, { language = 'tr', signal } = {
|
|
|
26
33
|
if (!apiKey) {
|
|
27
34
|
throw new Error('Google Maps API key not configured');
|
|
28
35
|
}
|
|
29
|
-
const
|
|
36
|
+
const isBrowser = typeof window !== 'undefined';
|
|
37
|
+
let url;
|
|
38
|
+
if (isBrowser) {
|
|
39
|
+
url = new URL('/api/places/details', window.location.origin);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
url = new URL('https://maps.googleapis.com/maps/api/place/details/json');
|
|
43
|
+
url.searchParams.set('key', apiKey);
|
|
44
|
+
}
|
|
30
45
|
url.searchParams.set('place_id', placeId);
|
|
31
46
|
url.searchParams.set('fields', 'geometry,address_components,formatted_address');
|
|
32
47
|
url.searchParams.set('language', language);
|
|
33
|
-
url.searchParams.set('key', apiKey);
|
|
34
48
|
const response = await fetch(url.toString(), { signal });
|
|
35
49
|
if (!response.ok) {
|
|
36
50
|
throw new Error(`Google Places API error: ${response.status}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use.auth.d.ts","sourceRoot":"","sources":["../../../../../core/feature/auth/hook/use.auth.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use.auth.d.ts","sourceRoot":"","sources":["../../../../../core/feature/auth/hook/use.auth.tsx"],"names":[],"mappings":"AAOA,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAChD,eAAO,MAAM,mBAAmB,6BAA6B,CAAC;AAE9D,eAAO,MAAM,OAAO;;;;;;;;kBAoEiC,MAAM;oBAAc,MAAM;;;;eAmBnC,MAAM;;;eAWN,MAAM;cAAQ,MAAM;;;eAYpB,MAAM;cAAQ,MAAM;kBAAY,MAAM;;;CAqBjF,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nexineAxios } from "../../../util/nexine.axios";
|
|
2
|
+
import { dispatchTokenChanged, removeToken, setToken } from "../../../util/storage.keys";
|
|
2
3
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
4
|
// Auth event'leri için custom eventler
|
|
4
5
|
export const LOGOUT_EVENT = 'intermarkt-logout';
|
|
@@ -30,14 +31,14 @@ export const useAuth = () => {
|
|
|
30
31
|
return nexineAxios.post('/auth/login/email', data);
|
|
31
32
|
},
|
|
32
33
|
onSuccess: (response) => {
|
|
33
|
-
|
|
34
|
+
setToken(response.data.token);
|
|
34
35
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
35
36
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
36
37
|
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
37
38
|
// Login success event'ini tetikle
|
|
38
39
|
if (typeof window !== 'undefined') {
|
|
39
40
|
window.dispatchEvent(new Event(LOGIN_SUCCESS_EVENT));
|
|
40
|
-
|
|
41
|
+
dispatchTokenChanged();
|
|
41
42
|
}
|
|
42
43
|
},
|
|
43
44
|
});
|
|
@@ -48,14 +49,14 @@ export const useAuth = () => {
|
|
|
48
49
|
return nexineAxios.post('/auth/login/guest');
|
|
49
50
|
},
|
|
50
51
|
onSuccess: (response) => {
|
|
51
|
-
|
|
52
|
+
setToken(response.data.token);
|
|
52
53
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
53
54
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
54
55
|
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
55
56
|
// Login success event'ini tetikle
|
|
56
57
|
if (typeof window !== 'undefined') {
|
|
57
58
|
window.dispatchEvent(new Event(LOGIN_SUCCESS_EVENT));
|
|
58
|
-
|
|
59
|
+
dispatchTokenChanged();
|
|
59
60
|
}
|
|
60
61
|
},
|
|
61
62
|
});
|
|
@@ -108,13 +109,13 @@ export const useAuth = () => {
|
|
|
108
109
|
});
|
|
109
110
|
},
|
|
110
111
|
logout: () => {
|
|
111
|
-
|
|
112
|
+
removeToken();
|
|
112
113
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
113
114
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
114
115
|
// Logout event'ini tetikle
|
|
115
116
|
if (typeof window !== 'undefined') {
|
|
116
117
|
window.dispatchEvent(new Event(LOGOUT_EVENT));
|
|
117
|
-
|
|
118
|
+
dispatchTokenChanged();
|
|
118
119
|
window.location.reload();
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -43,6 +43,8 @@ export interface RaxonProviderProps {
|
|
|
43
43
|
children: React.ReactNode;
|
|
44
44
|
apiKey: string;
|
|
45
45
|
apiUrl: string;
|
|
46
|
+
/** localStorage anahtarları için önek. Varsayılan: `raxon` (ör. `raxon-token`) */
|
|
47
|
+
storagePrefix?: string;
|
|
46
48
|
productPathPrefix?: string;
|
|
47
49
|
analyticAutoTrack?: boolean;
|
|
48
50
|
/** Varsayılan: `/api/bootstrap` — Next.js BFF veya özel proxy */
|
|
@@ -50,7 +52,7 @@ export interface RaxonProviderProps {
|
|
|
50
52
|
/** SSR ile layout'tan geçirilen bootstrap verisi */
|
|
51
53
|
initialBootstrapData?: RaxonBootstrapPayload | null;
|
|
52
54
|
}
|
|
53
|
-
export declare const RaxonProvider: ({ children, apiKey, apiUrl, productPathPrefix, analyticAutoTrack, bootstrapUrl, initialBootstrapData, }: RaxonProviderProps) => import("react").JSX.Element;
|
|
55
|
+
export declare const RaxonProvider: ({ children, apiKey, apiUrl, storagePrefix, productPathPrefix, analyticAutoTrack, bootstrapUrl, initialBootstrapData, }: RaxonProviderProps) => import("react").JSX.Element;
|
|
54
56
|
export declare const useRaxon: () => RaxonContextType;
|
|
55
57
|
export {};
|
|
56
58
|
//# sourceMappingURL=raxon.context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"raxon.context.d.ts","sourceRoot":"","sources":["../../core/raxon.context.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,OAAO,EACP,WAAW,EACX,MAAM,EACN,QAAQ,EAER,QAAQ,EACR,UAAU,EACV,cAAc,EACd,WAAW,EACX,GAAG,EACH,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,MAAM,EACN,IAAI,EACL,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAa,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAiC,gCAAgC,EAAE,MAAM,0DAA0D,CAAC;AAG3I,OAAO,EAAgB,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"raxon.context.d.ts","sourceRoot":"","sources":["../../core/raxon.context.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,OAAO,EACP,WAAW,EACX,MAAM,EACN,QAAQ,EAER,QAAQ,EACR,UAAU,EACV,cAAc,EACd,WAAW,EACX,GAAG,EACH,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,MAAM,EACN,IAAI,EACL,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAa,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAiC,gCAAgC,EAAE,MAAM,0DAA0D,CAAC;AAG3I,OAAO,EAAgB,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAGxE,eAAO,MAAM,YAAY,2CAAyD,CAAC;AAGnF,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,iBAAiB,EAAE,UAAU,EAAE,CAAC;IAChC,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,YAAY,EAAE,QAAQ,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,GAAG,EAAE,GAAG,EAAE,CAAC;IACX,IAAI,EAAE,IAAI,EAAE,CAAC;IACb,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC,aAAa,EAAE,aAAa,EAAE,CAAC;IAC/B,qBAAqB,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7C,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACnD,gCAAgC,EAAE,KAAK,CAAC,SAAS,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAAC;CAC5F;AAED,MAAM,WAAW,gBAAiB,SAAQ,kBAAkB,EAAE,SAAS;IACrE,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACrD;AAgJD,eAAO,MAAM,aAAa,GAAI,wHAS3B,kBAAkB,gCAkBpB,CAAC;AAEF,eAAO,MAAM,QAAQ,QAAO,gBAM3B,CAAC"}
|