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 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.3",
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(import_chalk.default.bold.cyan(" create-brainerce-store"));
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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.14.3",
3
+ "version": "1.14.5",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -0,0 +1,9 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ export const metadata: Metadata = {
4
+ robots: { index: false, follow: false },
5
+ };
6
+
7
+ export default function Layout({ children }: { children: React.ReactNode }) {
8
+ return <>{children}</>;
9
+ }
@@ -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 { OrderHistory } from '@/components/account/order-history';
11
- import { useTranslations } from '@/lib/translations';
12
-
13
- export default function AccountPage() {
14
- const router = useRouter();
15
- const { isLoggedIn, authLoading, logout } = useAuth();
16
- const t = useTranslations('account');
17
- const tc = useTranslations('common');
18
- const [profile, setProfile] = useState<CustomerProfile | null>(null);
19
- const [orders, setOrders] = useState<Order[]>([]);
20
- const [loading, setLoading] = useState(true);
21
- const [error, setError] = useState<string | null>(null);
22
-
23
- useEffect(() => {
24
- if (authLoading) return;
25
-
26
- if (!isLoggedIn) {
27
- router.push('/login');
28
- return;
29
- }
30
-
31
- async function loadAccountData() {
32
- try {
33
- const client = getClient();
34
- const [profileResult, ordersResult] = await Promise.allSettled([
35
- client.getMyProfile(),
36
- client.getMyOrders({ limit: 20 }),
37
- ]);
38
-
39
- if (profileResult.status === 'fulfilled') {
40
- setProfile(profileResult.value);
41
- } else {
42
- setError('Failed to load profile.');
43
- }
44
-
45
- if (ordersResult.status === 'fulfilled') {
46
- setOrders(ordersResult.value.data);
47
- }
48
- } catch (err) {
49
- const message = err instanceof Error ? err.message : 'Failed to load account data.';
50
- setError(message);
51
- } finally {
52
- setLoading(false);
53
- }
54
- }
55
-
56
- loadAccountData();
57
- }, [isLoggedIn, authLoading, router]);
58
-
59
- if (authLoading || !isLoggedIn) {
60
- return null;
61
- }
62
-
63
- if (loading) {
64
- return (
65
- <div className="flex min-h-[60vh] items-center justify-center">
66
- <LoadingSpinner size="lg" />
67
- </div>
68
- );
69
- }
70
-
71
- if (error && !profile) {
72
- return (
73
- <div className="mx-auto max-w-3xl px-4 py-16 text-center sm:px-6 lg:px-8">
74
- <h1 className="text-foreground text-2xl font-bold">{tc('error')}</h1>
75
- <p className="text-muted-foreground mt-2">{error}</p>
76
- <button
77
- type="button"
78
- onClick={() => window.location.reload()}
79
- className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
80
- >
81
- {tc('tryAgain')}
82
- </button>
83
- </div>
84
- );
85
- }
86
-
87
- return (
88
- <div className="mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8">
89
- <div className="mb-6 flex items-center justify-between">
90
- <h1 className="text-foreground text-2xl font-bold">{t('myAccount')}</h1>
91
- <button
92
- type="button"
93
- onClick={logout}
94
- className="text-muted-foreground hover:text-foreground text-sm transition-colors"
95
- >
96
- {t('signOut')}
97
- </button>
98
- </div>
99
-
100
- {/* Profile Section */}
101
- {profile && (
102
- <ProfileSection profile={profile} onProfileUpdate={setProfile} className="mb-8" />
103
- )}
104
-
105
- {/* Order History */}
106
- <div>
107
- <h2 className="text-foreground mb-4 text-lg font-semibold">{t('orderHistory')}</h2>
108
- <OrderHistory orders={orders} />
109
- </div>
110
- </div>
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
+ }
@@ -0,0 +1,9 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ export const metadata: Metadata = {
4
+ robots: { index: false, follow: false },
5
+ };
6
+
7
+ export default function Layout({ children }: { children: React.ReactNode }) {
8
+ return <>{children}</>;
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ export const metadata: Metadata = {
4
+ robots: { index: false, follow: false },
5
+ };
6
+
7
+ export default function Layout({ children }: { children: React.ReactNode }) {
8
+ return <>{children}</>;
9
+ }