@eifi1/ui-kit 0.38.1 → 0.39.1

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.
Files changed (57) hide show
  1. package/README.md +6 -5
  2. package/dist/auth/auth-errors.d.ts +8 -3
  3. package/dist/auth/auth-errors.js +3 -1
  4. package/dist/auth/auth-errors.js.map +1 -1
  5. package/dist/billing/billing-labels.d.ts +25 -2
  6. package/dist/billing/billing-labels.js +11 -2
  7. package/dist/billing/billing-labels.js.map +1 -1
  8. package/dist/billing/billing-overview.d.ts +50 -1
  9. package/dist/billing/checkout-processing.d.ts +15 -3
  10. package/dist/billing/checkout-processing.js +3 -1
  11. package/dist/billing/checkout-processing.js.map +1 -1
  12. package/dist/billing/pay-page.d.ts +55 -18
  13. package/dist/billing/pay-page.js +44 -10
  14. package/dist/billing/pay-page.js.map +1 -1
  15. package/dist/billing/plan-switch.d.ts +79 -0
  16. package/dist/billing/plan-switch.js +93 -0
  17. package/dist/billing/plan-switch.js.map +1 -0
  18. package/dist/bin/eifi1-pay-page.mjs +109 -21
  19. package/dist/eslint/index.js +1 -1
  20. package/dist/i18n/german.js +13 -2
  21. package/dist/i18n/german.js.map +1 -1
  22. package/dist/i18n/locales/en.js +14 -3
  23. package/dist/i18n/locales/en.js.map +2 -2
  24. package/dist/i18n/locales/es.js +13 -2
  25. package/dist/i18n/locales/es.js.map +1 -1
  26. package/dist/i18n/locales/fr.js +13 -2
  27. package/dist/i18n/locales/fr.js.map +1 -1
  28. package/dist/i18n/locales/hu.js +13 -2
  29. package/dist/i18n/locales/hu.js.map +1 -1
  30. package/dist/i18n/locales/it.js +13 -2
  31. package/dist/i18n/locales/it.js.map +1 -1
  32. package/dist/i18n/locales/zh.js +13 -2
  33. package/dist/i18n/locales/zh.js.map +1 -1
  34. package/dist/i18n/review.js +3 -0
  35. package/dist/i18n/review.js.map +1 -1
  36. package/dist/index.d.ts +6 -1
  37. package/dist/index.js +4 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/lib/translation-review.d.ts +11 -0
  40. package/dist/lib/translation-review.js.map +1 -1
  41. package/dist/pay/pay.js +102 -40
  42. package/package.json +1 -1
  43. package/src/auth/auth-errors.ts +12 -3
  44. package/src/billing/billing-labels.ts +38 -3
  45. package/src/billing/billing-overview.ts +53 -1
  46. package/src/billing/checkout-processing.ts +17 -3
  47. package/src/billing/pay-page.ts +111 -27
  48. package/src/billing/plan-switch.tsx +201 -0
  49. package/src/i18n/german.ts +13 -1
  50. package/src/i18n/locales/es.ts +12 -1
  51. package/src/i18n/locales/fr.ts +12 -2
  52. package/src/i18n/locales/hu.ts +12 -1
  53. package/src/i18n/locales/it.ts +12 -1
  54. package/src/i18n/locales/zh.ts +12 -1
  55. package/src/i18n/review.ts +3 -0
  56. package/src/index.ts +8 -0
  57. package/src/lib/translation-review.ts +12 -0
@@ -5,14 +5,23 @@ import process from "process";
5
5
  import { fileURLToPath } from "url";
6
6
 
7
7
  // pay-page/cli.ts
8
- import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
8
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
9
  import { dirname, join } from "path";
10
10
  import { parseArgs } from "util";
11
11
 
12
12
  // src/billing/pay-page.ts
13
13
  var PAY_CONFIG_FILE = "pay-config.json";
14
+ var APP_TAG = /^[a-z][a-z0-9_-]{0,63}$/;
15
+ function appAt(name, site, returnPath) {
16
+ return { name, returnUrl: `${site}${returnPath}`, termsUrl: `${site}/terms`, privacyUrl: `${site}/privacy` };
17
+ }
18
+ var PAY_PAGE_APPS = Object.freeze({
19
+ keksdose: Object.freeze(appAt("Keksdose", "https://keksdose.app", "/settings/subscription")),
20
+ kastlan: Object.freeze(appAt("Kastlan", "https://kastlan.app", "/admin/billing")),
21
+ kurvenschmiede: Object.freeze(appAt("Kurvenschmiede", "https://kurvenschmiede.app", "/settings/subscription"))
22
+ });
14
23
  var TOKEN_PREFIX = { sandbox: "test_", live: "live_" };
