Skip to content

Commit 88f2cac

Browse files
Merge pull request #105 from commercelayer/support-private-scopes
Add support for private scopes
2 parents 0067302 + 1f3c513 commit 88f2cac

22 files changed

+1799
-1582
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
"@biomejs/biome": "1.9.4",
3636
"concurrently": "^9.1.2",
3737
"husky": "^9.1.7",
38-
"lerna": "^8.2.1",
39-
"lint-staged": "^15.5.0",
38+
"lerna": "^8.2.2",
39+
"lint-staged": "^15.5.1",
4040
"replace-in-file": "^8.3.0",
41-
"typescript": "^5.8.2"
41+
"typescript": "^5.8.3"
4242
},
4343
"resolutions": {
4444
"prismjs": "^1.30.0"

packages/docs/package.json

+22-22
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,42 @@
1616
"email": "marco.montalbano@commercelayer.io"
1717
},
1818
"devDependencies": {
19-
"@babel/core": "^7.26.10",
20-
"@babel/preset-env": "^7.26.9",
21-
"@babel/preset-react": "^7.26.3",
22-
"@babel/preset-typescript": "^7.27.0",
23-
"@storybook/addon-essentials": "^8.6.11",
24-
"@storybook/addon-interactions": "^8.6.11",
25-
"@storybook/addon-links": "^8.6.11",
19+
"@babel/core": "^7.27.1",
20+
"@babel/preset-env": "^7.27.1",
21+
"@babel/preset-react": "^7.27.1",
22+
"@babel/preset-typescript": "^7.27.1",
23+
"@storybook/addon-essentials": "^8.6.12",
24+
"@storybook/addon-interactions": "^8.6.12",
25+
"@storybook/addon-links": "^8.6.12",
2626
"@storybook/addon-webpack5-compiler-swc": "^3.0.0",
27-
"@storybook/blocks": "^8.6.11",
28-
"@storybook/components": "^8.6.11",
29-
"@storybook/core-common": "^8.6.11",
30-
"@storybook/html": "^8.6.11",
31-
"@storybook/html-webpack5": "^8.6.11",
27+
"@storybook/blocks": "^8.6.12",
28+
"@storybook/components": "^8.6.12",
29+
"@storybook/core-common": "^8.6.12",
30+
"@storybook/html": "^8.6.12",
31+
"@storybook/html-webpack5": "^8.6.12",
3232
"@storybook/icons": "^1.4.0",
33-
"@storybook/manager-api": "^8.6.11",
34-
"@storybook/test": "^8.6.11",
35-
"@storybook/theming": "^8.6.11",
36-
"@storybook/types": "^8.6.11",
33+
"@storybook/manager-api": "^8.6.12",
34+
"@storybook/test": "^8.6.12",
35+
"@storybook/theming": "^8.6.12",
36+
"@storybook/types": "^8.6.12",
3737
"@types/common-tags": "^1.8.4",
3838
"@types/react": "^19",
3939
"@types/react-syntax-highlighter": "^15.5.13",
4040
"babel-loader": "^10.0.0",
4141
"common-tags": "^1.8.2",
4242
"http-server": "^14.1.1",
43-
"nodemon": "^3.1.9",
43+
"nodemon": "^3.1.10",
4444
"react": "^19",
4545
"react-dom": "^19",
4646
"react-syntax-highlighter": "^15.6.1",
4747
"remark-gfm": "^4.0.1",
48-
"sass": "^1.86.0",
49-
"storybook": "^8.6.11",
50-
"typescript": "^5.8.2",
51-
"webpack": "^5.98.0"
48+
"sass": "^1.87.0",
49+
"storybook": "^8.6.12",
50+
"typescript": "^5.8.3",
51+
"webpack": "^5.99.7"
5252
},
5353
"dependencies": {
5454
"@commercelayer/drop-in.js": "workspace:^2.11.2",
55-
"lit-html": "^3.2.1"
55+
"lit-html": "^3.3.0"
5656
}
5757
}

