@scayle/storefront-nuxt 7.44.0 → 7.45.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @scayle/storefront-nuxt
2
2
 
3
+ ## 7.45.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Migrate currency formatters to a composable
8
+
9
+ Because the currency formatter functions depend on the `useCurrentShop` composable, they must also be initialized through a composable. Additionally, because `formatPrice` was the same as `toCurrency` except that it excluded the symbol, the two functions have been merged into `formatCurrency`. If you don't want the currency symbol to be displayed, set the `style` option to `decimal`. These changes requires a small migration step.
10
+
11
+ Before:
12
+
13
+ ```typescript
14
+ toCurrency(100, { currency: 'EUR' })
15
+ formatPrice(100, { currencyFractionDigits: 2 })
16
+ ```
17
+
18
+ After:
19
+
20
+ ```typescript
21
+ const { formatCurrency } = useFormatHelpers()
22
+ formatCurrency(100, { currency: 'EUR' })
23
+ formatCurrency(100, { style: 'decimal', currencyFractionDigits: 2 })
24
+ ```
25
+
26
+ ## 7.44.1
27
+
28
+ ### Patch Changes
29
+
30
+ - The api/up route should not be bootstrapped with the context
31
+
3
32
  ## 7.44.0
4
33
 
5
34
  ### Minor Changes
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.7.0"
6
6
  },
7
- "version": "7.43.1"
7
+ "version": "7.44.1"
8
8
  }
