diff options
author | Florian Dold <florian@dold.me> | 2022-10-25 01:13:25 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-10-25 01:13:35 +0200 |
commit | 172425e0cfd0f3e48993382ee064c592a2441546 (patch) | |
tree | 87cb21f7ff9cc299576016e8dd630c743c23b3d8 /packages | |
parent | 1e39c57cb972c6de224c177dd28ae3a315fbe590 (diff) |
demobank-ui: clean up build system
Diffstat (limited to 'packages')
28 files changed, 1121 insertions, 1686 deletions
diff --git a/packages/demobank-ui/README.md b/packages/demobank-ui/README.md index c014929ce..2fc54a771 100644 --- a/packages/demobank-ui/README.md +++ b/packages/demobank-ui/README.md @@ -1,19 +1,11 @@ -# bank web +# Taler Demobank UI ## CLI Commands -- `npm install`: Installs dependencies +- `pnpm install`: Installs dependencies -- `npm run dev`: Run a development, HMR server +- `pnpm run build`: Production-ready build -- `npm run serve`: Run a production-like server +- `pnpm run check`: Run type checker -- `npm run build`: Production-ready build - -- `npm run lint`: Pass TypeScript files using ESLint - -- `npm run test`: Run Jest and Enzyme with - [`enzyme-adapter-preact-pure`](https://github.com/preactjs/enzyme-adapter-preact-pure) for - your tests - -For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). +- `pnpm run lint`: Pass TypeScript files using ESLint diff --git a/packages/demobank-ui/build.mjs b/packages/demobank-ui/build.mjs new file mode 100755 index 000000000..03664a7c8 --- /dev/null +++ b/packages/demobank-ui/build.mjs @@ -0,0 +1,160 @@ +#!/usr/bin/env node +/* + This file is part of GNU Taler + (C) 2022 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/> + */ + +import esbuild from "esbuild"; +import path from "path"; +import fs from "fs"; +import crypto from "crypto"; +import { sassPlugin } from "esbuild-sass-plugin"; + +const BASE = process.cwd(); + +const preact = path.join( + BASE, + "node_modules", + "preact", + "compat", + "dist", + "compat.module.js", +); + +const preactCompatPlugin = { + name: "preact-compat", + setup(build) { + build.onResolve({ filter: /^(react-dom|react)$/ }, (args) => { + //console.log("onresolve", JSON.stringify(args, undefined, 2)); + return { + path: preact, + }; + }); + }, +}; + +const entryPoints = ["src/index.tsx"]; + +let GIT_ROOT = BASE; +while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") { + GIT_ROOT = path.join(GIT_ROOT, "../"); +} +if (GIT_ROOT === "/") { + console.log("not found"); + process.exit(1); +} +const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash(); + +let _package = JSON.parse(fs.readFileSync(path.join(BASE, "package.json"))); + +function git_hash() { + const rev = fs + .readFileSync(path.join(GIT_ROOT, ".git", "HEAD")) + .toString() + .trim() + .split(/.*[: ]/) + .slice(-1)[0]; + if (rev.indexOf("/") === -1) { + return rev; + } else { + return fs.readFileSync(path.join(GIT_ROOT, ".git", rev)).toString().trim(); + } +} + +// FIXME: Put this into some helper library. +function copyFilesPlugin(options) { + const getDigest = (string) => { + const hash = crypto.createHash("md5"); + const data = hash.update(string, "utf-8"); + + return data.digest("hex"); + }; + + const getFileDigest = (path) => { + if (!fs.existsSync(path)) { + return null; + } + + if (fs.statSync(path).isDirectory()) { + return null; + } + + return getDigest(fs.readFileSync(path)); + }; + + function filter(src, dest) { + if (!fs.existsSync(dest)) { + return true; + } + + if (fs.statSync(dest).isDirectory()) { + return true; + } + + return getFileDigest(src) !== getFileDigest(dest); + } + + return { + name: "copy-files", + setup(build) { + let src = options.src || "./static"; + let dest = options.dest || "./dist"; + build.onEnd(() => + fs.cpSync(src, dest, { + dereference: options.dereference || true, + errorOnExist: options.errorOnExist || false, + filter: options.filter || filter, + force: options.force || true, + preserveTimestamps: options.preserveTimestamps || true, + recursive: options.recursive || true, + }), + ); + }, + }; +} + +export const buildConfig = { + entryPoints: [...entryPoints], + bundle: true, + outdir: "dist", + minify: false, + loader: { + ".svg": "text", + ".png": "dataurl", + ".jpeg": "dataurl", + }, + target: ["es6"], + format: "esm", + platform: "browser", + sourcemap: true, + jsxFactory: "h", + jsxFragment: "Fragment", + define: { + __VERSION__: `"${_package.version}"`, + __GIT_HASH__: `"${GIT_HASH}"`, + }, + plugins: [ + preactCompatPlugin, + sassPlugin(), + copyFilesPlugin({ + src: "static/index.html", + dest: "dist/index.html", + }), + ], +}; + +esbuild.build(buildConfig).catch((e) => { + console.log(e); + process.exit(1); +}); diff --git a/packages/demobank-ui/mocks/json-server/db.json b/packages/demobank-ui/mocks/json-server/db.json deleted file mode 100644 index 44d3ccab2..000000000 --- a/packages/demobank-ui/mocks/json-server/db.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "register": {}, - "transactions": {} -} diff --git a/packages/demobank-ui/mocks/window.js b/packages/demobank-ui/mocks/window.js deleted file mode 100644 index f396ff9e3..000000000 --- a/packages/demobank-ui/mocks/window.js +++ /dev/null @@ -1,27 +0,0 @@ -Object.defineProperty(window, 'requestAnimationFrame', { - value: function(cb) {} // Silence the browser. -}) - -Object.defineProperty(window, 'localStorage', { - value: { - store: {}, - getItem: function(key) { - return this.store[key]; - }, - setItem: function(key, value) { - return this.store[key] = value; - }, - clear: function() { - this.store = {}; - } - } -}); -Object.defineProperty(window, 'location', { - value: { - origin: "http://localhost:8080", /* where taler-local rev proxy listens to */ - search: "", - pathname: "/sandbox/demobanks/default", - } -}) - -export default window; diff --git a/packages/demobank-ui/package.json b/packages/demobank-ui/package.json index bd686365b..831ec3978 100644 --- a/packages/demobank-ui/package.json +++ b/packages/demobank-ui/package.json @@ -4,15 +4,10 @@ "version": "0.1.0", "license": "AGPL-3.0-OR-LATER", "scripts": { - "dev": "preact watch --port ${PORT:=9090} --no-sw --no-esm -c preact.mock.js", - "build": "preact build --no-sw --no-esm -c preact.single-config.js --dest build && sh remove-link-stylesheet.sh", - "serve": "sirv build --port ${PORT:=8080} --cors --single", + "build": "./build.mjs", + "check": "tsc", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", - "test": "jest ./tests", - "build-storybook": "build-storybook", - "serve-single": "sirv single --port ${PORT:=8080} --cors --single", - "pretty": "prettier --write src", - "storybook": "start-storybook -p 6006" + "pretty": "prettier --write src" }, "eslintConfig": { "parser": "@typescript-eslint/parser", @@ -24,77 +19,62 @@ "build/" ], "rules": { - "@typescript-eslint/no-explicit-any": [0], - "@typescript-eslint/ban-ts-comment": [1], - "quotes": [2, "single", {"allowTemplateLiterals": true,"avoidEscape": false}], - "indent": [2,2], - "prefer-arrow-callback": [2, {"allowNamedFunctions": false, "allowUnboundThis": true}], - "curly": [2,"multi"], - "prefer-template": [1] + "@typescript-eslint/no-explicit-any": [ + 0 + ], + "@typescript-eslint/ban-ts-comment": [ + 1 + ], + "quotes": [ + 2, + "single", + { + "allowTemplateLiterals": true, + "avoidEscape": false + } + ], + "indent": [ + 2, + 2 + ], + "prefer-arrow-callback": [ + 2, + { + "allowNamedFunctions": false, + "allowUnboundThis": true + } + ], + "curly": [ + 2, + "multi" + ], + "prefer-template": [ + 1 + ] } }, "dependencies": { - "base64-inline-loader": "1.1.1", - "date-fns": "2.25.0", + "date-fns": "2.29.3", "jed": "1.1.1", - "preact": "^10.5.15", - "preact-render-to-string": "^5.1.19", - "preact-router": "^3.2.1", + "preact-render-to-string": "^5.2.6", + "preact-router": "^4.1.0", "qrcode-generator": "^1.4.4", - "swr": "1.1" + "swr": "~1.3.0" }, "devDependencies": { - "@babel/core": "^7.13.16", - "@babel/plugin-transform-react-jsx": "^7.12.13", - "@babel/plugin-transform-react-jsx-source": "^7.12.13", - "@babel/preset-env": "^7.16.7", "@creativebulma/bulma-tooltip": "^1.2.0", "@gnu-taler/pogen": "^0.0.5", - "@storybook/addon-a11y": "6.2.9", - "@storybook/addon-actions": "6.2.9", - "@storybook/addon-essentials": "6.2.9", - "@storybook/addon-links": "6.2.9", - "@storybook/preact": "6.2.9", - "@storybook/preset-scss": "^1.0.3", - "@testing-library/jest-dom": "^5.16.1", - "@testing-library/preact": "^2.0.1", - "@testing-library/preact-hooks": "^1.1.0", - "@types/enzyme": "^3.10.10", - "@types/jest": "^27.0.2", - "@typescript-eslint/eslint-plugin": "^5.3.0", - "@typescript-eslint/parser": "^5.3.0", - "babel-loader": "^8.2.2", - "base64-inline-loader": "^1.1.1", - "bulma": "^0.9.3", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", + "bulma": "^0.9.4", "bulma-checkbox": "^1.1.1", "bulma-radio": "^1.1.1", - "enzyme": "^3.11.0", - "enzyme-adapter-preact-pure": "^3.2.0", - "eslint": "^8.1.0", + "esbuild": "^0.15.12", + "esbuild-sass-plugin": "^2.4.0", + "eslint": "^8.26.0", "eslint-config-preact": "^1.2.0", - "html-webpack-inline-chunk-plugin": "^1.1.1", - "html-webpack-inline-source-plugin": "0.0.10", - "html-webpack-skip-assets-plugin": "^1.0.1", - "inline-chunk-html-plugin": "^1.1.1", - "jest": "^27.3.1", - "jest-fetch-mock": "^3.0.3", - "jest-preset-preact": "^4.0.5", - "jest-watch-typeahead": "^1.0.0", - "jest-environment-jsdom": "^27.4.6", - "jssha": "^3.2.0", "po2json": "^0.4.5", - "preact-cli": "3.0.5", - "sass": "1.32.13", - "sass-loader": "^10", - "script-ext-html-webpack-plugin": "^2.1.5", - "sirv-cli": "^1.0.14", + "preact": "10.11.2", "typescript": "^4.4.4" - }, - "jest": { - "preset": "jest-preset-preact", - "setupFiles": [ - "<rootDir>/tests/__mocks__/browserMocks.ts", - "<rootDir>/tests/__mocks__/setupTests.ts" - ] } } diff --git a/packages/demobank-ui/preact.config.js b/packages/demobank-ui/preact.config.js deleted file mode 100644 index 8e640f3ff..000000000 --- a/packages/demobank-ui/preact.config.js +++ /dev/null @@ -1,70 +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 { DefinePlugin } from 'webpack'; - -import pack from './package.json'; -import * as cp from 'child_process'; - -const commitHash = cp.execSync('git rev-parse --short HEAD').toString(); - -export default { - webpack(config, env, helpers) { - // ensure that process.env will not be undefined on runtime - config.node.process = 'mock' - - // add __VERSION__ to be use in the html - config.plugins.push( - new DefinePlugin({ - 'process.env.__VERSION__': JSON.stringify(env.isProd ? pack.version : `dev-${commitHash}`) , - }), - ); - - // suddenly getting out of memory error from build process, error below [1] - // FIXME: remove preact-cli, use rollup - let { index } = helpers.getPluginsByName(config, 'WebpackFixStyleOnlyEntriesPlugin')[0] - config.plugins.splice(index, 1) - } -} - - - -/* [1] from this error decided to remove plugin 'webpack-fix-style-only-entries - leaving this error for future reference - - -<--- Last few GCs ---> - -[32479:0x2e01870] 19969 ms: Mark-sweep 1869.4 (1950.2) -> 1443.1 (1504.1) MB, 497.5 / 0.0 ms (average mu = 0.631, current mu = 0.455) allocation failure scavenge might not succeed -[32479:0x2e01870] 21907 ms: Mark-sweep 2016.9 (2077.9) -> 1628.6 (1681.4) MB, 1596.0 / 0.0 ms (average mu = 0.354, current mu = 0.176) allocation failure scavenge might not succeed - -<--- JS stacktrace ---> - -==== JS stack trace ========================================= - - 0: ExitFrame [pc: 0x13cf099] -Security context: 0x2f4ca66c08d1 <JSObject> - 1: /* anonymous * / [0x35d05555b4b9] [...path/merchant-backoffice/node_modules/.pnpm/webpack-fix-style-only-entries@0.5.2/node_modules/webpack-fix-style-only-entries/index.js:~80] [pc=0x2145e699d1a4](this=0x1149465410e9 <GlobalObject Object map = 0xff481b5b5f9>,0x047e52e36a49 <Dependency map = 0x1ed1fe41cd19>) - 2: arguments adaptor frame: 3... - -FATAL ERROR: invalid array length Allocation failed - JavaScript heap out of memory - -*/
\ No newline at end of file diff --git a/packages/demobank-ui/preact.mock.js b/packages/demobank-ui/preact.mock.js deleted file mode 100644 index dc3ceb66d..000000000 --- a/packages/demobank-ui/preact.mock.js +++ /dev/null @@ -1,55 +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 { DefinePlugin, ProvidePlugin } from 'webpack'; - -import pack from './package.json'; -import * as cp from 'child_process'; - -const commitHash = cp.execSync('git rev-parse --short HEAD').toString(); -import path from 'path'; - -export default { - webpack(config, env, helpers) { - // Ensure that process.env will not be undefined at runtime. - config.node.process = 'mock' - let DEMO_SITES = { - "Blog": process.env.TALER_ENV_URL_MERCHANT_BLOG, - "Donations": process.env.TALER_ENV_URL_MERCHANT_DONATIONS, - "Survey": process.env.TALER_ENV_URL_MERCHANT_SURVEY, - "Landing": process.env.TALER_ENV_URL_INTRO, - "Bank": process.env.TALER_ENV_URL_BANK, - } - console.log("demo links found", DEMO_SITES); - // Add __VERSION__ to be use in the html. - config.plugins.push( - new DefinePlugin({ - 'process.env.__VERSION__': JSON.stringify(env.isProd ? pack.version : `dev-${commitHash}`) , - }), - // 'window' gets mocked to point at a running euFin instance. - new ProvidePlugin({window: path.resolve("mocks/window")}), - new DefinePlugin({"DEMO_SITES": JSON.stringify(DEMO_SITES)}) - ); - - let { index } = helpers.getPluginsByName(config, 'WebpackFixStyleOnlyEntriesPlugin')[0] - config.plugins.splice(index, 1) - } -} diff --git a/packages/demobank-ui/preact.single-config.js b/packages/demobank-ui/preact.single-config.js deleted file mode 100644 index 0fb6f1d0e..000000000 --- a/packages/demobank-ui/preact.single-config.js +++ /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 defaultConfig from './preact.config' - -export default { - webpack(config, env, helpers, options) { - defaultConfig.webpack(config, env, helpers, options) - - //1. check no file is under /routers or /component/{routers,async} to prevent async components - // https://github.com/preactjs/preact-cli#route-based-code-splitting - - //2. remove devtools to prevent sourcemaps - config.devtool = false - - //3. change assetLoader to load assets inline - const loaders = helpers.getLoaders(config) - const assetsLoader = loaders.find(lo => lo.rule.test.test('something.woff')) - if (assetsLoader) { - assetsLoader.rule.use = 'base64-inline-loader' - assetsLoader.rule.loader = undefined - } - - //4. remove critters - //critters remove the css bundle from htmlWebpackPlugin.files.css - //for now, pushing all the content into the html is enough - const crittersWrapper = helpers.getPluginsByName(config, 'Critters') - if (crittersWrapper && crittersWrapper.length > 0) { - const [{ index }] = crittersWrapper - config.plugins.splice(index, 1) - } - - //5. remove favicon from src/assets - - //6. remove performance hints since we now that this is going to be big - if (config.performance) { - config.performance.hints = false - } - - //7. template.html should have a favicon and add js/css content - } -} diff --git a/packages/demobank-ui/remove-link-stylesheet.sh b/packages/demobank-ui/remove-link-stylesheet.sh deleted file mode 100755 index d3376b8e6..000000000 --- a/packages/demobank-ui/remove-link-stylesheet.sh +++ /dev/null @@ -1,8 +0,0 @@ -# This script has been placed in the public domain. - -FILE=$(ls build/bundle.*.css) -BUNDLE=${FILE#build} -grep -q '<link href="'$BUNDLE'" rel="stylesheet">' build/index.html || { echo bundle $BUNDLE not found in index.html; exit 1; } -echo -n Removing link from index.html ... -sed 's_<link href="'$BUNDLE'" rel="stylesheet">__' -i build/index.html -echo done diff --git a/packages/demobank-ui/src/assets/icons/auth_method/email.svg b/packages/demobank-ui/src/assets/icons/auth_method/email.svg deleted file mode 100644 index 3e44b8779..000000000 --- a/packages/demobank-ui/src/assets/icons/auth_method/email.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0l-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z"/></svg>
\ No newline at end of file diff --git a/packages/demobank-ui/src/assets/icons/auth_method/postal.svg b/packages/demobank-ui/src/assets/icons/auth_method/postal.svg deleted file mode 100644 index 3787b8350..000000000 --- a/packages/demobank-ui/src/assets/icons/auth_method/postal.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 15h2v2h-2zM17 11h2v2h-2zM17 7h2v2h-2zM13.74 7l1.26.84V7z"/><path d="M10 3v1.51l2 1.33V5h9v14h-4v2h6V3z"/><path d="M8.17 5.7L15 10.25V21H1V10.48L8.17 5.7zM10 19h3v-7.84L8.17 8.09 3 11.38V19h3v-6h4v6z"/></svg>
\ No newline at end of file diff --git a/packages/demobank-ui/src/assets/icons/auth_method/question.svg b/packages/demobank-ui/src/assets/icons/auth_method/question.svg deleted file mode 100644 index a346556b2..000000000 --- a/packages/demobank-ui/src/assets/icons/auth_method/question.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11 23.59v-3.6c-5.01-.26-9-4.42-9-9.49C2 5.26 6.26 1 11.5 1S21 5.26 21 10.5c0 4.95-3.44 9.93-8.57 12.4l-1.43.69zM11.5 3C7.36 3 4 6.36 4 10.5S7.36 18 11.5 18H13v2.3c3.64-2.3 6-6.08 6-9.8C19 6.36 15.64 3 11.5 3zm-1 11.5h2v2h-2zm2-1.5h-2c0-3.25 3-3 3-5 0-1.1-.9-2-2-2s-2 .9-2 2h-2c0-2.21 1.79-4 4-4s4 1.79 4 4c0 2.5-3 2.75-3 5z"/></svg>
\ No newline at end of file diff --git a/packages/demobank-ui/src/assets/icons/auth_method/sms.svg b/packages/demobank-ui/src/assets/icons/auth_method/sms.svg deleted file mode 100644 index ed15679bf..000000000 --- a/packages/demobank-ui/src/assets/icons/auth_method/sms.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17 1.01L7 1c-1.1 0-1.99.9-1.99 2v18c0 1.1.89 2 1.99 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"/></svg>
\ No newline at end of file diff --git a/packages/demobank-ui/src/assets/icons/auth_method/video.svg b/packages/demobank-ui/src/assets/icons/auth_method/video.svg deleted file mode 100644 index 69de5e0b4..000000000 --- a/packages/demobank-ui/src/assets/icons/auth_method/video.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M18,10.48V6c0-1.1-0.9-2-2-2H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-4.48l4,3.98v-11L18,10.48z M16,9.69V18H4V6h12V9.69z"/><circle cx="10" cy="10" r="2"/><path d="M14,15.43c0-0.81-0.48-1.53-1.22-1.85C11.93,13.21,10.99,13,10,13c-0.99,0-1.93,0.21-2.78,0.58C6.48,13.9,6,14.62,6,15.43 V16h8V15.43z"/></g></g></svg>
\ No newline at end of file diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index 5338c548e..ad8a45e9a 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -1,7 +1,6 @@ -import { FunctionalComponent, h } from 'preact'; -import { TranslationProvider } from '../context/translation'; -import { BankHome } from '../pages/home/index'; -import { Menu } from './menu'; +import { FunctionalComponent } from "preact"; +import { TranslationProvider } from "../context/translation"; +import { BankHome } from "../pages/home/index"; const App: FunctionalComponent = () => { return ( diff --git a/packages/demobank-ui/src/hooks/index.ts b/packages/demobank-ui/src/hooks/index.ts index 795df909d..2126cada5 100644 --- a/packages/demobank-ui/src/hooks/index.ts +++ b/packages/demobank-ui/src/hooks/index.ts @@ -19,14 +19,14 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { StateUpdater, useState } from 'preact/hooks'; +import { StateUpdater, useState } from "preact/hooks"; export type ValueOrFunction<T> = T | ((p: T) => T); const calculateRootPath = () => { const rootPath = typeof window !== undefined ? window.location.origin + window.location.pathname - : '/'; + : "/"; return rootPath; }; @@ -34,14 +34,14 @@ export function useBackendURL( url?: string, ): [string, boolean, StateUpdater<string>, () => void] { const [value, setter] = useNotNullLocalStorage( - 'backend-url', + "backend-url", url || calculateRootPath(), ); - const [triedToLog, setTriedToLog] = useLocalStorage('tried-login'); + const [triedToLog, setTriedToLog] = useLocalStorage("tried-login"); const checkedSetter = (v: ValueOrFunction<string>) => { - setTriedToLog('yes'); - return setter((p) => (v instanceof Function ? v(p) : v).replace(/\/$/, '')); + setTriedToLog("yes"); + return setter((p) => (v instanceof Function ? v(p) : v).replace(/\/$/, "")); }; const resetBackend = () => { @@ -53,8 +53,8 @@ export function useBackendURL( export function useBackendDefaultToken(): [ string | undefined, StateUpdater<string | undefined>, - ] { - return useLocalStorage('backend-token'); +] { + return useLocalStorage("backend-token"); } export function useBackendInstanceToken( @@ -64,59 +64,60 @@ export function useBackendInstanceToken( const [defaultToken, defaultSetToken] = useBackendDefaultToken(); // instance named 'default' use the default token - if (id === 'default') - return [defaultToken, defaultSetToken]; + if (id === "default") return [defaultToken, defaultSetToken]; return [token, setToken]; } export function useLang(initial?: string): [string, StateUpdater<string>] { const browserLang = - typeof window !== 'undefined' + typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined; - const defaultLang = (browserLang || initial || 'en').substring(0, 2); - const [value, setValue] = useNotNullLocalStorage('lang-preference', defaultLang); - function updateValue(newValue: (string | ((v: string) => string))) { + const defaultLang = (browserLang || initial || "en").substring(0, 2); + const [value, setValue] = useNotNullLocalStorage( + "lang-preference", + defaultLang, + ); + function updateValue(newValue: string | ((v: string) => string)) { if (document.body.parentElement) { - const htmlElement = document.body.parentElement - if (typeof newValue === 'string') { + const htmlElement = document.body.parentElement; + if (typeof newValue === "string") { htmlElement.lang = newValue; - setValue(newValue) - } else if (typeof newValue === 'function') + setValue(newValue); + } else if (typeof newValue === "function") setValue((old) => { - const nv = newValue(old) + const nv = newValue(old); htmlElement.lang = nv; - return nv - }) - } else setValue(newValue) + return nv; + }); + } else setValue(newValue); } - return [value, updateValue] + return [value, updateValue]; } export function useLocalStorage( key: string, initialValue?: string, ): [string | undefined, StateUpdater<string | undefined>] { - const [storedValue, setStoredValue] = useState<string | undefined>((): - | string - | undefined => { - return typeof window !== 'undefined' - ? window.localStorage.getItem(key) || initialValue - : initialValue; - }); + const [storedValue, setStoredValue] = useState<string | undefined>( + (): string | undefined => { + return typeof window !== "undefined" + ? window.localStorage.getItem(key) || initialValue + : initialValue; + }, + ); const setValue = ( value?: string | ((val?: string) => string | undefined), ) => { setStoredValue((p) => { + console.log("calling setStoredValue"); + console.log(window); const toStore = value instanceof Function ? value(p) : value; - if (typeof window !== 'undefined') - if (!toStore) - window.localStorage.removeItem(key); - else - window.localStorage.setItem(key, toStore); - + if (typeof window !== "undefined") + if (!toStore) window.localStorage.removeItem(key); + else window.localStorage.setItem(key, toStore); return toStore; }); @@ -130,7 +131,7 @@ export function useNotNullLocalStorage( initialValue: string, ): [string, StateUpdater<string>] { const [storedValue, setStoredValue] = useState<string>((): string => { - return typeof window !== 'undefined' + return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; }); @@ -138,13 +139,9 @@ export function useNotNullLocalStorage( const setValue = (value: string | ((val: string) => string)) => { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); - if (typeof window !== 'undefined') - if (!valueToStore) - window.localStorage.removeItem(key); - else - window.localStorage.setItem(key, valueToStore); - - + if (typeof window !== "undefined") + if (!valueToStore) window.localStorage.removeItem(key); + else window.localStorage.setItem(key, valueToStore); }; return [storedValue, setValue]; diff --git a/packages/demobank-ui/src/index.html b/packages/demobank-ui/src/index.html new file mode 100644 index 000000000..3df1ceb5c --- /dev/null +++ b/packages/demobank-ui/src/index.html @@ -0,0 +1,34 @@ +<!-- + This file is part of GNU Taler + (C) 2021--2022 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 +--> +<!DOCTYPE html> +<html lang="en" class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <meta name="mobile-web-app-capable" content="yes"> + <meta name="apple-mobile-web-app-capable" content="yes"> + + <link rel="icon" href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> + <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> + </head> + + <body> + <div id="app"></div> + <script type="module" src="index.tsx"></script> + </body> +</html> diff --git a/packages/demobank-ui/src/index.tsx b/packages/demobank-ui/src/index.tsx index a2f7b30f8..9aa79f4aa 100644 --- a/packages/demobank-ui/src/index.tsx +++ b/packages/demobank-ui/src/index.tsx @@ -1,3 +1,7 @@ -import App from './components/app'; - +import App from "./components/app"; export default App; +import { render, h, Fragment } from "preact"; + +const app = document.getElementById("app"); + +render(<App />, app as any); diff --git a/packages/demobank-ui/src/pages/home/index.tsx b/packages/demobank-ui/src/pages/home/index.tsx index 2116e1f89..8f328b115 100644 --- a/packages/demobank-ui/src/pages/home/index.tsx +++ b/packages/demobank-ui/src/pages/home/index.tsx @@ -17,6 +17,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import useSWR, { SWRConfig as _SWRConfig, useSWRConfig } from "swr"; import { h, Fragment, VNode, createContext } from "preact"; + import { useRef, useState, @@ -24,20 +25,37 @@ import { StateUpdater, useContext, } from "preact/hooks"; -import { Buffer } from "buffer"; -import { useTranslator, Translate } from "../../i18n"; -import { QR } from "../../components/QR"; -import { useNotNullLocalStorage, useLocalStorage } from "../../hooks"; +import { useTranslator, Translate } from "../../i18n/index.js"; +import { QR } from "../../components/QR.js"; +import { useNotNullLocalStorage, useLocalStorage } from "../../hooks/index.js"; import "../../scss/main.scss"; import talerLogo from "../../assets/logo-white.svg"; -import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector"; +import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js"; // FIXME: Fix usages of SWRConfig, doing this isn't the best practice (but hey, it works for now) const SWRConfig = _SWRConfig as any; -const UI_ALLOW_REGISTRATIONS = "__LIBEUFIN_UI_ALLOW_REGISTRATIONS__" ?? 1; -const UI_IS_DEMO = "__LIBEUFIN_UI_IS_DEMO__" ?? 0; -const UI_BANK_NAME = "__LIBEUFIN_UI_BANK_NAME__" ?? "Taler Bank"; +/** + * If the first argument does not look like a placeholder, return it. + * Otherwise, return the default. + * + * Useful for placeholder string replacements optionally + * done as part of the build system. + */ +const replacementOrDefault = (x: string, defaultVal: string) => { + if (x.startsWith("__")) { + return defaultVal; + } + return x; +}; + +const UI_ALLOW_REGISTRATIONS = + replacementOrDefault("__LIBEUFIN_UI_ALLOW_REGISTRATIONS__", "1") == "1"; +const UI_IS_DEMO = replacementOrDefault("__LIBEUFIN_UI_IS_DEMO__", "0") == "1"; +const UI_BANK_NAME = replacementOrDefault( + "__LIBEUFIN_UI_BANK_NAME__", + "Taler Bank", +); /** * FIXME: @@ -156,15 +174,6 @@ function maybeDemoContent(content: VNode) { if (UI_IS_DEMO) return content; } -async function fetcher(url: string) { - return fetch(url).then((r) => r.json()); -} - -function genCaptchaNumbers(): string { - return `${Math.floor(Math.random() * 10)} + ${Math.floor( - Math.random() * 10, - )}`; -} /** * Bring the state to show the public accounts page. */ @@ -276,22 +285,26 @@ function prepareHeaders(username: string, password: string) { const headers = new Headers(); headers.append( "Authorization", - `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`, + `Basic ${window.btoa(`${username}:${password}`)}`, ); headers.append("Content-Type", "application/json"); return headers; } -// Window can be mocked this way: -// https://gist.github.com/theKashey/07090691c0a4680ed773375d8dbeebc1#file-webpack-conf-js -// That allows the app to be pointed to a arbitrary -// euFin backend when launched via "pnpm dev". -const getRootPath = () => { +const getBankBackendBaseUrl = () => { + const overrideUrl = localStorage.getItem("bank-base-url"); + if (overrideUrl) { + console.log( + `using bank base URL ${overrideUrl} (override via bank-base-url localStorage)`, + ); + return overrideUrl; + } const maybeRootPath = typeof window !== undefined ? window.location.origin + window.location.pathname : "/"; if (!maybeRootPath.endsWith("/")) return `${maybeRootPath}/`; + console.log(`using bank base URL (${maybeRootPath})`); return maybeRootPath; }; @@ -785,7 +798,7 @@ async function loginCall( * let the Account component request the balance to check * whether the credentials are valid. */ pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: true })); - let baseUrl = getRootPath(); + let baseUrl = getBankBackendBaseUrl(); if (!baseUrl.endsWith("/")) baseUrl += "/"; backendStateSetter((prevState) => ({ @@ -813,7 +826,7 @@ async function registrationCall( backendStateSetter: StateUpdater<BackendStateTypeOpt>, pageStateSetter: StateUpdater<PageStateType>, ) { - let baseUrl = getRootPath(); + let baseUrl = getBankBackendBaseUrl(); /** * If the base URL doesn't end with slash and the path * is not empty, then the concatenation made by URL() @@ -873,19 +886,6 @@ async function registrationCall( * Functional components. * *************************/ -function Currency(): VNode { - const { data, error } = useSWR( - `${getRootPath()}integration-api/config`, - fetcher, - ); - if (typeof error !== "undefined") - return <b>error: currency could not be retrieved</b>; - - if (typeof data === "undefined") return <Fragment>"..."</Fragment>; - console.log("found bank config", data); - return data.currency; -} - function ErrorBanner(Props: any): VNode | null { const [pageState, pageStateSetter] = Props.pageState; const i18n = useTranslator(); @@ -2043,10 +2043,7 @@ function Account(Props: any): VNode { function SWRWithCredentials(props: any): VNode { const { username, password, backendUrl } = props; const headers = new Headers(); - headers.append( - "Authorization", - `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`, - ); + headers.append("Authorization", `Basic ${btoa(`${username}:${password}`)}`); console.log("Likely backend base URL", backendUrl); return ( <SWRConfig @@ -2179,13 +2176,11 @@ function PublicHistories(Props: any): VNode { export function BankHome(): VNode { const [backendState, backendStateSetter] = useBackendState(); const [pageState, pageStateSetter] = usePageState(); - const [accountState, accountStateSetter] = useAccountState(); - const setTxPageNumber = useTransactionPageNumber()[1]; const i18n = useTranslator(); if (pageState.showPublicHistories) return ( - <SWRWithoutCredentials baseUrl={getRootPath()}> + <SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}> <PageContext.Provider value={[pageState, pageStateSetter]}> <BankFrame> <PublicHistories pageStateSetter={pageStateSetter}> diff --git a/packages/demobank-ui/src/scss/pure.scss b/packages/demobank-ui/src/scss/pure.scss index 0d804d6bd..83fcadce7 100644 --- a/packages/demobank-ui/src/scss/pure.scss +++ b/packages/demobank-ui/src/scss/pure.scss @@ -1,404 +1,409 @@ /*! -Pure v0.6.2 +Pure v2.2.0 Copyright 2013 Yahoo! Licensed under the BSD License. -https://github.com/yahoo/pure/blob/master/LICENSE.md +https://github.com/pure-css/pure/blob/master/LICENSE */ /*! -normalize.css v^3.0 | MIT License | git.io/normalize +normalize.css v | MIT License | https://necolas.github.io/normalize.css/ Copyright (c) Nicolas Gallagher and Jonathan Neal */ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + /** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS and IE text size adjust after device orientation change, - * without disabling user zoom. + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. */ -html { - font-family: sans-serif; - /* 1 */ - -ms-text-size-adjust: 100%; - /* 2 */ - -webkit-text-size-adjust: 100%; - /* 2 */ } + + html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ /** - * Remove default margin. + * Remove the margin in all browsers. */ + body { - margin: 0; } + margin: 0; +} -/* HTML5 display definitions - ========================================================================== */ /** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. + * Render the `main` element consistently in IE. */ -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; } + +main { + display: block; +} /** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. */ -audio, -canvas, -progress, -video { - display: inline-block; - /* 1 */ - vertical-align: baseline; - /* 2 */ } + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ /** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. */ -audio:not([controls]) { - display: none; - height: 0; } + +hr { + -webkit-box-sizing: content-box; + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} /** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. */ -[hidden], -template { - display: none; } -/* Links +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics ========================================================================== */ + /** - * Remove the gray background color from active links in IE 10. + * Remove the gray background on active links in IE 10. */ + a { - background-color: transparent; } + background-color: transparent; +} /** - * Improve readability of focused elements when they are also in an - * active/hover state. + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ -a:active, -a:hover { - outline: 0; } -/* Text-level semantics - ========================================================================== */ -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ abbr[title] { - border-bottom: 1px dotted; } + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; /* 2 */ +} /** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + * Add the correct font weight in Chrome, Edge, and Safari. */ + b, strong { - font-weight: bold; } + font-weight: bolder; +} /** - * Address styling not present in Safari and Chrome. + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. */ -dfn { - font-style: italic; } -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ -h1 { - font-size: 2em; - margin: 0.67em 0; } +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} /** - * Address styling not present in IE 8/9. + * Add the correct font size in all browsers. */ -mark { - background: #ff0; - color: #000; } -/** - * Address inconsistent and variable font size in all browsers. - */ small { - font-size: 80%; } + font-size: 80%; +} /** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. */ + sub, sup { font-size: 75%; line-height: 0; position: relative; - vertical-align: baseline; } - -sup { - top: -0.5em; } + vertical-align: baseline; +} sub { - bottom: -0.25em; } + bottom: -0.25em; +} -/* Embedded content - ========================================================================== */ -/** - * Remove border when inside `a` element in IE 8/9/10. - */ -img { - border: 0; } - -/** - * Correct overflow not hidden in IE 9/10/11. - */ -svg:not(:root) { - overflow: hidden; } +sup { + top: -0.5em; +} -/* Grouping content +/* Embedded content ========================================================================== */ -/** - * Address margin not present in IE 8/9 and Safari. - */ -figure { - margin: 1em 40px; } - -/** - * Address differences between Firefox and other browsers. - */ -hr { - box-sizing: content-box; - height: 0; } /** - * Contain overflow in all browsers. + * Remove the border on images inside links in IE 10. */ -pre { - overflow: auto; } -/** - * Address odd `em`-unit font size rendering in all browsers. - */ -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; } +img { + border-style: none; +} /* Forms ========================================================================== */ + /** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. */ + button, input, optgroup, select, textarea { - color: inherit; - /* 1 */ - font: inherit; - /* 2 */ - margin: 0; - /* 3 */ } + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} /** - * Address `overflow` set to `hidden` in IE 8/9/10/11. + * Show the overflow in IE. + * 1. Show the overflow in Edge. */ -button { - overflow: visible; } + +button, +input { /* 1 */ + overflow: visible; +} /** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. */ + button, -select { - text-transform: none; } +select { /* 1 */ + text-transform: none; +} /** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. + * Correct the inability to style clickable types in iOS and Safari. */ + button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { +[type="button"], +[type="reset"], +[type="submit"] { -webkit-appearance: button; - /* 2 */ - cursor: pointer; - /* 3 */ } +} /** - * Re-set default cursor for disabled elements. + * Remove the inner border and padding in Firefox. */ -button[disabled], -html input[disabled] { - cursor: default; } + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} /** - * Remove inner padding and border in Firefox 4+. + * Restore the focus styles unset by the previous rule. */ -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; } + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} /** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. + * Correct the padding in Firefox. */ -input { - line-height: normal; } + +fieldset { + padding: 0.35em 0.75em 0.625em; +} /** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. */ -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; - /* 1 */ - padding: 0; - /* 2 */ } + +legend { + -webkit-box-sizing: border-box; + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} /** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. + * Add the correct vertical alignment in Chrome, Firefox, and Opera. */ -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; } + +progress { + vertical-align: baseline; +} /** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + * Remove the default vertical scrollbar in IE 10+. */ -input[type="search"] { - -webkit-appearance: textfield; - /* 1 */ - box-sizing: content-box; - /* 2 */ } + +textarea { + overflow: auto; +} /** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. */ -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; } + +[type="checkbox"], +[type="radio"] { + -webkit-box-sizing: border-box; + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} /** - * Define consistent border, margin, and padding. + * Correct the cursor style of increment and decrement buttons in Chrome. */ -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; } + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} /** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. */ -legend { - border: 0; - /* 1 */ - padding: 0; - /* 2 */ } + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} /** - * Remove default vertical scrollbar in IE 8/9/10/11. + * Remove the inner padding in Chrome and Safari on macOS. */ -textarea { - overflow: auto; } + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} /** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. */ -optgroup { - font-weight: bold; } -/* Tables +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + /** - * Remove most spacing between table cells. + * Add the correct display in IE 10+. */ -table { - border-collapse: collapse; - border-spacing: 0; } -td, -th { - padding: 0; } +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} /*csslint important:false*/ + /* ========================================================================== Pure Base Extras ========================================================================== */ + /** * Extra rules that Pure adds on top of Normalize.css */ + +html { + font-family: sans-serif; +} + /** * Always hide an element when it has the `hidden` HTML attribute. */ + .hidden, [hidden] { - display: none !important; } + display: none !important; +} /** * Add this class to an image to make it fit within it's fluid parent wrapper while maintaining * aspect ratio. */ .pure-img { - max-width: 100%; - height: auto; - display: block; } + max-width: 100%; + height: auto; + display: block; +} /*csslint regex-selectors:false, known-properties:false, duplicate-properties:false*/ + .pure-g { - letter-spacing: -0.31em; - /* Webkit: collapse white-space between units */ - *letter-spacing: normal; - /* reset IE < 8 */ - *word-spacing: -0.43em; - /* IE < 8: collapse white-space between units */ - text-rendering: optimizespeed; - /* Webkit: fixes text-rendering: optimizeLegibility */ + letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ + text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ + /* Sets the font stack to fonts known to work properly with the above letter - and word spacings. See: https://github.com/yahoo/pure/issues/41/ + and word spacings. See: https://github.com/pure-css/pure/issues/41/ The following font stack makes Pure Grids work on all known environments. @@ -412,48 +417,53 @@ th { * Helvetica, Arial, sans-serif: Common font stack on OS X and Windows. */ - font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; - /* Use flexbox when possible to avoid `letter-spacing` side-effects. */ - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-flow: row wrap; - -ms-flex-flow: row wrap; - flex-flow: row wrap; - /* Prevents distributing space between rows */ - -webkit-align-content: flex-start; - -ms-flex-line-pack: start; - align-content: flex-start; } + font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; + + /* Use flexbox when possible to avoid `letter-spacing` side-effects. */ + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + + /* Prevents distributing space between rows */ + -ms-flex-line-pack: start; + align-content: flex-start; +} /* IE10 display: -ms-flexbox (and display: flex in IE 11) does not work inside a table; fall back to block and rely on font hack */ @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { - table .pure-g { - display: block; } } + table .pure-g { + display: block; + } +} + /* Opera as of 12 on Windows needs word-spacing. The ".opera-only" selector is used to prevent actual prefocus styling and is not required in markup. */ .opera-only :-o-prefocus, .pure-g { - word-spacing: -0.43em; } + word-spacing: -0.43em; +} .pure-u { - display: inline-block; - *display: inline; - /* IE < 8: fake inline-block */ - zoom: 1; - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto; } + display: inline-block; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto; +} /* Resets the font family back to the OS/browser's default sans-serif font, this the same font stack that Normalize.css sets for the `body`. */ -.pure-g [class*="pure-u"] { - font-family: sans-serif; } +.pure-g [class *= "pure-u"] { + font-family: sans-serif; +} .pure-u-1, .pure-u-1-1, @@ -501,260 +511,255 @@ this the same font stack that Normalize.css sets for the `body`. .pure-u-22-24, .pure-u-23-24, .pure-u-24-24 { - display: inline-block; - *display: inline; - zoom: 1; - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto; } + display: inline-block; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto; +} .pure-u-1-24 { - width: 4.1667%; - *width: 4.1357%; } + width: 4.1667%; +} .pure-u-1-12, .pure-u-2-24 { - width: 8.3333%; - *width: 8.3023%; } + width: 8.3333%; +} .pure-u-1-8, .pure-u-3-24 { - width: 12.5000%; - *width: 12.4690%; } + width: 12.5000%; +} .pure-u-1-6, .pure-u-4-24 { - width: 16.6667%; - *width: 16.6357%; } + width: 16.6667%; +} .pure-u-1-5 { - width: 20%; - *width: 19.9690%; } + width: 20%; +} .pure-u-5-24 { - width: 20.8333%; - *width: 20.8023%; } + width: 20.8333%; +} .pure-u-1-4, .pure-u-6-24 { - width: 25%; - *width: 24.9690%; } + width: 25%; +} .pure-u-7-24 { - width: 29.1667%; - *width: 29.1357%; } + width: 29.1667%; +} .pure-u-1-3, .pure-u-8-24 { - width: 33.3333%; - *width: 33.3023%; } + width: 33.3333%; +} .pure-u-3-8, .pure-u-9-24 { - width: 37.5000%; - *width: 37.4690%; } + width: 37.5000%; +} .pure-u-2-5 { - width: 40%; - *width: 39.9690%; } + width: 40%; +} .pure-u-5-12, .pure-u-10-24 { - width: 41.6667%; - *width: 41.6357%; } + width: 41.6667%; +} .pure-u-11-24 { - width: 45.8333%; - *width: 45.8023%; } + width: 45.8333%; +} .pure-u-1-2, .pure-u-12-24 { - width: 50%; - *width: 49.9690%; } + width: 50%; +} .pure-u-13-24 { - width: 54.1667%; - *width: 54.1357%; } + width: 54.1667%; +} .pure-u-7-12, .pure-u-14-24 { - width: 58.3333%; - *width: 58.3023%; } + width: 58.3333%; +} .pure-u-3-5 { - width: 60%; - *width: 59.9690%; } + width: 60%; +} .pure-u-5-8, .pure-u-15-24 { - width: 62.5000%; - *width: 62.4690%; } + width: 62.5000%; +} .pure-u-2-3, .pure-u-16-24 { - width: 66.6667%; - *width: 66.6357%; } + width: 66.6667%; +} .pure-u-17-24 { - width: 70.8333%; - *width: 70.8023%; } + width: 70.8333%; +} .pure-u-3-4, .pure-u-18-24 { - width: 75%; - *width: 74.9690%; } + width: 75%; +} .pure-u-19-24 { - width: 79.1667%; - *width: 79.1357%; } + width: 79.1667%; +} .pure-u-4-5 { - width: 80%; - *width: 79.9690%; } + width: 80%; +} .pure-u-5-6, .pure-u-20-24 { - width: 83.3333%; - *width: 83.3023%; } + width: 83.3333%; +} .pure-u-7-8, .pure-u-21-24 { - width: 87.5000%; - *width: 87.4690%; } + width: 87.5000%; +} .pure-u-11-12, .pure-u-22-24 { - width: 91.6667%; - *width: 91.6357%; } + width: 91.6667%; +} .pure-u-23-24 { - width: 95.8333%; - *width: 95.8023%; } + width: 95.8333%; +} .pure-u-1, .pure-u-1-1, .pure-u-5-5, .pure-u-24-24 { - width: 100%; } - + width: 100%; +} .pure-button { - /* Structure */ - display: inline-block; - zoom: 1; - line-height: normal; - white-space: nowrap; - vertical-align: middle; - text-align: center; - cursor: pointer; - -webkit-user-drag: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - box-sizing: border-box; } + /* Structure */ + display: inline-block; + line-height: normal; + white-space: nowrap; + vertical-align: middle; + text-align: center; + cursor: pointer; + -webkit-user-drag: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} /* Firefox: Get rid of the inner focus border */ .pure-button::-moz-focus-inner { - padding: 0; - border: 0; } + padding: 0; + border: 0; +} /* Inherit .pure-g styles */ .pure-button-group { - letter-spacing: -0.31em; - /* Webkit: collapse white-space between units */ - *letter-spacing: normal; - /* reset IE < 8 */ - *word-spacing: -0.43em; - /* IE < 8: collapse white-space between units */ - text-rendering: optimizespeed; - /* Webkit: fixes text-rendering: optimizeLegibility */ } + letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ + text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ +} .opera-only :-o-prefocus, .pure-button-group { - word-spacing: -0.43em; } + word-spacing: -0.43em; +} .pure-button-group .pure-button { - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto; } + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto; +} /*csslint outline-none:false*/ + .pure-button { - font-family: inherit; - font-size: 100%; - padding: 0.5em 1em; - color: #444; - /* rgba not supported (IE 8) */ - color: rgba(0, 0, 0, 0.8); - /* rgba supported */ - border: 1px solid #999; - /*IE 6/7/8*/ - border: none rgba(0, 0, 0, 0); - /*IE9 + everything else*/ - background-color: #E6E6E6; - text-decoration: none; - border-radius: 2px; } + font-family: inherit; + font-size: 100%; + padding: 0.5em 1em; + color: rgba(0, 0, 0, 0.80); + border: none rgba(0, 0, 0, 0); + background-color: #E6E6E6; + text-decoration: none; + border-radius: 2px; +} .pure-button-hover, .pure-button:hover, .pure-button:focus { - /* csslint ignore:start */ - filter: alpha(opacity=90); - /* csslint ignore:end */ - background-image: -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.1)); - background-image: linear-gradient(transparent, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.1)); } - + background-image: -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(40%, rgba(0,0,0, 0.05)), to(rgba(0,0,0, 0.10))); + background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); +} .pure-button:focus { - outline: 0; } - + outline: 0; +} .pure-button-active, .pure-button:active { - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, 0 0 6px rgba(0, 0, 0, 0.2) inset; - border-color: #000; } + -webkit-box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; + box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; + border-color: #000; +} .pure-button[disabled], .pure-button-disabled, .pure-button-disabled:hover, .pure-button-disabled:focus, .pure-button-disabled:active { - border: none; - background-image: none; - /* csslint ignore:start */ - filter: alpha(opacity=40); - /* csslint ignore:end */ - opacity: 0.40; - cursor: not-allowed; - box-shadow: none; - pointer-events: none; } + border: none; + background-image: none; + opacity: 0.40; + cursor: not-allowed; + -webkit-box-shadow: none; + box-shadow: none; + pointer-events: none; +} .pure-button-hidden { - display: none; } + display: none; +} .pure-button-primary, .pure-button-selected, a.pure-button-primary, a.pure-button-selected { - background-color: #00509b; - color: #fff; } + background-color: rgb(0, 120, 231); + color: #fff; +} /* Button Groups */ .pure-button-group .pure-button { - margin: 0; - border-radius: 0; - border-right: 1px solid #111; - /* fallback color for rgba() for IE7/8 */ - border-right: 1px solid rgba(0, 0, 0, 0.2); } + margin: 0; + border-radius: 0; + border-right: 1px solid rgba(0, 0, 0, 0.2); -.pure-button-group .pure-button:first-child { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; } +} +.pure-button-group .pure-button:first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} .pure-button-group .pure-button:last-child { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - border-right: none; } + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-right: none; +} /*csslint box-model:false*/ /* @@ -763,6 +768,7 @@ also have border and padding. This is done because some browsers don't render the padding. We explicitly set the box-model for select elements to border-box, so we can ignore the csslint warning. */ + .pure-form input[type="text"], .pure-form input[type="password"], .pure-form input[type="email"], @@ -779,30 +785,39 @@ so we can ignore the csslint warning. .pure-form input[type="color"], .pure-form select, .pure-form textarea { - padding: 0.5em 0.6em; - display: inline-block; - border: 1px solid #ccc; - box-shadow: inset 0 1px 3px #ddd; - border-radius: 4px; - vertical-align: middle; - box-sizing: border-box; } + padding: 0.5em 0.6em; + display: inline-block; + border: 1px solid #ccc; + -webkit-box-shadow: inset 0 1px 3px #ddd; + box-shadow: inset 0 1px 3px #ddd; + border-radius: 4px; + vertical-align: middle; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} /* Need to separate out the :not() selector from the rest of the CSS 2.1 selectors since IE8 won't execute CSS that contains a CSS3 selector. */ .pure-form input:not([type]) { - padding: 0.5em 0.6em; - display: inline-block; - border: 1px solid #ccc; - box-shadow: inset 0 1px 3px #ddd; - border-radius: 4px; - box-sizing: border-box; } + padding: 0.5em 0.6em; + display: inline-block; + border: 1px solid #ccc; + -webkit-box-shadow: inset 0 1px 3px #ddd; + box-shadow: inset 0 1px 3px #ddd; + border-radius: 4px; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + /* Chrome (as of v.32/34 on OS X) needs additional room for color to display. */ /* May be able to remove this tweak as color inputs become more standardized across browsers. */ .pure-form input[type="color"] { - padding: 0.2em 0.5em; } + padding: 0.2em 0.5em; +} + .pure-form input[type="text"]:focus, .pure-form input[type="password"]:focus, @@ -820,27 +835,30 @@ since IE8 won't execute CSS that contains a CSS3 selector. .pure-form input[type="color"]:focus, .pure-form select:focus, .pure-form textarea:focus { - outline: 0; - border-color: #129FEA; } + outline: 0; + border-color: #129FEA; +} /* Need to separate out the :not() selector from the rest of the CSS 2.1 selectors since IE8 won't execute CSS that contains a CSS3 selector. */ .pure-form input:not([type]):focus { - outline: 0; - border-color: #129FEA; } + outline: 0; + border-color: #129FEA; +} .pure-form input[type="file"]:focus, .pure-form input[type="radio"]:focus, .pure-form input[type="checkbox"]:focus { - outline: thin solid #129FEA; - outline: 1px auto #129FEA; } - + outline: thin solid #129FEA; + outline: 1px auto #129FEA; +} .pure-form .pure-checkbox, .pure-form .pure-radio { - margin: 0.5em 0; - display: block; } + margin: 0.5em 0; + display: block; +} .pure-form input[type="text"][disabled], .pure-form input[type="password"][disabled], @@ -858,63 +876,64 @@ since IE8 won't execute CSS that contains a CSS3 selector. .pure-form input[type="color"][disabled], .pure-form select[disabled], .pure-form textarea[disabled] { - cursor: not-allowed; - background-color: #eaeded; - color: #cad2d3; } + cursor: not-allowed; + background-color: #eaeded; + color: #cad2d3; +} /* Need to separate out the :not() selector from the rest of the CSS 2.1 selectors since IE8 won't execute CSS that contains a CSS3 selector. */ .pure-form input:not([type])[disabled] { - cursor: not-allowed; - background-color: #eaeded; - color: #cad2d3; } - + cursor: not-allowed; + background-color: #eaeded; + color: #cad2d3; +} .pure-form input[readonly], .pure-form select[readonly], .pure-form textarea[readonly] { - background-color: #eee; - /* menu hover bg color */ - color: #777; - /* menu text color */ - border-color: #ccc; } + background-color: #eee; /* menu hover bg color */ + color: #777; /* menu text color */ + border-color: #ccc; +} .pure-form input:focus:invalid, .pure-form textarea:focus:invalid, .pure-form select:focus:invalid { - color: #b94a48; - border-color: #e9322d; } - + color: #b94a48; + border-color: #e9322d; +} .pure-form input[type="file"]:focus:invalid:focus, .pure-form input[type="radio"]:focus:invalid:focus, .pure-form input[type="checkbox"]:focus:invalid:focus { - outline-color: #e9322d; } - + outline-color: #e9322d; +} .pure-form select { - /* Normalizes the height; padding is not sufficient. */ - height: 2.25em; - border: 1px solid #ccc; - background-color: white; } - + /* Normalizes the height; padding is not sufficient. */ + height: 2.25em; + border: 1px solid #ccc; + background-color: white; +} .pure-form select[multiple] { - height: auto; } - + height: auto; +} .pure-form label { - margin: 0.5em 0 0.2em; } - + margin: 0.5em 0 0.2em; +} .pure-form fieldset { - margin: 0; - padding: 0.35em 0 0.75em; - border: 0; } - + margin: 0; + padding: 0.35em 0 0.75em; + border: 0; +} .pure-form legend { - display: block; - width: 100%; - padding: 0.3em 0; - margin-bottom: 0.3em; - color: #333; - border-bottom: 1px solid #e5e5e5; } + display: block; + width: 100%; + padding: 0.3em 0; + margin-bottom: 0.3em; + color: #333; + border-bottom: 1px solid #e5e5e5; +} .pure-form-stacked input[type="text"], .pure-form-stacked input[type="password"], @@ -934,365 +953,394 @@ since IE8 won't execute CSS that contains a CSS3 selector. .pure-form-stacked select, .pure-form-stacked label, .pure-form-stacked textarea { - display: block; - margin: 0.25em 0; } + display: block; + margin: 0.25em 0; +} /* Need to separate out the :not() selector from the rest of the CSS 2.1 selectors since IE8 won't execute CSS that contains a CSS3 selector. */ .pure-form-stacked input:not([type]) { - display: block; - margin: 0.25em 0; } - + display: block; + margin: 0.25em 0; +} .pure-form-aligned input, .pure-form-aligned textarea, .pure-form-aligned select, -.pure-form-aligned .pure-help-inline, .pure-form-message-inline { - display: inline-block; - *display: inline; - *zoom: 1; - vertical-align: middle; } - + display: inline-block; + vertical-align: middle; +} .pure-form-aligned textarea { - vertical-align: top; } + vertical-align: top; +} /* Aligned Forms */ .pure-form-aligned .pure-control-group { - margin-bottom: 0.5em; } - + margin-bottom: 0.5em; +} .pure-form-aligned .pure-control-group label { - text-align: right; - display: inline-block; - vertical-align: middle; - width: 10em; - margin: 0 1em 0 0; } - + text-align: right; + display: inline-block; + vertical-align: middle; + width: 10em; + margin: 0 1em 0 0; +} .pure-form-aligned .pure-controls { - margin: 1.5em 0 0 11em; } + margin: 1.5em 0 0 11em; +} /* Rounded Inputs */ .pure-form input.pure-input-rounded, .pure-form .pure-input-rounded { - border-radius: 2em; - padding: 0.5em 1em; } + border-radius: 2em; + padding: 0.5em 1em; +} /* Grouped Inputs */ .pure-form .pure-group fieldset { - margin-bottom: 10px; } - + margin-bottom: 10px; +} .pure-form .pure-group input, .pure-form .pure-group textarea { - display: block; - padding: 10px; - margin: 0 0 -1px; - border-radius: 0; - position: relative; - top: -1px; } - + display: block; + padding: 10px; + margin: 0 0 -1px; + border-radius: 0; + position: relative; + top: -1px; +} .pure-form .pure-group input:focus, .pure-form .pure-group textarea:focus { - z-index: 3; } - + z-index: 3; +} .pure-form .pure-group input:first-child, .pure-form .pure-group textarea:first-child { - top: 1px; - border-radius: 4px 4px 0 0; - margin: 0; } - + top: 1px; + border-radius: 4px 4px 0 0; + margin: 0; +} .pure-form .pure-group input:first-child:last-child, .pure-form .pure-group textarea:first-child:last-child { - top: 1px; - border-radius: 4px; - margin: 0; } - + top: 1px; + border-radius: 4px; + margin: 0; +} .pure-form .pure-group input:last-child, .pure-form .pure-group textarea:last-child { - top: -2px; - border-radius: 0 0 4px 4px; - margin: 0; } - + top: -2px; + border-radius: 0 0 4px 4px; + margin: 0; +} .pure-form .pure-group button { - margin: 0.35em 0; } + margin: 0.35em 0; +} .pure-form .pure-input-1 { - width: 100%; } - + width: 100%; +} .pure-form .pure-input-3-4 { - width: 75%; } - + width: 75%; +} .pure-form .pure-input-2-3 { - width: 66%; } - + width: 66%; +} .pure-form .pure-input-1-2 { - width: 50%; } - + width: 50%; +} .pure-form .pure-input-1-3 { - width: 33%; } - + width: 33%; +} .pure-form .pure-input-1-4 { - width: 25%; } + width: 25%; +} /* Inline help for forms */ -/* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ -.pure-form .pure-help-inline, .pure-form-message-inline { - display: inline-block; - padding-left: 0.3em; - color: #666; - vertical-align: middle; - font-size: 0.875em; } + display: inline-block; + padding-left: 0.3em; + color: #666; + vertical-align: middle; + font-size: 0.875em; +} /* Block help for forms */ .pure-form-message { - display: block; - color: #666; - font-size: 0.875em; } - -@media only screen and (max-width: 480px) { - .pure-form button[type="submit"] { - margin: 0.7em 0 0; } - - .pure-form input:not([type]), - .pure-form input[type="text"], - .pure-form input[type="password"], - .pure-form input[type="email"], - .pure-form input[type="url"], - .pure-form input[type="date"], - .pure-form input[type="month"], - .pure-form input[type="time"], - .pure-form input[type="datetime"], - .pure-form input[type="datetime-local"], - .pure-form input[type="week"], - .pure-form input[type="number"], - .pure-form input[type="search"], - .pure-form input[type="tel"], - .pure-form input[type="color"], - .pure-form label { - margin-bottom: 0.3em; - display: block; } - - .pure-group input:not([type]), - .pure-group input[type="text"], - .pure-group input[type="password"], - .pure-group input[type="email"], - .pure-group input[type="url"], - .pure-group input[type="date"], - .pure-group input[type="month"], - .pure-group input[type="time"], - .pure-group input[type="datetime"], - .pure-group input[type="datetime-local"], - .pure-group input[type="week"], - .pure-group input[type="number"], - .pure-group input[type="search"], - .pure-group input[type="tel"], - .pure-group input[type="color"] { - margin-bottom: 0; } - - .pure-form-aligned .pure-control-group label { - margin-bottom: 0.3em; - text-align: left; display: block; - width: 100%; } - - .pure-form-aligned .pure-controls { - margin: 1.5em 0 0 0; } + color: #666; + font-size: 0.875em; +} + +@media only screen and (max-width : 480px) { + .pure-form button[type="submit"] { + margin: 0.7em 0 0; + } + + .pure-form input:not([type]), + .pure-form input[type="text"], + .pure-form input[type="password"], + .pure-form input[type="email"], + .pure-form input[type="url"], + .pure-form input[type="date"], + .pure-form input[type="month"], + .pure-form input[type="time"], + .pure-form input[type="datetime"], + .pure-form input[type="datetime-local"], + .pure-form input[type="week"], + .pure-form input[type="number"], + .pure-form input[type="search"], + .pure-form input[type="tel"], + .pure-form input[type="color"], + .pure-form label { + margin-bottom: 0.3em; + display: block; + } + + .pure-group input:not([type]), + .pure-group input[type="text"], + .pure-group input[type="password"], + .pure-group input[type="email"], + .pure-group input[type="url"], + .pure-group input[type="date"], + .pure-group input[type="month"], + .pure-group input[type="time"], + .pure-group input[type="datetime"], + .pure-group input[type="datetime-local"], + .pure-group input[type="week"], + .pure-group input[type="number"], + .pure-group input[type="search"], + .pure-group input[type="tel"], + .pure-group input[type="color"] { + margin-bottom: 0; + } + + .pure-form-aligned .pure-control-group label { + margin-bottom: 0.3em; + text-align: left; + display: block; + width: 100%; + } + + .pure-form-aligned .pure-controls { + margin: 1.5em 0 0 0; + } + + .pure-form-message-inline, + .pure-form-message { + display: block; + font-size: 0.75em; + /* Increased bottom padding to make it group with its related input element. */ + padding: 0.2em 0 0.8em; + } +} - /* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ - .pure-form .pure-help-inline, - .pure-form-message-inline, - .pure-form-message { - display: block; - font-size: 0.75em; - /* Increased bottom padding to make it group with its related input element. */ - padding: 0.2em 0 0.8em; } } /*csslint adjoining-classes: false, box-model:false*/ .pure-menu { - box-sizing: border-box; } + -webkit-box-sizing: border-box; + box-sizing: border-box; +} .pure-menu-fixed { - position: fixed; - left: 0; - top: 0; - z-index: 3; } + position: fixed; + left: 0; + top: 0; + z-index: 3; +} .pure-menu-list, .pure-menu-item { - position: relative; } + position: relative; +} .pure-menu-list { - list-style: none; - margin: 0; - padding: 0; } + list-style: none; + margin: 0; + padding: 0; +} .pure-menu-item { - padding: 0; - margin: 0; - height: 100%; } + padding: 0; + margin: 0; + height: 100%; +} .pure-menu-link, .pure-menu-heading { - display: block; - text-decoration: none; - white-space: nowrap; } + display: block; + text-decoration: none; + white-space: nowrap; +} /* HORIZONTAL MENU */ .pure-menu-horizontal { - width: 100%; - white-space: nowrap; } + width: 100%; + white-space: nowrap; +} .pure-menu-horizontal .pure-menu-list { - display: inline-block; } + display: inline-block; +} /* Initial menus should be inline-block so that they are horizontal */ .pure-menu-horizontal .pure-menu-item, .pure-menu-horizontal .pure-menu-heading, .pure-menu-horizontal .pure-menu-separator { - display: inline-block; - *display: inline; - zoom: 1; - vertical-align: middle; } + display: inline-block; + vertical-align: middle; +} /* Submenus should still be display: block; */ .pure-menu-item .pure-menu-item { - display: block; } + display: block; +} .pure-menu-children { - display: none; - position: absolute; - left: 100%; - top: 0; - margin: 0; - padding: 0; - z-index: 3; } + display: none; + position: absolute; + left: 100%; + top: 0; + margin: 0; + padding: 0; + z-index: 3; +} .pure-menu-horizontal .pure-menu-children { - left: 0; - top: auto; - width: inherit; } + left: 0; + top: auto; + width: inherit; +} .pure-menu-allow-hover:hover > .pure-menu-children, .pure-menu-active > .pure-menu-children { - display: block; - position: absolute; } + display: block; + position: absolute; +} /* Vertical Menus - show the dropdown arrow */ .pure-menu-has-children > .pure-menu-link:after { - padding-left: 0.5em; - content: "\25B8"; - font-size: small; } + padding-left: 0.5em; + content: "\25B8"; + font-size: small; +} /* Horizontal Menus - show the dropdown arrow */ .pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after { - content: "\25BE"; } + content: "\25BE"; +} /* scrollable menus */ .pure-menu-scrollable { - overflow-y: scroll; - overflow-x: hidden; } + overflow-y: scroll; + overflow-x: hidden; +} .pure-menu-scrollable .pure-menu-list { - display: block; } + display: block; +} .pure-menu-horizontal.pure-menu-scrollable .pure-menu-list { - display: inline-block; } + display: inline-block; +} .pure-menu-horizontal.pure-menu-scrollable { - white-space: nowrap; - overflow-y: hidden; - overflow-x: auto; - -ms-overflow-style: none; - -webkit-overflow-scrolling: touch; - /* a little extra padding for this style to allow for scrollbars */ - padding: .5em 0; } - -.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar { - display: none; } + white-space: nowrap; + overflow-y: hidden; + overflow-x: auto; + /* a little extra padding for this style to allow for scrollbars */ + padding: .5em 0; +} /* misc default styling */ + .pure-menu-separator, .pure-menu-horizontal .pure-menu-children .pure-menu-separator { - background-color: #ccc; - height: 1px; - margin: .3em 0; } + background-color: #ccc; + height: 1px; + margin: .3em 0; +} .pure-menu-horizontal .pure-menu-separator { - width: 1px; - height: 1.3em; - margin: 0 0.3em; } + width: 1px; + height: 1.3em; + margin: 0 .3em ; +} /* Need to reset the separator since submenu is vertical */ .pure-menu-horizontal .pure-menu-children .pure-menu-separator { - display: block; - width: auto; } + display: block; + width: auto; +} .pure-menu-heading { - text-transform: uppercase; - color: #565d64; } + text-transform: uppercase; + color: #565d64; +} .pure-menu-link { - color: #777; } + color: #777; +} .pure-menu-children { - background-color: #fff; } + background-color: #fff; +} .pure-menu-link, -.pure-menu-disabled, .pure-menu-heading { - padding: .5em 1em; } + padding: .5em 1em; +} .pure-menu-disabled { - opacity: .5; } + opacity: .5; +} .pure-menu-disabled .pure-menu-link:hover { - background-color: transparent; } + background-color: transparent; + cursor: default; +} .pure-menu-active > .pure-menu-link, .pure-menu-link:hover, .pure-menu-link:focus { - background-color: #eee; } + background-color: #eee; +} -.pure-menu-selected .pure-menu-link, -.pure-menu-selected .pure-menu-link:visited { - color: #000; } +.pure-menu-selected > .pure-menu-link, +.pure-menu-selected > .pure-menu-link:visited { + color: #000; +} .pure-table { - /* Remove spacing between table cells (from Normalize.css) */ - border-collapse: collapse; - border-spacing: 0; - empty-cells: show; - border: 1px solid #cbcbcb; } + /* Remove spacing between table cells (from Normalize.css) */ + border-collapse: collapse; + border-spacing: 0; + empty-cells: show; + border: 1px solid #cbcbcb; +} .pure-table caption { - color: #000; - font: italic 85%/1 arial, sans-serif; - padding: 1em 0; - text-align: center; } + color: #000; + font: italic 85%/1 arial, sans-serif; + padding: 1em 0; + text-align: center; +} .pure-table td, .pure-table th { - border-left: 1px solid #cbcbcb; - /* inner column border */ - border-width: 0 0 0 1px; - font-size: inherit; - margin: 0; - overflow: visible; - /*to make ths where the title is really long work*/ - padding: 0.5em 1em; - /* cell padding */ } - -/* Consider removing this next declaration block, as it causes problems when -there's a rowspan on the first cell. Case added to the tests. issue#432 */ -.pure-table td:first-child, -.pure-table th:first-child { - border-left-width: 0; } + border-left: 1px solid #cbcbcb;/* inner column border */ + border-width: 0 0 0 1px; + font-size: inherit; + margin: 0; + overflow: visible; /*to make ths where the title is really long work*/ + padding: 0.5em 1em; /* cell padding */ +} .pure-table thead { - background-color: #e0e0e0; - color: #000; - text-align: left; - vertical-align: bottom; } + background-color: #e0e0e0; + color: #000; + text-align: left; + vertical-align: bottom; +} /* striping: @@ -1300,29 +1348,33 @@ striping: odd - #f2f2f2 (light gray) */ .pure-table td { - background-color: transparent; } - + background-color: transparent; +} .pure-table-odd td { - background-color: #f2f2f2; } + background-color: #f2f2f2; +} /* nth-child selector for modern browsers */ .pure-table-striped tr:nth-child(2n-1) td { - background-color: #f2f2f2; } + background-color: #f2f2f2; +} /* BORDERED TABLES */ .pure-table-bordered td { - border-bottom: 1px solid #cbcbcb; } - + border-bottom: 1px solid #cbcbcb; +} .pure-table-bordered tbody > tr:last-child > td { - border-bottom-width: 0; } + border-bottom-width: 0; +} + /* HORIZONTAL BORDERED TABLES */ + .pure-table-horizontal td, .pure-table-horizontal th { - border-width: 0 0 1px 0; - border-bottom: 1px solid #cbcbcb; } - + border-width: 0 0 1px 0; + border-bottom: 1px solid #cbcbcb; +} .pure-table-horizontal tbody > tr:last-child > td { - border-bottom-width: 0; } - -/*# sourceMappingURL=pure.css.map */ + border-bottom-width: 0; +}
\ No newline at end of file diff --git a/packages/demobank-ui/src/template.html b/packages/demobank-ui/src/template.html deleted file mode 100644 index 6d8443130..000000000 --- a/packages/demobank-ui/src/template.html +++ /dev/null @@ -1,52 +0,0 @@ -<!-- - This file is part of GNU Taler - (C) 2021--2022 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 ---> -<!DOCTYPE html> -<html lang="en" class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"> - <head> - <meta charset="utf-8"> - <title><%= htmlWebpackPlugin.options.title %></title> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <meta name="mobile-web-app-capable" content="yes"> - <meta name="apple-mobile-web-app-capable" content="yes"> - - <link rel="icon" href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> - <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> - - <% if (htmlWebpackPlugin.options.manifest.theme_color) { %> - <meta name="theme-color" content="<%= htmlWebpackPlugin.options.manifest.theme_color %>"> - <% } %> - - <% for (const index in htmlWebpackPlugin.files.css) { %> - <% const file = htmlWebpackPlugin.files.css[index] %> - <style data-href='<%= file %>' > - <%= compilation.assets[file.substr(htmlWebpackPlugin.files.publicPath.length)].source() %> - </style> - <% } %> - - </head> - <body> - - <script> - <%= compilation.assets[htmlWebpackPlugin.files.chunks["polyfills"].entry.substr(htmlWebpackPlugin.files.publicPath.length)].source() %> - </script> - <script> - <%= compilation.assets[htmlWebpackPlugin.files.chunks["bundle"].entry.substr(htmlWebpackPlugin.files.publicPath.length)].source() %> - </script> - - </body> -</html> diff --git a/packages/demobank-ui/static/index.html b/packages/demobank-ui/static/index.html new file mode 100644 index 000000000..6597eb26f --- /dev/null +++ b/packages/demobank-ui/static/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <title>Demobank</title> + <script type="module" src="index.js"></script> + <link rel="stylesheet" href="index.css" /> + </head> + <body> + <div id="app"></div> + </body> +</html> diff --git a/packages/demobank-ui/tests/__mocks__/browserMocks.ts b/packages/demobank-ui/tests/__mocks__/browserMocks.ts deleted file mode 100644 index 5be8c3ce6..000000000 --- a/packages/demobank-ui/tests/__mocks__/browserMocks.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage -/** - * An example how to mock localStorage is given below 👇 - */ - -/* -// Mocks localStorage -const localStorageMock = (function() { - let store = {}; - - return { - getItem: (key) => store[key] || null, - setItem: (key, value) => store[key] = value.toString(), - clear: () => store = {} - }; - -})(); - -Object.defineProperty(window, 'localStorage', { - value: localStorageMock -}); */ diff --git a/packages/demobank-ui/tests/__mocks__/fileMocks.ts b/packages/demobank-ui/tests/__mocks__/fileMocks.ts deleted file mode 100644 index 87109e355..000000000 --- a/packages/demobank-ui/tests/__mocks__/fileMocks.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This fixed an error related to the CSS and loading gif breaking my Jest test -// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets -export default 'test-file-stub'; diff --git a/packages/demobank-ui/tests/__mocks__/setupTests.ts b/packages/demobank-ui/tests/__mocks__/setupTests.ts deleted file mode 100644 index b0bebb589..000000000 --- a/packages/demobank-ui/tests/__mocks__/setupTests.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { configure } from 'enzyme'; -import Adapter from 'enzyme-adapter-preact-pure'; - -configure({ - adapter: new Adapter() as any -}); diff --git a/packages/demobank-ui/tests/__tests__/homepage.js b/packages/demobank-ui/tests/__tests__/homepage.js deleted file mode 100644 index 9ea0ed410..000000000 --- a/packages/demobank-ui/tests/__tests__/homepage.js +++ /dev/null @@ -1,466 +0,0 @@ -import "core-js/stable"; -import "regenerator-runtime/runtime"; -import "@testing-library/jest-dom"; -import { BankHome } from '../../src/pages/home'; -import { h } from 'preact'; -import { waitFor, cleanup, render, fireEvent, screen } from '@testing-library/preact'; -import expect from 'expect'; -import fetchMock from "jest-fetch-mock"; - -/** - * This mock makes the translator always return the - * english string. It didn't work within the 'beforeAll' - * function... - */ -jest.mock("../../src/i18n") -const i18n = require("../../src/i18n") -i18n.useTranslator.mockImplementation(() => function(arg) {return arg}) - -beforeAll(() => { - Object.defineProperty(window, 'location', { - value: { - origin: "http://localhost", - pathname: "/demobanks/default" - } - }) - global.Storage.prototype.setItem = jest.fn((key, value) => {}) -}) - -function fillCredentialsForm() { - const username = Math.random().toString().substring(2); - const u = screen.getByPlaceholderText("username"); - const p = screen.getByPlaceholderText("password"); - fireEvent.input(u, {target: {value: username}}) - fireEvent.input(p, {target: {value: "bar"}}) - const signinButton = screen.getByText("Login"); - return { - username: username, - signinButton: signinButton - }; -} -fetchMock.enableMocks(); - -function mockSuccessLoginOrRegistration() { - fetch.once("{}", { - status: 200 - }).once(JSON.stringify({ - balance: { - amount: "EUR:10", - credit_debit_indicator: "credit" - }, - paytoUri: "payto://iban/123/ABC" - })) -} - -/** - * Render homepage -> navigate to register page -> submit registration. - * 'webMock' is called before submission to mock the server response - */ -function signUp(context, webMock) { - render(<BankHome />); - const registerPage = screen.getByText("Register!"); - fireEvent.click(registerPage); - const username = Math.random().toString().substring(2); - const u = screen.getByPlaceholderText("username"); - const p = screen.getByPlaceholderText("password"); - fireEvent.input(u, {target: {value: username}}) - fireEvent.input(p, {target: {value: "bar"}}) - const registerButton = screen.getByText("Register"); - webMock(); - fireEvent.click(registerButton); - context.username = username; - return context; -} - -describe("wire transfer", () => { - beforeEach(() => { - signUp({}, mockSuccessLoginOrRegistration); // context unused - }) - test("Wire transfer success", async () => { - const transferButton = screen.getByText("Create wire transfer"); - const payto = screen.getByPlaceholderText("payto address"); - fireEvent.input(payto, {target: {value: "payto://only-checked-by-the-backend!"}}) - fetch.once("{}"); // 200 OK - fireEvent.click(transferButton); - await screen.findByText("wire transfer created", {exact: false}) - }) - test("Wire transfer fail", async () => { - const transferButton = screen.getByText("Create wire transfer"); - const payto = screen.getByPlaceholderText("payto address"); - fireEvent.input(payto, {target: {value: "payto://only-checked-by-the-backend!"}}) - fetch.once("{}", {status: 400}); - fireEvent.click(transferButton); - // assert this below does NOT appear. - await waitFor(() => expect( - screen.queryByText("wire transfer created", {exact: false})).not.toBeInTheDocument()); - }) -}) - -describe("withdraw", () => { - afterEach(() => { - fetch.resetMocks(); - cleanup(); - }) - - - let context = {}; - // Register and land on the profile page. - beforeEach(() => { - context = signUp(context, mockSuccessLoginOrRegistration); - }) - - test("network failure before withdrawal creation", async () => { - const a = screen.getAllByPlaceholderText("amount")[0]; - fireEvent.input(a, {target: {value: "10"}}); - let withdrawButton = screen.getByText("Charge Taler wallet"); - // mock network failure. - fetch.mockReject("API is down"); - fireEvent.click(withdrawButton); - await screen.findByText("could not create withdrawal operation", {exact: false}) - }) - - test("HTTP response error upon withdrawal creation", async () => { - const a = screen.getAllByPlaceholderText("amount")[0]; - fireEvent.input(a, {target: {value: "10,0"}}); - let withdrawButton = screen.getByText("Charge Taler wallet"); - fetch.once("{}", {status: 404}); - fireEvent.click(withdrawButton); - await screen.findByText("gave response error", {exact: false}) - }) - - test("Abort withdrawal", async () => { - const a = screen.getAllByPlaceholderText("amount")[0]; - fireEvent.input(a, {target: {value: "10,0"}}); - let withdrawButton = screen.getByText("Charge Taler wallet"); - fetch.once(JSON.stringify({ - taler_withdraw_uri: "taler://withdraw/foo", - withdrawal_id: "foo" - })); - /** - * After triggering a withdrawal, check if the taler://withdraw URI - * rendered, and confirm if so. Lastly, check that a success message - * appeared on the screen. - */ - fireEvent.click(withdrawButton); - const abortButton = await screen.findByText("abort withdrawal", {exact: false}) - fireEvent.click(abortButton); - expect(fetch).toHaveBeenLastCalledWith( - `http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals/foo/abort`, - expect.anything() - ) - await waitFor(() => expect( - screen.queryByText("abort withdrawal", {exact: false})).not.toBeInTheDocument()); - }) - - test("Successful withdrawal creation and confirmation", async () => { - const a = screen.getAllByPlaceholderText("amount")[0]; - fireEvent.input(a, {target: {value: "10,0"}}); - let withdrawButton = await screen.findByText("Charge Taler wallet"); - fetch.once(JSON.stringify({ - taler_withdraw_uri: "taler://withdraw/foo", - withdrawal_id: "foo" - })); - /** - * After triggering a withdrawal, check if the taler://withdraw URI - * rendered, and confirm if so. Lastly, check that a success message - * appeared on the screen. */ - fireEvent.click(withdrawButton); - expect(fetch).toHaveBeenCalledWith( - `http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals`, - expect.objectContaining({body: JSON.stringify({amount: "EUR:10.0"})}) - ) - // assume wallet POSTed the payment details. - const confirmButton = await screen.findByText("confirm withdrawal", {exact: false}) - /** - * Not expecting a new withdrawal possibility while one is being processed. - */ - await waitFor(() => expect( - screen.queryByText("charge taler wallet", {exact: false})).not.toBeInTheDocument()); - fetch.once("{}") - // Confirm currently processed withdrawal. - fireEvent.click(confirmButton); - /** - * After having confirmed above, wait that the - * pre-withdrawal elements disappears and a success - * message appears. - */ - await waitFor(() => expect( - screen.queryByText( - "confirm withdrawal", - {exact: false})).not.toBeInTheDocument() - ); - await waitFor(() => expect( - screen.queryByText( - "give this address to the taler wallet", - {exact: false})).not.toBeInTheDocument() - ); - expect(fetch).toHaveBeenLastCalledWith( - `http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals/foo/confirm`, - expect.anything()) - // success message - await screen.findByText("withdrawal confirmed", {exact: false}) - - /** - * Click on a "return to homepage / close" button, and - * check that the withdrawal confirmation is gone, and - * the option to withdraw again reappeared. - */ - const closeButton = await screen.findByText("close", {exact: false}) - fireEvent.click(closeButton); - - /** - * After closing the operation, the confirmation message is not expected. - */ - await waitFor(() => expect( - screen.queryByText("withdrawal confirmed", {exact: false})).not.toBeInTheDocument() - ); - - /** - * After closing the operation, the possibility to withdraw again should be offered. - */ - await waitFor(() => expect( - screen.queryByText( - "charge taler wallet", - {exact: false})).toBeInTheDocument() - ); - }) -}) - -describe("home page", () => { - afterEach(() => { - fetch.resetMocks(); - cleanup(); - }) - test("public histories", async () => { - render(<BankHome />); - /** - * Mock list of public accounts. 'bar' is - * the shown account, since it occupies the last - * position (and SPA picks it via the 'pop()' method) */ - fetch.once(JSON.stringify({ - "publicAccounts" : [ { - "balance" : "EUR:1", - "iban" : "XXX", - "accountLabel" : "foo" - }, { - "balance" : "EUR:2", - "iban" : "YYY", - "accountLabel" : "bar" - }] - })).once(JSON.stringify({ - transactions: [{ - debtorIban: "XXX", - debtorBic: "YYY", - debtorName: "Foo", - creditorIban: "AAA", - creditorBic: "BBB", - creditorName: "Bar", - direction: "DBIT", - amount: "EUR:5", - subject: "Reimbursement", - date: "1970-01-01" - }, { - debtorIban: "XXX", - debtorBic: "YYY", - debtorName: "Foo", - creditorIban: "AAA", - creditorBic: "BBB", - creditorName: "Bar", - direction: "CRDT", - amount: "EUR:5", - subject: "Bonus", - date: "2000-01-01" - }] - })).once(JSON.stringify({ - transactions: [{ - debtorIban: "XXX", - debtorBic: "YYY", - debtorName: "Foo", - creditorIban: "AAA", - creditorBic: "BBB", - creditorName: "Bar", - direction: "DBIT", - amount: "EUR:5", - subject: "Donation", - date: "1970-01-01" - }, { - debtorIban: "XXX", - debtorBic: "YYY", - debtorName: "Foo", - creditorIban: "AAA", - creditorBic: "BBB", - creditorName: "Bar", - direction: "CRDT", - amount: "EUR:5", - subject: "Refund", - date: "2000-01-01" - }] - })) - - // Navigate to dedicate public histories page. - const publicTxsPage = screen.getByText("transactions"); - fireEvent.click(publicTxsPage); - - /** - * Check that transactions data appears on the page. - */ - await screen.findByText("reimbursement", {exact: false}); - await screen.findByText("bonus", {exact: false}); - /** - * The transactions below should not appear, because only - * one public account renders. - */ - await waitFor(() => expect( - screen.queryByText("refund", {exact: false})).not.toBeInTheDocument()); - await waitFor(() => expect( - screen.queryByText("donation", {exact: false})).not.toBeInTheDocument()); - /** - * First HTTP mock: - */ - await expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/public-accounts" - ) - /** - * Only expecting this request (second mock), as SWR doesn't let - * the unshown history request to the backend: - */ - await expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/accounts/bar/transactions?page=0" - ) - /** - * Switch tab: - */ - let fooTab = await screen.findByText("foo", {exact: false}); - fireEvent.click(fooTab); - /** - * Last two HTTP mocks should render now: - */ - await screen.findByText("refund", {exact: false}); - await screen.findByText("donation", {exact: false}); - - // Expect SWR to have requested 'foo' history - // (consuming the last HTTP mock): - await expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/accounts/foo/transactions?page=0" - ) - let backButton = await screen.findByText("Go back", {exact: false}); - fireEvent.click(backButton); - await waitFor(() => expect( - screen.queryByText("donation", {exact: false})).not.toBeInTheDocument()); - await screen.findByText("welcome to eufin bank", {exact: false}) - }) - - // check page informs about the current balance - // after a successful registration. - - test("new registration response error 404", async () => { - var context = signUp({}, () => fetch.mockResponseOnce("Not found", {status: 404})); - await screen.findByText("has a problem", {exact: false}); - expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/testing/register", - expect.objectContaining( - {body: JSON.stringify({username: context.username, password: "bar"}), method: "POST"}, - )) - }) - - test("registration network failure", async () => { - let context = signUp({}, ()=>fetch.mockReject("API is down")); - await screen.findByText("has a problem", {exact: false}); - expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/testing/register", - expect.objectContaining( - {body: JSON.stringify({username: context.username, password: "bar"}), method: "POST"} - )) - }) - - test("login non existent user", async () => { - render(<BankHome />); - const { username, signinButton } = fillCredentialsForm(); - fetch.once("{}", {status: 404}); - fireEvent.click(signinButton); - await screen.findByText("username or account label not found", {exact: false}) - }) - test("login wrong credentials", async () => { - render(<BankHome />); - const { username, signinButton } = fillCredentialsForm(); - fetch.once("{}", {status: 401}); - fireEvent.click(signinButton); - await screen.findByText("wrong credentials given", {exact: false}) - }) - - /** - * Test that balance and last transactions get shown - * after a successful login. - */ - test("login success", async () => { - render(<BankHome />); - const { username, signinButton } = fillCredentialsForm(); - - // Response to balance request. - fetch.once(JSON.stringify({ - balance: { - amount: "EUR:10", - credit_debit_indicator: "credit" - }, - paytoUri: "payto://iban/123/ABC" - })).once(JSON.stringify({ // Response to history request. - transactions: [{ - debtorIban: "XXX", - debtorBic: "YYY", - debtorName: "Foo", - creditorIban: "AAA", - creditorBic: "BBB", - creditorName: "Bar", - direction: "DBIT", - amount: "EUR:5", - subject: "Donation", - date: "01-01-1970" - }, { - debtorIban: "XXX", - debtorBic: "YYY", - debtorName: "Foo", - creditorIban: "AAA", - creditorBic: "BBB", - creditorName: "Bar", - direction: "CRDT", - amount: "EUR:5", - subject: "Refund", - date: "01-01-2000" - }] - })) - fireEvent.click(signinButton); - expect(fetch).toHaveBeenCalledWith( - `http://localhost/demobanks/default/access-api/accounts/${username}`, - expect.anything() - ) - await screen.findByText("balance is 10 EUR", {exact: false}) - // The two transactions in the history mocked above. - await screen.findByText("refund", {exact: false}) - await screen.findByText("donation", {exact: false}) - expect(fetch).toHaveBeenCalledWith( - `http://localhost/demobanks/default/access-api/accounts/${username}/transactions?page=0`, - expect.anything() - ) - }) - - test("registration success", async () => { - let context = signUp({}, mockSuccessLoginOrRegistration); - /** - * Tests that a balance is shown after the successful - * registration. - */ - await screen.findByText("balance is 10 EUR", {exact: false}) - /** - * The expectation below tests whether the account - * balance was requested after the successful registration. - */ - expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/testing/register", - expect.anything() // no need to match auth headers. - ) - expect(fetch).toHaveBeenCalledWith( - `http://localhost/demobanks/default/access-api/accounts/${context.username}`, - expect.anything() // no need to match auth headers. - ) - }) -}) diff --git a/packages/demobank-ui/tests/declarations.d.ts b/packages/demobank-ui/tests/declarations.d.ts deleted file mode 100644 index 67e940277..000000000 --- a/packages/demobank-ui/tests/declarations.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Enable enzyme adapter's integration with TypeScript -// See: https://github.com/preactjs/enzyme-adapter-preact-pure#usage-with-typescript -/// <reference types="enzyme-adapter-preact-pure" /> diff --git a/packages/demobank-ui/tsconfig.json b/packages/demobank-ui/tsconfig.json index d04c5b964..d9d56ad4f 100644 --- a/packages/demobank-ui/tsconfig.json +++ b/packages/demobank-ui/tsconfig.json @@ -1,19 +1,13 @@ { "compilerOptions": { /* Basic Options */ - "target": "ES5" /* 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: */ + "target": "ES5", + "module": "ES6", + "lib": ["DOM", "ES2016"], "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ - "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, - "jsxFactory": "h" /* Specify the JSX factory function to use when targeting react JSX emit, e.g. React.createElement or h. */, - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "removeComments": true, /* Do not emit comments to output. */ + "jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, + "jsxImportSource": "preact", "noEmit": true /* Do not emit outputs. */, // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ @@ -21,11 +15,7 @@ /* Strict Type-Checking Options */ "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - + "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ @@ -33,14 +23,14 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "moduleResolution": "Node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, "esModuleInterop": true /* */, // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ @@ -56,5 +46,5 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */ }, - "include": ["src/**/*", "tests/**/*"] + "include": ["src/**/*"] } |