packages/docs/stories/assets/constants.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ export const codes = {
1515

1616
// // stg
1717
// export const createConfig = (scope: string): CommerceLayerConfig => ({
18-
// clientId: 'gQMSINLyMm2TrZo0UGEEdubC7uSgm9-',
18+
// clientId: "gQMSINLyMm2TrZo0UGEEdubC7uSgm9-",
1919
// scope,
20-
// debug: 'all',
21-
// domain: 'commercelayer.co'
20+
// debug: "all",
21+
// domain: "commercelayer.co"
2222
// })
2323

2424
// prd

packages/docs/stories/getting-started.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Assuming you already have a Commerce Layer account ([sign up](https://dashboard.
1414

1515
| Attribute | Type | Required | Description |
1616
|-----------------------------|--------|:--------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
17-
| **`clientId`** | String || Your [sales channel](https://docs.commercelayer.io/core/applications#sales-channel) client ID. You can find it in your application details page on your dashboard. |
18-
| **`scope`** | String || Your sales channel [scope](https://docs.commercelayer.io/core/authentication#authorization-scopes) (use it to restrict the dataset of your application). You can find the allowed scopes in your application details page on your dashboard. |
17+
| **`clientId`** | String || Your [sales channel](https://docs.commercelayer.io/core/api-credentials#sales-channel) client ID. You can find it in your API credential details page on your dashboard. |
18+
| **`scope`** | String || Your sales channel [scope](https://docs.commercelayer.io/core/authentication#authorization-scopes) (use it to restrict the dataset of your application). You can find the allowed scopes in your API credential details page on your dashboard. |
1919
| `debug` | String | | The debug level. Can be one of `none` or `all`. Default is `none`. |
2020
| `storageOrderKeySuffix` | String | | The suffix appended to the storage key for the order. This is especially useful for differentiating carts when the `clientId` and `scope` are the same. When set, the storage key follows this format: `commercelayer_order-id-<clientId>-<scope>-<suffix>`. |
2121
| `defaultAttributes.orders` | Object | | Default [attributes](https://docs.commercelayer.io/core/api-reference/orders/object) when creating an `orders` resource type. See the snippet below for a working example. |
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
1-
import { Canvas, Meta, Story, Controls } from '@storybook/blocks'
1+
import { Canvas, Meta, Story, Controls, Source } from '@storybook/blocks'
22
import * as Stories from './cl-identity-link.stories.ts';
33
import { codes } from '../assets/constants.ts'
4+
import { getSelectedScopeValue } from '../../.storybook/addon-scope-selector/constants'
45

56
<Meta of={Stories} />
67

78
# Identity
89

910
Any Commerce Layer account comes with a hosted version of the [open-source Identity application](https://github.com/commercelayer/mfe-identity) that is automatically enabled. It's a React-based, production-ready implementation that provides signup and login functionalities for a specific organization that developers can integrate with just a single URL link as explained in detail [here](https://github.com/commercelayer/mfe-identity#hosted-version).
1011

11-
Once you have [imported](?path=/docs/getting-started--docs) the drop-in library, you can use the `cl-identity-link` component as an in-place replacement for an `<a>` tag pointing to the signup or login URLs or performing the logout action, based on the value of the `type` attribute, which is required (if missing, the component will not be rendered). You also have the option to specify the `target`, as shown in the code snippet below.
12+
Once you have [imported](?path=/docs/getting-started--docs) the drop-in library, you can use the `cl-identity-link` component as an in-place replacement for an `<a>` tag pointing to the signup or login URLs or performing the logout action, based on the value of the `type` attribute, which is required (if missing, the component will not be rendered). You also have the option to specify the `target`, as shown in the code snippet below, and [override](#private-markets) the original `scope` specified in the drop-in library [configuration](?path=/docs/getting-started--docs).
1213

1314
<Canvas of={Stories.Basic} />
1415

15-
<Controls of={Stories.Basic} />
16+
<Controls of={Stories.Basic} />
17+
18+
## Private markets
19+
20+
When you associate a [customer group](https://docs.commercelayer.io/core/api-reference/customer_groups) with a market, that market becomes private and can be accessed only by the customers belonging to the group. You can use private markets to handle scenarios where you need dedicated price lists, custom shipping methods, or other specific features available for a restricted pool of customers, such as managing B2B deals, B2C loyalty programs, private sales, and more.
21+
22+
To make handling these use cases easier, the `cl-identity-link` accepts a `scope` attribute that allows you to specify the market to be used for the login process. This parameter is optional and, if omitted, the drop-in library [configuration](?path=/docs/getting-started--docs) scope will be used. In the example below, the specified scope identifies a private market so that when customers belonging to the associated customer group log in, they will be redirected to the private market instead of the public one and will be able to see only the products that are available for them, take advantage of dedicated promotions, and more.
23+
24+
<Source language='html' dark code={`
25+
<cl-identity-link
26+
type="login"
27+
target="_parent"
28+
scope="${getSelectedScopeValue()}-employees"
29+
>
30+
Login
31+
</cl-identity-link>
32+
`} />
33+
34+
{/* <Canvas sourceState='hidden' of={Stories.PrivateMarkets} /> */}

packages/docs/stories/identity/cl-identity-link.stories.ts

+74
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { DropInArgs } from "@commercelayer/drop-in.js/dist/custom-elements-args"
22
import type { Meta, StoryFn } from "@storybook/html"
33
import { html, nothing } from "lit-html"
4+
import { getSelectedScopeValue } from "../../.storybook/addon-scope-selector/constants"
45
import { create } from "../../utils"
6+
import { codes } from "../assets/constants"
57

68
type Args = DropInArgs["cl-identity-link"]
79

@@ -17,6 +19,7 @@ const Template: StoryFn<Args> = (args) => {
1719
<cl-identity-link
1820
type=${args.type ?? nothing}
1921
target=${args.target ?? nothing}
22+
scope=${args.scope ?? nothing}
2023
>
2124
Identity link
2225
</cl-identity-link>
@@ -30,3 +33,74 @@ Basic.args = {
3033
}
3134

3235
export const WithoutAttributes = Template.bind({})
36+
37+
export const PrivateMarkets: StoryFn = () => {
38+
return create(
39+
html`
40+
<!-- for demonstration purpose only -->
41+
<script src="https://cdn.tailwindcss.com"></script>
42+
43+
<!-- header -->
44+
<nav class="flex p-6">
45+
<span class="font-semibold text-xl tracking-tight">Employee Store</span>
46+
<div class="flex items-center flex-grow justify-end">
47+
<cl-identity-status type="guest" class="flex gap-2">
48+
<cl-identity-link type="login" target="_parent" scope="${getSelectedScopeValue()}-employees">
49+
Login
50+
</cl-identity-link>
51+
</cl-identity-status>
52+
<cl-identity-status type="customer" class="flex gap-2">
53+
<cl-my-account-link target="_blank">
54+
My Account
55+
</cl-my-account-link>
56+
<cl-identity-link type="logout">
57+
Logout
58+
</cl-identity-link>
59+
</cl-identity-status>
60+
<cl-cart-link target="_blank" aria-label="Cart" class="ml-4">
61+
<svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none">
62+
<path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M23 23H8.725L5.238 3.825A1 1 0 0 0 4.261 3H2M10 28a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM23 28a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/>
63+
<path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7.813 18h15.7a1.988 1.988 0 0 0 1.962-1.637L27 8H6"/>
64+
</svg>
65+
<cl-cart-count hide-when-empty></cl-cart-count>
66+
<cl-cart open-on-add></cl-cart>
67+
</cl-cart-link>
68+
</div>
69+
</nav>
70+
71+
72+
<!-- product list -->
73+
<section class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 text-center">
74+
75+
<div class="flex flex-col">
76+
<div class="h-80 sm:h-64">
77+
<img
78+
src="https://data.commercelayer.app/seed/images/skus/${codes.noDiscount}_FLAT.png"
79+
class="w-full h-full object-center object-contain"
80+
/>
81+
</div>
82+
<h3 class="mt-6 font-medium">White Backpack</h3>
83+
<small class="text-gray-300">${codes.noDiscount}</small>
84+
<cl-price code="${codes.noDiscount}" class="my-2">
85+
<cl-price-amount type="compare-at"></cl-price-amount>
86+
<cl-price-amount type="price"></cl-price-amount>
87+
</cl-price>
88+
<cl-add-to-cart code="${codes.noDiscount}" class="my-2 mx-auto">
89+
Add to cart
90+
</cl-add-to-cart>
91+
<cl-availability code="${codes.noDiscount}" class="my-2 text-sm">
92+
<cl-availability-status type="available" class="text-green-400">available</cl-availability-status>
93+
<cl-availability-status type="unavailable" class="text-red-400">unavailable</cl-availability-status>
94+
<cl-availability-status type="available-with-info" class="block">
95+
ready to be shipped in
96+
<cl-availability-info type="min-days"></cl-availability-info
97+
>-<cl-availability-info type="max-days"></cl-availability-info>
98+
days
99+
</cl-availability-status>
100+
</cl-availability>
101+
</div>
102+
103+
</section>
104+
`,
105+
)
106+
}

packages/drop-in/package.json

+10-10
Original file line numberDiff line numberDiff line change
@@ -47,30 +47,30 @@
4747
"dependencies": {
4848
"@commercelayer/js-auth": "^6.7.2",
4949
"@commercelayer/organization-config": "^2.2.0",
50-
"@commercelayer/sdk": "^6.37.0",
50+
"@commercelayer/sdk": "^6.40.0",
5151
"@types/lodash": "^4.17.16",
5252
"iframe-resizer": "4.3.11",
5353
"js-cookie": "^3.0.5",
5454
"lodash": "^4.17.21",
55-
"type-fest": "^4.38.0"
55+
"type-fest": "^4.40.1"
5656
},
5757
"peerDependencies": {
5858
"@stencil/core": "^4.0.0"
5959
},
6060
"devDependencies": {
61-
"@stencil/core": "^4.28.2",
62-
"@stencil/sass": "^3.2.0",
61+
"@stencil/core": "^4.30.0",
62+
"@stencil/sass": "^3.2.1",
6363
"@types/iframe-resizer": "^4.0.0",
6464
"@types/jest": "^29.5.14",
6565
"@types/js-cookie": "^3.0.6",
66-
"@types/node": "^20.17.24",
66+
"@types/node": "^20.17.32",
6767
"jest": "^29.7.0",
6868
"jest-cli": "^29.7.0",
69-
"nodemon": "^3.1.9",
70-
"puppeteer": "24.4.0",
71-
"sass": "^1.86.0",
72-
"ts-jest": "^29.3.0",
73-
"typescript": "^5.8.2"
69+
"nodemon": "^3.1.10",
70+
"puppeteer": "24.7.2",
71+
"sass": "^1.87.0",
72+
"ts-jest": "^29.3.2",
73+
"typescript": "^5.8.3"
7474
},
7575
"license": "MIT",
7676
"engines": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"reference": "customer_group_1",
4+
"name": "Employees"
5+
},
6+
{
7+
"reference": "customer_group_2",
8+
"name": "VIP Customers"
9+
}
10+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

packages/drop-in/seed/data/markets.json

+30
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
"code": "europe",
99
"subscription_model": "subscription_model_1"
1010
},
11+
{
12+
"reference": "market_1_employees",
13+
"merchant": "merchant_1",
14+
"inventory_model": "inventory_model_1",
15+
"price_list": "price_list_1_employees",
16+
"name": "Europe (employees)",
17+
"code": "europe-employees",
18+
"subscription_model": "subscription_model_1",
19+
"customer_group": "customer_group_1"
20+
},
1121
{
1222
"reference": "market_2",
1323
"merchant": "merchant_1",
@@ -17,6 +27,16 @@
1727
"code": "usa",
1828
"subscription_model": "subscription_model_1"
1929
},
30+
{
31+
"reference": "market_2_employees",
32+
"merchant": "merchant_1",
33+
"inventory_model": "inventory_model_2",
34+
"price_list": "price_list_2_employees",
35+
"name": "USA (employees)",
36+
"code": "usa-employees",
37+
"subscription_model": "subscription_model_1",
38+
"customer_group": "customer_group_1"
39+
},
2040
{
2141
"reference": "market_3",
2242
"merchant": "merchant_1",
@@ -25,5 +45,15 @@
2545
"name": "UK",
2646
"code": "uk",
2747
"subscription_model": "subscription_model_1"
48+
},
49+
{
50+
"reference": "market_3_employees",
51+
"merchant": "merchant_1",
52+
"inventory_model": "inventory_model_1",
53+
"price_list": "price_list_3_employees",
54+
"name": "UK (employees)",
55+
"code": "uk-employees",
56+
"subscription_model": "subscription_model_1",
57+
"customer_group": "customer_group_1"
2858
}
2959
]

packages/drop-in/seed/data/price_lists.json

+16
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,31 @@
44
"name": "EUR Price List",
55
"currency_code": "EUR"
66
},
7+
{
8+
"reference": "price_list_1_employees",
9+
"name": "EUR Price List (employees)",
10+
"currency_code": "EUR"
11+
},
712
{
813
"reference": "price_list_2",
914
"name": "USD Price List",
1015
"currency_code": "USD",
1116
"tax_included": false
1217
},
18+
{
19+
"reference": "price_list_2_employees",
20+
"name": "USD Price List (employees)",
21+
"currency_code": "USD",
22+
"tax_included": false
23+
},
1324
{
1425
"reference": "price_list_3",
1526
"name": "GBP Price List",
1627
"currency_code": "GBP"
28+
},
29+
{
30+
"reference": "price_list_3_employees",
31+
"name": "GBP Price List (employees)",
32+
"currency_code": "GBP"
1733
}
1834
]

0 commit comments

Comments
 (0)