Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
SPREE_API_URL=http://localhost:3000
SPREE_PUBLISHABLE_KEY=your_publishable_api_key

# Wholesale B2B portal (opt-in addon - powers the gated /wholesale surface)
#
# SPREE_WHOLESALE_CHANNEL is the ON/OFF switch. There is no default: leave it
# unset and the storefront runs DTC-only — every wholesale entry point (nav,
# footer, homepage section) is hidden and the /wholesale routes 404. Set it to
# the code of a gated Spree channel to enable the portal. Requests to that
# surface carry the code via the X-Spree-Channel header.
#
# SPREE_WHOLESALE_PUBLISHABLE_KEY is optional. The channel header alone selects
# the channel, so this falls back to SPREE_PUBLISHABLE_KEY when unset; set it
# only to bind a channel-scoped publishable key.
#
# SPREE_WHOLESALE_CHANNEL=wholesale
# SPREE_WHOLESALE_PUBLISHABLE_KEY=

# Store defaults (should match your Spree store settings)
# These are used by the middleware for initial redirects before API data is loaded
# Set these to your store's default_country_iso and default_locale
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ next-env.d.ts

# claude code
.claude/

10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ SPREE_PUBLISHABLE_KEY=your_publishable_api_key_here
| `SENTRY_ORG` | Sentry organization slug (for source map uploads) | _(none)_ |
| `SENTRY_PROJECT` | Sentry project slug (for source map uploads) | _(none)_ |
| `SENTRY_AUTH_TOKEN` | Sentry auth token (for source map uploads in CI) | _(none)_ |
| `SPREE_WHOLESALE_CHANNEL` | Enable switch for the wholesale B2B portal — the code of a gated Spree channel to bind the `/wholesale` surface to. No default: unset means DTC-only (see below) | _(disabled)_ |
| `SPREE_WHOLESALE_PUBLISHABLE_KEY` | Channel-scoped publishable key for the wholesale surface. Optional — the channel header selects the channel, so this falls back to `SPREE_PUBLISHABLE_KEY` | _(falls back to `SPREE_PUBLISHABLE_KEY`)_ |
| `SPREE_WEBHOOK_SECRET` | Webhook endpoint secret key (for transactional emails) | _(disabled)_ |
| `RESEND_API_KEY` | [Resend](https://resend.com) API key for sending emails in production | _(dev: writes to disk)_ |
| `EMAIL_FROM` | "From" address for transactional emails (e.g. `Store <orders@mystore.com>`) | `orders@example.com` |
Expand All @@ -129,6 +131,14 @@ SPREE_PUBLISHABLE_KEY=your_publishable_api_key_here

> **Privacy note:** PII collection is disabled by default. Only set `SENTRY_SEND_DEFAULT_PII` / `NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII` to `true` if you have appropriate user consent or a privacy policy covering this data.

#### Wholesale B2B portal (opt-in addon)

The storefront ships an optional gated wholesale portal at `/wholesale`, a B2B surface that runs on a separate Spree channel while sharing the same storefront and backend as DTC.

`SPREE_WHOLESALE_CHANNEL` is the enable switch and has **no default**. Leave it unset and the storefront is DTC-only: the wholesale nav link, footer link, homepage section, and the `/wholesale` routes are all hidden — the routes return 404. Set it to the code of a gated channel on your Spree backend (e.g. `wholesale`) to turn the portal on. `SPREE_WHOLESALE_PUBLISHABLE_KEY` is optional and only needed to bind a channel-scoped publishable key; without it the surface reuses `SPREE_PUBLISHABLE_KEY` and selects the channel via the `X-Spree-Channel` header.

See the [wholesale portal guide](https://spreecommerce.org/docs/developer/storefront/nextjs/wholesale) for how channel switching works end to end.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Development

```bash
Expand Down
102 changes: 99 additions & 3 deletions messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"viewAllCategory": "Alle {category} anzeigen",
"openSearch": "Suche öffnen",
"closeSearch": "Suche schließen",
"myAccount": "Mein Konto"
"myAccount": "Mein Konto",
"wholesale": "Großhandel"
},
"footer": {
"description": "Open-Source-E-Commerce basierend auf Spree REST API, TypeScript SDK und Next.js. Selbst hosten. Eigene Daten. Keine Plattformgebühren.",
Expand All @@ -53,7 +54,8 @@
"cart": "Warenkorb",
"policies": "Richtlinien",
"poweredBy": "Betrieben von",
"copyright": "© {year} {storeName}. Unterstützt durch Spree Commerce."
"copyright": "© {year} {storeName}. Unterstützt durch Spree Commerce.",
"wholesale": "Handel & Großhandel"
},
"home": {
"welcome": "{storeName} Storefront",
Expand All @@ -68,7 +70,18 @@
"fastShipping": "Schneller Versand",
"shippingDescription": "Schnelle und zuverlässige Lieferung bis an Ihre Haustür.",
"support": "24/7 Support",
"supportDescription": "Unser Team ist jederzeit für Sie da."
"supportDescription": "Unser Team ist jederzeit für Sie da.",
"wholesaleBadge": "Handel & Großhandel",
"wholesaleTitle": "Einkauf für Ihr Unternehmen?",
"wholesaleDescription": "Freigegebene Geschäftskunden erhalten Großhandelspreise auf den gesamten Katalog, schnelle Sammelbestellung per SKU und ihren eigenen Bestellverlauf. Einmal bewerben — wir prüfen und aktivieren Ihr Konto.",
"wholesaleCtaPrimary": "Zum Großhandelsportal",
"wholesaleCtaSecondary": "Konto beantragen",
"wholesaleBenefitPricingTitle": "Handelspreise",
"wholesaleBenefitPricingDescription": "Ihre ausgehandelten Konditionen werden automatisch auf den gesamten Katalog angewendet.",
"wholesaleBenefitQuickOrderTitle": "Schnelle Sammelbestellung",
"wholesaleBenefitQuickOrderDescription": "Geben Sie SKUs und Mengen ein, um in Sekunden eine große Bestellung zusammenzustellen.",
"wholesaleBenefitOrdersTitle": "Bestellverlauf",
"wholesaleBenefitOrdersDescription": "Sehen Sie vergangene Bestellungen ein und bestellen Sie Ihre Standardartikel mit wenigen Klicks nach."
},
"cart": {
"cart": "Warenkorb",
Expand Down Expand Up @@ -524,5 +537,88 @@
"shipping": "Versand",
"finalizingPayment": "Zahlung wird abgeschlossen...",
"or": "oder"
},
"wholesale": {
"portalTitle": "Großhandelsportal",
"portalName": "Großhandelsportal",
"badge": "Großhandel",
"nav": {
"catalog": "Katalog",
"quickOrder": "Schnellbestellung",
"cart": "Warenkorb",
"backToStore": "Zurück zum Shop",
"signOut": "Abmelden",
"signIn": "Anmelden"
},
"signInWall": {
"title": "Handelspreise für freigegebene Käufer",
"description": "Melden Sie sich bei Ihrem Großhandelskonto an, um den gesamten Katalog zu Ihren ausgehandelten Handelspreisen zu durchsuchen und Sammelbestellungen aufzugeben.",
"gatedNotice": "Dieses Portal steht ausschließlich freigegebenen Großhandelskäufern zur Verfügung. Preise und Produkte sind nach der Anmeldung sichtbar.",
"noAccount": "Sie haben kein Großhandelskonto?",
"applyLink": "Zugang beantragen",
"formTitle": "Anmelden",
"formDescription": "Verwenden Sie die E-Mail-Adresse und das Passwort Ihres Großhandelskontos.",
"submit": "Anmelden"
},
"pending": {
"title": "Ihre Bewerbung wird geprüft",
"description": "Vielen Dank für Ihre Bewerbung. Ihr Konto ist eingerichtet, aber der Großhandelszugang steht noch zur Genehmigung aus.",
"nameLabel": "Name",
"emailLabel": "E-Mail",
"whatNext": "Unser Team prüft neue Großhandelsbewerbungen und aktiviert nach der Genehmigung die Handelspreise. Sobald Ihr Konto aktiviert ist, können Sie sich hier anmelden.",
"support": "Fragen? Kontaktieren Sie wholesale@example.com"
},
"plp": {
"title": "Großhandelskatalog",
"subtitle": "Ihre Handelspreise werden an der Kasse automatisch angewendet.",
"empty": "Im Großhandelskatalog sind noch keine Produkte verfügbar.",
"noMatchingProducts": "Keine Produkte entsprechen \"{query}\".",
"tradePriceNote": "Alle angezeigten Katalogpreise sind Handelspreise und gelten für Bestellungen ab {min} pro Artikel."
},
"pdp": {
"tradePriceHint": "Handelspreis gilt für Bestellungen ab {min}"
},
"cart": {
"title": "Großhandels-Warenkorb",
"emptyDescription": "Fügen Sie Produkte aus dem Katalog hinzu oder nutzen Sie die Schnellbestellung, um zu beginnen.",
"browseCatalog": "Katalog durchsuchen",
"unlockNudge": "{count} weitere hinzufügen, um Handelspreise freizuschalten",
"tradePriceApplied": "Handelspreis angewendet"
},
"quickOrder": {
"title": "Schnellbestellung",
"subtitle": "Geben Sie SKUs und Mengen ein, um mehrere Produkte auf einmal zu Ihrem Warenkorb hinzuzufügen.",
"skuHeader": "SKU",
"qtyHeader": "Menge",
"actionsHeader": "Aktionen",
"skuPlaceholder": "SKU eingeben",
"addRow": "Zeile hinzufügen",
"removeRow": "Zeile entfernen",
"addAll": "Alle zum Warenkorb hinzufügen",
"adding": "Wird hinzugefügt…",
"added": "{name} hinzugefügt",
"skuNotFound": "Kein Produkt für diese SKU gefunden.",
"notPurchasable": "Dieser Artikel ist nicht käuflich verfügbar.",
"addFailed": "Dieser Artikel konnte nicht hinzugefügt werden. Bitte versuchen Sie es erneut.",
"searching": "Suche läuft…",
"noResults": "Keine Produkte gefunden"
},
"apply": {
"title": "Großhandelszugang beantragen",
"description": "Erzählen Sie uns von Ihrem Unternehmen. Wir prüfen Ihre Bewerbung und aktivieren nach der Genehmigung die Handelspreise.",
"companyLabel": "Firmenname",
"companyPlaceholder": "Acme Trading Co.",
"phoneLabel": "Telefon",
"phonePlaceholder": "+1 (555) 000-0000",
"submit": "Bewerbung absenden",
"alreadyMember": "Sie haben bereits ein Großhandelskonto?",
"receivedTitle": "Bewerbung eingegangen",
"receivedDescription": "Vielen Dank für Ihre Bewerbung. Wir prüfen Ihr Konto und geben den Großhandelszugang in Kürze frei.",
"goToPortal": "Zum Portal"
},
"hiddenPrice": {
"signInForPricing": "Zur Preisanzeige anmelden",
"signInToOrder": "Zum Bestellen anmelden"
}
}
}
102 changes: 99 additions & 3 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"viewAllCategory": "View all {category}",
"openSearch": "Open search",
"closeSearch": "Close search",
"myAccount": "My Account"
"myAccount": "My Account",
"wholesale": "Wholesale"
},
"footer": {
"description": "Open-source ecommerce powered by Spree REST API, TypeScript SDK, and Next.js. Self-host it. Own your data. Zero platform fees.",
Expand All @@ -53,7 +54,8 @@
"cart": "Cart",
"policies": "Policies",
"poweredBy": "Powered by",
"copyright": "© {year} {storeName}. Powered by Spree Commerce."
"copyright": "© {year} {storeName}. Powered by Spree Commerce.",
"wholesale": "Trade & Wholesale"
},
"home": {
"welcome": "{storeName} Storefront",
Expand All @@ -68,7 +70,18 @@
"fastShipping": "Fast Shipping",
"shippingDescription": "Quick and reliable delivery to your doorstep.",
"support": "24/7 Support",
"supportDescription": "Our team is here to help you anytime."
"supportDescription": "Our team is here to help you anytime.",
"wholesaleBadge": "Trade & Wholesale",
"wholesaleTitle": "Buying for your business?",
"wholesaleDescription": "Approved trade buyers get wholesale pricing on the full catalog, bulk quick-order by SKU, and their own order history. Apply once — we'll review and activate your account.",
"wholesaleCtaPrimary": "Enter wholesale portal",
"wholesaleCtaSecondary": "Apply for an account",
"wholesaleBenefitPricingTitle": "Trade pricing",
"wholesaleBenefitPricingDescription": "Your negotiated rates applied automatically across the full catalog.",
"wholesaleBenefitQuickOrderTitle": "Bulk quick-order",
"wholesaleBenefitQuickOrderDescription": "Enter SKUs and quantities to build a large order in seconds.",
"wholesaleBenefitOrdersTitle": "Order history",
"wholesaleBenefitOrdersDescription": "Review past orders and reorder your regular lines in a couple of clicks."
},
"cart": {
"cart": "Cart",
Expand Down Expand Up @@ -524,5 +537,88 @@
"shipping": "Shipping",
"finalizingPayment": "Finalizing your payment...",
"or": "or"
},
"wholesale": {
"portalTitle": "Wholesale Portal",
"portalName": "Wholesale Portal",
"badge": "Wholesale",
"nav": {
"catalog": "Catalog",
"quickOrder": "Quick Order",
"cart": "Cart",
"backToStore": "Back to store",
"signOut": "Sign out",
"signIn": "Sign in"
},
"signInWall": {
"title": "Trade pricing for approved buyers",
"description": "Sign in to your wholesale account to browse the full catalog at your negotiated trade pricing and place bulk orders.",
"gatedNotice": "This portal is available to approved wholesale buyers only. Prices and products are visible after you sign in.",
"noAccount": "Don't have a wholesale account?",
"applyLink": "Apply for access",
"formTitle": "Sign in",
"formDescription": "Use the email and password for your wholesale account.",
"submit": "Sign in"
},
"pending": {
"title": "Your application is under review",
"description": "Thanks for applying. Your account is set up, but wholesale access is still pending approval.",
"nameLabel": "Name",
"emailLabel": "Email",
"whatNext": "Our team reviews new wholesale applications and enables trade pricing once approved. You'll be able to sign in here as soon as your account is activated.",
"support": "Questions? Contact wholesale@example.com"
},
"plp": {
"title": "Wholesale Catalog",
"subtitle": "Your trade pricing is applied automatically at checkout.",
"empty": "No products are available on the wholesale catalog yet.",
"noMatchingProducts": "No products match \"{query}\".",
"tradePriceNote": "All catalog prices shown are trade prices, applied on orders of {min}+ per item."
},
"pdp": {
"tradePriceHint": "Trade price applies on orders of {min}+"
},
"cart": {
"title": "Wholesale Cart",
"emptyDescription": "Add products from the catalog or use quick order to get started.",
"browseCatalog": "Browse catalog",
"unlockNudge": "Add {count} more to unlock trade pricing",
"tradePriceApplied": "Trade price applied"
},
"quickOrder": {
"title": "Quick Order",
"subtitle": "Enter SKUs and quantities to add multiple products to your cart at once.",
"skuHeader": "SKU",
"qtyHeader": "Qty",
"actionsHeader": "Actions",
"skuPlaceholder": "Enter a SKU",
"addRow": "Add row",
"removeRow": "Remove row",
"addAll": "Add all to cart",
"adding": "Adding…",
"added": "Added {name}",
"skuNotFound": "No product found for this SKU.",
"notPurchasable": "This item is not available to purchase.",
"addFailed": "Couldn't add this item. Please try again.",
"searching": "Searching…",
"noResults": "No products found"
},
"apply": {
"title": "Apply for wholesale access",
"description": "Tell us about your business. We'll review your application and enable trade pricing once approved.",
"companyLabel": "Company name",
"companyPlaceholder": "Acme Trading Co.",
"phoneLabel": "Phone",
"phonePlaceholder": "+1 (555) 000-0000",
"submit": "Submit application",
"alreadyMember": "Already have a wholesale account?",
"receivedTitle": "Application received",
"receivedDescription": "Thanks for applying. We'll review your account and approve wholesale access shortly.",
"goToPortal": "Go to portal"
},
"hiddenPrice": {
"signInForPricing": "Sign in for pricing",
"signInToOrder": "Sign in to order"
}
}
}
Loading
Loading