diff options
Diffstat (limited to 'packages/merchant-backend-ui')
15 files changed, 257 insertions, 569 deletions
diff --git a/packages/merchant-backend-ui/README.md b/packages/merchant-backend-ui/README.md index bbf826e0e..7f9bcf5dc 100644 --- a/packages/merchant-backend-ui/README.md +++ b/packages/merchant-backend-ui/README.md @@ -4,9 +4,7 @@ Merchant Backend pages This project generate 5 templates for the merchant backend: -- DepletedTip - OfferRefund -- OfferTip - RequestPayment - ShowOrderDetails diff --git a/packages/merchant-backend-ui/build.mjs b/packages/merchant-backend-ui/build.mjs index fd2a52b9e..e72113dc5 100755 --- a/packages/merchant-backend-ui/build.mjs +++ b/packages/merchant-backend-ui/build.mjs @@ -44,7 +44,7 @@ const preactCompatPlugin = { }, }; -const pages = ["OfferTip","OfferRefund","DepletedTip","RequestPayment","ShowOrderDetails"] +const pages = ["OfferRefund", "RequestPayment", "ShowOrderDetails"] const entryPoints = pages.map(p => `src/pages/${p}.tsx`); let GIT_ROOT = BASE; @@ -87,8 +87,8 @@ function templatePlugin(options) { setup(build) { build.onEnd(() => { for (const pageName of options.pages) { - const css = fs.readFileSync(path.join(build.initialOptions.outdir, `${pageName}.css`),"utf8").toString() - const js = fs.readFileSync(path.join(build.initialOptions.outdir, `${pageName}.js`),"utf8").toString() + const css = fs.readFileSync(path.join(build.initialOptions.outdir, `${pageName}.css`), "utf8").toString() + const js = fs.readFileSync(path.join(build.initialOptions.outdir, `${pageName}.js`), "utf8").toString() const location = path.join(build.initialOptions.outdir, toCamelCaseName(pageName)) const render = new Function(`${js}; return page.buildTimeRendering();`)() const html = ` @@ -113,20 +113,18 @@ function templatePlugin(options) { }; } - - export const buildConfig = { entryPoints: [...entryPoints], bundle: true, outdir: "dist/pages", - /* - * Doing a minified version will replace templatestring to common strings - * This app is building mustache template with placeholders that will be replaced - * with string in runtime by the merchant-backend - * - * To the date, merchant backend is replacing with multiline string so - * doing minified version will brake at runtime - * */ + /* + * Doing a minified version will replace templatestring to common strings + * This app is building mustache template with placeholders that will be replaced + * with string in runtime by the merchant-backend + * + * To the date, merchant backend is replacing with multiline string so + * doing minified version will brake at runtime + * */ minify: false, loader: { ".svg": "file", @@ -137,7 +135,7 @@ export const buildConfig = { '.woff2': 'file', '.eot': 'file', }, - target: ["es6"], + target: ["es2020"], format: "iife", platform: "browser", sourcemap: false, @@ -157,8 +155,36 @@ export const buildConfig = { sourceMap: true, }), preactCompatPlugin, - templatePlugin({pages}) + templatePlugin({ pages }) ], }; await esbuild.build(buildConfig) + +export const testingConfig = { + entryPoints: ["src/render-examples.ts"], + bundle: true, + outdir: "dist/test", + minify: false, + loader: { + ".svg": "file", + ".png": "dataurl", + ".jpeg": "dataurl", + '.ttf': 'file', + '.woff': 'file', + '.woff2': 'file', + '.eot': 'file', + }, + target: ["es2020"], + format: "iife", + platform: "node", + sourcemap: true, + define: { + __VERSION__: `"${_package.version}"`, + __GIT_HASH__: `"${GIT_HASH}"`, + }, + plugins: [ + ], +}; + +await esbuild.build(testingConfig) diff --git a/packages/merchant-backend-ui/package.json b/packages/merchant-backend-ui/package.json index e8a72bf09..bd16317f5 100644 --- a/packages/merchant-backend-ui/package.json +++ b/packages/merchant-backend-ui/package.json @@ -1,12 +1,12 @@ { "private": true, "name": "@gnu-taler/merchant-backend-ui", - "version": "0.0.5", + "version": "0.10.7", "license": "AGPL-3.0-or-later", "scripts": { "compile": "tsc && ./build.mjs", "build": "pnpm compile", - "render-examples": "ts-node -O '{\"module\": \"commonjs\"}' -T render-examples.ts dist/pages dist/examples", + "render-examples": "node dist/test/render-examples.js dist/pages dist/examples", "lint-check": "eslint '{src,tests}/**/*.{js,jsx,ts,tsx}'", "lint-fix": "eslint --fix '{src,tests}/**/*.{js,jsx,ts,tsx}'", "clean": "rm -rf dist lib tsconfig.tsbuildinfo", @@ -42,7 +42,7 @@ }, "devDependencies": { "@babel/core": "7.18.9", - "@gnu-taler/pogen": "^0.0.5", + "@gnu-taler/pogen": "workspace:*", "@linaria/babel-preset": "3.0.0-beta.22", "@linaria/core": "3.0.0-beta.22", "@linaria/react": "3.0.0-beta.22", @@ -50,6 +50,7 @@ "@linaria/webpack-loader": "3.0.0-beta.22", "@types/mocha": "^8.2.2", "@types/mustache": "^4.1.2", + "@types/node": "^20.11.13", "@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/parser": "^4.22.0", "babel-loader": "^8.2.2", diff --git a/packages/merchant-backend-ui/render-examples.ts b/packages/merchant-backend-ui/render-examples.ts deleted file mode 100644 index e8c4a8cd0..000000000 --- a/packages/merchant-backend-ui/render-examples.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import mustache from "mustache"; -import fs from "fs"; -import { format, formatDuration, intervalToDuration } from "date-fns"; - -/** - * This script will emulate what the merchant backend will do when being requested - * - */ - -const sourceDirectory = process.argv[2]; -const destDirectory = process.argv[3]; - -if (!sourceDirectory || !destDirectory) { - console.log("usage: render-mustache <source-directory> <dest-directory>"); - process.exit(1); -} - -if (!fs.existsSync(destDirectory)) { - fs.mkdirSync(destDirectory); -} - -function fromCamelCaseName(name) { - const result = name - .replace(/^[a-z]/, (letter) => `${letter.toUpperCase()}`) //first letter lowercase - .replace(/_[a-z]/g, (letter) => `${letter[1].toUpperCase()}`); //snake case - return result; -} -/** - * Load all the html files - */ -const files = fs.readdirSync(sourceDirectory).filter((f) => /.html/.test(f)); - -files.forEach((file) => { - const html = fs.readFileSync(`${sourceDirectory}/${file}`, "utf8"); - - const testName = file.replace(".en.html", ""); - const exampleFileName = `./src/pages/${fromCamelCaseName(testName)}.examples`; - if (!fs.existsSync(exampleFileName + ".ts")) { - console.log(`skipping ${testName}: no examples found`); - return; - } - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { exampleData } = require(exampleFileName); - - Object.keys(exampleData).forEach((exampleName) => { - const example = exampleData[exampleName]; - - //enhance the example with more information - example.contract_terms_json = () => JSON.stringify(example.contract_terms); - example.contract_terms.timestamp_str = () => - example.contract_terms.timestamp && - format(example.contract_terms.timestamp.t_s, "dd MMM yyyy HH:mm:ss"); - - example.contract_terms.hasProducts = () => - example.contract_terms.products?.length > 0; - example.contract_terms.hasAuditors = () => - example.contract_terms.auditors?.length > 0; - example.contract_terms.hasExchanges = () => - example.contract_terms.exchanges?.length > 0; - - example.contract_terms.products.forEach((p) => { - p.delivery_date_str = () => - p.delivery_date && format(p.delivery_date.t_s, "dd MM yyyy HH:mm:ss"); - p.hasTaxes = () => p.taxes?.length > 0; - }); - example.contract_terms.has_delivery_info = () => - example.contract_terms.delivery_date || - example.contract_terms.delivery_location; - - example.contract_terms.delivery_date_str = () => - example.contract_terms.delivery_date && - format(example.contract_terms.delivery_date.t_s, "dd MM yyyy HH:mm:ss"); - example.contract_terms.pay_deadline_str = () => - example.contract_terms.pay_deadline && - format(example.contract_terms.pay_deadline.t_s, "dd MM yyyy HH:mm:ss"); - example.contract_terms.wire_transfer_deadline_str = () => - example.contract_terms.wire_transfer_deadline && - format( - example.contract_terms.wire_transfer_deadline.t_s, - "dd MM yyyy HH:mm:ss", - ); - example.contract_terms.refund_deadline_str = () => - example.contract_terms.refund_deadline && - format(example.contract_terms.refund_deadline.t_s, "dd MM yyyy HH:mm:ss"); - example.contract_terms.auto_refund_str = () => - example.contract_terms.auto_refund && - formatDuration( - intervalToDuration({ - start: 0, - end: example.contract_terms.auto_refund.d_us, - }), - ); - - const output = mustache.render(html, example); - - fs.writeFileSync( - `${destDirectory}/${testName}.${exampleName}.html`, - output, - ); - }); -}); diff --git a/packages/merchant-backend-ui/rollup.config.js b/packages/merchant-backend-ui/rollup.config.js deleted file mode 100644 index 18d72e56b..000000000 --- a/packages/merchant-backend-ui/rollup.config.js +++ /dev/null @@ -1,116 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -// rollup.config.js -import linaria from '@linaria/rollup'; -import nodeResolve from "@rollup/plugin-node-resolve"; -import alias from "@rollup/plugin-alias"; -import image from '@rollup/plugin-image'; -import json from "@rollup/plugin-json"; -import ts from "@rollup/plugin-typescript"; -import replace from "@rollup/plugin-replace"; -import css from 'rollup-plugin-css-only'; -import html from '@rollup/plugin-html'; -import commonjs from "@rollup/plugin-commonjs"; - -const template = async ({ - files, -}) => { - const scripts = (files.js || []).map(({ code }) => `<script>${code}</script>`).join('\n'); - const css = (files.css || []).map(({ source }) => `<style>${source}</style>`).join('\n'); - const ssr = (files.js || []).map(({ code }) => code).join('\n'); - const page = new Function(`${ssr}; return page.buildTimeRendering();`)() - return ` -<!doctype html> -<html> - <head> - ${page.head} - ${css} - </head> - <script id="built_time_data"> - </script> - <body> - ${page.body} - ${scripts} - <script>page.mount()</script> - </body> -</html>`; -}; - -const makePlugins = (name) => [ - alias({ - entries: [ - { find: 'react', replacement: 'preact/compat' }, - { find: 'react-dom', replacement: 'preact/compat' } - ] - }), - - replace({ - "process.env.NODE_ENV": JSON.stringify("production"), - preventAssignment: true, - }), - - commonjs({ - include: [/node_modules/, /dist/], - extensions: [".js"], - ignoreGlobal: true, - sourceMap: true, - }), - - nodeResolve({ - browser: true, - preferBuiltins: true, - }), - - json(), - image(), - - linaria({ - sourceMap: process.env.NODE_ENV !== 'production', - }), - css(), - ts({ - sourceMap: false, - outputToFilesystem: false, - }), - html({ template, fileName: name }), -]; - -function formatHtmlName(name) { - return name - .replace(/^[A-Z]/, letter => `${letter.toLowerCase()}`) //first letter lowercase - .replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) //snake case - .concat(".en.html"); //extension -} - -const pageDefinition = (name) => ({ - input: `src/pages/${name}.tsx`, - output: { - file: `dist/pages/${name}.js`, - format: "iife", - exports: 'named', - name: 'page', - }, - plugins: makePlugins(formatHtmlName(name)), -}); - -export default [ - pageDefinition("OfferTip"), - pageDefinition("OfferRefund"), - pageDefinition("DepletedTip"), - pageDefinition("RequestPayment"), - pageDefinition("ShowOrderDetails"), -] diff --git a/packages/merchant-backend-ui/src/pages/DepletedTip.tsx b/packages/merchant-backend-ui/src/pages/DepletedTip.tsx deleted file mode 100644 index 61fc52cdf..000000000 --- a/packages/merchant-backend-ui/src/pages/DepletedTip.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ -import { Fragment, h, render, VNode } from "preact"; -import { render as renderToString } from "preact-render-to-string"; -import { Footer } from "../components/Footer"; -import "../css/pure-min.css"; -import "../css/style.css"; -import { Page } from "../styled"; - -function Head(): VNode { - return <title>Status of your tip</title>; -} - -export function DepletedTip(): VNode { - return ( - <Page> - <section> - <h1>Tip already collected</h1> - <div>You have already collected this tip.</div> - </section> - <Footer /> - </Page> - ); -} - -export function mount(): void { - try { - render(<DepletedTip />, document.body); - } catch (e) { - console.error("got error", e); - if (e instanceof Error) { - document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`; - } - } -} - -export function buildTimeRendering(): { head: string; body: string } { - return { - head: renderToString(<Head />), - body: renderToString(<DepletedTip />), - }; -} diff --git a/packages/merchant-backend-ui/src/pages/OfferRefund.tsx b/packages/merchant-backend-ui/src/pages/OfferRefund.tsx index ffd657e7e..b1cf63572 100644 --- a/packages/merchant-backend-ui/src/pages/OfferRefund.tsx +++ b/packages/merchant-backend-ui/src/pages/OfferRefund.tsx @@ -52,6 +52,8 @@ function Head({ order_summary }: { order_summary?: string }): VNode { return <Fragment> <meta charSet="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta name="taler-support" content="uri" /> + <meta name="taler-uri" content="{{ taler_refund_uri }}"></meta> <noscript> <meta http-equiv="refresh" content="1" /> </noscript> diff --git a/packages/merchant-backend-ui/src/pages/OfferTip.stories.tsx b/packages/merchant-backend-ui/src/pages/OfferTip.stories.tsx deleted file mode 100644 index dfbf71fff..000000000 --- a/packages/merchant-backend-ui/src/pages/OfferTip.stories.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, VNode, FunctionalComponent } from 'preact'; -import { createSVG } from '../components/QR'; -import { OfferTip as TestedComponent } from './OfferTip'; - - -export default { - title: 'OfferTip', - component: TestedComponent, - argTypes: { - }, -}; - -function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { - const r = (args: any) => <Component {...args} /> - r.args = props - return r -} - -const TIP_URI_EXAMPLE = 'taler+http://tip/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0' - -export const Example = createExample(TestedComponent, { - tipURI: TIP_URI_EXAMPLE, - qr_code: createSVG(TIP_URI_EXAMPLE) -}); diff --git a/packages/merchant-backend-ui/src/pages/OfferTip.tsx b/packages/merchant-backend-ui/src/pages/OfferTip.tsx deleted file mode 100644 index cb3ce33fd..000000000 --- a/packages/merchant-backend-ui/src/pages/OfferTip.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { Fragment, h, render, VNode } from 'preact'; -import { render as renderToString } from 'preact-render-to-string'; -import { useEffect } from 'preact/hooks'; -import { Footer } from '../components/Footer'; -import { QR } from '../components/QR'; -import "../css/pure-min.css"; -import "../css/style.css"; -import { Page, QRPlaceholder, WalletLink } from '../styled'; - - -/** - * This page creates a tip offer QR code - * - * It will build into a mustache html template for server side rendering - * - * server side rendering params: - * - tip_status_url - * - taler_tip_qrcode_svg - * - taler_tip_uri - * - * request params: - * - tip_uri - * - tip_status_url - */ - -interface Props { - tipURI?: string, - tip_status_url?: string, - qr_code?: string, -} - -export function Head(): VNode { - return <Fragment> - <meta charSet="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <noscript> - <meta http-equiv="refresh" content="1" /> - </noscript> - <title>Tip available</title> - </Fragment> -} - -export function OfferTip({ tipURI, qr_code, tip_status_url }: Props): VNode { - useEffect(() => { - const longpollDelayMs = 60 * 1000; - const delayMs = 500; - let checkUrl: URL; - try { - checkUrl = new URL(tip_status_url ? tip_status_url : "{{& tip_status_url }}"); - } catch (e) { - return; - } - checkUrl.searchParams.set("timeout_ms", longpollDelayMs.toString()); - - function check() { - let retried = false; - function retryOnce() { - if (!retried) { - retried = true; - check(); - } - } - const req = new XMLHttpRequest(); - req.onreadystatechange = function () { - if (req.readyState === XMLHttpRequest.DONE) { - if (req.status === 410) { - window.location.reload(); - } - setTimeout(retryOnce, delayMs); - } - }; - req.onerror = function () { - setTimeout(retryOnce, delayMs); - } - req.open("GET", checkUrl.href); - req.send(); - } - - setTimeout(check, delayMs); - }) - return <Page> - <section> - <h1 >Collect Taler tip</h1> - <p> - Scan this QR code with your Taler mobile wallet: - </p> - <QRPlaceholder dangerouslySetInnerHTML={{ __html: qr_code ? qr_code : `{{{ taler_tip_qrcode_svg }}}` }} /> - <p> - <WalletLink href={tipURI ? tipURI : `{{ taler_tip_uri }}`}> - Or open your Taler wallet - </WalletLink> - </p> - <p> - <a href="https://wallet.taler.net/">Don't have a Taler wallet yet? Install it!</a> - </p> - </section> - <Footer /> - </Page> -} - -export function mount(): void { - try { - const fromLocation = new URL(window.location.href).searchParams - - const uri = fromLocation.get('tip_uri') || undefined - const tsu = fromLocation.get('tip_status_url') || undefined - - render(<OfferTip tipURI={uri} tip_status_url={tsu} />, document.body); - } catch (e) { - console.error("got error", e); - if (e instanceof Error) { - document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`; - } - } -} - -export function buildTimeRendering(): { head: string, body: string } { - return { - head: renderToString(<Head />), - body: renderToString(<OfferTip />) - } -} diff --git a/packages/merchant-backend-ui/src/pages/RequestPayment.tsx b/packages/merchant-backend-ui/src/pages/RequestPayment.tsx index 86c7e6f60..513438ba2 100644 --- a/packages/merchant-backend-ui/src/pages/RequestPayment.tsx +++ b/packages/merchant-backend-ui/src/pages/RequestPayment.tsx @@ -55,6 +55,8 @@ function Head({ order_summary }: { order_summary?: string }): VNode { <Fragment> <meta charSet="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta name="taler-support" content="uri" /> + <meta name="taler-uri" content="{{ taler_pay_uri }}"></meta> <noscript> <meta http-equiv="refresh" content="1" /> </noscript> @@ -81,7 +83,7 @@ export function RequestPayment({ } catch (e) { return; } - checkUrl.searchParams.set("timeout_s", longpollDelayMs.toString()); + checkUrl.searchParams.set("timeout_ms", longpollDelayMs.toString()); const delayMs = 500; function check() { let retried = false; @@ -99,6 +101,8 @@ export function RequestPayment({ const resp = JSON.parse(req.responseText); if (resp.fulfillment_url) { window.location.replace(resp.fulfillment_url); + } else { + window.location.reload() } } catch (e) { console.error("could not parse response:", e); @@ -109,6 +113,8 @@ export function RequestPayment({ const resp = JSON.parse(req.responseText); if (resp.fulfillment_url) { window.location.replace(resp.fulfillment_url); + } else { + window.location.reload() } } catch (e) { console.error("could not parse response:", e); diff --git a/packages/merchant-backend-ui/src/pages/ShowOrderDetails.examples.ts b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.examples.ts index d80401129..86992c9e1 100644 --- a/packages/merchant-backend-ui/src/pages/ShowOrderDetails.examples.ts +++ b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.examples.ts @@ -61,7 +61,7 @@ const defaultContractTerms: MerchantBackend.ContractTerms = { }, wire_method: 'x-taler-bank', wire_transfer_deadline: { - t_s: Math.round(new Date().getTime() / 1000) + 3 * 24 * 60 * 60 + t_s: Math.round(new Date().getTime() / 1000) + 3 * 24 * 60 * 60 }, }; @@ -224,4 +224,30 @@ export const exampleData: { [name: string]: Props } = { fulfillment_message: "Congratulations! You just purchased an valuable item!" }, }, + WithFulfillmentMessage: { + order_summary: 'this is the order with fulfillment message', + contract_terms: { + ...defaultContractTerms, + fulfillment_message: "Congratulations! You just purchased an valuable item!" + }, + }, + WithoutWireTransferDeadline: { + order_summary: 'this is the order without transfer deadline', + contract_terms: { + ...defaultContractTerms, + // @ts-ignore + wire_transfer_deadline: undefined, + }, + }, + ZeroFee: { + order_summary: 'example with zero fee', + contract_terms: { + ...defaultContractTerms, + // @ts-ignore + max_fee: undefined, + // @ts-ignore + max_wire_fee: undefined, + fulfillment_message: "Congratulations! You just purchased an valuable item!" + }, + }, } diff --git a/packages/merchant-backend-ui/src/pages/ShowOrderDetails.tsx b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.tsx index ca93c3f7d..7d11eb21d 100644 --- a/packages/merchant-backend-ui/src/pages/ShowOrderDetails.tsx +++ b/packages/merchant-backend-ui/src/pages/ShowOrderDetails.tsx @@ -27,6 +27,7 @@ import "../css/pure-min.css"; import "../css/style.css"; import { MerchantBackend } from "../declaration"; import { Page, InfoBox, TableExpanded, TableSimple } from "../styled"; +import { TIME_DATE_FORMAT } from "../utils"; /** * This page creates a payment request QR code @@ -56,6 +57,7 @@ function Head({ order_summary }: { order_summary?: string }): VNode { <Fragment> <meta charSet="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta name="taler-support" content="uri" /> <noscript> <meta http-equiv="refresh" content="1" /> </noscript> @@ -79,7 +81,7 @@ function Location({ location: MerchantBackend.Location | undefined; btr?: boolean; }) { - //FIXME: mustache strings show be constructed in a way that ends in the final output of the html but is not present in the + //FIXME: mustache strings will be constructed in a way that ends in the final output of the html but is not present in the // javascript code, otherwise when mustache render engine run over the html it will also replace string in the javascript code // that is made to run when the browser has javascript enable leading into undefined behavior. // that's why in the next fields we are using concatenations to build the mustache placeholder. @@ -196,9 +198,9 @@ export function ShowOrderDetails({ {contract_terms?.timestamp ? contract_terms?.timestamp.t_s != "never" ? format( - contract_terms?.timestamp.t_s, - "dd MMM yyyy HH:mm:ss", - ) + contract_terms?.timestamp.t_s * 1000, + TIME_DATE_FORMAT, + ) : "never" : `{{ contract_terms.timestamp_str }}`}{" "} </dd> @@ -256,9 +258,9 @@ export function ShowOrderDetails({ {p.delivery_date ? p.delivery_date.t_s != "never" ? format( - p.delivery_date.t_s, - "dd MMM yyyy HH:mm:ss", - ) + p.delivery_date.t_s, + TIME_DATE_FORMAT, + ) : "never" : `{{ delivery_date_str }}`}{" "} </dd> @@ -306,9 +308,9 @@ export function ShowOrderDetails({ {contract_terms?.delivery_date ? contract_terms?.delivery_date.t_s != "never" ? format( - contract_terms?.delivery_date.t_s, - "dd MMM yyyy HH:mm:ss", - ) + contract_terms?.delivery_date.t_s, + TIME_DATE_FORMAT, + ) : "never" : `{{ contract_terms.delivery_date_str }}`}{" "} </dd> @@ -336,48 +338,47 @@ export function ShowOrderDetails({ <section> <h2>Full payment information</h2> <TableExpanded> - <dt>Amount paid:</dt> - <dd>{contract_terms?.amount || `{{ contract_terms.amount }}`}</dd> - <dt>Wire transfer method:</dt> - <dd> - {contract_terms?.wire_method || - `{{ contract_terms.wire_method }}`} - </dd> - <dt>Payment deadline:</dt> - <dd> - {contract_terms?.pay_deadline - ? contract_terms?.pay_deadline.t_s != "never" - ? format( - contract_terms?.pay_deadline.t_s, - "dd MMM yyyy HH:mm:ss", - ) - : "never" - : `{{ contract_terms.pay_deadline_str }}`}{" "} - </dd> <dt>Exchange transfer deadline:</dt> + {btr && `{{` + `#contract_terms.wire_transfer_deadline_str}}`} <dd> {contract_terms?.wire_transfer_deadline ? contract_terms?.wire_transfer_deadline.t_s != "never" ? format( - contract_terms?.wire_transfer_deadline.t_s, - "dd MMM yyyy HH:mm:ss", - ) + contract_terms?.wire_transfer_deadline.t_s * 1000, + TIME_DATE_FORMAT, + ) : "never" : `{{ contract_terms.wire_transfer_deadline_str }}`}{" "} </dd> + {btr && `{{` + `/contract_terms.wire_transfer_deadline_str}}`} + + {btr && `{{` + `^contract_terms.wire_transfer_deadline_str}}`} + <dd> + Wire transfer settled. + </dd> + {btr && `{{` + `/contract_terms.wire_transfer_deadline_str}}`} + + {btr && `{{` + `#contract_terms.max_fee}}`} <dt>Maximum deposit fee:</dt> <dd>{contract_terms?.max_fee || `{{ contract_terms.max_fee }}`}</dd> + {btr && `{{` + `/contract_terms.max_fee}}`} + + {btr && `{{` + `#contract_terms.max_wire_fee}}`} <dt>Maximum wire fee:</dt> <dd> {contract_terms?.max_wire_fee || `{{ contract_terms.max_wire_fee }}`} </dd> + {btr && `{{` + `/contract_terms.max_wire_fee}}`} + + {btr && `{{` + `#contract_terms.wire_fee_amortization}}`} <dt>Wire fee amortization:</dt> <dd> {contract_terms?.wire_fee_amortization || `{{ contract_terms.wire_fee_amortization }}`}{" "} transactions </dd> + {btr && `{{` + `/contract_terms.wire_fee_amortization}}`} </TableExpanded> </section> @@ -389,9 +390,9 @@ export function ShowOrderDetails({ {contract_terms?.refund_deadline ? contract_terms?.refund_deadline.t_s != "never" ? format( - contract_terms?.refund_deadline.t_s, - "dd MMM yyyy HH:mm:ss", - ) + contract_terms?.refund_deadline.t_s * 1000, + TIME_DATE_FORMAT, + ) : "never" : `{{ contract_terms.refund_deadline_str }}`}{" "} </dd> @@ -404,11 +405,11 @@ export function ShowOrderDetails({ {contract_terms?.auto_refund ? contract_terms?.auto_refund.d_us != "forever" ? formatDuration( - intervalToDuration({ - start: 0, - end: contract_terms?.auto_refund.d_us, - }), - ) + intervalToDuration({ + start: 0, + end: contract_terms?.auto_refund.d_us, + }), + ) : "forever" : `{{ contract_terms.auto_refund_str }}`}{" "} </dd> @@ -539,7 +540,7 @@ export function mount(): void { let contractTerms: MerchantBackend.ContractTerms | undefined; try { contractTerms = JSON.parse((window as any).contractTermsStr); - } catch {} + } catch { } render( <ShowOrderDetails diff --git a/packages/merchant-backend-ui/src/render-examples.ts b/packages/merchant-backend-ui/src/render-examples.ts new file mode 100644 index 000000000..957e06a58 --- /dev/null +++ b/packages/merchant-backend-ui/src/render-examples.ts @@ -0,0 +1,112 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import fs from "fs"; +import mustache from "mustache"; +import { createDateToStringFunction, createDurationToStringFunction, createNonEmptyFunction } from "./utils.js" +import { exampleData as ShowOrderDetailsExamples } from "./pages/ShowOrderDetails.examples.js"; +/** + * This script will emulate what the merchant backend will do when being requested + * + */ + +const templateDirectory = process.argv[2]; +const destDirectory = process.argv[3]; + +if (!templateDirectory || !destDirectory) { + console.log("usage: render-mustache <source-directory> <dest-directory>"); + process.exit(1); +} + +if (!fs.existsSync(destDirectory)) { + fs.mkdirSync(destDirectory); +} + +function fromCamelCaseName(name: string) { + const result = name + .replace(/^[a-z]/, (letter) => `${letter.toUpperCase()}`) //first letter lowercase + .replace(/_[a-z]/g, (letter) => `${letter[1].toUpperCase()}`); //snake case + return result; +} +/** + * Load all the html files + */ +const templateFiles = fs.readdirSync(templateDirectory).filter((f) => /.html/.test(f)); +const exampleByTemplate: Record<string, any> = { + "show_order_details.en.html": ShowOrderDetailsExamples +} + +templateFiles.forEach((templateFile) => { + const html = fs.readFileSync(`${templateDirectory}/${templateFile}`, "utf8"); + + const templateFileWithoutExt = templateFile.replace(".en.html", ""); + // const exampleFileName = `src/pages/${fromCamelCaseName(testName)}.examples`; + // if (!fs.existsSync(`./${exampleFileName}.ts`)) { + // console.log(`- skipping ${testName}: no examples found`); + // return; + // } + // eslint-disable-next-line @typescript-eslint/no-var-requires + // const pepe = `./${exampleFileName}.ts` + // const { exampleData } = require(pepe); + + const exampleData = exampleByTemplate[templateFile] + if (!exampleData) { + console.log(`- skipping ${templateFile}: no examples found`); + return; + } + const exampleNames = Object.keys(exampleData) + console.log(`+ rendering ${templateFile}: ${exampleNames.length} examples`); + exampleNames.forEach((exampleName) => { + const example = exampleData[exampleName]; + + //enhance the example with more information + example.contract_terms_json = () => JSON.stringify(example.contract_terms); + + example.contract_terms.timestamp_str = createDateToStringFunction(example.contract_terms.timestamp) + + example.contract_terms.hasProducts = createNonEmptyFunction(example.contract_terms.products) + example.contract_terms.hasAuditors = createNonEmptyFunction(example.contract_terms.auditors) + example.contract_terms.hasExchanges = createNonEmptyFunction(example.contract_terms.exchanges) + + example.contract_terms.products.forEach((p: any) => { + p.delivery_date_str = createDateToStringFunction(p.delivery_date) + p.hasTaxes = createNonEmptyFunction(p.taxes) + }); + + example.contract_terms.has_delivery_info = () => + example.contract_terms.delivery_date || + example.contract_terms.delivery_location; + + example.contract_terms.delivery_date_str = createDateToStringFunction(example.contract_terms.delivery_date) + example.contract_terms.pay_deadline_str = createDateToStringFunction(example.contract_terms.pay_deadline) + example.contract_terms.wire_transfer_deadline_str = createDateToStringFunction(example.contract_terms.wire_transfer_deadline) + + example.contract_terms.refund_deadline_str = createDateToStringFunction(example.contract_terms.refund_deadline) + example.contract_terms.auto_refund_str = createDurationToStringFunction(example.contract_terms.auto_refund) + + const output = mustache.render(html, example); + + fs.writeFileSync( + `${destDirectory}/${templateFileWithoutExt}.${exampleName}.html`, + output, + ); + }); +}); diff --git a/packages/merchant-backend-ui/src/pages/DepletedTip.stories.tsx b/packages/merchant-backend-ui/src/utils.ts index 41c3e26a5..0a420aa22 100644 --- a/packages/merchant-backend-ui/src/pages/DepletedTip.stories.tsx +++ b/packages/merchant-backend-ui/src/utils.ts @@ -14,27 +14,28 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ +import { format, formatDuration, intervalToDuration } from "date-fns"; + +export const TIME_DATE_FORMAT = "dd MMM yyyy HH:mm:ss" + +export function createDateToStringFunction(date: any) { + return () => { + if (!date) return ""; + return format(date.t_s * 1000, TIME_DATE_FORMAT); + } +} + +export function createDurationToStringFunction(duration: any) { + return () => { + if (!duration) return ""; + return formatDuration(intervalToDuration({ start: 0, end: duration.d_us })); + } +} -import { h, VNode, FunctionalComponent } from "preact"; -import { DepletedTip as TestedComponent } from "./DepletedTip"; - -export default { - title: "DepletedTip", - component: TestedComponent, - argTypes: {}, -}; - -function createExample<Props>( - Component: FunctionalComponent<Props>, - props: Partial<Props>, -) { - const r = (args: any) => <Component {...args} />; - r.args = props; - return r; +export function createNonEmptyFunction(list: any) { + return () => { + if (!list) return false; + return list.length > 0; + } } -export const Example = createExample(TestedComponent, {}); diff --git a/packages/merchant-backend-ui/tsconfig.json b/packages/merchant-backend-ui/tsconfig.json index 7a4d70a17..d9cd57c4e 100644 --- a/packages/merchant-backend-ui/tsconfig.json +++ b/packages/merchant-backend-ui/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { /* Basic Options */ - "target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ + "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ "module": "ESNext", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation: */ "allowJs": true, /* Allow javascript files to be compiled. */ |