create-brainerce-store 1.14.3 → 1.14.5
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/dist/index.js +47 -4
- package/messages/en.json +37 -4
- package/messages/he.json +37 -4
- package/package.json +1 -1
- package/templates/nextjs/base/src/app/account/layout.tsx +9 -0
- package/templates/nextjs/base/src/app/account/page.tsx +122 -112
- package/templates/nextjs/base/src/app/cart/layout.tsx +9 -0
- package/templates/nextjs/base/src/app/checkout/layout.tsx +9 -0
- package/templates/nextjs/base/src/app/checkout/page.tsx +101 -3
- package/templates/nextjs/base/src/app/layout.tsx.ejs +29 -1
- package/templates/nextjs/base/src/app/login/layout.tsx +9 -0
- package/templates/nextjs/base/src/app/order-confirmation/layout.tsx +9 -0
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +5 -1
- package/templates/nextjs/base/src/app/products/layout.tsx +18 -0
- package/templates/nextjs/base/src/app/products/page.tsx +1 -0
- package/templates/nextjs/base/src/app/register/layout.tsx +9 -0
- package/templates/nextjs/base/src/app/register/page.tsx +1 -0
- package/templates/nextjs/base/src/app/verify-email/page.tsx +1 -1
- package/templates/nextjs/base/src/components/account/address-book.tsx +432 -0
- package/templates/nextjs/base/src/components/auth/register-form.tsx +232 -184
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +359 -305
- package/templates/nextjs/base/src/components/products/product-card.tsx +26 -4
- package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +40 -7
- package/templates/nextjs/base/src/lib/auth.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "create-brainerce-store",
|
|
34
|
-
version: "1.14.
|
|
34
|
+
version: "1.14.5",
|
|
35
35
|
description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
|
|
36
36
|
bin: {
|
|
37
37
|
"create-brainerce-store": "dist/index.js"
|
|
@@ -412,12 +412,22 @@ async function fetchStoreInfo(connectionId, baseUrl = "https://api.brainerce.com
|
|
|
412
412
|
// src/utils/logger.ts
|
|
413
413
|
var import_chalk = __toESM(require("chalk"));
|
|
414
414
|
var logger = {
|
|
415
|
-
banner() {
|
|
415
|
+
banner(version) {
|
|
416
416
|
console.log();
|
|
417
|
-
console.log(
|
|
417
|
+
console.log(
|
|
418
|
+
import_chalk.default.bold.cyan(" create-brainerce-store") + (version ? import_chalk.default.dim(` v${version}`) : "")
|
|
419
|
+
);
|
|
418
420
|
console.log(import_chalk.default.dim(" Scaffold a production-ready e-commerce storefront"));
|
|
419
421
|
console.log();
|
|
420
422
|
},
|
|
423
|
+
updateAvailable(latest, current) {
|
|
424
|
+
console.log();
|
|
425
|
+
console.log(
|
|
426
|
+
import_chalk.default.yellow(` Update available: `) + import_chalk.default.dim(current) + import_chalk.default.yellow(" \u2192 ") + import_chalk.default.green.bold(latest)
|
|
427
|
+
);
|
|
428
|
+
console.log(import_chalk.default.dim(" Run: ") + import_chalk.default.white("npm install -g create-brainerce-store"));
|
|
429
|
+
console.log();
|
|
430
|
+
},
|
|
421
431
|
info(message) {
|
|
422
432
|
console.log(import_chalk.default.cyan(message));
|
|
423
433
|
},
|
|
@@ -446,10 +456,41 @@ function createSpinner(text) {
|
|
|
446
456
|
|
|
447
457
|
// src/index.ts
|
|
448
458
|
var pkg = require_package();
|
|
459
|
+
async function checkForUpdate(name, current) {
|
|
460
|
+
try {
|
|
461
|
+
const https2 = require("https");
|
|
462
|
+
return await new Promise((resolve) => {
|
|
463
|
+
const req = https2.get(
|
|
464
|
+
`https://registry.npmjs.org/${name}/latest`,
|
|
465
|
+
{ timeout: 3e3 },
|
|
466
|
+
(res) => {
|
|
467
|
+
let data = "";
|
|
468
|
+
res.on("data", (chunk) => data += chunk);
|
|
469
|
+
res.on("end", () => {
|
|
470
|
+
try {
|
|
471
|
+
const latest = JSON.parse(data).version;
|
|
472
|
+
resolve(latest && latest !== current ? latest : null);
|
|
473
|
+
} catch {
|
|
474
|
+
resolve(null);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
);
|
|
479
|
+
req.on("error", () => resolve(null));
|
|
480
|
+
req.on("timeout", () => {
|
|
481
|
+
req.destroy();
|
|
482
|
+
resolve(null);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
} catch {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
449
489
|
var program = new import_commander.Command();
|
|
450
490
|
program.name("create-brainerce-store").description("Scaffold a production-ready e-commerce storefront connected to Brainerce").version(pkg.version).argument("[project-name]", "Name for the project directory").option("--connection-id <id>", "Brainerce vibe-coded connection ID (vc_*)").option("--language <lang>", "Store language (en, he)").option("--framework <framework>", "Framework to use", "nextjs").option("--theme <theme>", "Theme to apply", "minimal").option("--pkg-manager <manager>", "Package manager (npm, pnpm, yarn, bun)").option("--no-git", "Skip git initialization").option("--no-install", "Skip dependency installation").action(async (projectNameArg, options) => {
|
|
451
491
|
try {
|
|
452
|
-
logger.banner();
|
|
492
|
+
logger.banner(pkg.version);
|
|
493
|
+
const updateCheck = checkForUpdate(pkg.name, pkg.version);
|
|
453
494
|
let projectName = projectNameArg;
|
|
454
495
|
let connectionId = options.connectionId;
|
|
455
496
|
let language = options.language;
|
|
@@ -547,6 +588,8 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
547
588
|
logger.step(`${pkgManager}${pkgManager === "npm" ? " run" : ""} dev`);
|
|
548
589
|
logger.info(`
|
|
549
590
|
Your store will be running at http://localhost:3000`);
|
|
591
|
+
const latestVersion = await updateCheck;
|
|
592
|
+
if (latestVersion) logger.updateAvailable(latestVersion, pkg.version);
|
|
550
593
|
} catch (err) {
|
|
551
594
|
if (err instanceof Error && err.message === "PROMPT_CANCELLED") {
|
|
552
595
|
logger.info("\nSetup cancelled.");
|
package/messages/en.json
CHANGED
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"day": "day",
|
|
35
35
|
"days": "days",
|
|
36
36
|
"allRightsReserved": "All rights reserved.",
|
|
37
|
-
"store": "Store"
|
|
37
|
+
"store": "Store",
|
|
38
|
+
"save": "Save",
|
|
39
|
+
"cancel": "Cancel"
|
|
38
40
|
},
|
|
39
41
|
"nav": {
|
|
40
42
|
"products": "Products",
|
|
@@ -174,7 +176,11 @@
|
|
|
174
176
|
"addressRequired": "Address is required",
|
|
175
177
|
"cityRequired": "City is required",
|
|
176
178
|
"postalCodeRequired": "Postal code is required",
|
|
177
|
-
"countryRequired": "Country is required"
|
|
179
|
+
"countryRequired": "Country is required",
|
|
180
|
+
"privacyAcceptPrefix": "I have read and agree to the",
|
|
181
|
+
"privacyPolicyLink": "Privacy Policy",
|
|
182
|
+
"privacyRequired": "You must accept the privacy policy to continue",
|
|
183
|
+
"acceptsMarketing": "Send me news, promotions, and updates by email"
|
|
178
184
|
},
|
|
179
185
|
"auth": {
|
|
180
186
|
"loginPageTitle": "Sign In",
|
|
@@ -240,7 +246,11 @@
|
|
|
240
246
|
"passwordResetSuccess": "Password reset successfully! Redirecting to login...",
|
|
241
247
|
"passwordsMustMatch": "Passwords must match",
|
|
242
248
|
"invalidResetLink": "Invalid reset link",
|
|
243
|
-
"invalidResetLinkDesc": "This password reset link is invalid or has expired. Please request a new one."
|
|
249
|
+
"invalidResetLinkDesc": "This password reset link is invalid or has expired. Please request a new one.",
|
|
250
|
+
"privacyAcceptPrefix": "I have read and agree to the",
|
|
251
|
+
"privacyPolicyLink": "Privacy Policy",
|
|
252
|
+
"privacyRequired": "You must accept the privacy policy to continue",
|
|
253
|
+
"acceptsMarketing": "Send me news, promotions, and updates by email"
|
|
244
254
|
},
|
|
245
255
|
"account": {
|
|
246
256
|
"pageTitle": "My Account",
|
|
@@ -273,7 +283,30 @@
|
|
|
273
283
|
"downloadsRemaining": "{used} of {limit} downloads used",
|
|
274
284
|
"unlimitedDownloads": "Unlimited downloads",
|
|
275
285
|
"expiresAt": "Expires {date}",
|
|
276
|
-
"noExpiry": "No expiry"
|
|
286
|
+
"noExpiry": "No expiry",
|
|
287
|
+
"addressBook": "Address Book",
|
|
288
|
+
"addAddress": "Add Address",
|
|
289
|
+
"editAddress": "Edit Address",
|
|
290
|
+
"deleteAddress": "Delete",
|
|
291
|
+
"setDefault": "Set as default",
|
|
292
|
+
"defaultAddress": "Default",
|
|
293
|
+
"noAddresses": "No saved addresses yet.",
|
|
294
|
+
"addressSaved": "Address saved",
|
|
295
|
+
"addressDeleted": "Address deleted",
|
|
296
|
+
"addressLabel": "Label (e.g. Home, Work)",
|
|
297
|
+
"line1": "Street Address",
|
|
298
|
+
"line2": "Apt, Suite, etc.",
|
|
299
|
+
"city": "City",
|
|
300
|
+
"region": "State / Region",
|
|
301
|
+
"postalCode": "Postal Code",
|
|
302
|
+
"country": "Country",
|
|
303
|
+
"isDefault": "Set as my default address"
|
|
304
|
+
},
|
|
305
|
+
"checkoutAddress": {
|
|
306
|
+
"saveToProfile": "Save this address to my profile?",
|
|
307
|
+
"saveYes": "Save",
|
|
308
|
+
"saveNo": "No thanks",
|
|
309
|
+
"addressSaved": "Address saved to your profile"
|
|
277
310
|
},
|
|
278
311
|
"orderConfirmation": {
|
|
279
312
|
"pageTitle": "Order Confirmation",
|
package/messages/he.json
CHANGED
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"day": "יום",
|
|
35
35
|
"days": "ימים",
|
|
36
36
|
"allRightsReserved": "כל הזכויות שמורות.",
|
|
37
|
-
"store": "חנות"
|
|
37
|
+
"store": "חנות",
|
|
38
|
+
"save": "שמירה",
|
|
39
|
+
"cancel": "ביטול"
|
|
38
40
|
},
|
|
39
41
|
"nav": {
|
|
40
42
|
"products": "מוצרים",
|
|
@@ -174,7 +176,11 @@
|
|
|
174
176
|
"addressRequired": "כתובת היא שדה חובה",
|
|
175
177
|
"cityRequired": "עיר היא שדה חובה",
|
|
176
178
|
"postalCodeRequired": "מיקוד הוא שדה חובה",
|
|
177
|
-
"countryRequired": "מדינה היא שדה חובה"
|
|
179
|
+
"countryRequired": "מדינה היא שדה חובה",
|
|
180
|
+
"privacyAcceptPrefix": "קראתי ואני מסכים/ה ל",
|
|
181
|
+
"privacyPolicyLink": "מדיניות הפרטיות",
|
|
182
|
+
"privacyRequired": "יש לאשר את מדיניות הפרטיות כדי להמשיך",
|
|
183
|
+
"acceptsMarketing": "שלחו לי חדשות, מבצעים ועדכונים במייל"
|
|
178
184
|
},
|
|
179
185
|
"auth": {
|
|
180
186
|
"loginPageTitle": "התחברות",
|
|
@@ -240,7 +246,11 @@
|
|
|
240
246
|
"passwordResetSuccess": "...הסיסמא אופסה בהצלחה! מעביר להתחברות",
|
|
241
247
|
"passwordsMustMatch": "הסיסמאות חייבות להיות זהות",
|
|
242
248
|
"invalidResetLink": "קישור איפוס לא תקין",
|
|
243
|
-
"invalidResetLinkDesc": "קישור האיפוס הזה אינו תקין או שפג תוקפו. בקשו קישור חדש."
|
|
249
|
+
"invalidResetLinkDesc": "קישור האיפוס הזה אינו תקין או שפג תוקפו. בקשו קישור חדש.",
|
|
250
|
+
"privacyAcceptPrefix": "קראתי ואני מסכים/ה ל",
|
|
251
|
+
"privacyPolicyLink": "מדיניות הפרטיות",
|
|
252
|
+
"privacyRequired": "יש לאשר את מדיניות הפרטיות כדי להמשיך",
|
|
253
|
+
"acceptsMarketing": "שלחו לי חדשות, מבצעים ועדכונים במייל"
|
|
244
254
|
},
|
|
245
255
|
"account": {
|
|
246
256
|
"pageTitle": "החשבון שלי",
|
|
@@ -273,7 +283,30 @@
|
|
|
273
283
|
"downloadsRemaining": "{used} מתוך {limit} הורדות נוצלו",
|
|
274
284
|
"unlimitedDownloads": "הורדות ללא הגבלה",
|
|
275
285
|
"expiresAt": "פג תוקף {date}",
|
|
276
|
-
"noExpiry": "ללא תפוגה"
|
|
286
|
+
"noExpiry": "ללא תפוגה",
|
|
287
|
+
"addressBook": "ספר כתובות",
|
|
288
|
+
"addAddress": "הוסף כתובת",
|
|
289
|
+
"editAddress": "עריכת כתובת",
|
|
290
|
+
"deleteAddress": "מחיקה",
|
|
291
|
+
"setDefault": "הגדר כברירת מחדל",
|
|
292
|
+
"defaultAddress": "ברירת מחדל",
|
|
293
|
+
"noAddresses": "אין כתובות שמורות עדיין.",
|
|
294
|
+
"addressSaved": "הכתובת נשמרה",
|
|
295
|
+
"addressDeleted": "הכתובת נמחקה",
|
|
296
|
+
"addressLabel": "תווית (למשל בית, עבודה)",
|
|
297
|
+
"line1": "כתובת",
|
|
298
|
+
"line2": "דירה, קומה וכו'",
|
|
299
|
+
"city": "עיר",
|
|
300
|
+
"region": "מדינה / אזור",
|
|
301
|
+
"postalCode": "מיקוד",
|
|
302
|
+
"country": "מדינה",
|
|
303
|
+
"isDefault": "הגדר ככתובת ברירת המחדל שלי"
|
|
304
|
+
},
|
|
305
|
+
"checkoutAddress": {
|
|
306
|
+
"saveToProfile": "לשמור כתובת זו בפרופיל שלי?",
|
|
307
|
+
"saveYes": "שמור",
|
|
308
|
+
"saveNo": "לא תודה",
|
|
309
|
+
"addressSaved": "הכתובת נשמרה בפרופיל שלך"
|
|
277
310
|
},
|
|
278
311
|
"orderConfirmation": {
|
|
279
312
|
"pageTitle": "אישור הזמנה",
|
package/package.json
CHANGED
|
@@ -1,112 +1,122 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
|
-
import type { CustomerProfile, Order } from 'brainerce';
|
|
6
|
-
import { getClient } from '@/lib/brainerce';
|
|
7
|
-
import { useAuth } from '@/providers/store-provider';
|
|
8
|
-
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
9
|
-
import { ProfileSection } from '@/components/account/profile-section';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const [
|
|
20
|
-
const [
|
|
21
|
-
const [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
client.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import type { CustomerProfile, Order } from 'brainerce';
|
|
6
|
+
import { getClient } from '@/lib/brainerce';
|
|
7
|
+
import { useAuth } from '@/providers/store-provider';
|
|
8
|
+
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
9
|
+
import { ProfileSection } from '@/components/account/profile-section';
|
|
10
|
+
import { AddressBook } from '@/components/account/address-book';
|
|
11
|
+
import { OrderHistory } from '@/components/account/order-history';
|
|
12
|
+
import { useTranslations } from '@/lib/translations';
|
|
13
|
+
|
|
14
|
+
export default function AccountPage() {
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
const { isLoggedIn, authLoading, logout } = useAuth();
|
|
17
|
+
const t = useTranslations('account');
|
|
18
|
+
const tc = useTranslations('common');
|
|
19
|
+
const [profile, setProfile] = useState<CustomerProfile | null>(null);
|
|
20
|
+
const [orders, setOrders] = useState<Order[]>([]);
|
|
21
|
+
const [loading, setLoading] = useState(true);
|
|
22
|
+
const [error, setError] = useState<string | null>(null);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (authLoading) return;
|
|
26
|
+
|
|
27
|
+
if (!isLoggedIn) {
|
|
28
|
+
router.push('/login');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function loadAccountData() {
|
|
33
|
+
try {
|
|
34
|
+
const client = getClient();
|
|
35
|
+
const [profileResult, ordersResult] = await Promise.allSettled([
|
|
36
|
+
client.getMyProfile(),
|
|
37
|
+
client.getMyOrders({ limit: 20 }),
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
if (profileResult.status === 'fulfilled') {
|
|
41
|
+
setProfile(profileResult.value);
|
|
42
|
+
} else {
|
|
43
|
+
setError('Failed to load profile.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (ordersResult.status === 'fulfilled') {
|
|
47
|
+
setOrders(ordersResult.value.data);
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : 'Failed to load account data.';
|
|
51
|
+
setError(message);
|
|
52
|
+
} finally {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
loadAccountData();
|
|
58
|
+
}, [isLoggedIn, authLoading, router]);
|
|
59
|
+
|
|
60
|
+
if (authLoading || !isLoggedIn) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (loading) {
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex min-h-[60vh] items-center justify-center">
|
|
67
|
+
<LoadingSpinner size="lg" />
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (error && !profile) {
|
|
73
|
+
return (
|
|
74
|
+
<div className="mx-auto max-w-3xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
75
|
+
<h1 className="text-foreground text-2xl font-bold">{tc('error')}</h1>
|
|
76
|
+
<p className="text-muted-foreground mt-2">{error}</p>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
onClick={() => window.location.reload()}
|
|
80
|
+
className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
81
|
+
>
|
|
82
|
+
{tc('tryAgain')}
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8">
|
|
90
|
+
<div className="mb-6 flex items-center justify-between">
|
|
91
|
+
<h1 className="text-foreground text-2xl font-bold">{t('myAccount')}</h1>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={logout}
|
|
95
|
+
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
|
96
|
+
>
|
|
97
|
+
{t('signOut')}
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Profile Section */}
|
|
102
|
+
{profile && (
|
|
103
|
+
<ProfileSection profile={profile} onProfileUpdate={setProfile} className="mb-6" />
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{/* Address Book */}
|
|
107
|
+
{profile && (
|
|
108
|
+
<AddressBook
|
|
109
|
+
addresses={profile.addresses ?? []}
|
|
110
|
+
onUpdate={(updated) => setProfile((p) => (p ? { ...p, addresses: updated } : p))}
|
|
111
|
+
className="mb-8"
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{/* Order History */}
|
|
116
|
+
<div>
|
|
117
|
+
<h2 className="text-foreground mb-4 text-lg font-semibold">{t('orderHistory')}</h2>
|
|
118
|
+
<OrderHistory orders={orders} />
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|