@metron-labs/quote-react 0.1.70 → 0.1.71
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/LICENSE +31 -31
- package/README.md +253 -253
- package/dist/app/stages/BasketStage.js +4 -0
- package/dist/embed/quote.js +6964 -6964
- package/dist/styles.css +6961 -6961
- package/dist/util/pricing.d.ts +3 -1
- package/dist/util/pricing.js +8 -0
- package/package.json +53 -53
package/LICENSE
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
Copyright (c) 2026 Metron Labs Ltd. All rights reserved.
|
|
2
|
-
|
|
3
|
-
This software and its accompanying files, including images and stylesheets (the
|
|
4
|
-
"Software"), are proprietary to Metron Labs Ltd ("Metron Labs").
|
|
5
|
-
|
|
6
|
-
Metron Labs grants you a non-exclusive, non-transferable licence to install the Software and
|
|
7
|
-
include it in websites and applications you operate, solely to provide Metron Labs's services
|
|
8
|
-
to your own customers, for as long as you hold a current agreement with Metron Labs that
|
|
9
|
-
covers the Software. That agreement governs your use of the Software and takes
|
|
10
|
-
precedence over this notice.
|
|
11
|
-
|
|
12
|
-
Except as that agreement expressly permits, you may not:
|
|
13
|
-
|
|
14
|
-
- copy, modify, merge or create derivative works of the Software;
|
|
15
|
-
- distribute, publish, sublicense, sell or otherwise make the Software available to
|
|
16
|
-
any third party, other than as part of your own website or application;
|
|
17
|
-
- use the Software with any service other than Metron Labs'; or
|
|
18
|
-
- reverse engineer the Software, except to the extent the law allows despite this
|
|
19
|
-
restriction.
|
|
20
|
-
|
|
21
|
-
Your licence ends automatically when your agreement with Metron Labs ends. You must then stop
|
|
22
|
-
using the Software and remove it from your websites, applications and systems.
|
|
23
|
-
|
|
24
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
25
|
-
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
26
|
-
PURPOSE AND NON-INFRINGEMENT, EXCEPT AS SET OUT IN YOUR AGREEMENT WITH Metron Labs. TO THE
|
|
27
|
-
EXTENT PERMITTED BY LAW, METRON LABS IS NOT LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY
|
|
28
|
-
ARISING FROM OR IN CONNECTION WITH THE SOFTWARE OR ITS USE, EXCEPT AS SET OUT IN THAT
|
|
29
|
-
AGREEMENT.
|
|
30
|
-
|
|
31
|
-
For licensing questions, contact your Metron Labs account manager.
|
|
1
|
+
Copyright (c) 2026 Metron Labs Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its accompanying files, including images and stylesheets (the
|
|
4
|
+
"Software"), are proprietary to Metron Labs Ltd ("Metron Labs").
|
|
5
|
+
|
|
6
|
+
Metron Labs grants you a non-exclusive, non-transferable licence to install the Software and
|
|
7
|
+
include it in websites and applications you operate, solely to provide Metron Labs's services
|
|
8
|
+
to your own customers, for as long as you hold a current agreement with Metron Labs that
|
|
9
|
+
covers the Software. That agreement governs your use of the Software and takes
|
|
10
|
+
precedence over this notice.
|
|
11
|
+
|
|
12
|
+
Except as that agreement expressly permits, you may not:
|
|
13
|
+
|
|
14
|
+
- copy, modify, merge or create derivative works of the Software;
|
|
15
|
+
- distribute, publish, sublicense, sell or otherwise make the Software available to
|
|
16
|
+
any third party, other than as part of your own website or application;
|
|
17
|
+
- use the Software with any service other than Metron Labs'; or
|
|
18
|
+
- reverse engineer the Software, except to the extent the law allows despite this
|
|
19
|
+
restriction.
|
|
20
|
+
|
|
21
|
+
Your licence ends automatically when your agreement with Metron Labs ends. You must then stop
|
|
22
|
+
using the Software and remove it from your websites, applications and systems.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
25
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
26
|
+
PURPOSE AND NON-INFRINGEMENT, EXCEPT AS SET OUT IN YOUR AGREEMENT WITH Metron Labs. TO THE
|
|
27
|
+
EXTENT PERMITTED BY LAW, METRON LABS IS NOT LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY
|
|
28
|
+
ARISING FROM OR IN CONNECTION WITH THE SOFTWARE OR ITS USE, EXCEPT AS SET OUT IN THAT
|
|
29
|
+
AGREEMENT.
|
|
30
|
+
|
|
31
|
+
For licensing questions, contact your Metron Labs account manager.
|
package/README.md
CHANGED
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
# @metron-labs/quote-react
|
|
2
|
-
|
|
3
|
-
Metron's quoting tools as React components you place in your own website:
|
|
4
|
-
|
|
5
|
-
- **Quick Quote** — your customers upload a schedule, paste a materials list or photograph
|
|
6
|
-
a handwritten one. Metron matches each line to products in your catalogue, then shows
|
|
7
|
-
prices, quantities and a basket they can download as a spreadsheet.
|
|
8
|
-
- **Estimating Tools** — 12 calculators (fencing, decking, brick and block, paint, timber,
|
|
9
|
-
paving and more) that turn a customer's measurements into a priced list of your products.
|
|
10
|
-
|
|
11
|
-
Both widgets call Metron's API straight from the browser, so you don't need your own server.
|
|
12
|
-
|
|
13
|
-
## Before you start
|
|
14
|
-
|
|
15
|
-
Your Metron account manager will give you:
|
|
16
|
-
|
|
17
|
-
| What | Example | Used for |
|
|
18
|
-
| ---------------- | ----------- | -------------------------------------------- |
|
|
19
|
-
| Client code | `ABCD-1234` | Identifies your catalogue. Not secret. |
|
|
20
|
-
| API key | `boq_...` | Authenticates the widgets with Metron's API. |
|
|
21
|
-
| npm access token | `npm_...` | Installing this package, which is private. |
|
|
22
|
-
|
|
23
|
-
Tell Metron every web address the widgets will run on: production, staging and local
|
|
24
|
-
development (for example `http://localhost:3000`). The API key only works from addresses
|
|
25
|
-
registered to your account (see [Security](#security)).
|
|
26
|
-
|
|
27
|
-
## Requirements
|
|
28
|
-
|
|
29
|
-
- React 18.3 or later, or React 19.
|
|
30
|
-
- A bundler that can import images and CSS, such as Vite, Next.js, webpack 5 or Parcel.
|
|
31
|
-
The widget images ship inside the package, and your build serves them like your own.
|
|
32
|
-
|
|
33
|
-
Not using React? Ask Metron for the script-tag version, which works on any website.
|
|
34
|
-
|
|
35
|
-
## Install
|
|
36
|
-
|
|
37
|
-
The package is private, so npm needs your access token. Add a `.npmrc` file to your
|
|
38
|
-
project root:
|
|
39
|
-
|
|
40
|
-
```ini
|
|
41
|
-
@metron-labs:registry=https://registry.npmjs.org/
|
|
42
|
-
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Set `NPM_TOKEN` in your shell and CI, then install:
|
|
46
|
-
|
|
47
|
-
```sh
|
|
48
|
-
npm install @metron-labs/quote-react
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Don't commit the token itself; `${NPM_TOKEN}` reads it from the environment.
|
|
52
|
-
|
|
53
|
-
## Quick start
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
import { QuoteProvider, QuickQuote } from "@metron-labs/quote-react";
|
|
57
|
-
import "@metron-labs/quote-react/styles.css";
|
|
58
|
-
|
|
59
|
-
const quoteConfig = {
|
|
60
|
-
apiBaseUrl: "https://api.metronlabs.ai",
|
|
61
|
-
client: "ABCD-1234",
|
|
62
|
-
apiKey: "boq_...",
|
|
63
|
-
theme: { "--accent": "#0a7d3b" },
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export default function QuotePage() {
|
|
67
|
-
return (
|
|
68
|
-
<QuoteProvider config={quoteConfig}>
|
|
69
|
-
<QuickQuote />
|
|
70
|
-
</QuoteProvider>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
For the calculators, render `<EstimatingTools />` in the same way.
|
|
76
|
-
|
|
77
|
-
Import `styles.css` once, anywhere in your app. Without it the widgets render unstyled.
|
|
78
|
-
|
|
79
|
-
### Both widgets on one site
|
|
80
|
-
|
|
81
|
-
Use one `QuoteProvider` per page, and put every widget on that page inside it:
|
|
82
|
-
|
|
83
|
-
```tsx
|
|
84
|
-
<QuoteProvider config={quoteConfig}>{tab === "quote" ? <QuickQuote hideHeader /> : <EstimatingTools hideHeader />}</QuoteProvider>
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The widgets don't include site navigation. To offer both, link between them from your own
|
|
88
|
-
menu or tabs.
|
|
89
|
-
|
|
90
|
-
### Next.js (App Router)
|
|
91
|
-
|
|
92
|
-
The widgets are client components and already marked `"use client"`, so you can render them
|
|
93
|
-
from a server component page. Import `styles.css` in your root `layout.tsx` or in the page
|
|
94
|
-
itself.
|
|
95
|
-
|
|
96
|
-
### Keeping settings per environment
|
|
97
|
-
|
|
98
|
-
To use different API keys for staging and production, read the config from environment
|
|
99
|
-
variables at build time. For example, with Vite:
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
const quoteConfig = {
|
|
103
|
-
apiBaseUrl: import.meta.env.VITE_METRON_API_URL,
|
|
104
|
-
client: import.meta.env.VITE_METRON_CLIENT,
|
|
105
|
-
apiKey: import.meta.env.VITE_METRON_API_KEY,
|
|
106
|
-
};
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Configuration
|
|
110
|
-
|
|
111
|
-
`QuoteProvider` takes a single `config` prop:
|
|
112
|
-
|
|
113
|
-
| Option | Type | Required | Description |
|
|
114
|
-
| ------------ | ------------------------ | -------- | ------------------------------------------------------- |
|
|
115
|
-
| `apiBaseUrl` | `string` | Yes | Metron's API address, e.g. `https://api.metronlabs.ai`. |
|
|
116
|
-
| `client` | `string` | Yes | Your client code. |
|
|
117
|
-
| `apiKey` | `string` | Yes | Your API key. |
|
|
118
|
-
| `theme` | `Record<string, string>` | No | Colour and style overrides; see [Theming](#theming). |
|
|
119
|
-
| `onEvent` | `(event) => void` | No | Called with each funnel event; see [Tracking](#tracking). |
|
|
120
|
-
| `analytics` | `"all" | "events-only" | "none"` | No | Limits tracking; see [Tracking](#tracking). Default `"all"`. |
|
|
121
|
-
|
|
122
|
-
## Widget props
|
|
123
|
-
|
|
124
|
-
`QuickQuote` and `EstimatingTools` take the same optional props:
|
|
125
|
-
|
|
126
|
-
| Prop | Default | Description |
|
|
127
|
-
| ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
128
|
-
| `hideHeader` | `false` | Hides the widget's own heading, for pages that already show one. |
|
|
129
|
-
| `fullPage` | `false` | Gives the widget its own background and full viewport height. Leave it off to place the widget inside your page layout. |
|
|
130
|
-
|
|
131
|
-
## Theming
|
|
132
|
-
|
|
133
|
-
Pass CSS custom properties in `config.theme` to match your brand. Anything you leave out
|
|
134
|
-
keeps the default shown here.
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
theme: {
|
|
138
|
-
"--accent": "#0a7d3b",
|
|
139
|
-
"--accent-light": "#d3eddd",
|
|
140
|
-
"--accent-dark": "#075e2c",
|
|
141
|
-
"--accent-hover": "#eef8f2",
|
|
142
|
-
"--font-display": '"Your Font", system-ui, sans-serif',
|
|
143
|
-
"--radius": "8px",
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
| Variable | Default | Used for |
|
|
148
|
-
| --------------------------- | ------------------------------------ | ---------------------------------- |
|
|
149
|
-
| `--accent` | `#e8622c` | Buttons, highlights, active states |
|
|
150
|
-
| `--accent-light` | `#fbe3d5` | Tinted accent backgrounds |
|
|
151
|
-
| `--accent-dark` | `#c94f1f` | Accent text and pressed states |
|
|
152
|
-
| `--accent-hover` | `#fff3ec` | Hover backgrounds |
|
|
153
|
-
| `--on-accent` | `#fff` | Text on accent buttons (use a dark colour for light accents) |
|
|
154
|
-
| `--link` | same as `--accent` | Links |
|
|
155
|
-
| `--navy` | `#10213f` | Dark hero panels |
|
|
156
|
-
| `--navy-light` | `#182c52` | Raised areas on dark panels |
|
|
157
|
-
| `--navy-border` | `rgba(255, 255, 255, 0.12)` | Borders on dark panels |
|
|
158
|
-
| `--bg` | `#ffffff` | Cards and raised panels |
|
|
159
|
-
| `--bg2` | `#f9fafb` | Widget background |
|
|
160
|
-
| `--bg3` | `#f3f4f6` | Subtle fills |
|
|
161
|
-
| `--border` | `#e5e7eb` | Borders |
|
|
162
|
-
| `--border-dark` | `#d1d5db` | Stronger borders |
|
|
163
|
-
| `--text` | `#111827` | Main text |
|
|
164
|
-
| `--text-mid` | `#6b7280` | Secondary text |
|
|
165
|
-
| `--text-dim` | `#9ca3af` | Hints and placeholders |
|
|
166
|
-
| `--green` / `--green-light` | `#16a34a` / `#dcfce7` | Matched and success states |
|
|
167
|
-
| `--red` / `--red-light` | `#dc2626` / `#fee2e2` | Errors |
|
|
168
|
-
| `--amber` / `--amber-light` | `#d97706` / `#fef3c7` | Warnings and items to review |
|
|
169
|
-
| `--font-display` | Inter, then the system font | All widget text |
|
|
170
|
-
| `--font-mono` | IBM Plex Mono, then system monospace | Product codes |
|
|
171
|
-
| `--radius` / `--radius-sm` | `0` | Corner rounding |
|
|
172
|
-
| `--shadow` / `--shadow-md` | subtle drop shadows | Cards and popovers |
|
|
173
|
-
|
|
174
|
-
The widgets don't load any fonts. Inter is used only if your page already loads it;
|
|
175
|
-
otherwise they fall back to the system font. Set `--font-display` to match your site.
|
|
176
|
-
|
|
177
|
-
### How the styles interact with your page
|
|
178
|
-
|
|
179
|
-
Every rule in `styles.css` is scoped to the widget, so it won't restyle anything else on
|
|
180
|
-
your page. The scoping only works one way, though: a broad rule on your site, such as one
|
|
181
|
-
that targets every `button` or `input`, can still reach elements inside the widget. If a
|
|
182
|
-
widget looks wrong on your site, check for global element selectors first.
|
|
183
|
-
|
|
184
|
-
## Tracking
|
|
185
|
-
|
|
186
|
-
The widgets report each step a customer takes, so you can see where they drop off. They load
|
|
187
|
-
no tracker and set no cookies; events contain counts and totals only, never customer input.
|
|
188
|
-
Every event has `widget` (`quick-quote` or `estimating-tools`) and `step`:
|
|
189
|
-
|
|
190
|
-
| `step` | When | Extra fields |
|
|
191
|
-
| --- | --- | --- |
|
|
192
|
-
| `viewed` | The widget appears | – |
|
|
193
|
-
| `method_selected` | Customer opens paste or upload | `method`: `paste` or `upload` |
|
|
194
|
-
| `list_submitted` | Customer sends a list to be matched | `method`: `paste`, `upload` or `photo`; `lines` for pasted lists |
|
|
195
|
-
| `results_shown` | Matched products appear | `lines`, `matched`, `unmatched` |
|
|
196
|
-
| `error` | Matching or photo reading failed | `stage`: `matching` or `photo` |
|
|
197
|
-
| `calculator_selected` | Customer opens a calculator | `calculator`, e.g. `fencing` |
|
|
198
|
-
| `estimate_completed` | A calculator produces its materials list | `calculator`, `lines` |
|
|
199
|
-
| `basket_viewed` | The basket opens | `items`, `total` (inc. VAT) |
|
|
200
|
-
| `downloaded` | Customer downloads a spreadsheet | `type`: `schedule` or `basket` |
|
|
201
|
-
| `quote_requested` | A quote request is sent | `items`, `total` (inc. VAT) |
|
|
202
|
-
|
|
203
|
-
Each event goes to:
|
|
204
|
-
|
|
205
|
-
- `config.onEvent`, e.g. `onEvent: (event) => myAnalytics.track(event.step, event)`.
|
|
206
|
-
- A `metron:step` DOM event (the `STEP_EVENT` export) that bubbles from the widget.
|
|
207
|
-
- `window.dataLayer`, when the page already has one (Google Tag Manager), as
|
|
208
|
-
`{ event: "metron_step", widget, step, ... }`.
|
|
209
|
-
|
|
210
|
-
Set `analytics: "events-only"` to skip the `dataLayer`, or `"none"` to send nothing. The
|
|
211
|
-
event types (`WidgetEvent`, `Step`) are exported.
|
|
212
|
-
|
|
213
|
-
## Security
|
|
214
|
-
|
|
215
|
-
The API key is included in your page's JavaScript, so treat it as public. Metron protects
|
|
216
|
-
your account by only accepting the key from the web addresses registered to it. A copied
|
|
217
|
-
key won't work from any other site.
|
|
218
|
-
|
|
219
|
-
- Register each environment's address with Metron before going live. If an address isn't
|
|
220
|
-
registered, the widgets load but their requests are rejected.
|
|
221
|
-
- If you think a key has been misused, ask Metron to issue a new one.
|
|
222
|
-
- The npm access token is different: it's a real secret. Keep it in your CI secrets and
|
|
223
|
-
local environment, never in source code.
|
|
224
|
-
|
|
225
|
-
## Troubleshooting
|
|
226
|
-
|
|
227
|
-
**The widget renders unstyled.** Import `@metron-labs/quote-react/styles.css`.
|
|
228
|
-
|
|
229
|
-
**Requests fail with 401 or a CORS error in the browser console.** The page's address isn't
|
|
230
|
-
registered for your API key. Send Metron the exact origin, including protocol and port
|
|
231
|
-
(e.g. `https://staging.example.com`).
|
|
232
|
-
|
|
233
|
-
**`npm install` fails with 401, 403 or 404.** Check that `.npmrc` is in your project root and
|
|
234
|
-
that `NPM_TOKEN` is set wherever you install, including CI.
|
|
235
|
-
|
|
236
|
-
**The build fails on a `.webp` import.** Your bundler needs to handle image imports. Vite,
|
|
237
|
-
Next.js and Parcel do this automatically. In webpack 5, add an `asset/resource` rule for
|
|
238
|
-
`.webp`.
|
|
239
|
-
|
|
240
|
-
## Versioning
|
|
241
|
-
|
|
242
|
-
Releases follow semantic versioning. While the version is `0.x`, a minor release
|
|
243
|
-
(`0.1` → `0.2`) may include breaking changes, so allow patch updates only
|
|
244
|
-
(`"~0.1.0"`) and upgrade minor versions deliberately.
|
|
245
|
-
|
|
246
|
-
## Support
|
|
247
|
-
|
|
248
|
-
Contact your Metron account manager.
|
|
249
|
-
|
|
250
|
-
## Licence
|
|
251
|
-
|
|
252
|
-
Proprietary. You may use this package only under a current agreement with Metron Labs Ltd;
|
|
253
|
-
see [LICENSE](LICENSE) for the terms.
|
|
1
|
+
# @metron-labs/quote-react
|
|
2
|
+
|
|
3
|
+
Metron's quoting tools as React components you place in your own website:
|
|
4
|
+
|
|
5
|
+
- **Quick Quote** — your customers upload a schedule, paste a materials list or photograph
|
|
6
|
+
a handwritten one. Metron matches each line to products in your catalogue, then shows
|
|
7
|
+
prices, quantities and a basket they can download as a spreadsheet.
|
|
8
|
+
- **Estimating Tools** — 12 calculators (fencing, decking, brick and block, paint, timber,
|
|
9
|
+
paving and more) that turn a customer's measurements into a priced list of your products.
|
|
10
|
+
|
|
11
|
+
Both widgets call Metron's API straight from the browser, so you don't need your own server.
|
|
12
|
+
|
|
13
|
+
## Before you start
|
|
14
|
+
|
|
15
|
+
Your Metron account manager will give you:
|
|
16
|
+
|
|
17
|
+
| What | Example | Used for |
|
|
18
|
+
| ---------------- | ----------- | -------------------------------------------- |
|
|
19
|
+
| Client code | `ABCD-1234` | Identifies your catalogue. Not secret. |
|
|
20
|
+
| API key | `boq_...` | Authenticates the widgets with Metron's API. |
|
|
21
|
+
| npm access token | `npm_...` | Installing this package, which is private. |
|
|
22
|
+
|
|
23
|
+
Tell Metron every web address the widgets will run on: production, staging and local
|
|
24
|
+
development (for example `http://localhost:3000`). The API key only works from addresses
|
|
25
|
+
registered to your account (see [Security](#security)).
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- React 18.3 or later, or React 19.
|
|
30
|
+
- A bundler that can import images and CSS, such as Vite, Next.js, webpack 5 or Parcel.
|
|
31
|
+
The widget images ship inside the package, and your build serves them like your own.
|
|
32
|
+
|
|
33
|
+
Not using React? Ask Metron for the script-tag version, which works on any website.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
The package is private, so npm needs your access token. Add a `.npmrc` file to your
|
|
38
|
+
project root:
|
|
39
|
+
|
|
40
|
+
```ini
|
|
41
|
+
@metron-labs:registry=https://registry.npmjs.org/
|
|
42
|
+
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Set `NPM_TOKEN` in your shell and CI, then install:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm install @metron-labs/quote-react
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Don't commit the token itself; `${NPM_TOKEN}` reads it from the environment.
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { QuoteProvider, QuickQuote } from "@metron-labs/quote-react";
|
|
57
|
+
import "@metron-labs/quote-react/styles.css";
|
|
58
|
+
|
|
59
|
+
const quoteConfig = {
|
|
60
|
+
apiBaseUrl: "https://api.metronlabs.ai",
|
|
61
|
+
client: "ABCD-1234",
|
|
62
|
+
apiKey: "boq_...",
|
|
63
|
+
theme: { "--accent": "#0a7d3b" },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default function QuotePage() {
|
|
67
|
+
return (
|
|
68
|
+
<QuoteProvider config={quoteConfig}>
|
|
69
|
+
<QuickQuote />
|
|
70
|
+
</QuoteProvider>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For the calculators, render `<EstimatingTools />` in the same way.
|
|
76
|
+
|
|
77
|
+
Import `styles.css` once, anywhere in your app. Without it the widgets render unstyled.
|
|
78
|
+
|
|
79
|
+
### Both widgets on one site
|
|
80
|
+
|
|
81
|
+
Use one `QuoteProvider` per page, and put every widget on that page inside it:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<QuoteProvider config={quoteConfig}>{tab === "quote" ? <QuickQuote hideHeader /> : <EstimatingTools hideHeader />}</QuoteProvider>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The widgets don't include site navigation. To offer both, link between them from your own
|
|
88
|
+
menu or tabs.
|
|
89
|
+
|
|
90
|
+
### Next.js (App Router)
|
|
91
|
+
|
|
92
|
+
The widgets are client components and already marked `"use client"`, so you can render them
|
|
93
|
+
from a server component page. Import `styles.css` in your root `layout.tsx` or in the page
|
|
94
|
+
itself.
|
|
95
|
+
|
|
96
|
+
### Keeping settings per environment
|
|
97
|
+
|
|
98
|
+
To use different API keys for staging and production, read the config from environment
|
|
99
|
+
variables at build time. For example, with Vite:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const quoteConfig = {
|
|
103
|
+
apiBaseUrl: import.meta.env.VITE_METRON_API_URL,
|
|
104
|
+
client: import.meta.env.VITE_METRON_CLIENT,
|
|
105
|
+
apiKey: import.meta.env.VITE_METRON_API_KEY,
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
`QuoteProvider` takes a single `config` prop:
|
|
112
|
+
|
|
113
|
+
| Option | Type | Required | Description |
|
|
114
|
+
| ------------ | ------------------------ | -------- | ------------------------------------------------------- |
|
|
115
|
+
| `apiBaseUrl` | `string` | Yes | Metron's API address, e.g. `https://api.metronlabs.ai`. |
|
|
116
|
+
| `client` | `string` | Yes | Your client code. |
|
|
117
|
+
| `apiKey` | `string` | Yes | Your API key. |
|
|
118
|
+
| `theme` | `Record<string, string>` | No | Colour and style overrides; see [Theming](#theming). |
|
|
119
|
+
| `onEvent` | `(event) => void` | No | Called with each funnel event; see [Tracking](#tracking). |
|
|
120
|
+
| `analytics` | `"all" | "events-only" | "none"` | No | Limits tracking; see [Tracking](#tracking). Default `"all"`. |
|
|
121
|
+
|
|
122
|
+
## Widget props
|
|
123
|
+
|
|
124
|
+
`QuickQuote` and `EstimatingTools` take the same optional props:
|
|
125
|
+
|
|
126
|
+
| Prop | Default | Description |
|
|
127
|
+
| ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `hideHeader` | `false` | Hides the widget's own heading, for pages that already show one. |
|
|
129
|
+
| `fullPage` | `false` | Gives the widget its own background and full viewport height. Leave it off to place the widget inside your page layout. |
|
|
130
|
+
|
|
131
|
+
## Theming
|
|
132
|
+
|
|
133
|
+
Pass CSS custom properties in `config.theme` to match your brand. Anything you leave out
|
|
134
|
+
keeps the default shown here.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
theme: {
|
|
138
|
+
"--accent": "#0a7d3b",
|
|
139
|
+
"--accent-light": "#d3eddd",
|
|
140
|
+
"--accent-dark": "#075e2c",
|
|
141
|
+
"--accent-hover": "#eef8f2",
|
|
142
|
+
"--font-display": '"Your Font", system-ui, sans-serif',
|
|
143
|
+
"--radius": "8px",
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Variable | Default | Used for |
|
|
148
|
+
| --------------------------- | ------------------------------------ | ---------------------------------- |
|
|
149
|
+
| `--accent` | `#e8622c` | Buttons, highlights, active states |
|
|
150
|
+
| `--accent-light` | `#fbe3d5` | Tinted accent backgrounds |
|
|
151
|
+
| `--accent-dark` | `#c94f1f` | Accent text and pressed states |
|
|
152
|
+
| `--accent-hover` | `#fff3ec` | Hover backgrounds |
|
|
153
|
+
| `--on-accent` | `#fff` | Text on accent buttons (use a dark colour for light accents) |
|
|
154
|
+
| `--link` | same as `--accent` | Links |
|
|
155
|
+
| `--navy` | `#10213f` | Dark hero panels |
|
|
156
|
+
| `--navy-light` | `#182c52` | Raised areas on dark panels |
|
|
157
|
+
| `--navy-border` | `rgba(255, 255, 255, 0.12)` | Borders on dark panels |
|
|
158
|
+
| `--bg` | `#ffffff` | Cards and raised panels |
|
|
159
|
+
| `--bg2` | `#f9fafb` | Widget background |
|
|
160
|
+
| `--bg3` | `#f3f4f6` | Subtle fills |
|
|
161
|
+
| `--border` | `#e5e7eb` | Borders |
|
|
162
|
+
| `--border-dark` | `#d1d5db` | Stronger borders |
|
|
163
|
+
| `--text` | `#111827` | Main text |
|
|
164
|
+
| `--text-mid` | `#6b7280` | Secondary text |
|
|
165
|
+
| `--text-dim` | `#9ca3af` | Hints and placeholders |
|
|
166
|
+
| `--green` / `--green-light` | `#16a34a` / `#dcfce7` | Matched and success states |
|
|
167
|
+
| `--red` / `--red-light` | `#dc2626` / `#fee2e2` | Errors |
|
|
168
|
+
| `--amber` / `--amber-light` | `#d97706` / `#fef3c7` | Warnings and items to review |
|
|
169
|
+
| `--font-display` | Inter, then the system font | All widget text |
|
|
170
|
+
| `--font-mono` | IBM Plex Mono, then system monospace | Product codes |
|
|
171
|
+
| `--radius` / `--radius-sm` | `0` | Corner rounding |
|
|
172
|
+
| `--shadow` / `--shadow-md` | subtle drop shadows | Cards and popovers |
|
|
173
|
+
|
|
174
|
+
The widgets don't load any fonts. Inter is used only if your page already loads it;
|
|
175
|
+
otherwise they fall back to the system font. Set `--font-display` to match your site.
|
|
176
|
+
|
|
177
|
+
### How the styles interact with your page
|
|
178
|
+
|
|
179
|
+
Every rule in `styles.css` is scoped to the widget, so it won't restyle anything else on
|
|
180
|
+
your page. The scoping only works one way, though: a broad rule on your site, such as one
|
|
181
|
+
that targets every `button` or `input`, can still reach elements inside the widget. If a
|
|
182
|
+
widget looks wrong on your site, check for global element selectors first.
|
|
183
|
+
|
|
184
|
+
## Tracking
|
|
185
|
+
|
|
186
|
+
The widgets report each step a customer takes, so you can see where they drop off. They load
|
|
187
|
+
no tracker and set no cookies; events contain counts and totals only, never customer input.
|
|
188
|
+
Every event has `widget` (`quick-quote` or `estimating-tools`) and `step`:
|
|
189
|
+
|
|
190
|
+
| `step` | When | Extra fields |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| `viewed` | The widget appears | – |
|
|
193
|
+
| `method_selected` | Customer opens paste or upload | `method`: `paste` or `upload` |
|
|
194
|
+
| `list_submitted` | Customer sends a list to be matched | `method`: `paste`, `upload` or `photo`; `lines` for pasted lists |
|
|
195
|
+
| `results_shown` | Matched products appear | `lines`, `matched`, `unmatched` |
|
|
196
|
+
| `error` | Matching or photo reading failed | `stage`: `matching` or `photo` |
|
|
197
|
+
| `calculator_selected` | Customer opens a calculator | `calculator`, e.g. `fencing` |
|
|
198
|
+
| `estimate_completed` | A calculator produces its materials list | `calculator`, `lines` |
|
|
199
|
+
| `basket_viewed` | The basket opens | `items`, `total` (inc. VAT) |
|
|
200
|
+
| `downloaded` | Customer downloads a spreadsheet | `type`: `schedule` or `basket` |
|
|
201
|
+
| `quote_requested` | A quote request is sent | `items`, `total` (inc. VAT) |
|
|
202
|
+
|
|
203
|
+
Each event goes to:
|
|
204
|
+
|
|
205
|
+
- `config.onEvent`, e.g. `onEvent: (event) => myAnalytics.track(event.step, event)`.
|
|
206
|
+
- A `metron:step` DOM event (the `STEP_EVENT` export) that bubbles from the widget.
|
|
207
|
+
- `window.dataLayer`, when the page already has one (Google Tag Manager), as
|
|
208
|
+
`{ event: "metron_step", widget, step, ... }`.
|
|
209
|
+
|
|
210
|
+
Set `analytics: "events-only"` to skip the `dataLayer`, or `"none"` to send nothing. The
|
|
211
|
+
event types (`WidgetEvent`, `Step`) are exported.
|
|
212
|
+
|
|
213
|
+
## Security
|
|
214
|
+
|
|
215
|
+
The API key is included in your page's JavaScript, so treat it as public. Metron protects
|
|
216
|
+
your account by only accepting the key from the web addresses registered to it. A copied
|
|
217
|
+
key won't work from any other site.
|
|
218
|
+
|
|
219
|
+
- Register each environment's address with Metron before going live. If an address isn't
|
|
220
|
+
registered, the widgets load but their requests are rejected.
|
|
221
|
+
- If you think a key has been misused, ask Metron to issue a new one.
|
|
222
|
+
- The npm access token is different: it's a real secret. Keep it in your CI secrets and
|
|
223
|
+
local environment, never in source code.
|
|
224
|
+
|
|
225
|
+
## Troubleshooting
|
|
226
|
+
|
|
227
|
+
**The widget renders unstyled.** Import `@metron-labs/quote-react/styles.css`.
|
|
228
|
+
|
|
229
|
+
**Requests fail with 401 or a CORS error in the browser console.** The page's address isn't
|
|
230
|
+
registered for your API key. Send Metron the exact origin, including protocol and port
|
|
231
|
+
(e.g. `https://staging.example.com`).
|
|
232
|
+
|
|
233
|
+
**`npm install` fails with 401, 403 or 404.** Check that `.npmrc` is in your project root and
|
|
234
|
+
that `NPM_TOKEN` is set wherever you install, including CI.
|
|
235
|
+
|
|
236
|
+
**The build fails on a `.webp` import.** Your bundler needs to handle image imports. Vite,
|
|
237
|
+
Next.js and Parcel do this automatically. In webpack 5, add an `asset/resource` rule for
|
|
238
|
+
`.webp`.
|
|
239
|
+
|
|
240
|
+
## Versioning
|
|
241
|
+
|
|
242
|
+
Releases follow semantic versioning. While the version is `0.x`, a minor release
|
|
243
|
+
(`0.1` → `0.2`) may include breaking changes, so allow patch updates only
|
|
244
|
+
(`"~0.1.0"`) and upgrade minor versions deliberately.
|
|
245
|
+
|
|
246
|
+
## Support
|
|
247
|
+
|
|
248
|
+
Contact your Metron account manager.
|
|
249
|
+
|
|
250
|
+
## Licence
|
|
251
|
+
|
|
252
|
+
Proprietary. You may use this package only under a current agreement with Metron Labs Ltd;
|
|
253
|
+
see [LICENSE](LICENSE) for the terms.
|
|
@@ -23,6 +23,10 @@ const orderSummary = (item, pricing) => {
|
|
|
23
23
|
if (conversion && conversion.quantityPerSaleUnit < 1) {
|
|
24
24
|
return `${requested} requested → order ${order} (${Math.round(1 / conversion.quantityPerSaleUnit)} ${unitDisplayText(pricing.sellUnit, 2)} per ${unitDisplayText(pricing.requestedUnit, 1)})`;
|
|
25
25
|
}
|
|
26
|
+
const bulkBagKg = pricing.sellUnit === "bulk bag" ? item.description.match(/(\d+(?:\.\d+)?)\s*kg\b/i) : null;
|
|
27
|
+
if (bulkBagKg) {
|
|
28
|
+
return `${requested} requested → order ${order} (${bulkBagKg[1]} kg each)`;
|
|
29
|
+
}
|
|
26
30
|
if (item.coverageQty && /^m per /i.test(item.coverageUom ?? "")) {
|
|
27
31
|
return `${requested} requested → order ${order} (${item.coverageQty} ${item.coverageUom})`;
|
|
28
32
|
}
|