@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,111 @@
|
|
|
1
|
+
import {ProductContent, ProductType, Property, SaleType, WeightType} from '@/core/interface/prisma.interface';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export interface Price {
|
|
5
|
+
mainPrice: number;
|
|
6
|
+
discountPrice: number;
|
|
7
|
+
taxAmount?: number;
|
|
8
|
+
depositAmount?: number;
|
|
9
|
+
basketPrice?: number;
|
|
10
|
+
payPrice?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface Product {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
saleType: SaleType;
|
|
16
|
+
createdAt?: string;
|
|
17
|
+
updatedAt?: string;
|
|
18
|
+
type: ProductType;
|
|
19
|
+
slug: string;
|
|
20
|
+
stock?: number;
|
|
21
|
+
sku?: string;
|
|
22
|
+
productContent? : ProductContent
|
|
23
|
+
articleNumber?: string;
|
|
24
|
+
tags?: string[];
|
|
25
|
+
currentStock?: number;
|
|
26
|
+
icon?: string;
|
|
27
|
+
shortDescription?: string;
|
|
28
|
+
brand?: string;
|
|
29
|
+
weightType: WeightType;
|
|
30
|
+
weightValue: number;
|
|
31
|
+
barcode?: string;
|
|
32
|
+
dynamicContent?: any;
|
|
33
|
+
isFavorite?: boolean;
|
|
34
|
+
status?: 'PUBLISHED' | 'OUT_OF_STOCK' | 'UNKNOWN';
|
|
35
|
+
price?: Price;
|
|
36
|
+
review: {
|
|
37
|
+
count: number;
|
|
38
|
+
rating: number;
|
|
39
|
+
};
|
|
40
|
+
images: {
|
|
41
|
+
relativePath: string;
|
|
42
|
+
variantIds: string[];
|
|
43
|
+
attributeOptionId: string;
|
|
44
|
+
id: string;
|
|
45
|
+
}[];
|
|
46
|
+
categories: {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
slug: string;
|
|
50
|
+
}[];
|
|
51
|
+
productUnit?: {
|
|
52
|
+
id: string;
|
|
53
|
+
unit: {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
};
|
|
57
|
+
quantityPerUnitOfMeasure: number;
|
|
58
|
+
price?: Price;
|
|
59
|
+
}[];
|
|
60
|
+
variant: {
|
|
61
|
+
id: string;
|
|
62
|
+
price: Price;
|
|
63
|
+
stock: number;
|
|
64
|
+
attributeOption1: {
|
|
65
|
+
id: string;
|
|
66
|
+
label: string;
|
|
67
|
+
value?: string | null;
|
|
68
|
+
icon?: string | null;
|
|
69
|
+
attributeId?: string | null;
|
|
70
|
+
};
|
|
71
|
+
attributeOption2: {
|
|
72
|
+
id: string;
|
|
73
|
+
label: string;
|
|
74
|
+
value?: string | null;
|
|
75
|
+
icon?: string | null;
|
|
76
|
+
attributeId?: string | null;
|
|
77
|
+
};
|
|
78
|
+
}[]
|
|
79
|
+
}
|
|
80
|
+
export interface ProductDetail extends Product {
|
|
81
|
+
baseUnit: string;
|
|
82
|
+
productUnits: {
|
|
83
|
+
id: string;
|
|
84
|
+
unit: string;
|
|
85
|
+
quantityPerUnitOfMeasure: number;
|
|
86
|
+
}[];
|
|
87
|
+
richContent: string;
|
|
88
|
+
description: string;
|
|
89
|
+
saleUnit: string;
|
|
90
|
+
property : Property[];
|
|
91
|
+
purchaseUnit: string;
|
|
92
|
+
shortDescription: string;
|
|
93
|
+
productContent: ProductContent;
|
|
94
|
+
matrix?: ProductPriceMatrixResponse[] | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ProductPriceMatrixResponse {
|
|
98
|
+
supplier: {
|
|
99
|
+
id: string;
|
|
100
|
+
name: string;
|
|
101
|
+
} | null;
|
|
102
|
+
unit: {
|
|
103
|
+
id: string;
|
|
104
|
+
name: string;
|
|
105
|
+
} | null;
|
|
106
|
+
price: {
|
|
107
|
+
mainPrice: number;
|
|
108
|
+
discountPrice: number;
|
|
109
|
+
minimumQuantity: number;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Article,
|
|
5
|
+
BankAccount,
|
|
6
|
+
Branch,
|
|
7
|
+
Campaign,
|
|
8
|
+
CampaignType,
|
|
9
|
+
Category,
|
|
10
|
+
Collection,
|
|
11
|
+
DeliveryMethod,
|
|
12
|
+
DynamicData,
|
|
13
|
+
Faq,
|
|
14
|
+
Feed,
|
|
15
|
+
Holiday,
|
|
16
|
+
Material,
|
|
17
|
+
PaymentMethod,
|
|
18
|
+
Review,
|
|
19
|
+
User,
|
|
20
|
+
} from "@/core/interface/prisma.interface";
|
|
21
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
22
|
+
import { nexineAxios } from "@/core/util/nexine.axios";
|
|
23
|
+
import { Product as CustomProduct } from "@/core/interface/product.interface";
|
|
24
|
+
import { ModalAuth, ModalAuthRef } from "@/core/feature/auth/modal/modal.auth";
|
|
25
|
+
import { ModalNewsletterVariantProduct, ModalNewsletterVariantProductRef } from "@/core/feature/newsletter/modal/modal.newsletter.product";
|
|
26
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
27
|
+
import { useSecurityState } from "@/core/context/security.context";
|
|
28
|
+
import { useCartState, CartState } from "@/core/context/cart.context";
|
|
29
|
+
import { AnalyticEventProvider } from "@/core/feature/analytic-event/analytic.event.context";
|
|
30
|
+
import { RaxonContextBrand } from "./interface/context.interface";
|
|
31
|
+
import { RaxonBootstrapPayload } from "./interface/bootstrap.interface";
|
|
32
|
+
|
|
33
|
+
export const RaxonContext = createContext<RaxonContextType | undefined>(undefined);
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
interface RaxonBootstrapData {
|
|
37
|
+
campaign: Campaign[];
|
|
38
|
+
basketCampaign: Campaign[];
|
|
39
|
+
branch: Branch | null;
|
|
40
|
+
banner: Collection[];
|
|
41
|
+
collection: Collection[];
|
|
42
|
+
subHeroCollection: Collection[];
|
|
43
|
+
category: Category[];
|
|
44
|
+
flatCategory: Category[];
|
|
45
|
+
review: Review[];
|
|
46
|
+
article: Article[];
|
|
47
|
+
faq: Faq[];
|
|
48
|
+
feed: Feed[];
|
|
49
|
+
material: Material[];
|
|
50
|
+
holiday: Holiday[];
|
|
51
|
+
product: CustomProduct[];
|
|
52
|
+
bankAccount: BankAccount[];
|
|
53
|
+
bestSeller: CustomProduct[];
|
|
54
|
+
dynamicData: DynamicData[];
|
|
55
|
+
deliveryMethod: DeliveryMethod[];
|
|
56
|
+
paymentMethod: PaymentMethod[];
|
|
57
|
+
defaultDeliveryMethod: DeliveryMethod | null;
|
|
58
|
+
brand: RaxonContextBrand[];
|
|
59
|
+
modalAuthRef: React.RefObject<ModalAuthRef | null>;
|
|
60
|
+
modalNewsletterVariantProductRef: React.RefObject<ModalNewsletterVariantProductRef | null>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RaxonContextType extends RaxonBootstrapData, CartState {
|
|
64
|
+
isLoading: boolean;
|
|
65
|
+
profile: User | null | undefined;
|
|
66
|
+
authLoading: boolean;
|
|
67
|
+
isAuthenticated: boolean;
|
|
68
|
+
isGuest: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface RaxonProviderProps {
|
|
72
|
+
children: React.ReactNode;
|
|
73
|
+
apiKey: string;
|
|
74
|
+
apiUrl: string;
|
|
75
|
+
productPathPrefix?: string;
|
|
76
|
+
analyticAutoTrack?: boolean;
|
|
77
|
+
/** Varsayılan: `/api/bootstrap` — Next.js BFF veya özel proxy */
|
|
78
|
+
bootstrapUrl?: string;
|
|
79
|
+
/** SSR ile layout'tan geçirilen bootstrap verisi */
|
|
80
|
+
initialBootstrapData?: RaxonBootstrapPayload | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeBootstrapPayload(raxon: RaxonBootstrapPayload | null | undefined) {
|
|
84
|
+
if (!raxon) return null;
|
|
85
|
+
|
|
86
|
+
const flatten = (categories: Category[]): Category[] => {
|
|
87
|
+
return categories.flatMap((cat) => [cat, ...flatten(cat.children ?? [])]);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const category = Array.isArray(raxon.category) ? raxon.category : [];
|
|
91
|
+
const flatCategory = flatten(category);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
campaign: raxon.campaign ?? [],
|
|
95
|
+
basketCampaign: (raxon.campaign ?? []).filter((it) => it.type !== CampaignType.COLLECTION),
|
|
96
|
+
branch: raxon.branch ?? null,
|
|
97
|
+
collection: raxon.collection ?? [],
|
|
98
|
+
subHeroCollection: raxon.subHeroCollection ?? [],
|
|
99
|
+
banner: (raxon.collection ?? []).filter((it) => it.tags?.includes("banner")),
|
|
100
|
+
category,
|
|
101
|
+
flatCategory,
|
|
102
|
+
faq: raxon.faq ?? [],
|
|
103
|
+
product: raxon.product ?? [],
|
|
104
|
+
bankAccount: raxon.bankAccount ?? [],
|
|
105
|
+
material: raxon.material ?? [],
|
|
106
|
+
holiday: raxon.holiday ?? [],
|
|
107
|
+
bestSeller: raxon.bestSeller ?? [],
|
|
108
|
+
dynamicData: raxon.dynamicData ?? [],
|
|
109
|
+
deliveryMethod: raxon.deliveryMethod ?? [],
|
|
110
|
+
paymentMethod: raxon.paymentMethod ?? [],
|
|
111
|
+
defaultDeliveryMethod: (raxon.deliveryMethod ?? []).find((it) => it.isDefault) ?? null,
|
|
112
|
+
review: raxon.review ?? [],
|
|
113
|
+
article: raxon.article ?? [],
|
|
114
|
+
feed: raxon.feed ?? [],
|
|
115
|
+
brand: raxon.brand ?? [],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const RaxonProviderInner = ({
|
|
120
|
+
children,
|
|
121
|
+
apiKey,
|
|
122
|
+
apiUrl,
|
|
123
|
+
productPathPrefix,
|
|
124
|
+
analyticAutoTrack,
|
|
125
|
+
bootstrapUrl = "/api/bootstrap",
|
|
126
|
+
initialBootstrapData = null,
|
|
127
|
+
}: RaxonProviderProps) => {
|
|
128
|
+
const initialNormalized = useMemo(
|
|
129
|
+
() => normalizeBootstrapPayload(initialBootstrapData),
|
|
130
|
+
[initialBootstrapData],
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const [raxon, setRaxon] = useState<RaxonBootstrapPayload | null>(
|
|
134
|
+
initialBootstrapData ?? null,
|
|
135
|
+
);
|
|
136
|
+
const [isLoading, setIsLoading] = useState<boolean>(!initialNormalized);
|
|
137
|
+
const [hasFetched, setHasFetched] = useState<boolean>(!!initialNormalized);
|
|
138
|
+
|
|
139
|
+
const modalAuthRef = useRef<ModalAuthRef>(null);
|
|
140
|
+
const modalNewsletterVariantProductRef = useRef<ModalNewsletterVariantProductRef>(null);
|
|
141
|
+
|
|
142
|
+
if (typeof window !== "undefined") {
|
|
143
|
+
(window as any).__RAXON_API_KEY__ = apiKey;
|
|
144
|
+
(window as any).__RAXON_API_URL__ = apiUrl;
|
|
145
|
+
nexineAxios.defaults.baseURL = apiUrl;
|
|
146
|
+
nexineAxios.defaults.headers.common["x-api-key"] = apiKey;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { profile, authLoading, isAuthenticated, isGuest } = useSecurityState();
|
|
150
|
+
const cartState = useCartState(isAuthenticated);
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (hasFetched || !bootstrapUrl) return;
|
|
154
|
+
setHasFetched(true);
|
|
155
|
+
|
|
156
|
+
fetch(bootstrapUrl, { credentials: "same-origin" })
|
|
157
|
+
.then((res) => {
|
|
158
|
+
if (!res.ok) throw new Error(`Bootstrap ${res.status}`);
|
|
159
|
+
return res.json();
|
|
160
|
+
})
|
|
161
|
+
.then((data: RaxonBootstrapPayload) => {
|
|
162
|
+
setRaxon(data);
|
|
163
|
+
setIsLoading(false);
|
|
164
|
+
})
|
|
165
|
+
.catch((err) => {
|
|
166
|
+
console.error("[RAXON] ERROR:", err);
|
|
167
|
+
setIsLoading(false);
|
|
168
|
+
});
|
|
169
|
+
}, [hasFetched, bootstrapUrl]);
|
|
170
|
+
|
|
171
|
+
const normalized = useMemo(
|
|
172
|
+
() => normalizeBootstrapPayload(raxon) ?? initialNormalized,
|
|
173
|
+
[raxon, initialNormalized],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const value = useMemo<RaxonContextType>(
|
|
177
|
+
() => ({
|
|
178
|
+
campaign: normalized?.campaign ?? [],
|
|
179
|
+
basketCampaign: normalized?.basketCampaign ?? [],
|
|
180
|
+
branch: normalized?.branch ?? null,
|
|
181
|
+
collection: normalized?.collection ?? [],
|
|
182
|
+
subHeroCollection: normalized?.subHeroCollection ?? [],
|
|
183
|
+
banner: normalized?.banner ?? [],
|
|
184
|
+
category: normalized?.category ?? [],
|
|
185
|
+
flatCategory: normalized?.flatCategory ?? [],
|
|
186
|
+
faq: normalized?.faq ?? [],
|
|
187
|
+
product: normalized?.product ?? [],
|
|
188
|
+
bankAccount: normalized?.bankAccount ?? [],
|
|
189
|
+
material: normalized?.material ?? [],
|
|
190
|
+
holiday: normalized?.holiday ?? [],
|
|
191
|
+
bestSeller: normalized?.bestSeller ?? [],
|
|
192
|
+
isLoading,
|
|
193
|
+
dynamicData: normalized?.dynamicData ?? [],
|
|
194
|
+
deliveryMethod: normalized?.deliveryMethod ?? [],
|
|
195
|
+
paymentMethod: normalized?.paymentMethod ?? [],
|
|
196
|
+
defaultDeliveryMethod: normalized?.defaultDeliveryMethod ?? null,
|
|
197
|
+
review: normalized?.review ?? [],
|
|
198
|
+
article: normalized?.article ?? [],
|
|
199
|
+
feed: normalized?.feed ?? [],
|
|
200
|
+
brand: normalized?.brand ?? [],
|
|
201
|
+
modalAuthRef,
|
|
202
|
+
modalNewsletterVariantProductRef,
|
|
203
|
+
profile,
|
|
204
|
+
authLoading,
|
|
205
|
+
isAuthenticated,
|
|
206
|
+
isGuest,
|
|
207
|
+
...cartState,
|
|
208
|
+
}),
|
|
209
|
+
[normalized, isLoading, profile, authLoading, isAuthenticated, isGuest, cartState],
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<RaxonContext.Provider value={value}>
|
|
214
|
+
<AnalyticEventProvider productPathPrefix={productPathPrefix} autoTrack={analyticAutoTrack}>
|
|
215
|
+
{children}
|
|
216
|
+
</AnalyticEventProvider>
|
|
217
|
+
<ModalAuth ref={modalAuthRef} />
|
|
218
|
+
<ModalNewsletterVariantProduct ref={modalNewsletterVariantProductRef} />
|
|
219
|
+
</RaxonContext.Provider>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const RaxonProvider = ({
|
|
224
|
+
children,
|
|
225
|
+
apiKey,
|
|
226
|
+
apiUrl,
|
|
227
|
+
productPathPrefix,
|
|
228
|
+
analyticAutoTrack,
|
|
229
|
+
bootstrapUrl,
|
|
230
|
+
initialBootstrapData,
|
|
231
|
+
}: RaxonProviderProps) => {
|
|
232
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<QueryClientProvider client={queryClient}>
|
|
236
|
+
<RaxonProviderInner
|
|
237
|
+
apiKey={apiKey}
|
|
238
|
+
apiUrl={apiUrl}
|
|
239
|
+
productPathPrefix={productPathPrefix}
|
|
240
|
+
analyticAutoTrack={analyticAutoTrack}
|
|
241
|
+
bootstrapUrl={bootstrapUrl}
|
|
242
|
+
initialBootstrapData={initialBootstrapData}
|
|
243
|
+
>
|
|
244
|
+
{children}
|
|
245
|
+
</RaxonProviderInner>
|
|
246
|
+
</QueryClientProvider>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const useRaxon = (): RaxonContextType => {
|
|
251
|
+
const context = useContext(RaxonContext);
|
|
252
|
+
if (context === undefined) {
|
|
253
|
+
throw new Error("useRaxon must be used within a RaxonProvider");
|
|
254
|
+
}
|
|
255
|
+
return context;
|
|
256
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const addressCreateSchema = z.object({
|
|
4
|
+
title: z.string().min(1, 'Adres başlığı zorunludur'),
|
|
5
|
+
fullName: z.string().min(3, 'Ad soyad zorunludur'),
|
|
6
|
+
phoneNumber: z.string().min(10, 'Geçerli telefon giriniz'),
|
|
7
|
+
country: z.string().min(1, 'Ülke seçiniz'),
|
|
8
|
+
administrativeAreaLevel1: z.string().min(1, 'İl seçiniz'),
|
|
9
|
+
administrativeAreaLevel2: z.string().min(1, 'İlçe seçiniz'),
|
|
10
|
+
fullAddress: z.string().min(5, 'Adres zorunludur'),
|
|
11
|
+
postalCode: z.string().optional(),
|
|
12
|
+
invoiceType: z.enum(['individual', 'corporate']),
|
|
13
|
+
taxNumber: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const checkoutSchema = z.object({
|
|
17
|
+
deliveryAddressId: z.string().optional(),
|
|
18
|
+
invoiceAddressId: z.string().optional(),
|
|
19
|
+
differentInvoiceAddress: z.boolean().optional(),
|
|
20
|
+
deliveryMethodId: z.string().min(1, 'Teslimat yöntemi seçiniz'),
|
|
21
|
+
paymentMethodId: z.string().min(1, 'Ödeme yöntemi seçiniz'),
|
|
22
|
+
bankAccountId: z.string().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type AddressCreateFormData = z.infer<typeof addressCreateSchema>;
|
|
26
|
+
export type CheckoutFormData = z.infer<typeof checkoutSchema>;
|
|
27
|
+
|
|
28
|
+
export function getDeliveryFee(
|
|
29
|
+
method: { courierFee?: number | null; minimumOrderAmount?: number | null; isForceFee?: boolean },
|
|
30
|
+
subtotal: number
|
|
31
|
+
) {
|
|
32
|
+
const fee = Number(method.courierFee ?? 0);
|
|
33
|
+
const minAmount = Number(method.minimumOrderAmount ?? 0);
|
|
34
|
+
|
|
35
|
+
if (minAmount > 0 && subtotal >= minAmount && !method.isForceFee) {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return fee;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatTry(amount: number) {
|
|
43
|
+
return new Intl.NumberFormat('tr-TR', {
|
|
44
|
+
style: 'currency',
|
|
45
|
+
currency: 'TRY',
|
|
46
|
+
maximumFractionDigits: 2,
|
|
47
|
+
}).format(amount);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type PaymentUiType = 'credit_card' | 'bank_transfer' | 'garanti' | 'other';
|
|
51
|
+
|
|
52
|
+
export function getPaymentUiType(name: string, provider?: string): PaymentUiType {
|
|
53
|
+
const n = name.toLowerCase();
|
|
54
|
+
const p = (provider || '').toUpperCase();
|
|
55
|
+
|
|
56
|
+
if (p === 'GARANTI' || p.includes('GARANTI') || n.includes('garanti')) return 'garanti';
|
|
57
|
+
if (p === 'PAYTR' || p.includes('PAYTR') || p === 'STRIPE' || n.includes('kredi') || n.includes('kart')) {
|
|
58
|
+
return 'credit_card';
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
p === 'BANK_TRANSFER' ||
|
|
62
|
+
p.includes('BANK') ||
|
|
63
|
+
n.includes('havale') ||
|
|
64
|
+
n.includes('eft') ||
|
|
65
|
+
n.includes('peşin') ||
|
|
66
|
+
n.includes('pesin')
|
|
67
|
+
) {
|
|
68
|
+
return 'bank_transfer';
|
|
69
|
+
}
|
|
70
|
+
return 'other';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getPaymentDisplayName(name: string, provider?: string): string {
|
|
74
|
+
const type = getPaymentUiType(name, provider);
|
|
75
|
+
if (type === 'credit_card') return 'Kredi Kartı';
|
|
76
|
+
if (type === 'bank_transfer') return 'Havale';
|
|
77
|
+
if (type === 'garanti') return 'GarantiPay';
|
|
78
|
+
return name;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function sortPaymentMethods<T extends { name: string; provider?: string }>(methods: T[]): T[] {
|
|
82
|
+
const order: PaymentUiType[] = ['credit_card', 'bank_transfer', 'garanti', 'other'];
|
|
83
|
+
return [...methods].sort((a, b) => {
|
|
84
|
+
const ai = order.indexOf(getPaymentUiType(a.name, a.provider));
|
|
85
|
+
const bi = order.indexOf(getPaymentUiType(b.name, b.provider));
|
|
86
|
+
return ai - bi;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Checkout'ta gösterilecek ödeme yöntemleri: Kredi Kartı → Havale → GarantiPay */
|
|
91
|
+
export function getCheckoutPaymentMethods<T extends { id: string; name: string; provider?: string }>(
|
|
92
|
+
methods: T[]
|
|
93
|
+
): T[] {
|
|
94
|
+
const order: PaymentUiType[] = ['credit_card', 'bank_transfer', 'garanti'];
|
|
95
|
+
const picked: T[] = [];
|
|
96
|
+
|
|
97
|
+
for (const type of order) {
|
|
98
|
+
const match = methods.find((m) => getPaymentUiType(m.name, m.provider) === type);
|
|
99
|
+
if (match) picked.push(match);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return picked;
|
|
103
|
+
}
|
|
@@ -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 };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BasketItemSummaryInterface } from '../interface/basket.interface';
|
|
2
|
+
|
|
3
|
+
export function formatBasketItemVariantLine(item: BasketItemSummaryInterface): string | null {
|
|
4
|
+
const v = item.variant;
|
|
5
|
+
if (!v) return null;
|
|
6
|
+
const parts: string[] = [];
|
|
7
|
+
if (v.attributeOption1) {
|
|
8
|
+
const n = v.attributeOption1.name;
|
|
9
|
+
const label = v.attributeOption1.attribute?.name;
|
|
10
|
+
parts.push(label && n ? `${label}: ${n}` : n || '');
|
|
11
|
+
}
|
|
12
|
+
if (v.attributeOption2) {
|
|
13
|
+
const n = v.attributeOption2.name;
|
|
14
|
+
const label = v.attributeOption2.attribute?.name;
|
|
15
|
+
parts.push(label && n ? `${label}: ${n}` : n || '');
|
|
16
|
+
}
|
|
17
|
+
const s = parts.filter(Boolean).join(' · ');
|
|
18
|
+
return s.length > 0 ? s : null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import capitalize from 'lodash/capitalize';
|
|
2
|
+
import { Category } from '../interface/prisma.interface';
|
|
3
|
+
|
|
4
|
+
/** Header/footer ile aynı görünen kategori adı (çok dilli + eski getName formatı) */
|
|
5
|
+
export function categoryNavLabel(cat: Category): string {
|
|
6
|
+
if (Array.isArray(cat.name) && cat.name.length > 0) {
|
|
7
|
+
return cat.name[0]?.value || '';
|
|
8
|
+
}
|
|
9
|
+
const raw = (cat.name as unknown as { getName?: () => string })?.getName?.() ?? '';
|
|
10
|
+
return raw ? capitalize(raw) : '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getCategoryNavProductCount(cat: Category): number {
|
|
14
|
+
const c = cat as Category & {
|
|
15
|
+
totalProductCount?: number;
|
|
16
|
+
_count?: { product?: number; products?: number };
|
|
17
|
+
};
|
|
18
|
+
return c.totalProductCount ?? c._count?.product ?? c._count?.products ?? 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function categoryHasStoredParent(cat: Category): boolean {
|
|
22
|
+
const p = cat.parentId;
|
|
23
|
+
return p != null && String(p).trim().length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Header/footer menü kökleri: bazı API yanıtlarında tek şemsiye düğüm veya üst dizide hep dolu parentId gelir.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveCategoryNavRoots(categoryTreeTopLevel: Category[]): Category[] {
|
|
30
|
+
if (!categoryTreeTopLevel?.length) return [];
|
|
31
|
+
|
|
32
|
+
const aliveTop = categoryTreeTopLevel.filter(c => !c.deletedAt);
|
|
33
|
+
const rootsWithoutParent = aliveTop.filter(c => !categoryHasStoredParent(c));
|
|
34
|
+
|
|
35
|
+
if (rootsWithoutParent.length === 1) {
|
|
36
|
+
const only = rootsWithoutParent[0];
|
|
37
|
+
const children = (only.children ?? []).filter(c => !c.deletedAt);
|
|
38
|
+
if (children.length > 0) return children;
|
|
39
|
+
return [only];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (rootsWithoutParent.length > 0) return rootsWithoutParent;
|
|
43
|
+
|
|
44
|
+
/** parentId hep dolu: sunucunun döndürdüğü üst diziyi menü seviyesi kabul et */
|
|
45
|
+
return aliveTop;
|
|
46
|
+
}
|