Skip to content

Commit

Permalink
fix(pricing): feedback Pierre & Mathieu (fixup later)
Browse files Browse the repository at this point in the history
  • Loading branch information
florian-sanders-cc committed Nov 29, 2024
1 parent befccae commit af21fd9
Show file tree
Hide file tree
Showing 19 changed files with 4,540 additions and 4,598 deletions.
2 changes: 1 addition & 1 deletion demo-smart/cc-pricing-page/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
</head>
<body>
<cc-smart-container
context='{ "zoneId": "par","addonFeatures":["cpu","memory","max-db-size","disk-size","connection-limit","version","databases","has-logs","has-metrics"] }'
context='{ "apiConfig": {"API_HOST": "https://api.clever-cloud.com"},"addonFeatures":["cpu","memory","max-db-size","disk-size","connection-limit","version","databases","has-logs","has-metrics"] }'
>
<cc-pricing-page>
<div class="header">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export class CcPricingEstimation extends LitElement {
* @return {number} the total price for the given plan
*/
_getTotalPlanPrice(plan) {
if (this.state.type === 'loading' || this.state.type === 'error') {
if (this.state.type === 'loading') {
return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import './cc-pricing-estimation.js';
defineSmartComponent({
selector: 'cc-pricing-estimation',
params: {
apiConfig: { type: Object, optional: true },
apiConfig: { type: Object },
zoneId: { type: String, optional: true },
currency: { type: String, optional: true },
},
/**
* @param {Object} settings
* @param {CcPricingEstimation} settings.component
* @param {{apiConfig?: ApiConfig, zoneId?: string, currency?: string }} settings.context
* @param {{apiConfig: ApiConfig, zoneId?: string, currency?: string }} settings.context
* @param {(type: string, listener: (detail: any) => void) => void} settings.onEvent
* @param {function} settings.updateComponent
* @param {AbortSignal} settings.signal
Expand All @@ -30,13 +30,12 @@ defineSmartComponent({
const { apiConfig, zoneId = 'par', currency = 'EUR' } = context;

/**
* This `cc-smart-container` is placed around the whole `cc-pricing-page` component.
* Within the `cc-pricing-page`, every `cc-pricing-product` component is placed inside a distinct `cc-smart-container`.
*
* This smart component targets `cc-pricing-estimation` but when `currency` changes, we want to trigger
* a new fetch from all pricing product smart.
* To do so, this smart component modifies its own context.
* To do so, this smart component modifies the global `<cc-smart-container>` context wrapping all pricing components.
* Since all pricing product smart share this context and watch for `currency` changes, it triggers new fetches.
*
* For more info, refer to the `Smart` docs about the `cc-pricing-estimation` component in the `Notes` section.
*/
onEvent(
'cc-pricing-estimation:change-currency',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ title: '💡 Smart'

| Name | Type | Required | Details | Default |
|-------------|:-----------:|:--------:|----------------------------------------------------------------------------------|------------------------------------------------|
| `apiConfig` | `ApiConfig` | No | Object with API configuration (target host) | `{ API_HOST: "https://api.clever-cloud.com" }` |
| `apiConfig` | `ApiConfig` | Yes | Object with API configuration (target host) | `{ API_HOST: "https://api.clever-cloud.com" }` |
| `zoneId` | `string` | No | Name from [`/v4/products/zones`](https://api.clever-cloud.com/v4/products/zones) | `par` |
| `currency` | `string` | No | Currency code for pricing | `EUR` |

Expand All @@ -40,14 +40,41 @@ title: '💡 Smart'

## 📄 Notes

This smart component is designed to work within a `cc-pricing-page` component with at least a `cc-pricing-product` or `cc-pricing-product-consumption` component.
When the `zoneId` or `currency` changes, the smart component fetches pricing data (price system) and formats it so it can be consumed by the `cc-pricing-estimation` component.
Pricing components rely on nested `cc-smart-container` components.

It handles both runtime prices which are fixed prices, and countable prices which depend on different consumption metrics.
1. There is a global `<cc-smart-container>` wrapping all pricing components to provide shared context,
2. There are individual `<cc-smart-container>` wrapping each `<cc-pricing-product>` & `<cc-pricing-product-consumption>` component slotted within the `<cc-pricing-page>` to provide context specific to each product.

Events listed below allow the smart component to trigger a new price system fetch in other pricing components.
The new fetch is triggered by changing the context of their common smart container when the zone or currency changes.
The typical HTML for a basic `cc-pricing-page` implementation looks like this:
```html
<cc-smart-container context='{
"zoneId": "mtl",
"currency": "USD"
}'>
<cc-pricing-page>
<cc-pricing-header currencies="..." temporalities="..."></cc-pricing-header>
<cc-smart-container context="{ productId: 'php'}">
<cc-pricing-product mode="runtime" action="add"></cc-pricing-product>
</cc-smart-container>

<cc-smart-container context="{ productId: 'redis-addon'}">
<cc-pricing-product mode="addon" action="add"></cc-pricing-product>
</cc-smart-container>
<cc-pricing-estimation currencies="..." temporalities="..."></cc-pricing-estimation>
</cc-pricing-page>
</cc-smart-container>
```

`zoneId` and `currency` need to be shared between all pricing components because they drive the displayed prices.
When `zoneId` or `currency` changes, we need all pricing components that show prices to fetch the `priceSystem` (list of prices by product) corresponding to the selected zone and selected currency.
Components showing prices are:

- `cc-pricing-product`,
- `cc-pricing-product-consumption`,
- `cc-pricing-estimation`.

This is why when `zoneId` or `currency` is changed by selecting a different value in either `cc-pricing-header` or `cc-pricing-estimation` dropdowns, the global `cc-smart-container` context is updated.

Since all `cc-smart-container` inherit the context of their parent, every `cc-smart-container` wrapping a `cc-pricing-product` or `cc-pricing-product-consumption` retrieve the `zoneId` / `currency` and fetch the corresponding `priceSystem`.

Events:
- `cc-pricing-estimation:change-zone`: Updates the `zoneId` in the context.
- `cc-pricing-estimation:change-currency`: Updates the `currency` in the context.
Of course, the `priceSystem` API call is cached so that we don't actually trigger redundant calls.
20 changes: 9 additions & 11 deletions src/components/cc-pricing-header/cc-pricing-header.smart.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import './cc-pricing-header.js';
defineSmartComponent({
selector: 'cc-pricing-header',
params: {
apiConfig: { type: Object, optional: true },
apiConfig: { type: Object },
zoneId: { type: String, optional: true },
},
/**
* @param {Object} settings
* @param {CcPricingHeader} settings.component
* @param {{apiConfig?: ApiConfig, zoneId?: string }} settings.context
* @param {{apiConfig: ApiConfig, zoneId?: string }} settings.context
* @param {(type: string, listener: (detail: any) => void) => void} settings.onEvent
* @param {function} settings.updateComponent
* @param {AbortSignal} settings.signal
Expand All @@ -33,13 +33,12 @@ defineSmartComponent({
const { apiConfig, zoneId = 'par' } = context;

/**
* This `cc-smart-container` is placed around the whole `cc-pricing-page` component.
* Within the `cc-pricing-page`, every `cc-pricing-product` component is placed inside a distinct `cc-smart-container`.
*
* This smart component targets `cc-pricing-header` but when `zoneId` changes, we want to trigger
* a new fetch from all pricing product smart.
* To do so, this smart component modifies its own context.
* Since all pricing product smart share this context and watch for `zoneId` changes, it triggers new fetches.
* To do so, this smart component modifies the global `<cc-smart-container>` context wrapping all pricing components.
* Since all pricing product smart share this context and watch for `currency` changes, it triggers new fetches.
*
* For more info, refer to the `Smart` docs about the `cc-pricing-header` component in the `Notes` section.
*/
onEvent(
'cc-pricing-header:change-zone',
Expand All @@ -50,13 +49,12 @@ defineSmartComponent({
);

/**
* This `cc-smart-container` is placed around the whole `cc-pricing-page` component.
* Within the `cc-pricing-page`, every `cc-pricing-product` component is placed inside a distinct `cc-smart-container`.
*
* This smart component targets `cc-pricing-header` but when `currency` changes, we want to trigger
* a new fetch from all pricing product smart.
* To do so, this smart component modifies its own context.
* To do so, this smart component modifies the global `<cc-smart-container>` context wrapping all pricing components.
* Since all pricing product smart share this context and watch for `currency` changes, it triggers new fetches.
*
* For more info, refer to the `Smart` docs about the `cc-pricing-header` component in the `Notes` section.
*/
onEvent(
'cc-pricing-header:change-currency',
Expand Down
43 changes: 42 additions & 1 deletion src/components/cc-pricing-header/cc-pricing-header.smart.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ title: '💡 Smart'

| Name | Type | Required | Details | Default |
|-------------|:-----------:|:--------:|----------------------------------------------------------------------------------|------------------------------------------------|
| `apiConfig` | `ApiConfig` | No | Object with API configuration (target host) | `{ API_HOST: "https://api.clever-cloud.com" }` |
| `apiConfig` | `ApiConfig` | Yes | Object with API configuration (target host) | `{ API_HOST: "https://api.clever-cloud.com" }` |
| `zoneId` | `string` | No | Name from [`/v4/products/zones`](https://api.clever-cloud.com/v4/products/zones) | `par` |

## 🌐 API endpoints
Expand All @@ -35,3 +35,44 @@ title: '💡 Smart'
<cc-pricing-header></cc-pricing-header>
</cc-smart-container>
```

## 📄 Notes

Pricing components rely on nested `cc-smart-container` components.

1. There is a global `<cc-smart-container>` wrapping all pricing components to provide shared context,
2. There are individual `<cc-smart-container>` wrapping each `<cc-pricing-product>` & `<cc-pricing-product-consumption>` component slotted within the `<cc-pricing-page>` to provide context specific to each product.

The typical HTML for a basic `cc-pricing-page` implementation looks like this:
```html
<cc-smart-container context='{
"zoneId": "mtl",
"currency": "USD"
}'>
<cc-pricing-page>
<cc-pricing-header currencies="..." temporalities="..."></cc-pricing-header>
<cc-smart-container context="{ productId: 'php'}">
<cc-pricing-product mode="runtime" action="add"></cc-pricing-product>
</cc-smart-container>

<cc-smart-container context="{ productId: 'redis-addon'}">
<cc-pricing-product mode="addon" action="add"></cc-pricing-product>
</cc-smart-container>
<cc-pricing-estimation currencies="..." temporalities="..."></cc-pricing-estimation>
</cc-pricing-page>
</cc-smart-container>
```

`zoneId` and `currency` need to be shared between all pricing components because they drive the displayed prices.
When `zoneId` or `currency` changes, we need all pricing components that show prices to fetch the `priceSystem` (list of prices by product) corresponding to the selected zone and selected currency.
Components showing prices are:

- `cc-pricing-product`,
- `cc-pricing-product-consumption`,
- `cc-pricing-estimation`.

This is why when `zoneId` or `currency` is changed by selecting a different value in either `cc-pricing-header` or `cc-pricing-estimation` dropdowns, the global `cc-smart-container` context is updated.

Since all `cc-smart-container` inherit the context of their parent, every `cc-smart-container` wrapping a `cc-pricing-product` or `cc-pricing-product-consumption` retrieve the `zoneId` / `currency` and fetch the corresponding `priceSystem`.

Of course, the `priceSystem` API call is cached so that we don't actually trigger redundant calls.
2 changes: 2 additions & 0 deletions src/components/cc-pricing-page/cc-pricing-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const DEFAULT_TEMPORALITY = { type: '30-days', digits: 2 };
* To do so, the component relies on a mutation observer that detects slotted pricing components and keeps refs of the corresponding DOM elements.
* It listens to pricing related events and updates the relevant pricing DOM elements depending on the event.
*
* Make sure to `Smart` docs for the `cc-pricing-header` / `cc-pricing-estimation` components to understand how all of this work together with smart components.
*
* @cssdisplay block
*
* @slot - Use this slot to insert your pricing components and their related content (headings, descriptions, etc.)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class CcPricingProductConsumption extends LitElement {
/** @type {ActionType} Sets the type of action: "add" to display the "add" button for the product and "none" for no actions (defaults to "add") */
this.action = 'add';

/** @type {string} Sets the currency used to display the prices (defaults to euros) */
/** @type {string} Sets the currency used to display the prices (defaults to `EUR`) */
this.currency = CURRENCY_EUR;

/** @type {PricingProductConsumptionState} Sets the state of the product */
Expand Down Expand Up @@ -553,10 +553,9 @@ export class CcPricingProductConsumption extends LitElement {
}
/*
* these elements could be removed but they help the readability of the whole template
* in source code and browser devtools
* The correspondig HTML elements could be removed but they make it easier to understand how things are grouped and what they represent
* when inspecting the source code in your editor or the browser devtools
*/
.section,
.section-header,
.interval-line {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import './cc-pricing-product-consumption.js';
defineSmartComponent({
selector: 'cc-pricing-product-consumption',
params: {
apiConfig: { type: Object, optional: true },
apiConfig: { type: Object },
productId: { type: String },
zoneId: { type: String, optional: true },
currency: { type: String, optional: true },
},
/**
* @param {Object} settings
* @param {CcPricingProductConsumption} settings.component
* @param {{ apiConfig?: ApiConfig, productId: string, zoneId?: string, currency?: string }} settings.context
* @param {{ apiConfig: ApiConfig, productId: string, zoneId?: string, currency?: string }} settings.context
* @param {(type: string, listener: (detail: any) => void) => void} settings.onEvent
* @param {function} settings.updateComponent
* @param {AbortSignal} settings.signal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ title: '💡 Smart'

| Name | Type | Required | Details | Default |
|-------------|-------------|----------|--------------------------------------------------------------------------------------------------|-------------------------------------------------|
| `apiConfig` | `ApiConfig` | No | Object with API configuration (target host) | `{ API_HOST: "https://api.clever-cloud.com" }` |
| `apiConfig` | `ApiConfig` | Yes | Object with API configuration (target host) | `{ API_HOST: "https://api.clever-cloud.com" }` |
| `currency` | `string` | No | Currency code matching the ISO 4217 format | `EUR` |
| `productId` | `string` | Yes | `cellar`, `fsbucket`, `heptapod`, or `pulsar` | |
| `zoneId` | `string` | No | Name from [`/v4/products/zones`](https://api.clever-cloud.com/v4/products/zones) | `par` |
Expand Down
2 changes: 1 addition & 1 deletion src/components/cc-pricing-product/cc-pricing-product.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class CcPricingProduct extends LitElement {
/** @type {ActionType} Sets the type of action: "add" to display add buttons for each plan and "none" for no actions (defaults to "add"). */
this.action = 'add';

/** @type {string} Sets the currency used to display the prices (defaults to euros). */
/** @type {string} Sets the currency used to display the prices (defaults to `EUR`). */
this.currency = CURRENCY_EUR;

/** @type {PricingProductState} Sets the state of the pricing product component. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import './cc-pricing-product.js';
defineSmartComponent({
selector: 'cc-pricing-product[mode="addon"]',
params: {
apiConfig: { type: Object, optional: true },
apiConfig: { type: Object },
addonFeatures: { type: Array, optional: true },
productId: { type: String },
zoneId: { type: String, optional: true },
Expand All @@ -31,7 +31,7 @@ defineSmartComponent({
/**
* @param {Object} settings
* @param {CcPricingProduct} settings.component
* @param {{apiConfig?: ApiConfig, productId: string, zoneId?: string, currency?: string, addonFeatures?: Array<FormattedFeature['code']> }} settings.context
* @param {{apiConfig: ApiConfig, productId: string, zoneId?: string, currency?: string, addonFeatures?: Array<FormattedFeature['code']> }} settings.context
* @param {(type: string, listener: (detail: any) => void) => void} settings.onEvent
* @param {Function} settings.updateComponent
* @param {AbortSignal} settings.signal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ title: '💡 Smart (add-on)'

| Name | Type | Required | Details | Default |
|-----------------|----------------------|----------|--------------------------------------------------------------------------------------------------|------------------------------------------------|
| `addonFeatures` | `Array<FeatureCode>` | No | List of features used to filter and sort displayed features | |
| `addonFeatures` | `Array<FeatureCode>` | Yes | List of features used to filter and sort displayed features | |
| `apiConfig` | `ApiConfig` | No | Object with API configuration (target host) | `{ API_HOST: "https://api.clever-cloud.com" }` |
| `currency` | `string` | No | Currency code matching the ISO 4217 format | `EUR` |
| `productId` | `string` | Yes | id from [`/v2/products/addonproviders`](https://api.clever-cloud.com/v2/products/addonproviders) | |
Expand Down
Loading

0 comments on commit af21fd9

Please sign in to comment.