@@ -0,0 +1,14 @@
1
+ type FormatCurrencyOptions = {
2
+ locale: string;
3
+ currencyFractionDigits?: number;
4
+ style?: 'currency';
5
+ currency: string;
6
+ } | {
7
+ locale: string;
8
+ currencyFractionDigits?: number;
9
+ style: 'decimal';
10
+ };
11
+ export default function useFormatHelpers(): {
12
+ formatCurrency: (value: number, options?: FormatCurrencyOptions) => string;
13
+ };
14
+ export {};
@@ -0,0 +1,27 @@
1
+ import { getDefaultFractionDigits } from "@scayle/storefront-core";
2
+ import { useCurrentShop } from "./useCurrentShop.mjs";
3
+ export default function useFormatHelpers() {
4
+ const currentShop = useCurrentShop();
5
+ const formatCurrency = (value, options) => {
6
+ const {
7
+ locale = currentShop.value.locale,
8
+ currencyFractionDigits = currentShop.value.currencyFractionDigits
9
+ } = options || {};
10
+ const currency = options?.style !== "decimal" ? options?.currency ?? currentShop.value.currency : void 0;
11
+ const minimumFractionDigits = options?.style !== "decimal" ? getDefaultFractionDigits(
12
+ locale,
13
+ options?.currency ?? currentShop.value.currency
14
+ ) : currencyFractionDigits;
15
+ return (value / 100).toLocaleString(locale, {
16
+ style: options?.style ?? "currency",
17
+ ...currencyFractionDigits && {
18
+ minimumFractionDigits: minimumFractionDigits !== void 0 ? Math.min(minimumFractionDigits, currencyFractionDigits) : currencyFractionDigits
19
+ },
20
+ maximumFractionDigits: currencyFractionDigits,
21
+ currency
22
+ });
23
+ };
24
+ return {
25
+ formatCurrency
26
+ };
27
+ }
@@ -125,11 +125,11 @@ export default defineEventHandler(async (event) => {
125
125
  }
126
126
  const config = useRuntimeConfig();
127
127
  const $storefrontConfig = config.storefront;
128
- const $shopConfig = getShopConfig(event, config);
129
- const apiBasePath = ($storefrontConfig.shopSelector === "path" && $shopConfig.path ? `/${$shopConfig.path}` : "") + ($shopConfig.apiBasePath || $storefrontConfig.apiBasePath || "/api");
130
- if (url.pathname === `${apiBasePath}/up`) {
128
+ if (url.pathname === `${$storefrontConfig.apiBasePath ?? "/api"}/up`) {
131
129
  return;
132
130
  }
131
+ const $shopConfig = getShopConfig(event, config);
132
+ const apiBasePath = ($storefrontConfig.shopSelector === "path" && $shopConfig.path ? `/${$shopConfig.path}` : "") + ($shopConfig.apiBasePath || $storefrontConfig.apiBasePath || "/api");
133
133
  const log = createLog(
134
134
  createConsola,
135
135
  $storefrontConfig.log.name,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@scayle/storefront-nuxt",
3
3
  "type": "module",
4
- "version": "7.44.0",
4
+ "version": "7.45.0",
5
5
  "description": "Nuxt integration for the SCAYLE Commerce Engine and Storefront API",
6
6
  "author": "SCAYLE Commerce Engine",
7
7
  "license": "MIT",
@@ -85,8 +85,8 @@
85
85
  "@nuxt/test-utils": "3.8.1",
86
86
  "@scayle/eslint-config-storefront": "3.2.6",
87
87
  "@scayle/prettier-config-storefront": "2.0.2",
88
- "@types/node": "20.10.4",
89
- "eslint": "8.55.0",
88
+ "@types/node": "20.10.5",
89
+ "eslint": "8.56.0",
90
90
  "eslint-formatter-gitlab": "5.1.0",
91
91
  "node-mocks-http": "1.14.0",
92
92
  "nuxt": "3.8.2",
@@ -1,6 +0,0 @@
1
- declare module '../../../module' {
2
- interface AdditionalShopConfig {
3
- paymentProviders: string[];
4
- }
5
- }
6
- export {};
@@ -1,277 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { createRequest, createResponse } from "node-mocks-http";
3
- import { createEvent } from "h3";
4
- import { getCurrentShopForRequest, getPublicShopData } from "./bootstrap.utils.mjs";
5
- const DOMAIN_DE = "de.scayle.example";
6
- const DOMAIN_AT = "at.scayle.example";
7
- const DOMAIN_US = "us.scayle.example";
8
- const CHECKOUT_HOST = "https://checkout.scayle.cloud";
9
- const baseShop = {
10
- appKeys: {
11
- wishlistKey: "wishlist_{shopId}_{userId}",
12
- basketKey: "basket_{shopId}_{userId}",
13
- hashAlgorithm: "sha256"
14
- },
15
- auth: {
16
- resetPasswordUrl: ""
17
- },
18
- domain: "demo.test",
19
- paymentProviders: [],
20
- checkout: {
21
- host: CHECKOUT_HOST,
22
- token: "",
23
- secret: "",
24
- user: ""
25
- }
26
- };
27
- describe("getCurrentShopForRequest", () => {
28
- it("respects the x-shop-id header", () => {
29
- const req = createRequest({
30
- url: "/api/getProducts",
31
- headers: {
32
- "x-shop-id": "1002"
33
- }
34
- });
35
- const moduleOptions = {
36
- shopSelector: "domain",
37
- stores: {
38
- DE_DE: {
39
- ...baseShop,
40
- shopId: 1001,
41
- domain: DOMAIN_DE,
42
- locale: "de-DE",
43
- currency: "EUR"
44
- },
45
- DE_AT: {
46
- ...baseShop,
47
- shopId: 1002,
48
- domain: DOMAIN_AT,
49
- locale: "de-AT",
50
- currency: "EUR"
51
- },
52
- DE_US: {
53
- ...baseShop,
54
- shopId: 1003,
55
- domain: DOMAIN_US,
56
- locale: "en-US",
57
- currency: "USD"
58
- }
59
- }
60
- };
61
- const shop = getCurrentShopForRequest(
62
- createEvent(req, createResponse()),
63
- moduleOptions
64
- );
65
- expect(shop.shopId).to.eq(1002);
66
- });
67
- it("falls back to the path when shopSelector = path", () => {
68
- const moduleOptions = {
69
- shopSelector: "path",
70
- stores: {
71
- DE_DE: {
72
- ...baseShop,
73
- shopId: 1001,
74
- path: "de",
75
- locale: "de-DE",
76
- currency: "EUR"
77
- },
78
- DE_AT: {
79
- ...baseShop,
80
- shopId: 1002,
81
- path: "at",
82
- locale: "de-AT",
83
- currency: "EUR"
84
- },
85
- EN_US: {
86
- ...baseShop,
87
- shopId: 1003,
88
- path: "foobar",
89
- locale: "en-US",
90
- currency: "USD"
91
- },
92
- DE_CH: {
93
- ...baseShop,
94
- shopId: 1004,
95
- path: "de-ch",
96
- locale: "de-CH",
97
- currency: "CHF"
98
- }
99
- }
100
- };
101
- expect(
102
- getCurrentShopForRequest(
103
- createEvent(
104
- createRequest({
105
- url: "https://de.scayle.example/foobar/men/shirts"
106
- }),
107
- createResponse()
108
- ),
109
- moduleOptions
110
- ).shopId
111
- ).to.eq(1003);
112
- expect(
113
- getCurrentShopForRequest(
114
- createEvent(
115
- createRequest({
116
- url: "/foobar/men/shirts"
117
- }),
118
- createResponse()
119
- ),
120
- moduleOptions
121
- ).shopId
122
- ).to.eq(1003);
123
- expect(
124
- getCurrentShopForRequest(
125
- createEvent(
126
- createRequest({
127
- url: "https://de.scayle.example/at/men/shirts"
128
- }),
129
- createResponse()
130
- ),
131
- moduleOptions
132
- ).shopId
133
- ).to.eq(1002);
134
- expect(
135
- getCurrentShopForRequest(
136
- createEvent(
137
- createRequest({
138
- url: "https://de.scayle.example/de-ch/men/shirts"
139
- }),
140
- createResponse()
141
- ),
142
- moduleOptions
143
- ).shopId
144
- ).to.eq(1004);
145
- });
146
- it("falls back to the domain when shopSelector = domain", () => {
147
- const req = createRequest({
148
- url: "https://de.scayle.example/at/frauen"
149
- });
150
- const moduleOptions = {
151
- shopSelector: "domain",
152
- stores: {
153
- DE_DE: {
154
- ...baseShop,
155
- shopId: 1001,
156
- domain: DOMAIN_DE,
157
- locale: "de-DE",
158
- currency: "EUR"
159
- },
160
- DE_AT: {
161
- ...baseShop,
162
- shopId: 1002,
163
- domain: DOMAIN_AT,
164
- locale: "de-AT",
165
- currency: "EUR"
166
- },
167
- EN_US: {
168
- ...baseShop,
169
- shopId: 1003,
170
- domain: DOMAIN_US,
171
- locale: "en-US",
172
- currency: "USD"
173
- }
174
- }
175
- };
176
- const shop = getCurrentShopForRequest(
177
- createEvent(req, createResponse()),
178
- moduleOptions
179
- );
180
- expect(shop.shopId).to.eq(1001);
181
- });
182
- it("falls back to the first shop when there is no match", () => {
183
- const req = createRequest({
184
- url: "https://de.scayle.example/foobar/men/shirts",
185
- headers: {
186
- "x-shop-id": "1005"
187
- }
188
- });
189
- const moduleOptions = {
190
- shopSelector: "domain",
191
- stores: {
192
- DE_DE: {
193
- ...baseShop,
194
- shopId: 1001,
195
- domain: DOMAIN_DE,
196
- locale: "de-DE",
197
- currency: "EUR"
198
- },
199
- DE_AT: {
200
- ...baseShop,
201
- shopId: 1002,
202
- domain: DOMAIN_AT,
203
- locale: "de-AT",
204
- currency: "EUR"
205
- },
206
- EN_US: {
207
- ...baseShop,
208
- shopId: 1003,
209
- domain: DOMAIN_US,
210
- locale: "en-US",
211
- currency: "USD"
212
- }
213
- }
214
- };
215
- const shop = getCurrentShopForRequest(
216
- createEvent(req, createResponse()),
217
- moduleOptions
218
- );
219
- expect(shop.shopId).to.eq(1001);
220
- });
221
- });
222
- describe("getPublicShopData", () => {
223
- it("excludes checkout secrets from the public shop data", () => {
224
- const shopConfig = {
225
- ...baseShop,
226
- shopId: 1003,
227
- domain: DOMAIN_US,
228
- path: "en",
229
- locale: "en-US",
230
- currency: "USD"
231
- };
232
- expect(getPublicShopData(shopConfig, 1003, "/api")).to.eql({
233
- shopId: 1003,
234
- domain: DOMAIN_US,
235
- path: "en",
236
- locale: "en-US",
237
- currency: "USD",
238
- isActive: true,
239
- checkout: {
240
- host: CHECKOUT_HOST
241
- },
242
- apiBasePath: "/api"
243
- });
244
- });
245
- it("includes additional shop data for keys in storefront.publicShopData", () => {
246
- const shopConfig = {
247
- ...baseShop,
248
- shopId: 1003,
249
- domain: DOMAIN_US,
250
- path: "en",
251
- locale: "en-US",
252
- currency: "USD",
253
- paymentProviders: ["visa", "klarna"],
254
- checkout: {
255
- host: CHECKOUT_HOST,
256
- token: "",
257
- secret: "",
258
- user: ""
259
- }
260
- };
261
- expect(
262
- getPublicShopData(shopConfig, 1003, "/api", ["paymentProviders"])
263
- ).to.eql({
264
- shopId: 1003,
265
- domain: DOMAIN_US,
266
- path: "en",
267
- locale: "en-US",
268
- currency: "USD",
269
- paymentProviders: ["visa", "klarna"],
270
- isActive: true,
271
- checkout: {
272
- host: CHECKOUT_HOST
273
- },
274
- apiBasePath: "/api"
275
- });
276
- });
277
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,66 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { getRedirectLookupUrls, getTargetLocation } from "./redirects.utils.mjs";
3
- describe("redirects module", () => {
4
- describe("getRedirectLookupUrls", () => {
5
- it("returns the relative and absolute URL", () => {
6
- expect(
7
- getRedirectLookupUrls(
8
- new URL("https://demo.example/de/frauen/kleidung"),
9
- /* @__PURE__ */ new Set()
10
- )
11
- ).to.eql({
12
- relativeSourceUrl: "/de/frauen/kleidung",
13
- absoluteSourceUrl: "https://demo.example/de/frauen/kleidung"
14
- });
15
- });
16
- it("strips trailing slashes", () => {
17
- expect(
18
- getRedirectLookupUrls(new URL("https://demo.example/men/"), /* @__PURE__ */ new Set())
19
- ).to.eql({
20
- relativeSourceUrl: "/men",
21
- absoluteSourceUrl: "https://demo.example/men"
22
- });
23
- });
24
- it("strips query params", () => {
25
- expect(
26
- getRedirectLookupUrls(
27
- new URL("https://demo.example/products?p=20&term=foo"),
28
- /* @__PURE__ */ new Set()
29
- )
30
- ).to.eql({
31
- relativeSourceUrl: "/products",
32
- absoluteSourceUrl: "https://demo.example/products"
33
- });
34
- });
35
- it("does not strip whitelisted query params", () => {
36
- expect(
37
- getRedirectLookupUrls(
38
- new URL("https://demo.example/test/page/?foo=1&bar=10"),
39
- /* @__PURE__ */ new Set(["bar"])
40
- )
41
- ).to.eql({
42
- relativeSourceUrl: "/test/page?bar=10",
43
- absoluteSourceUrl: "https://demo.example/test/page?bar=10"
44
- });
45
- });
46
- });
47
- describe("getTargetLocation", () => {
48
- it("uses the source as the base for relative URLs", () => {
49
- expect(
50
- getTargetLocation(
51
- new URL("http://demo.test/source/location"),
52
- "/target"
53
- )
54
- ).to.eq("http://demo.test/target");
55
- });
56
- it("copies non-whitelisted query params from source", () => {
57
- expect(
58
- getTargetLocation(
59
- new URL("http://demo.test/source?foo=10&bar=20"),
60
- "/redirect",
61
- /* @__PURE__ */ new Set(["foo"])
62
- )
63
- ).to.eq("http://demo.test/redirect?bar=20");
64
- });
65
- });
66
- });
@@ -1,11 +0,0 @@
1
- type BaseOptions = {
2
- locale: string;
3
- currencyFractionDigits?: number;
4
- };
5
- type FormatOptions = BaseOptions;
6
- type ToCurrencyOptions = BaseOptions & {
7
- currency: string;
8
- };
9
- export declare const toCurrency: (value: number, options?: ToCurrencyOptions) => string;
10
- export declare const formatPrice: (value: number, formatOptions?: FormatOptions) => string;
11
- export {};
@@ -1,32 +0,0 @@
1
- import { getDefaultFractionDigits } from "@scayle/storefront-core";
2
- import { useCurrentShop } from "../composables/core/useCurrentShop.mjs";
3
- export const toCurrency = (value, options) => {
4
- const currentShop = useCurrentShop();
5
- const {
6
- locale = currentShop.value.locale,
7
- currency = currentShop.value.currency,
8
- currencyFractionDigits = currentShop.value.currencyFractionDigits
9
- } = options || {};
10
- const minimumFractionDigits = getDefaultFractionDigits(locale, currency);
11
- return (value / 100).toLocaleString(locale, {
12
- style: "currency",
13
- ...currencyFractionDigits && {
14
- minimumFractionDigits: Math.min(
15
- minimumFractionDigits,
16
- currencyFractionDigits
17
- )
18
- },
19
- maximumFractionDigits: currencyFractionDigits,
20
- currency
21
- });
22
- };
23
- export const formatPrice = (value, formatOptions) => {
24
- const currentShop = useCurrentShop();
25
- const {
26
- locale = currentShop.value.locale,
27
- currencyFractionDigits = currentShop.value.currencyFractionDigits
28
- } = formatOptions || {};
29
- return (value / 100).toLocaleString(locale, {
30
- minimumFractionDigits: currencyFractionDigits
31
- });
32
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,83 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { ref } from "vue";
3
- import { toCurrency, formatPrice } from "./price.mjs";
4
- describe("Price utils", () => {
5
- vi.mock("../composables/core/useCurrentShop", () => ({
6
- useCurrentShop: () => ref({
7
- locale: "de-DE",
8
- currency: "EUR",
9
- currencyFractionDigits: 2
10
- })
11
- }));
12
- describe("toCurrency", () => {
13
- it("should return value with EUR currency", () => {
14
- const options = {
15
- locale: "de-DE",
16
- currency: "EUR",
17
- currencyFractionDigits: 2
18
- };
19
- const currency = toCurrency(200, options);
20
- expect(currency).be.eq("2,00\xA0\u20AC");
21
- });
22
- it("should return value with EUR currency with default options", () => {
23
- const currency = toCurrency(200);
24
- expect(currency).be.eq("2,00\xA0\u20AC");
25
- });
26
- it("should return value with EUR currency with default options", () => {
27
- const currency = toCurrency(200);
28
- expect(currency).be.eq("2,00\xA0\u20AC");
29
- });
30
- it("should return value with USD currency", () => {
31
- const options = {
32
- locale: "de-DE",
33
- currency: "USD",
34
- currencyFractionDigits: 2
35
- };
36
- const currency = toCurrency(200, options);
37
- expect(currency).be.eq("2,00\xA0$");
38
- });
39
- it("should return value with one fraction digit", () => {
40
- const options = {
41
- locale: "de-DE",
42
- currency: "USD",
43
- currencyFractionDigits: 1
44
- };
45
- const currency = toCurrency(200, options);
46
- expect(currency).be.eq("2,0\xA0$");
47
- });
48
- it("should return value with two fraction digits if no fraction digits set", () => {
49
- const options = {
50
- locale: "de-DE",
51
- currency: "USD"
52
- };
53
- const currency = toCurrency(200, options);
54
- expect(currency).be.eq("2,00\xA0$");
55
- });
56
- it("should return value properly formatted for en-US locale", () => {
57
- const options = {
58
- locale: "en-US",
59
- currency: "USD"
60
- };
61
- const currency = toCurrency(200, options);
62
- expect(currency).be.eq("$2.00");
63
- });
64
- });
65
- describe("formatPrice", () => {
66
- it("should return formatted price with two fraction digits", () => {
67
- const options = {
68
- locale: "en-US",
69
- currencyFractionDigits: 2
70
- };
71
- const price = formatPrice(200, options);
72
- expect(price).be.eq("2.00");
73
- });
74
- it("should return formatted price with one fraction digits", () => {
75
- const options = {
76
- locale: "en-US",
77
- currencyFractionDigits: 1
78
- };
79
- const price = formatPrice(200, options);
80
- expect(price).be.eq("2.0");
81
- });
82
- });
83
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,15 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { getCurrentPage } from "./route.mjs";
3
- describe("getCurrentPage", () => {
4
- it("should return parsed current page", () => {
5
- const page = getCurrentPage({ page: "20" });
6
- expect(page).be.equal(20);
7
- });
8
- it("should return undefined if page does not exist inside the query", () => {
9
- const page = getCurrentPage({});
10
- expect(page).be.equal(void 0);
11
- });
12
- it("should return undefined if page is not a string", () => {
13
- expect(getCurrentPage({ page: ["1", "2"] })).toBe(void 0);
14
- });
15
- });