15
- function isPageUrl(value) {
24
+ function isPayPageUrl(value) {
16
25
  if (typeof value !== "string") return false;
17
26
  let url;
18
27
  try {
@@ -23,6 +32,19 @@ function isPageUrl(value) {
23
32
  if (url.protocol === "https:") return true;
24
33
  return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
25
34
  }
35
+ function hasApp(apps, tag) {
36
+ return typeof tag === "string" && APP_TAG.test(tag) && typeof apps === "object" && apps !== null && Object.prototype.hasOwnProperty.call(apps, tag);
37
+ }
38
+ function appProblems(tag, app) {
39
+ if (typeof app !== "object" || app === null) return [`apps.${tag} is not an object`];
40
+ const a = app;
41
+ const problems = [];
42
+ if (typeof a.name !== "string" || a.name.trim() === "") problems.push(`apps.${tag}.name is missing`);
43
+ for (const key of ["returnUrl", "termsUrl", "privacyUrl"]) {
44
+ if (!isPayPageUrl(a[key])) problems.push(`apps.${tag}.${key} must be an https:// URL (http:// for localhost only)`);
45
+ }
46
+ return problems;
47
+ }
26
48
  function payPageConfigProblems(config) {
27
49
  if (typeof config !== "object" || config === null) return ["the configuration is not an object"];
28
50
  const c = config;
@@ -43,10 +65,20 @@ function payPageConfigProblems(config) {
43
65
  );
44
66
  }
45
67
  }
46
- for (const key of ["returnUrl", "termsUrl", "privacyUrl"]) {
47
- if (!isPageUrl(c[key])) problems.push(`${key} must be an https:// URL (http:// for localhost only)`);
68
+ const { apps, app } = c;
69
+ if (typeof apps !== "object" || apps === null || Array.isArray(apps) || Object.keys(apps).length === 0) {
70
+ problems.push("apps must map each app's tag to its name and URLs");
71
+ } else {
72
+ for (const [tag, entry] of Object.entries(apps)) {
73
+ if (!APP_TAG.test(tag)) problems.push(`apps: ${JSON.stringify(tag)} is not an app tag (a-z, 0-9, _ and -)`);
74
+ else problems.push(...appProblems(tag, entry));
75
+ }
76
+ }
77
+ if (typeof app !== "string" || app === "") problems.push("app is missing: the host's own app tag");
78
+ else if (!hasApp(apps, app)) {
79
+ const known = typeof apps === "object" && apps !== null ? Object.keys(apps).join(", ") : "";
80
+ problems.push(`app ${JSON.stringify(app)} is not in apps${known ? ` (${known})` : ""}`);
48
81
  }
49
- if (typeof c.appName !== "string" || c.appName.trim() === "") problems.push("appName is missing");
50
82
  return problems;
51
83
  }
52
84
 
@@ -69,16 +101,60 @@ var given = (value) => {
69
101
  const trimmed = (value ?? "").trim();
70
102
  return trimmed === "" ? null : trimmed;
71
103
  };
104
+ function moved(url, site) {
105
+ let parsed;
106
+ try {
107
+ parsed = new URL(url);
108
+ } catch {
109
+ return url;
110
+ }
111
+ return `${site.replace(/\/+$/, "")}${parsed.pathname}${parsed.search}${parsed.hash}`;
112
+ }
113
+ function mergedApps(over) {
114
+ const apps = {};
115
+ for (const [tag, app] of Object.entries(PAY_PAGE_APPS)) apps[tag] = { ...app };
116
+ for (const [tag, app] of Object.entries(over ?? {})) {
117
+ const base = Object.prototype.hasOwnProperty.call(apps, tag) ? apps[tag] : {};
118
+ Object.defineProperty(apps, tag, { value: { ...base, ...app }, enumerable: true, writable: true, configurable: true });
119
+ }
120
+ return apps;
121
+ }
72
122
  function payPageConfig(options) {
123
+ const app = (options.app ?? "").trim();
124
+ const apps = mergedApps(options.apps);
125
+ const problems = [];
126
+ const site = given(options.appUrl);
127
+ if (site !== null) {
128
+ if (!isPayPageUrl(site)) problems.push("--app-url must be an https:// URL (http:// for localhost only)");
129
+ else if (Object.prototype.hasOwnProperty.call(apps, app)) {
130
+ const own = apps[app];
131
+ apps[app] = {
132
+ ...own,
133
+ returnUrl: moved(own.returnUrl, site),
134
+ termsUrl: moved(own.termsUrl, site),
135
+ privacyUrl: moved(own.privacyUrl, site)
136
+ };
137
+ }
138
+ }
73
139
  const config = {
74
140
  token: given(options.token),
75
141
  environment: given(options.environment),
76
- returnUrl: (options.returnUrl ?? "").trim(),
77
- appName: (options.appName ?? "").trim(),
78
- termsUrl: (options.termsUrl ?? "").trim(),
79
- privacyUrl: (options.privacyUrl ?? "").trim()
142
+ app,
143
+ apps
80
144
  };
81
- return { config, problems: payPageConfigProblems(config) };
145
+ return { config, problems: [...problems, ...payPageConfigProblems(config)] };
146
+ }
147
+ function readAppsFile(path) {
148
+ let parsed;
149
+ try {
150
+ parsed = JSON.parse(readFileSync(path, "utf8"));
151
+ } catch (err) {
152
+ throw new PayPageError([`--apps ${path} can't be read as JSON: ${err.message}`]);
153
+ }
154
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
155
+ throw new PayPageError([`--apps ${path} must hold a JSON object of apps by tag`]);
156
+ }
157
+ return parsed;
82
158
  }
83
159
  function writePayPage(options, bundleDir) {
84
160
  const { config, problems } = payPageConfig(options);
@@ -99,10 +175,15 @@ function writePayPage(options, bundleDir) {
99
175
  return config;
100
176
  }
101
177
  var USAGE = `Usage: eifi1-pay-page --out <dir> [--token <test_\u2026|live_\u2026> --environment <sandbox|live>]
102
- --return-url <https://\u2026> --app-name <name> --terms-url <https://\u2026> --privacy-url <https://\u2026>
178
+ --app <tag> [--app-url <https://\u2026>] [--apps <file>]
103
179
 
104
- Copies @eifi1/ui-kit's pay page into <dir> and writes its ${PAY_CONFIG_FILE}.
180
+ Copies @eifi1/ui-kit's pay page into <dir> and writes its ${PAY_CONFIG_FILE}: the
181
+ token, the host's app (--app: ${Object.keys(PAY_PAGE_APPS).join(", ")}) and the kit's
182
+ map of apps, which the page reads each transaction's app from. --app-url moves the
183
+ host's app to another site; --apps merges a JSON map of apps by tag over the kit's.
105
184
  Without a token (billing off) the page says there is nothing to pay.`;
185
+ var OLD_FLAGS = ["return-url", "app-name", "terms-url", "privacy-url"];
186
+ var OLD_FLAGS_REFUSED = "eifi1-pay-page: --return-url, --app-name, --terms-url and --privacy-url are gone (ui-kit 0.39): every pay page serves every app, from the kit's map. Pass --app <tag> instead, with --app-url <https://\u2026> for another site (your PAY_APP_URL) and --apps <file> to merge your own map.";
106
187
  function runPayPageCli(argv, io) {
107
188
  let values;
108
189
  try {
@@ -114,10 +195,11 @@ function runPayPageCli(argv, io) {
114
195
  out: { type: "string" },
115
196
  token: { type: "string" },
116
197
  environment: { type: "string" },
117
- "return-url": { type: "string" },
118
- "app-name": { type: "string" },
119
- "terms-url": { type: "string" },
120
- "privacy-url": { type: "string" },
198
+ app: { type: "string" },
199
+ "app-url": { type: "string" },
200
+ apps: { type: "string" },
201
+ // Parsed only to be refused with a line that names the new flags.
202
+ ...Object.fromEntries(OLD_FLAGS.map((flag) => [flag, { type: "string" }])),
121
203
  help: { type: "boolean", short: "h" }
122
204
  }
123
205
  }));
@@ -131,22 +213,28 @@ ${USAGE}`);
131
213
  io.log(USAGE);
132
214
  return 0;
133
215
  }
216
+ if (OLD_FLAGS.some((flag) => values[flag] !== void 0)) {
217
+ io.error(`${OLD_FLAGS_REFUSED}
218
+
219
+ ${USAGE}`);
220
+ return 2;
221
+ }
134
222
  const text = (key) => typeof values[key] === "string" ? values[key] : "";
135
223
  try {
224
+ const appsFile = given(text("apps"));
136
225
  const config = writePayPage(
137
226
  {
138
227
  out: text("out"),
139
228
  token: text("token"),
140
229
  environment: text("environment"),
141
- returnUrl: text("return-url"),
142
- appName: text("app-name"),
143
- termsUrl: text("terms-url"),
144
- privacyUrl: text("privacy-url")
230
+ app: text("app"),
231
+ appUrl: text("app-url"),
232
+ apps: appsFile === null ? void 0 : readAppsFile(appsFile)
145
233
  },
146
234
  io.bundleDir
147
235
  );
148
236
  io.log(
149
- `eifi1-pay-page: wrote the pay page to ${text("out").trim()} ` + (config.token ? `(${config.environment})` : "(no token: billing off, the page says there is nothing to pay)")
237
+ `eifi1-pay-page: wrote ${config.app}'s pay page (serving ${Object.keys(config.apps).join(", ")}) to ${text("out").trim()} ` + (config.token ? `(${config.environment})` : "(no token: billing off, the page says there is nothing to pay)")
150
238
  );
