Skip to content
Open
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ CS_API_KEY=<STACK_API_KEY>
CS_DELIVERY_TOKEN=<DELIVERY_TOKEN>
CS_ENVIRONMENT=development
CS_REGION=US

# Live Preview (optional) — see "Live Preview" in the README.
CS_LIVE_PREVIEW=false
# CS_PREVIEW_TOKEN=<PREVIEW_TOKEN>
# CS_PREVIEW_HOST=rest-preview.contentstack.com
116 changes: 82 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
# SAP Composable Storefront (Spartacus) + Contentstack — Electronics Demo

A B2C electronics storefront built with **Angular 21** and **SAP Composable Storefront
(Spartacus `221121.15.1`)**, using the
[`@contentstack/contentstack-spartacus-connector`](https://github.com/contentstack/contentstack-spartacus-connector)
to drive its CMS layer from **Contentstack**.

Commerce data (products, pricing, cart, checkout) comes from a SAP Commerce Cloud (OCC) backend;
page content — the homepage, navigation, footer, and marketing slots — comes from Contentstack,
rendered as a hybrid over the OCC base (unauthored pages/slots fall back to OCC automatically).

> **ℹ️ About the Contentstack stack**
> This demo reads its content from a Contentstack stack. To run it against your own stack, you must
> first configure the **content models and entries in Contentstack that correspond to this demo
> site** — the storefront only renders content that already exists in the stack.
>
> **For internal use:** Contentstack developers don't need to build this from scratch — an
> already-configured demo stack exists. **Ask the repo owner for the stack details** (API key +
> delivery token), add them to `.env`, and the demo runs locally with no further setup.
A working SAP Composable Storefront (Spartacus) whose **entire content layer is managed in
Contentstack**. Commerce data — products, pricing, cart, checkout — still comes from the SAP
Commerce Cloud (OCC) backend, but everything an author touches — the homepage, navigation, footer,
and marketing slots — is created and served from Contentstack through the
[`@contentstack/contentstack-spartacus-connector`](https://github.com/contentstack/contentstack-spartacus-connector).

The point of the demo: you can run a complete SAP storefront's content from Contentstack. It renders
as a hybrid over the OCC base, so any page or slot you haven't authored in Contentstack falls back to
SAP automatically — you configure as much or as little of the site as you want.

Built with **Angular 21** and **SAP Composable Storefront (Spartacus `221121.15.1`)**.

## Prerequisites

| Requirement | Notes |
| --- | --- |
| Node.js | `^22.22.0` (older 22.x prints non-fatal `EBADENGINE` warnings) |
| Angular CLI 21 | `npx -p @angular/cli@21 ng ...` — no global install required |
| SAP RBSC registry access | Needed to install `@spartacus/*` packages — see below |
| A Contentstack stack | Delivery token + API key (read-only) |
| SAP RBSC registry access | Needed to install `@spartacus/*` packages — see Step 1 |
| A Contentstack stack | An empty stack to import the demo content into |

### RBSC `.npmrc`
## Step 1 — Install dependencies

`@spartacus/*` packages are served from SAP's RBSC registry, not public npm. Create a `.npmrc` in
the project root:
Expand All @@ -38,26 +32,87 @@ the project root:
legacy-peer-deps=true
```

This file is gitignored — it holds an organization credential and must never be committed.

## Setup
This file is gitignored — it holds an organization credential and must never be committed. Then:

```bash
npm install
```

## Step 2 — Import the demo content into your stack

The demo content (17 content types, 509 entries, 294 assets, 4 locales) ships with the repo as a
Contentstack CLI export at
[`import-electronics-demo-content/`](import-electronics-demo-content/). Import it into an **empty
stack** whose master locale is **English - United States (`en-us`)**:

```bash
npm install -g @contentstack/cli
csdx config:set:region US # or EU | AZURE-NA | ... to match your org
csdx auth:login # dev machine only — provisioning credential
csdx cm:stacks:import \
--stack-api-key <YOUR_STACK_API_KEY> \
--data-dir ./import-electronics-demo-content \
--yes
```

The import also publishes the entries to the `development` environment. Then, in the Contentstack UI,
create a **Delivery token** for `development`. Keep your **stack API key** and the **delivery token**
— you'll put them in `.env` next. (Full details are in the pack's
Comment on lines +58 to +60
[README](import-electronics-demo-content/README.md).)

## Step 3 — Configure and run

```bash
cp .env.example .env
# fill in your Contentstack stack's CS_API_KEY / CS_DELIVERY_TOKEN in .env
# fill in the values from Step 2:
# CS_API_KEY=<YOUR_STACK_API_KEY>
# CS_DELIVERY_TOKEN=<YOUR_DELIVERY_TOKEN>
# CS_ENVIRONMENT=development
# CS_REGION=US # match the region you imported into
npm start
```

Open `http://localhost:4200/` — the storefront now renders content from your stack.

> **⚠️ Accept the backend's self-signed certificate first**
> Commerce data comes from the SAP Commerce Cloud (OCC) backend configured in
> [`src/app/spartacus/spartacus-configuration.module.ts`](src/app/spartacus/spartacus-configuration.module.ts),
> which serves a **self-signed TLS certificate**. Browsers block it by default, so the storefront
> loads as a **blank page** with a `net::ERR_CERT_AUTHORITY_INVALID` error in the console. Fix it
> once per browser: open the backend URL directly — e.g.
> `https://40.76.109.9:9002/occ/v2/electronics-spa/basesites` — click **Advanced → Proceed anyway**
> to accept the certificate, then reload `http://localhost:4200/`.

`npm start` (and `npm run build`) automatically generate
`src/environments/contentstack.environment.ts` from your `.env` via
`scripts/generate-contentstack-env.js` — that generated file is gitignored too, so nothing
credential-bearing ever needs to live in source control. See `.env.example` for all supported
variables.

On a hosting platform, set the same variables (`CS_API_KEY`, `CS_DELIVERY_TOKEN`, `CS_ENVIRONMENT`,
`CS_REGION`, `CS_LIVE_PREVIEW`) as real environment variables instead of a `.env` file — the
generator script reads `process.env` either way.
On a hosting platform, set the same variables as real environment variables instead of a `.env`
file — the generator script reads `process.env` either way.

## Live Preview (optional)

Contentstack [Live Preview](https://www.contentstack.com/docs/developers/set-up-live-preview) lets
authors see edits render in the storefront in real time. To enable it, add these to your `.env`:

```bash
CS_LIVE_PREVIEW=true
CS_PREVIEW_TOKEN=<YOUR_PREVIEW_TOKEN> # Stack → Settings → Tokens → create a Preview token
CS_PREVIEW_HOST=rest-preview.contentstack.com # match your region (see table below)
```

| Region | `CS_PREVIEW_HOST` |
| --- | --- |
| US | `rest-preview.contentstack.com` |
| EU | `eu-rest-preview.contentstack.com` |
| AZURE-NA | `azure-na-rest-preview.contentstack.com` |
| AZURE-EU | `azure-eu-rest-preview.contentstack.com` |

Then restart (`npm start`) and, in the Contentstack UI, go to **Settings → Live Preview**, enable
it, and set the preview URL to `http://localhost:4200`. Open any entry and use the **Live Preview**
pane to see your edits render live.

## Development server

Expand All @@ -74,10 +129,3 @@ ng build
```

Build artifacts are written to `dist/`.

## Notes on credentials

`CS_API_KEY`/`CS_DELIVERY_TOKEN` are **read-only, environment-scoped Contentstack Delivery API
tokens** — safe to ship in the client bundle (they can only read already-published content, not
write or delete). They are kept out of git history for portability/rotation hygiene, not because
they're a runtime secret.
12 changes: 7 additions & 5 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ self-signed cert.
| `contentstack-live-preview.spec.ts` | No `data-cslp` tags leak into the DOM with Live Preview off (current default). Set `E2E_LIVE_PREVIEW_EXPECTED=true` to instead assert tags appear and match the active locale. |
| `contentstack-locale.spec.ts` | Switching language re-fetches Contentstack content for the mapped locale and drops the previous locale's assets. |

**Known gap, not fixed here:** `scripts/generate-contentstack-env.js` never writes a
`previewToken` into `contentstack.environment.ts`, so `CS_LIVE_PREVIEW=true` alone can't
actually turn Live Preview on ([contentstack-client.service.ts](../../../../contentstack-spartacus-connector/src/client/contentstack-client.service.ts)
requires `livePreview` + `previewToken` + dev mode together). The live-preview spec is written
against that reality — it only asserts the on-state when explicitly told to expect it.
**Enabling Live Preview:** `scripts/generate-contentstack-env.js` writes `previewToken` (and
`previewHost`) into `contentstack.environment.ts` when `CS_PREVIEW_TOKEN` / `CS_PREVIEW_HOST` are
set, so `CS_LIVE_PREVIEW=true` + `CS_PREVIEW_TOKEN` turns Live Preview on in a dev build
([contentstack-client.service.ts](../../../../contentstack-spartacus-connector/src/client/contentstack-client.service.ts)
requires `livePreview` + `previewToken` + dev mode together). By default the suite asserts the
off-state; set `E2E_LIVE_PREVIEW_EXPECTED=true` when running against a build with Live Preview on to
assert the tags appear instead.
10 changes: 5 additions & 5 deletions e2e/contentstack-live-preview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ const CSLP_PATTERN = /^[^.]+\.[^.]+\.[a-z]{2}-[a-z]{2}(\..+)?$/;

/**
* Live Preview is force-disabled unless `delivery.livePreview`, a `previewToken`, AND dev
* mode are all true (contentstack-client.service.ts) — and as of this writing the example
* app's env generator (scripts/generate-contentstack-env.js) never writes a previewToken at
* all, so it can't actually be turned on via `.env` yet. Set E2E_LIVE_PREVIEW_EXPECTED=true
* once that's wired up (or when pointing this suite at an app build that has it) to assert
* tags actually appear; otherwise this suite only asserts the (current, correct) off-state.
* mode are all true (contentstack-client.service.ts). The example app's env generator
* (scripts/generate-contentstack-env.js) wires this up from `.env`: set `CS_LIVE_PREVIEW=true`
* plus `CS_PREVIEW_TOKEN` (and `CS_PREVIEW_HOST`) to turn it on. Set E2E_LIVE_PREVIEW_EXPECTED=true
* when running against such a build to assert tags actually appear; otherwise this suite only
* asserts the (default) off-state.
*/
const expectLivePreview = process.env.E2E_LIVE_PREVIEW_EXPECTED === 'true';

Expand Down
67 changes: 67 additions & 0 deletions import-electronics-demo-content/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Electronics Demo Content

A full **Contentstack CLI (`csdx`) export** of the demo stack that backs this storefront.
Import it into an **empty stack of your own** to get the exact same content this demo renders —
then point the app at your stack and run it. No need to ask the repo owner for stack access.

> This is a complete export data-dir (every module folder is present, as `csdx` import requires).
> It contains **no secrets** — only the source stack's public API key and environment URLs. The
> runtime **delivery token** is never part of an export; you create your own after import (below).

## What's inside

| Module | Count | Notes |
| --- | --- | --- |
| Content types | **17** | Page types (`landing_page`, `product_page`, `category_page`, `content_page`), `global_slots` shell, nav (flat + legacy), and marketing components |
| Entries | **509** | Across `en-us` + localized locales |
| Assets | **294** | 147 files + metadata |
| Locales | **4** | `en-us` (master) + `de-de`, `ja-jp`, `zh-cn` (fall back to `en-us`) |
| Environments | **1** | `development` (recreated on import) |

## Import into your own stack

Create an **empty stack** whose master locale is **English - United States (`en-us`)**, then:

```bash
npm install -g @contentstack/cli
csdx config:set:region US # or EU | AZURE-NA | ... to match your org
csdx auth:login # dev machine only — provisioning credential
csdx cm:stacks:import \
--stack-api-key <YOUR_STACK_API_KEY> \
--data-dir ./import-electronics-demo-content \
--yes
```

One command imports the **17 content types**, **4 locales**, **509 entries**, **294 assets**,
and creates the **`development`** environment.

Then, in the Contentstack UI, create a **delivery token** for `development` — this is the only runtime credential the storefront needs.

## Run the storefront against your stack

Copy `.env.example` to `.env` (gitignored) in the repo root and fill in your values:

```bash
CS_API_KEY=<YOUR_STACK_API_KEY>
CS_DELIVERY_TOKEN=<YOUR_DELIVERY_TOKEN> # from the step above
CS_ENVIRONMENT=development
CS_REGION=US # match the region you imported into
CS_LIVE_PREVIEW=false
```

Then:

```bash
npm install
npm start
```

The app's `prestart` hook generates the Contentstack environment config from `.env`, so the
storefront boots reading content from **your** stack — identical to this demo.

## Notes

- **Branches** are not enabled on the source stack (the export notes this — informational, not an
error).
- Locales `ja-jp` / `zh-cn` may be intentionally unlocalized for some entries and fall back to
`en-us` — set `includeFallback: true` on the storefront to exercise fallback everywhere.
1 change: 1 addition & 0 deletions import-electronics-demo-content/assets/assets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"1":"f4fab54c-ee95-4532-9db5-19e392a96d68-assets.json"}

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions import-electronics-demo-content/assets/metadata.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"title":"Category Navigation Component","uid":"category_navigation_component","schema":[{"display_name":"Title","uid":"title","data_type":"text","mandatory":true,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"_default":true,"instruction":"Internal name.","version":3}},{"display_name":"Wrap After","uid":"wrap_after","data_type":"text","mandatory":false,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"instruction":"Optional: wrap the menu after N items.","version":3}},{"display_name":"Navigation Node","uid":"navigation_node","data_type":"reference","reference_to":["nav_node"],"mandatory":false,"multiple":true,"unique":false,"non_localizable":false,"field_metadata":{"ref_multiple":true,"ref_multiple_content_types":true,"instruction":"Root of the navigation tree."}},{"display_name":"SAP UID","uid":"sap_uid","data_type":"text","mandatory":false,"unique":true,"multiple":false,"field_metadata":{"instruction":"Seed idempotency key = SAP component uid.","version":3},"non_localizable":false}],"description":"Header/category navigation. SAP typeCode CategoryNavigationComponent.","options":{"is_page":false,"singleton":false,"sub_title":[],"title":"title"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"title":"Category Navigation (Flat)","uid":"category_navigation_flat","schema":[{"display_name":"Title","uid":"title","data_type":"text","mandatory":true,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"_default":true,"instruction":"Internal name.","version":3}},{"display_name":"Wrap After","uid":"wrap_after","data_type":"number","mandatory":false,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"instruction":"Optional: wrap the menu after N items."}},{"display_name":"All Nodes","uid":"all_nodes","data_type":"reference","reference_to":["nav_node_flat"],"mandatory":false,"unique":false,"multiple":true,"non_localizable":false,"field_metadata":{"ref_multiple":true,"ref_multiple_content_types":true,"instruction":"The flat pool of every node in this menu (all levels). The connector builds the tree from each node's parent_id."}},{"display_name":"SAP UID","uid":"sap_uid","data_type":"text","mandatory":false,"unique":true,"multiple":false,"field_metadata":{"instruction":"Seed idempotency key = SAP component uid.","version":3},"non_localizable":false}],"description":"Header/category navigation (flat model, SAP CategoryNavigationComponent). all_nodes holds every node of the menu; the connector rebuilds the tree from each node's parent_id in one shallow include chain, whatever the depth.","options":{"is_page":false,"singleton":false,"sub_title":[],"title":"title"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"title":"Category Page","uid":"category_page","schema":[{"display_name":"Title","uid":"title","data_type":"text","mandatory":true,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"_default":true,"instruction":"Page title.","version":3}},{"display_name":"URL","uid":"url","data_type":"text","mandatory":false,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"instruction":"Route this page resolves at. Use \"/\" for the homepage.","version":3}},{"display_name":"Page Type","uid":"page_type","data_type":"text","mandatory":false,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"instruction":"SAP page type discriminator (e.g. ContentPage / ProductPage / CategoryPage).","version":3}},{"display_name":"Template","uid":"template","data_type":"text","mandatory":false,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"instruction":"SAP page template. Keep \"ProductListPageTemplate\".","version":3}},{"display_name":"Left Refinements","uid":"product_left_refinements","data_type":"reference","reference_to":["simple_responsive_banner_component","simple_banner_component","product_carousel_component","cms_paragraph_component","cms_link_component","cms_flex_component"],"mandatory":false,"multiple":true,"unique":false,"non_localizable":false,"field_metadata":{"ref_multiple":true,"ref_multiple_content_types":true,"instruction":"Maps to SAP slot ProductLeftRefinements. Leave empty to keep SAP's."}},{"display_name":"Product Grid","uid":"product_grid_slot","data_type":"reference","reference_to":["simple_responsive_banner_component","simple_banner_component","product_carousel_component","cms_paragraph_component","cms_link_component","cms_flex_component"],"mandatory":false,"multiple":true,"unique":false,"non_localizable":false,"field_metadata":{"ref_multiple":true,"ref_multiple_content_types":true,"instruction":"Maps to SAP slot ProductGridSlot. Leave empty to keep SAP's."}},{"display_name":"Product List","uid":"product_list_slot","data_type":"reference","reference_to":["simple_responsive_banner_component","simple_banner_component","product_carousel_component","cms_paragraph_component","cms_link_component","cms_flex_component"],"mandatory":false,"multiple":true,"unique":false,"non_localizable":false,"field_metadata":{"ref_multiple":true,"ref_multiple_content_types":true,"instruction":"Maps to SAP slot ProductListSlot. Leave empty to keep SAP's."}},{"display_name":"Search Results Grid","uid":"search_results_grid_slot","data_type":"reference","reference_to":["simple_responsive_banner_component","simple_banner_component","product_carousel_component","cms_paragraph_component","cms_link_component","cms_flex_component"],"mandatory":false,"multiple":true,"unique":false,"non_localizable":false,"field_metadata":{"ref_multiple":true,"ref_multiple_content_types":true,"instruction":"Maps to SAP slot SearchResultsGridSlot. Leave empty to keep SAP's."}}],"description":"CMS overrides for SAP ProductListPageTemplate (category/search). One shared entry serves every PLP (facets/products hydrate from OCC).","options":{"is_page":false,"singleton":false,"sub_title":["url"],"title":"title"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"title":"CMS Flex Component","uid":"cms_flex_component","schema":[{"display_name":"Title","uid":"title","data_type":"text","mandatory":true,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"_default":true,"instruction":"Internal name.","version":3}},{"display_name":"Flex Type","uid":"flex_type","data_type":"text","mandatory":false,"unique":false,"multiple":false,"non_localizable":false,"field_metadata":{"instruction":"The SAP flexType that selects the Angular component (e.g. ProductIntroComponent, PageTitleComponent).","version":3}},{"display_name":"SAP UID","uid":"sap_uid","data_type":"text","mandatory":false,"unique":true,"multiple":false,"field_metadata":{"instruction":"Seed idempotency key = SAP component uid.","version":3},"non_localizable":false}],"description":"A functional block selected by its flex type (e.g. ProductIntroComponent). SAP typeCode CMSFlexComponent.","options":{"is_page":false,"singleton":false,"sub_title":[],"title":"title"}}
Loading
Loading