create-brainerce-store 1.73.0 → 1.74.0

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.
Files changed (77) hide show
  1. package/dist/index.js +35 -2
  2. package/messages/en.json +27 -1
  3. package/messages/he.json +27 -1
  4. package/package.json +1 -1
  5. package/templates/nextjs/base/AGENTS.md.ejs +63 -16
  6. package/templates/nextjs/base/AI-GUIDE.md +17 -4
  7. package/templates/nextjs/base/CLAUDE.md.ejs +67 -20
  8. package/templates/nextjs/base/src/app/account/page.tsx +168 -122
  9. package/templates/nextjs/base/src/app/category/[slug]/page.tsx +10 -1
  10. package/templates/nextjs/base/src/app/checkout/page.tsx +145 -40
  11. package/templates/nextjs/base/src/app/layout.tsx.ejs +48 -4
  12. package/templates/nextjs/base/src/app/order-confirmation/page.tsx +98 -3
  13. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +14 -2
  14. package/templates/nextjs/base/src/components/account/loyalty-panel.tsx +226 -0
  15. package/templates/nextjs/base/src/components/account/order-history.tsx +58 -7
  16. package/templates/nextjs/base/src/components/account/saved-payment-methods.tsx +104 -0
  17. package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +618 -612
  18. package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +354 -306
  19. package/templates/nextjs/base/src/components/checkout/tax-display.tsx +141 -74
  20. package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +97 -6
  21. package/templates/nextjs/base/src/core/hooks/use-home-data.ts +63 -51
  22. package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +379 -295
  23. package/templates/nextjs/base/src/core/hooks/use-product-page.ts +33 -1
  24. package/templates/nextjs/base/src/core/lib/capabilities.ts +65 -0
  25. package/templates/nextjs/base/src/core/lib/display-price.ts +139 -0
  26. package/templates/nextjs/base/src/core/lib/region.server.ts +112 -0
  27. package/templates/nextjs/base/src/core/lib/region.ts +112 -0
  28. package/templates/nextjs/base/src/core/lib/store-info.ts +19 -0
  29. package/templates/nextjs/base/src/core/providers/store-provider.tsx.ejs +423 -243
  30. package/templates/nextjs/base/src/ui/cart/cart-item.tsx +10 -0
  31. package/templates/nextjs/base/src/ui/cart/cart-summary.tsx +109 -110
  32. package/templates/nextjs/base/src/ui/cart/cart-view.tsx +45 -2
  33. package/templates/nextjs/base/src/ui/cart/gift-card-input.tsx +191 -0
  34. package/templates/nextjs/base/src/ui/cart/tax-estimate-line.tsx +108 -0
  35. package/templates/nextjs/base/src/ui/home/home-client.tsx +27 -29
  36. package/templates/nextjs/base/src/ui/layout/header-search.tsx +249 -0
  37. package/templates/nextjs/base/src/ui/layout/region-switcher.tsx +59 -0
  38. package/templates/nextjs/base/src/ui/layout/site-header.tsx.ejs +171 -152
  39. package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +197 -191
  40. package/templates/nextjs/base/src/ui/product/product-card.tsx +25 -52
  41. package/templates/nextjs/base/src/ui/product/product-client-section.tsx +12 -3
  42. package/templates/nextjs/base/src/ui/product/product-listing.tsx +460 -446
  43. package/templates/nextjs/base/src/ui/product/recommendation-section.tsx +108 -102
  44. package/templates/nextjs/base/src/ui/product/stock-badge.tsx +15 -3
  45. package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +48 -4
  46. package/templates/nextjs/designs/atelier/messages-patch/en.json +21 -25
  47. package/templates/nextjs/designs/atelier/messages-patch/he.json +21 -25
  48. package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +10 -0
  49. package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +106 -107
  50. package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +56 -11
  51. package/templates/nextjs/designs/atelier/ui/cart/tax-estimate-line.tsx +108 -0
  52. package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +10 -1
  53. package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +178 -174
  54. package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +4 -6
  55. package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +150 -78
  56. package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +79 -46
  57. package/templates/nextjs/designs/atelier/ui/layout/header-search.tsx +264 -0
  58. package/templates/nextjs/designs/atelier/ui/layout/region-switcher.tsx +44 -0
  59. package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +5 -0
  60. package/templates/nextjs/designs/atelier/ui/product/frequently-bought-together.tsx +202 -196
  61. package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +27 -52
  62. package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +12 -3
  63. package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +332 -260
  64. package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +101 -95
  65. package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +17 -2
  66. package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +9 -0
  67. package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +98 -99
  68. package/templates/nextjs/ui-canvas/cart/gift-card-input.tsx +142 -0
  69. package/templates/nextjs/ui-canvas/cart/tax-estimate-line.tsx +104 -0
  70. package/templates/nextjs/ui-canvas/home/home-client.tsx +24 -26
  71. package/templates/nextjs/ui-canvas/layout/region-switcher.tsx +51 -0
  72. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +3 -0
  73. package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +174 -168
  74. package/templates/nextjs/ui-canvas/product/product-card.tsx +9 -51
  75. package/templates/nextjs/ui-canvas/product/product-client-section.tsx +5 -3
  76. package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +105 -99
  77. package/templates/nextjs/ui-canvas/product/stock-badge.tsx +17 -2
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * The customer's vaulted payment methods.
5
+ *
6
+ * ⛔ AUTO-HIDES. `getMySavedPaymentMethods()` returns `[]` for everyone who has
7
+ * never vaulted a card, which on a new store is everyone, so the whole section
8
+ * renders null. It also renders null when the call fails or the shopper is
9
+ * logged out. There is no empty state and no error box: a shopper who has no
10
+ * saved cards does not need to be told about a feature they have not used.
11
+ *
12
+ * ⛔ THIS IS NOT ONE-CLICK CHECKOUT, and there is deliberately no "pay with
13
+ * this card" button. The ONLY storefront call that charges a vaulted card is
14
+ * `subscribeToMembership({ planId, savedPaymentTokenId })`. Nothing on the
15
+ * normal checkout accepts a saved token, so a pay-with-saved-card button would
16
+ * be a dead control. This is a read-only list.
17
+ *
18
+ * ⛔ NO CARD DATA IS HERE AND NONE IS AVAILABLE. The response carries display
19
+ * fields only: `paymentMethod`, `brand`, `last4`, `expMonth`, `expYear`,
20
+ * `isDefault`. Every one of those can be null except the first and last.
21
+ *
22
+ * Cards get here by checking out with `createPaymentIntent(checkoutId,
23
+ * { saveCard: true })`, which is honored only when the checkout has a known
24
+ * `customerId` (so never for a guest) and is silently ignored by providers
25
+ * without tokenization (Grow today). Treat vaulting as best-effort and never
26
+ * promise the shopper their card was definitely saved.
27
+ */
28
+
29
+ import { useEffect, useState } from 'react';
30
+ import type { StorefrontSavedPaymentMethod } from 'brainerce';
31
+ import { getClient } from '@/core/lib/brainerce';
32
+ import { useTranslations } from '@/core/lib/translations';
33
+ import { useAuth } from '@/core/providers/store-provider';
34
+ import { cn } from '@/core/lib/utils';
35
+
36
+ /** "12/2027" from expMonth/expYear, or null when the provider gave neither. */
37
+ function formatExpiry(month: number | null, year: number | null): string | null {
38
+ if (month == null || year == null) return null;
39
+ return `${String(month).padStart(2, '0')}/${year}`;
40
+ }
41
+
42
+ export function SavedPaymentMethods({ className }: { className?: string }) {
43
+ const t = useTranslations('account');
44
+ const { isLoggedIn } = useAuth();
45
+ const [methods, setMethods] = useState<StorefrontSavedPaymentMethod[]>([]);
46
+
47
+ useEffect(() => {
48
+ if (!isLoggedIn) {
49
+ setMethods([]);
50
+ return;
51
+ }
52
+ let cancelled = false;
53
+ getClient()
54
+ .getMySavedPaymentMethods()
55
+ .then((result) => {
56
+ if (!cancelled) setMethods(Array.isArray(result) ? result : []);
57
+ })
58
+ .catch(() => {
59
+ // Swallowed on purpose. The account page must render its profile,
60
+ // addresses and orders whether or not this optional list resolves.
61
+ if (!cancelled) setMethods([]);
62
+ });
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }, [isLoggedIn]);
67
+
68
+ if (methods.length === 0) return null;
69
+
70
+ return (
71
+ <div className={cn('', className)}>
72
+ <h2 className="text-foreground mb-4 text-lg font-semibold">{t('paymentMethod')}</h2>
73
+ <ul className="border-border divide-border divide-y rounded-lg border">
74
+ {methods.map((method) => {
75
+ const expiry = formatExpiry(method.expMonth, method.expYear);
76
+ return (
77
+ <li key={method.id} className="flex items-center justify-between gap-4 px-4 py-3">
78
+ <span className="text-foreground text-sm font-medium">
79
+ {/* Brand + last4 when the provider gave them; otherwise the
80
+ method itself ("paypal", "bank_account"). All store data,
81
+ so no translation key is involved. */}
82
+ {method.brand && method.last4
83
+ ? `${method.brand} •••• ${method.last4}`
84
+ : method.paymentMethod}
85
+ </span>
86
+ <span className="flex items-center gap-3">
87
+ {expiry ? (
88
+ <span className="text-muted-foreground text-sm" dir="ltr">
89
+ {expiry}
90
+ </span>
91
+ ) : null}
92
+ {method.isDefault ? (
93
+ <span className="bg-secondary text-secondary-foreground rounded px-2 py-0.5 text-xs font-medium">
94
+ {t('defaultAddress')}
95
+ </span>
96
+ ) : null}
97
+ </span>
98
+ </li>
99
+ );
100
+ })}
101
+ </ul>
102
+ </div>
103
+ );
104
+ }