151
239
  return 0;
152
240
  } catch (err) {
@@ -47,7 +47,7 @@ const roleUtilities = {
47
47
  };
48
48
  }
49
49
  };
50
- const VERSION = true ? "0.38.1" : "0.0.0-source";
50
+ const VERSION = true ? "0.39.1" : "0.0.0-source";
51
51
  const recommended = {
52
52
  name: "@eifi1/ui-kit/recommended",
53
53
  // Not the tests, as check:tokens skips the kit's: a test names the old class to assert it
@@ -1628,6 +1628,14 @@ Die vollst\xE4ndige Postanschrift wird betroffenen Personen und Aufsichtsbeh\xF6
1628
1628
  payNothing: "Hier ist nichts zu bezahlen. Um einen Tarif zu w\xE4hlen, kehren Sie zur App zur\xFCck.",
1629
1629
  payFailed: "Der Bezahlvorgang konnte nicht ge\xF6ffnet werden. Bitte kehren Sie zur\xFCck und versuchen Sie es gleich noch einmal.",
1630
1630
  payBack: "Der Bezahlvorgang wurde geschlossen. Sie k\xF6nnen zur\xFCckkehren oder diese Seite neu laden, um ihn wieder zu \xF6ffnen.",
1631
+ payDone: "Zahlung abgeschlossen. Sie k\xF6nnen diese Seite schlie\xDFen.",
1632
+ // 0.39 (§15.2.10): PlanSwitchConfirm.
1633
+ switchTitle: "Tarif \xE4ndern",
1634
+ switchFromTo: (from, to) => `Von ${from} zu ${to}`,
1635
+ switchChargedNow: "Jetzt belastet",
1636
+ switchCredited: "Gutschrift auf die n\xE4chsten Belastungen",
1637
+ switchNextCharge: (date) => `N\xE4chste Belastung am ${date}`,
1638
+ switchConfirm: "Tarif \xE4ndern",
1631
1639
  waitingChanges: (count) => count === 1 ? `${n(count)} \xC4nderung wartet auf einen Tarif` : `${n(count)} \xC4nderungen warten auf einen Tarif`,
1632
1640
  // "Verwerfen", as `wizard.cancelConfirmLabel`.
1633
1641
  discardWaiting: "Wartende \xC4nderungen verwerfen",
@@ -1635,8 +1643,11 @@ Die vollst\xE4ndige Postanschrift wird betroffenen Personen und Aufsichtsbeh\xF6
1635
1643
  disabled: "Abonnements sind hier nicht verf\xFCgbar.",
1636
1644
  // §14.2: server-kit 0.7's refusals. "Zahlung und Rechnungen", as `manage`.
1637
1645
  providerUnavailable: "Der Zahlungsanbieter ist gerade nicht erreichbar. Bitte versuchen Sie es gleich noch einmal.",
1638
- alreadySubscribed: "Sie haben bereits ein Abonnement. Ihren Tarif \xE4ndern Sie unter \u201EZahlung und Rechnungen\u201C.",
1639
- planNotSold: "Dieser Tarif ist f\xFCr diesen Abrechnungszeitraum oder diese W\xE4hrung nicht erh\xE4ltlich. Bitte w\xE4hlen Sie einen anderen."
1646
+ alreadySubscribed: "Sie haben bereits ein Abonnement. \xC4ndern Sie Ihren Tarif oben.",
1647
+ planNotSold: "Dieser Tarif ist f\xFCr diesen Abrechnungszeitraum oder diese W\xE4hrung nicht erh\xE4ltlich. Bitte w\xE4hlen Sie einen anderen.",
1648
+ // 0.39 (§15.2.10): the plan switch's refusals.
1649
+ notSubscribed: "Es l\xE4uft kein Abonnement, das sich \xE4ndern lie\xDFe. W\xE4hlen Sie einen Tarif, um eines abzuschlie\xDFen.",
1650
+ planUnchanged: "Das ist bereits Ihr Tarif. W\xE4hlen Sie einen anderen Tarif oder Abrechnungszeitraum."
1640
1651
  },
1641
1652
  // 0.33.0 — docs/billing-harmonization.md §14.12: the operator's plan parts, "Tarif" as
1642
1653
  // in `billing`; keksdose's reviewed admin wording where it had one.