create-brainerce-store 1.50.5 → 1.51.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.
- package/dist/index.js +171 -66
- package/messages/en.json +4 -1
- package/messages/he.json +4 -1
- package/package.json +1 -1
- package/templates/nextjs/base/.claude/commands/design.md +51 -0
- package/templates/nextjs/base/.env.local.ejs +5 -0
- package/templates/nextjs/base/.eslintrc.json +57 -3
- package/templates/nextjs/base/AGENTS.md +41 -0
- package/templates/nextjs/base/AI-GUIDE.md +94 -0
- package/templates/nextjs/base/CLAUDE.md +42 -0
- package/templates/nextjs/base/TRANSLATIONS.md +1 -1
- package/templates/nextjs/base/next.config.ts +2 -2
- package/templates/nextjs/base/src/app/account/page.tsx +5 -5
- package/templates/nextjs/base/src/app/api/auth/logout/route.ts +1 -1
- package/templates/nextjs/base/src/app/api/auth/reset-password/route.ts +2 -2
- package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +1 -1
- package/templates/nextjs/base/src/app/auth/callback/page.tsx +4 -4
- package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +4 -4
- package/templates/nextjs/base/src/app/blog/page.tsx.ejs +2 -2
- package/templates/nextjs/base/src/app/cart/page.tsx +2 -173
- package/templates/nextjs/base/src/app/checkout/page.tsx +12 -11
- package/templates/nextjs/base/src/app/contact/page.tsx +3 -3
- package/templates/nextjs/base/src/app/faq/page.tsx.ejs +2 -2
- package/templates/nextjs/base/src/app/forgot-password/page.tsx +4 -4
- package/templates/nextjs/base/src/app/layout.tsx.ejs +12 -12
- package/templates/nextjs/base/src/app/login/page.tsx +4 -4
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +19 -12
- package/templates/nextjs/base/src/app/page.tsx +4 -4
- package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +3 -3
- package/templates/nextjs/base/src/app/payment-complete/page.tsx +1 -1
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +6 -6
- package/templates/nextjs/base/src/app/products/page.tsx +4 -623
- package/templates/nextjs/base/src/app/register/page.tsx +4 -4
- package/templates/nextjs/base/src/app/reset-password/page.tsx +5 -5
- package/templates/nextjs/base/src/app/sitemap.ts +1 -1
- package/templates/nextjs/base/src/app/verify-email/page.tsx +5 -5
- package/templates/nextjs/base/src/components/account/address-book.tsx +3 -3
- package/templates/nextjs/base/src/components/account/order-customizations.tsx +3 -3
- package/templates/nextjs/base/src/components/account/order-history.tsx +13 -5
- package/templates/nextjs/base/src/components/account/order-payment-block.tsx +2 -2
- package/templates/nextjs/base/src/components/account/order-shipping-block.tsx +2 -2
- package/templates/nextjs/base/src/components/account/order-status-timeline.tsx +2 -2
- package/templates/nextjs/base/src/components/account/profile-section.tsx +3 -3
- package/templates/nextjs/base/src/components/auth/login-form.tsx +4 -4
- package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +4 -4
- package/templates/nextjs/base/src/components/auth/register-form.tsx +4 -4
- package/templates/nextjs/base/src/components/brainerce-bot.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +24 -3
- package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/order-bump-card.tsx +4 -4
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +6 -6
- package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +3 -3
- package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +3 -3
- package/templates/nextjs/base/src/components/checkout/tax-display.tsx +3 -3
- package/templates/nextjs/base/src/components/seo/organization-json-ld.tsx +2 -2
- package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +3 -3
- package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +58 -0
- package/templates/nextjs/base/src/core/hooks/use-home-data.ts +51 -0
- package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +295 -0
- package/templates/nextjs/base/src/core/hooks/use-product-page.ts +278 -0
- package/templates/nextjs/base/src/{hooks → core/hooks}/use-search.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/brainerce.ts.ejs +1 -1
- package/templates/nextjs/base/src/core/lib/product-options.ts +121 -0
- package/templates/nextjs/base/src/{lib → core/lib}/resolve-currency.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/safe-redirect.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/sanitize.ts.ejs +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/use-currency.ts +1 -1
- package/templates/nextjs/base/src/{providers → core/providers}/store-provider.tsx.ejs +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/cart-bundle-offer.tsx +5 -5
- package/templates/nextjs/base/src/{components → ui}/cart/cart-item.tsx +6 -6
- package/templates/nextjs/base/src/{components → ui}/cart/cart-nudges.tsx +1 -1
- package/templates/nextjs/base/src/{components → ui}/cart/cart-summary.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/cart-upgrade-banner.tsx +5 -5
- package/templates/nextjs/base/src/ui/cart/cart-view.tsx +149 -0
- package/templates/nextjs/base/src/{components → ui}/cart/coupon-input.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/free-shipping-bar.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/reservation-countdown.tsx +2 -2
- package/templates/nextjs/base/src/ui/home/discount-banner-strip.tsx +20 -0
- package/templates/nextjs/base/src/ui/home/featured-products-section.tsx +24 -0
- package/templates/nextjs/base/src/ui/home/hero-section.tsx +29 -0
- package/templates/nextjs/base/src/ui/home/home-client.tsx +29 -0
- package/templates/nextjs/base/src/{components/content → ui/layout}/faq-section.tsx.ejs +2 -2
- package/templates/nextjs/base/src/{components/content → ui/layout}/header-account.tsx +3 -3
- package/templates/nextjs/base/src/{components/content → ui/layout}/rich-text-block.tsx.ejs +1 -1
- package/templates/nextjs/base/src/{components/content → ui/layout}/site-header.tsx.ejs +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/customization-fields.tsx +18 -51
- package/templates/nextjs/base/src/{components/products → ui/product}/discount-badge.tsx +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/frequently-bought-together.tsx +6 -6
- package/templates/nextjs/base/src/{components/products → ui/product}/modifier-group-selector.tsx +10 -73
- package/templates/nextjs/base/src/{components/shared → ui/product}/price-display.tsx +2 -2
- package/templates/nextjs/base/src/{components/products → ui/product}/product-card.tsx +10 -10
- package/templates/nextjs/base/src/{app/products/[slug] → ui/product}/product-client-section.tsx +45 -206
- package/templates/nextjs/base/src/{components/products → ui/product}/product-grid.tsx +3 -3
- package/templates/nextjs/base/src/ui/product/product-listing.tsx +429 -0
- package/templates/nextjs/base/src/{components/products → ui/product}/recommendation-section.tsx +4 -4
- package/templates/nextjs/base/src/{components/reviews → ui/product}/review-form.tsx +2 -2
- package/templates/nextjs/base/src/{components/reviews → ui/product}/reviews-section.tsx +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/stock-badge.tsx +2 -2
- package/templates/nextjs/base/src/{components/products → ui/product}/variant-selector.tsx +3 -3
- package/templates/nextjs/base/src/{components → ui}/shared/cdn-image.tsx +1 -1
- package/templates/nextjs/base/src/{components → ui}/shared/loading-spinner.tsx +2 -2
- package/templates/nextjs/base/tailwind.config.ts +12 -0
- package/templates/nextjs/designs/passepartout/app-overlay/layout.tsx.ejs +253 -0
- package/templates/nextjs/designs/passepartout/globals.css +412 -0
- package/templates/nextjs/designs/passepartout/messages-patch/en.json +87 -0
- package/templates/nextjs/designs/passepartout/messages-patch/he.json +87 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-bundle-offer.tsx +111 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +39 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-upgrade-banner.tsx +141 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +149 -0
- package/templates/nextjs/designs/passepartout/ui/cart/free-shipping-bar.tsx +62 -0
- package/templates/nextjs/designs/passepartout/ui/cart/reservation-countdown.tsx +103 -0
- package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +401 -0
- package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +139 -0
- package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +45 -0
- package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +103 -0
- package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +159 -0
- package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +155 -0
- package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +60 -0
- package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +34 -0
- package/templates/nextjs/designs/passepartout/ui/product/discount-badge.tsx +22 -0
- package/templates/nextjs/designs/passepartout/ui/product/frequently-bought-together.tsx +205 -0
- package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +111 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +265 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +667 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +35 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +427 -0
- package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +113 -0
- package/templates/nextjs/designs/passepartout/ui/product/review-form.tsx +294 -0
- package/templates/nextjs/designs/passepartout/ui/product/reviews-section.tsx.ejs +96 -0
- package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +52 -0
- package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +307 -0
- package/templates/nextjs/designs/passepartout/ui/shared/fly-to-cart.ts +85 -0
- package/templates/nextjs/designs/passepartout/ui/shared/loading-spinner.tsx +33 -0
- package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +87 -0
- package/templates/nextjs/ui-canvas/cart/cart-item.tsx +121 -0
- package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +25 -0
- package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +97 -0
- package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +103 -0
- package/templates/nextjs/ui-canvas/cart/cart-view.tsx +106 -0
- package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +117 -0
- package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +52 -0
- package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +79 -0
- package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +19 -0
- package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +23 -0
- package/templates/nextjs/ui-canvas/home/hero-section.tsx +21 -0
- package/templates/nextjs/ui-canvas/home/home-client.tsx +26 -0
- package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +101 -0
- package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +71 -0
- package/templates/nextjs/ui-canvas/layout/header-account.tsx +31 -0
- package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +74 -0
- package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +34 -0
- package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +82 -0
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +119 -0
- package/templates/nextjs/ui-canvas/product/customization-fields.tsx +443 -0
- package/templates/nextjs/ui-canvas/product/discount-badge.tsx +17 -0
- package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +166 -0
- package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +163 -0
- package/templates/nextjs/ui-canvas/product/price-display.tsx +50 -0
- package/templates/nextjs/ui-canvas/product/product-card.tsx +197 -0
- package/templates/nextjs/ui-canvas/product/product-client-section.tsx +352 -0
- package/templates/nextjs/ui-canvas/product/product-grid.tsx +27 -0
- package/templates/nextjs/ui-canvas/product/product-listing.tsx +224 -0
- package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +95 -0
- package/templates/nextjs/ui-canvas/product/review-form.tsx +269 -0
- package/templates/nextjs/ui-canvas/product/reviews-section.tsx +78 -0
- package/templates/nextjs/ui-canvas/product/stock-badge.tsx +43 -0
- package/templates/nextjs/ui-canvas/product/variant-selector.tsx +210 -0
- package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +29 -0
- package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +25 -0
- package/templates/nextjs/base/src/app/home-client.tsx +0 -98
- /package/templates/nextjs/base/src/{lib → core/lib}/auth.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/csrf.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/image-hosts.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/navigation.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/nonce.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/sanitize-html.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/seo.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/store-info.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/translations.ts.ejs +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/utils.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/validation.ts +0 -0
- /package/templates/nextjs/base/src/{components/content → ui/layout}/announcement-bar.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{components → ui}/layout/language-switcher.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{components/content → ui/layout}/site-footer.tsx.ejs +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { useRouter, Link } from '@/lib/navigation';
|
|
5
|
-
import { useAuth } from '@/providers/store-provider';
|
|
6
|
-
import { proxyRegister } from '@/lib/auth';
|
|
4
|
+
import { useRouter, Link } from '@/core/lib/navigation';
|
|
5
|
+
import { useAuth } from '@/core/providers/store-provider';
|
|
6
|
+
import { proxyRegister } from '@/core/lib/auth';
|
|
7
7
|
import { RegisterForm } from '@/components/auth/register-form';
|
|
8
8
|
import { OAuthButtons } from '@/components/auth/oauth-buttons';
|
|
9
|
-
import { useTranslations } from '@/lib/translations';
|
|
9
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
10
10
|
|
|
11
11
|
export default function RegisterPage() {
|
|
12
12
|
const router = useRouter();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { useRouter, Link } from '@/lib/navigation';
|
|
5
|
-
import { proxyResetPassword } from '@/lib/auth';
|
|
6
|
-
import { LoadingSpinner } from '@/
|
|
7
|
-
import { useTranslations } from '@/lib/translations';
|
|
8
|
-
import { getPasswordError } from '@/lib/validation';
|
|
4
|
+
import { useRouter, Link } from '@/core/lib/navigation';
|
|
5
|
+
import { proxyResetPassword } from '@/core/lib/auth';
|
|
6
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
7
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
8
|
+
import { getPasswordError } from '@/core/lib/validation';
|
|
9
9
|
|
|
10
10
|
export default function ResetPasswordPage() {
|
|
11
11
|
const router = useRouter();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MetadataRoute } from 'next';
|
|
2
|
-
import { getServerClient } from '@/lib/brainerce';
|
|
2
|
+
import { getServerClient } from '@/core/lib/brainerce';
|
|
3
3
|
|
|
4
4
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
5
5
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Suspense, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
-
import { useRouter, Link } from '@/lib/navigation';
|
|
5
|
-
import { useAuth } from '@/providers/store-provider';
|
|
6
|
-
import { proxyVerifyEmail, proxyResendVerification } from '@/lib/auth';
|
|
7
|
-
import { LoadingSpinner } from '@/
|
|
8
|
-
import { useTranslations } from '@/lib/translations';
|
|
4
|
+
import { useRouter, Link } from '@/core/lib/navigation';
|
|
5
|
+
import { useAuth } from '@/core/providers/store-provider';
|
|
6
|
+
import { proxyVerifyEmail, proxyResendVerification } from '@/core/lib/auth';
|
|
7
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
8
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
9
9
|
|
|
10
10
|
const CODE_LENGTH = 6;
|
|
11
11
|
const RESEND_COOLDOWN_SECONDS = 60;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import type { CustomerAddress, CreateAddressDto } from 'brainerce';
|
|
5
|
-
import { getClient } from '@/lib/brainerce';
|
|
6
|
-
import { useTranslations } from '@/lib/translations';
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
5
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
6
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
7
|
+
import { cn } from '@/core/lib/utils';
|
|
8
8
|
|
|
9
9
|
interface AddressBookProps {
|
|
10
10
|
addresses: CustomerAddress[];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { CdnImage as Image } from '@/
|
|
3
|
+
import { CdnImage as Image } from '@/ui/shared/cdn-image';
|
|
4
4
|
import type { OrderItem } from 'brainerce';
|
|
5
|
-
import { useTranslations } from '@/lib/translations';
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
5
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
6
|
+
import { cn } from '@/core/lib/utils';
|
|
7
7
|
|
|
8
8
|
type Customizations = NonNullable<OrderItem['customizations']>;
|
|
9
9
|
type CustomizationEntry = Customizations[string];
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import { CdnImage as Image } from '@/
|
|
4
|
+
import { CdnImage as Image } from '@/ui/shared/cdn-image';
|
|
5
5
|
import type { Order, OrderStatus, OrderDownloadLink } from 'brainerce';
|
|
6
6
|
import { formatPrice } from 'brainerce';
|
|
7
|
-
import { getClient } from '@/lib/brainerce';
|
|
8
|
-
import { useCurrency } from '@/lib/use-currency';
|
|
9
|
-
import { useTranslations } from '@/lib/translations';
|
|
10
|
-
import { cn } from '@/lib/utils';
|
|
7
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
8
|
+
import { useCurrency } from '@/core/lib/use-currency';
|
|
9
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
10
|
+
import { cn } from '@/core/lib/utils';
|
|
11
11
|
import { OrderCustomizations } from './order-customizations';
|
|
12
12
|
import { OrderStatusTimeline } from './order-status-timeline';
|
|
13
13
|
import { OrderShippingBlock } from './order-shipping-block';
|
|
@@ -207,6 +207,14 @@ function OrderCard({ order }: { order: Order }) {
|
|
|
207
207
|
<OrderShippingBlock order={order} />
|
|
208
208
|
<OrderPaymentBlock order={order} />
|
|
209
209
|
|
|
210
|
+
{/* The shopper's own checkout note, read-only */}
|
|
211
|
+
{order.notes && (
|
|
212
|
+
<div className="border-border border-t pt-2">
|
|
213
|
+
<p className="text-foreground text-sm font-medium">{t('orderNotes')}</p>
|
|
214
|
+
<p className="text-muted-foreground text-sm whitespace-pre-wrap">{order.notes}</p>
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
|
|
210
218
|
{/* Downloads section */}
|
|
211
219
|
{order.hasDownloads && <OrderDownloads orderId={order.id} />}
|
|
212
220
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { Order } from 'brainerce';
|
|
4
|
-
import { useTranslations } from '@/lib/translations';
|
|
5
|
-
import { cn } from '@/lib/utils';
|
|
4
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
5
|
+
import { cn } from '@/core/lib/utils';
|
|
6
6
|
|
|
7
7
|
interface OrderPaymentBlockProps {
|
|
8
8
|
order: Order;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { Order } from 'brainerce';
|
|
4
|
-
import { useTranslations } from '@/lib/translations';
|
|
5
|
-
import { cn } from '@/lib/utils';
|
|
4
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
5
|
+
import { cn } from '@/core/lib/utils';
|
|
6
6
|
|
|
7
7
|
interface OrderShippingBlockProps {
|
|
8
8
|
order: Order;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { Order, OrderStatus } from 'brainerce';
|
|
4
|
-
import { useTranslations } from '@/lib/translations';
|
|
5
|
-
import { cn } from '@/lib/utils';
|
|
4
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
5
|
+
import { cn } from '@/core/lib/utils';
|
|
6
6
|
|
|
7
7
|
const STATUS_KEYS: Record<
|
|
8
8
|
OrderStatus,
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import type { CustomerProfile } from 'brainerce';
|
|
5
|
-
import { getClient } from '@/lib/brainerce';
|
|
6
|
-
import { useTranslations } from '@/lib/translations';
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
5
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
6
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
7
|
+
import { cn } from '@/core/lib/utils';
|
|
8
8
|
|
|
9
9
|
interface ProfileSectionProps {
|
|
10
10
|
profile: CustomerProfile;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { Link } from '@/lib/navigation';
|
|
5
|
-
import { useTranslations } from '@/lib/translations';
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
7
|
-
import { LoadingSpinner } from '@/
|
|
4
|
+
import { Link } from '@/core/lib/navigation';
|
|
5
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
6
|
+
import { cn } from '@/core/lib/utils';
|
|
7
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
8
8
|
|
|
9
9
|
interface LoginFormProps {
|
|
10
10
|
onSubmit: (email: string, password: string) => Promise<void>;
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import type { CustomerOAuthProvider } from 'brainerce';
|
|
5
|
-
import { getClient } from '@/lib/brainerce';
|
|
6
|
-
import { useTranslations } from '@/lib/translations';
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
8
|
-
import { LoadingSpinner } from '@/
|
|
5
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
6
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
7
|
+
import { cn } from '@/core/lib/utils';
|
|
8
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
9
9
|
|
|
10
10
|
const PROVIDER_CONFIG: Record<CustomerOAuthProvider, { labelKey: string; icon: React.ReactNode }> =
|
|
11
11
|
{
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useMemo } from 'react';
|
|
4
|
-
import { useTranslations } from '@/lib/translations';
|
|
5
|
-
import { cn } from '@/lib/utils';
|
|
6
|
-
import { LoadingSpinner } from '@/
|
|
7
|
-
import { getPasswordError } from '@/lib/validation';
|
|
4
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
5
|
+
import { cn } from '@/core/lib/utils';
|
|
6
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
7
|
+
import { getPasswordError } from '@/core/lib/validation';
|
|
8
8
|
|
|
9
9
|
interface RegisterData {
|
|
10
10
|
firstName: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
4
|
|
|
5
|
-
import { useCart } from '@/providers/store-provider';
|
|
5
|
+
import { useCart } from '@/core/providers/store-provider';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Mounts the Brainerce AI chat widget (the store's shopping assistant).
|
|
@@ -36,7 +36,7 @@ export function BrainerceBotWidget() {
|
|
|
36
36
|
baseUrl: process.env.NEXT_PUBLIC_BRAINERCE_API_URL || undefined,
|
|
37
37
|
onAddToCart: async ({ productId, variantId, quantity }) => {
|
|
38
38
|
try {
|
|
39
|
-
const { getClient } = await import('@/lib/brainerce');
|
|
39
|
+
const { getClient } = await import('@/core/lib/brainerce');
|
|
40
40
|
await getClient().smartAddToCart({
|
|
41
41
|
productId,
|
|
42
42
|
variantId: variantId ?? undefined,
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef } from 'react';
|
|
4
4
|
import type { SetShippingAddressDto, ShippingDestinations } from 'brainerce';
|
|
5
|
-
import { useTranslations } from '@/lib/translations';
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
7
|
-
import { isValidEmail } from '@/lib/validation';
|
|
5
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
6
|
+
import { cn } from '@/core/lib/utils';
|
|
7
|
+
import { isValidEmail } from '@/core/lib/validation';
|
|
8
8
|
|
|
9
9
|
interface CheckoutConsent {
|
|
10
10
|
acceptsMarketing: boolean;
|
|
@@ -41,6 +41,7 @@ export function CheckoutForm({
|
|
|
41
41
|
postalCode: initialValues?.postalCode || '',
|
|
42
42
|
country: initialValues?.country || '',
|
|
43
43
|
phone: initialValues?.phone || '',
|
|
44
|
+
notes: initialValues?.notes || '',
|
|
44
45
|
});
|
|
45
46
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
46
47
|
const [privacyAccepted, setPrivacyAccepted] = useState(false);
|
|
@@ -65,6 +66,7 @@ export function CheckoutForm({
|
|
|
65
66
|
postalCode: initialValues.postalCode || prev.postalCode,
|
|
66
67
|
country: initialValues.country || prev.country,
|
|
67
68
|
phone: initialValues.phone || prev.phone || '',
|
|
69
|
+
notes: prev.notes || '',
|
|
68
70
|
}));
|
|
69
71
|
}, [initialValues]);
|
|
70
72
|
|
|
@@ -346,6 +348,25 @@ export function CheckoutForm({
|
|
|
346
348
|
</>
|
|
347
349
|
)}
|
|
348
350
|
|
|
351
|
+
{/* Order notes (optional) */}
|
|
352
|
+
<div>
|
|
353
|
+
<label htmlFor="orderNotes" className="text-foreground mb-1 block text-sm font-medium">
|
|
354
|
+
{t('orderNotes')}
|
|
355
|
+
</label>
|
|
356
|
+
<textarea
|
|
357
|
+
id="orderNotes"
|
|
358
|
+
value={formData.notes || ''}
|
|
359
|
+
onChange={(e) => updateField('notes', e.target.value)}
|
|
360
|
+
maxLength={2000}
|
|
361
|
+
rows={3}
|
|
362
|
+
className={cn(
|
|
363
|
+
inputClass,
|
|
364
|
+
'border-border h-auto min-h-[80px] resize-y py-2 leading-relaxed'
|
|
365
|
+
)}
|
|
366
|
+
placeholder={t('orderNotesPlaceholder')}
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
349
370
|
{/* Privacy Policy (required) */}
|
|
350
371
|
<div>
|
|
351
372
|
<label className="flex cursor-pointer items-start gap-2">
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import type { CheckoutCustomFieldDefinition } from 'brainerce';
|
|
5
|
-
import { useTranslations } from '@/lib/translations';
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
5
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
6
|
+
import { cn } from '@/core/lib/utils';
|
|
7
7
|
|
|
8
8
|
interface CustomFieldsStepProps {
|
|
9
9
|
fields: CheckoutCustomFieldDefinition[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useTranslations } from '@/lib/translations';
|
|
4
|
-
import { cn } from '@/lib/utils';
|
|
3
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
4
|
+
import { cn } from '@/core/lib/utils';
|
|
5
5
|
|
|
6
6
|
interface DeliveryMethodStepProps {
|
|
7
7
|
onSelect: (method: 'shipping' | 'pickup') => void;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useMemo } from 'react';
|
|
4
|
-
import { CdnImage as Image } from '@/
|
|
4
|
+
import { CdnImage as Image } from '@/ui/shared/cdn-image';
|
|
5
5
|
import type { OrderBump, RecommendationVariant } from 'brainerce';
|
|
6
6
|
import { formatPrice, getVariantOptions } from 'brainerce';
|
|
7
|
-
import { useCurrency } from '@/lib/use-currency';
|
|
8
|
-
import { useTranslations } from '@/lib/translations';
|
|
9
|
-
import { cn } from '@/lib/utils';
|
|
7
|
+
import { useCurrency } from '@/core/lib/use-currency';
|
|
8
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
9
|
+
import { cn } from '@/core/lib/utils';
|
|
10
10
|
|
|
11
11
|
interface OrderBumpCardProps {
|
|
12
12
|
bump: OrderBump;
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import { useEffect, useState, useRef, useCallback, type CSSProperties } from 'react';
|
|
4
4
|
import type { PaymentIntent, PaymentClientSdk } from 'brainerce';
|
|
5
5
|
import { formatPrice } from 'brainerce';
|
|
6
|
-
import { getClient } from '@/lib/brainerce';
|
|
7
|
-
import { useTranslations } from '@/lib/translations';
|
|
8
|
-
import { LoadingSpinner } from '@/
|
|
9
|
-
import { useStoreInfo } from '@/providers/store-provider';
|
|
10
|
-
import { cn } from '@/lib/utils';
|
|
11
|
-
import { isAllowedPaymentUrl, isValidCheckoutId, safePaymentRedirect } from '@/lib/safe-redirect';
|
|
6
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
7
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
8
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
9
|
+
import { useStoreInfo } from '@/core/providers/store-provider';
|
|
10
|
+
import { cn } from '@/core/lib/utils';
|
|
11
|
+
import { isAllowedPaymentUrl, isValidCheckoutId, safePaymentRedirect } from '@/core/lib/safe-redirect';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Backward-compat defaults when backend doesn't return clientSdk.
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import type { PickupLocation } from 'brainerce';
|
|
5
5
|
import { formatPrice } from 'brainerce';
|
|
6
|
-
import { useTranslations } from '@/lib/translations';
|
|
7
|
-
import { useCurrency } from '@/lib/use-currency';
|
|
8
|
-
import { cn } from '@/lib/utils';
|
|
6
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
7
|
+
import { useCurrency } from '@/core/lib/use-currency';
|
|
8
|
+
import { cn } from '@/core/lib/utils';
|
|
9
9
|
|
|
10
10
|
interface PickupStepProps {
|
|
11
11
|
locations: PickupLocation[];
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ShippingRate } from 'brainerce';
|
|
4
4
|
import { formatPrice } from 'brainerce';
|
|
5
|
-
import { useTranslations } from '@/lib/translations';
|
|
6
|
-
import { useCurrency } from '@/lib/use-currency';
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
5
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
6
|
+
import { useCurrency } from '@/core/lib/use-currency';
|
|
7
|
+
import { cn } from '@/core/lib/utils';
|
|
8
8
|
|
|
9
9
|
interface ShippingStepProps {
|
|
10
10
|
rates: ShippingRate[];
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { TaxBreakdown } from 'brainerce';
|
|
4
4
|
import { formatPrice } from 'brainerce';
|
|
5
|
-
import { useTranslations } from '@/lib/translations';
|
|
6
|
-
import { useCurrency } from '@/lib/use-currency';
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
5
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
6
|
+
import { useCurrency } from '@/core/lib/use-currency';
|
|
7
|
+
import { cn } from '@/core/lib/utils';
|
|
8
8
|
|
|
9
9
|
interface TaxDisplayProps {
|
|
10
10
|
/** Whether shipping address has been set */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StoreInfo } from 'brainerce';
|
|
2
|
-
import { getNonce } from '@/lib/nonce';
|
|
3
|
-
import { stripHtmlForSeo } from '@/lib/seo';
|
|
2
|
+
import { getNonce } from '@/core/lib/nonce';
|
|
3
|
+
import { stripHtmlForSeo } from '@/core/lib/seo';
|
|
4
4
|
|
|
5
5
|
interface OrganizationJsonLdProps {
|
|
6
6
|
storeInfo: StoreInfo;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Product } from 'brainerce';
|
|
2
2
|
import { getProductPriceInfo } from 'brainerce';
|
|
3
|
-
import { getNonce } from '@/lib/nonce';
|
|
4
|
-
import { resolveCurrency } from '@/lib/resolve-currency';
|
|
5
|
-
import { stripHtmlForSeo } from '@/lib/seo';
|
|
3
|
+
import { getNonce } from '@/core/lib/nonce';
|
|
4
|
+
import { resolveCurrency } from '@/core/lib/resolve-currency';
|
|
5
|
+
import { stripHtmlForSeo } from '@/core/lib/seo';
|
|
6
6
|
|
|
7
7
|
interface ProductJsonLdProps {
|
|
8
8
|
product: Product;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import type {
|
|
5
|
+
Cart,
|
|
6
|
+
CartRecommendationsResponse,
|
|
7
|
+
CartUpgradesResponse,
|
|
8
|
+
CartBundlesResponse,
|
|
9
|
+
} from 'brainerce';
|
|
10
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
11
|
+
import { useCart } from '@/core/providers/store-provider';
|
|
12
|
+
|
|
13
|
+
export interface UseCartPageResult {
|
|
14
|
+
cart: Cart | null;
|
|
15
|
+
cartLoading: boolean;
|
|
16
|
+
refreshCart: () => Promise<void>;
|
|
17
|
+
itemCount: number;
|
|
18
|
+
/** Cross-sell recommendations for the current cart contents. */
|
|
19
|
+
cartRecs: CartRecommendationsResponse | null;
|
|
20
|
+
/** Per-item upgrade suggestions keyed by productId. */
|
|
21
|
+
upgrades: CartUpgradesResponse | null;
|
|
22
|
+
/** Bundle offers matching the current cart. */
|
|
23
|
+
bundles: CartBundlesResponse | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Cart-page behavior: the shared cart state (from StoreProvider) plus the
|
|
28
|
+
* enrichment fetch (recommendations / upgrades / bundles) that runs whenever
|
|
29
|
+
* the cart contents change. Pure data/behavior — rendering lives in
|
|
30
|
+
* `ui/cart/cart-view.tsx`.
|
|
31
|
+
*/
|
|
32
|
+
export function useCartPage(): UseCartPageResult {
|
|
33
|
+
const { cart, cartLoading, refreshCart, itemCount } = useCart();
|
|
34
|
+
const [cartRecs, setCartRecs] = useState<CartRecommendationsResponse | null>(null);
|
|
35
|
+
const [upgrades, setUpgrades] = useState<CartUpgradesResponse | null>(null);
|
|
36
|
+
const [bundles, setBundles] = useState<CartBundlesResponse | null>(null);
|
|
37
|
+
|
|
38
|
+
// Load recommendations, upgrades, and bundles in a single request
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!cart?.id || cart.items.length === 0) {
|
|
41
|
+
setCartRecs(null);
|
|
42
|
+
setUpgrades(null);
|
|
43
|
+
setBundles(null);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const client = getClient();
|
|
47
|
+
client
|
|
48
|
+
.getCart(cart.id, { include: ['recommendations', 'upgrades', 'bundles'] })
|
|
49
|
+
.then((enriched) => {
|
|
50
|
+
setCartRecs(enriched.recommendations ?? null);
|
|
51
|
+
setUpgrades(enriched.upgrades ?? null);
|
|
52
|
+
setBundles(enriched.bundles ?? null);
|
|
53
|
+
})
|
|
54
|
+
.catch(() => {});
|
|
55
|
+
}, [cart?.id, cart?.items.length]);
|
|
56
|
+
|
|
57
|
+
return { cart, cartLoading, refreshCart, itemCount, cartRecs, upgrades, bundles };
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import type { Product, DiscountBanner } from 'brainerce';
|
|
5
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
6
|
+
|
|
7
|
+
export interface UseHomeDataResult {
|
|
8
|
+
/** Newest products for the featured grid (max 8). */
|
|
9
|
+
products: Product[];
|
|
10
|
+
/** Active store-wide discount banners. */
|
|
11
|
+
banners: DiscountBanner[];
|
|
12
|
+
/** True while the initial fetch is in flight. */
|
|
13
|
+
loading: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Homepage data: featured products + discount banners.
|
|
18
|
+
* Pure data/behavior — rendering lives in `ui/home/`.
|
|
19
|
+
*/
|
|
20
|
+
export function useHomeData(): UseHomeDataResult {
|
|
21
|
+
const [products, setProducts] = useState<Product[]>([]);
|
|
22
|
+
const [banners, setBanners] = useState<DiscountBanner[]>([]);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
async function load() {
|
|
27
|
+
try {
|
|
28
|
+
const client = getClient();
|
|
29
|
+
const [productsRes, bannersRes] = await Promise.allSettled([
|
|
30
|
+
client.getProducts({ limit: 8, sortBy: 'createdAt', sortOrder: 'desc' }),
|
|
31
|
+
client.getDiscountBanners(),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
if (productsRes.status === 'fulfilled') {
|
|
35
|
+
setProducts(productsRes.value.data);
|
|
36
|
+
}
|
|
37
|
+
if (bannersRes.status === 'fulfilled') {
|
|
38
|
+
setBanners(bannersRes.value);
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error('Failed to load home page data:', err);
|
|
42
|
+
} finally {
|
|
43
|
+
setLoading(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
load();
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return { products, banners, loading };
|
|
51
|
+
}
|