add template from merchant backoffice
This commit is contained in:
parent
269022a526
commit
5883d42d80
@ -23,15 +23,20 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnu-taler/taler-util": "workspace:^0.8.3",
|
"@gnu-taler/taler-util": "workspace:^0.8.3",
|
||||||
"anastasis-core": "workspace:^0.0.1",
|
"anastasis-core": "workspace:^0.0.1",
|
||||||
|
"jed": "1.1.1",
|
||||||
"preact": "^10.3.1",
|
"preact": "^10.3.1",
|
||||||
"preact-render-to-string": "^5.1.4",
|
"preact-render-to-string": "^5.1.4",
|
||||||
"preact-router": "^3.2.1"
|
"preact-router": "^3.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@creativebulma/bulma-tooltip": "^1.2.0",
|
||||||
"@types/enzyme": "^3.10.5",
|
"@types/enzyme": "^3.10.5",
|
||||||
"@types/jest": "^26.0.8",
|
"@types/jest": "^26.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.25.0",
|
"@typescript-eslint/eslint-plugin": "^2.25.0",
|
||||||
"@typescript-eslint/parser": "^2.25.0",
|
"@typescript-eslint/parser": "^2.25.0",
|
||||||
|
"bulma": "^0.9.3",
|
||||||
|
"bulma-checkbox": "^1.1.1",
|
||||||
|
"bulma-radio": "^1.1.1",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-adapter-preact-pure": "^3.1.0",
|
"enzyme-adapter-preact-pure": "^3.1.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
@ -39,6 +44,8 @@
|
|||||||
"jest": "^26.2.2",
|
"jest": "^26.2.2",
|
||||||
"jest-preset-preact": "^4.0.2",
|
"jest-preset-preact": "^4.0.2",
|
||||||
"preact-cli": "^3.2.2",
|
"preact-cli": "^3.2.2",
|
||||||
|
"sass": "^1.32.13",
|
||||||
|
"sass-loader": "^10.1.1",
|
||||||
"sirv-cli": "^1.0.0-next.3",
|
"sirv-cli": "^1.0.0-next.3",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.7.5"
|
||||||
},
|
},
|
||||||
|
48
packages/anastasis-webui/src/assets/icons/languageicon.svg
Normal file
48
packages/anastasis-webui/src/assets/icons/languageicon.svg
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 2411.2 2794" style="enable-background:new 0 0 2411.2 2794;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
.st1{fill-rule:evenodd;clip-rule:evenodd;}
|
||||||
|
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g id="Layer_2">
|
||||||
|
</g>
|
||||||
|
<g id="Layer_x5F_1_x5F_1">
|
||||||
|
<g>
|
||||||
|
<polygon points="1204.6,359.2 271.8,30 271.8,2060.1 1204.6,1758.3 "/>
|
||||||
|
<polygon class="st0" points="1182.2,358.1 2150.6,29 2150.6,2059 1182.2,1757.3 "/>
|
||||||
|
<polygon class="st0" points="30,2415.4 1182.2,2031.4 1182.2,357.9 30,742 "/>
|
||||||
|
<polygon points="1707.2,2440.7 1870.5,2709.4 1956.6,2459.8 "/>
|
||||||
|
<g>
|
||||||
|
<path d="M421.7,934.8c-6.1-6,8,49.1,27.6,68.9c34.8,35.1,61.9,39.6,76.4,40.2c32,1.3,71.5-8,94.9-17.8
|
||||||
|
c22.7-9.7,62.4-30,77.5-59.6c3.2-6.3,11.9-17,6.4-43.2c-4.2-20.2-17-27.3-32.7-26.2c-15.7,1.1-63.2,13.7-86.1,20.8
|
||||||
|
c-23,7-70.3,21.4-90.9,25.8C474.3,948.2,429,941.7,421.7,934.8z"/>
|
||||||
|
<path d="M1003.1,1593.7c-9.1-3.3-196.9-81.1-223.6-93.9c-21.8-10.5-75.2-33.1-100.4-43.3c70.8-109.2,115.5-191.6,121.5-204.1
|
||||||
|
c11-23,86-169.6,87.7-178.7c1.7-9.1,3.8-42.9,2.2-51c-1.7-8.2-29.1,7.6-66.4,20.2c-37.4,12.6-108.4,58.8-135.8,64.6
|
||||||
|
c-27.5,5.7-115.5,39.1-160.5,54c-45,14.9-130.2,40.9-165.2,50.4c-35.1,9.5-65.7,10.2-85.3,16.2c0,0,2.6,27.5,7.8,35.7
|
||||||
|
c5.2,8.2,23.7,28.4,45.3,34.1c21.6,5.7,57.3,3.4,73.6-0.3c16.3-3.8,44.4-17.5,48.2-23.6c3.8-6.1-2-24.9,4.5-30.6
|
||||||
|
c6.5-5.6,92.2-25.7,124.6-35.4c32.4-10,156.3-52.6,173.1-50.5c-5.3,17.7-105,215.1-137.1,274c-32.1,58.9-218.6,318-258.3,363.6
|
||||||
|
c-30.1,34.7-103.2,123.5-128.5,143.6c6.4,1.8,51.6-2.1,59.9-7.2c51.3-31.6,136.9-138.1,164.4-170.5
|
||||||
|
c81.9-96,153.8-196.8,210.8-283.4h0.1c11.1,4.6,100.9,77.8,124.4,94c23.4,16.2,115.9,67.8,136,76.4c20,8.7,97.1,44.2,100.3,32.2
|
||||||
|
C1029.4,1668,1012.2,1597.1,1003.1,1593.7z"/>
|
||||||
|
</g>
|
||||||
|
<path class="st1" d="M569,2572c18,11,35,20,54,29c38,19,81,39,122,54c56,21,112,38,168,51c31,7,65,13,98,18c3,0,92,11,110,11h90
|
||||||
|
c35-3,68-5,103-10c28-4,59-9,89-16c22-5,45-10,67-17c21-6,45-14,68-22c15-5,31-12,47-18c13-6,29-13,44-19c18-8,39-19,59-29
|
||||||
|
c16-8,34-18,51-28c13-7,43-30,59-30c18,0,30,16,30,30c0,29-39,38-57,51c-19,13-42,23-62,34c-40,21-81,39-120,54
|
||||||
|
c-51,19-107,37-157,49c-19,4-38,9-57,12c-10,2-114,18-143,18h-132c-35-3-72-7-107-12c-31-5-64-11-95-18c-24-5-50-12-73-19
|
||||||
|
c-40-11-79-25-117-40c-69-26-141-60-209-105c-12-8-13-16-13-25c0-15,11-29,29-29C531,2546,563,2569,569,2572z"/>
|
||||||
|
<path class="st1" d="M1151,2009L61,2372V764l1090-363V2009z M1212,354v1680c-1,5-3,10-7,15c-2,3-6,7-9,8c-25,10-1151,388-1166,388
|
||||||
|
c-12,0-23-8-29-21c0-1-1-2-1-4V739c2-5,3-12,7-16c8-11,22-13,31-16c17-6,1126-378,1142-378C1190,329,1212,336,1212,354z"/>
|
||||||
|
<path class="st1" d="M2120,2017l-907-282V380l907-308V2017z M2181,32v2023c-1,23-17,33-32,33c-13,0-107-32-123-37
|
||||||
|
c-126-39-253-78-378-117c-28-9-57-18-84-27c-24-7-50-15-74-23c-107-33-216-66-323-102c-4-1-14-15-14-18V351c2-5,4-11,9-15
|
||||||
|
c8-9,351-123,486-168c36-13,487-168,501-168C2167,0,2181,13,2181,32z"/>
|
||||||
|
<polygon points="2411.2,2440.7 1199.5,2054.5 1204.6,373.2 2411.2,757.2 "/>
|
||||||
|
<g>
|
||||||
|
<path class="st2" d="M1800.3,1124.6L1681.4,1412l218.6,66.3L1800.3,1124.6z M1729,853.2l156.1,47.3l284.4,1025l-160.3-48.7
|
||||||
|
l-57.6-210.4L1620.2,1566l-71.3,171.4l-160.4-48.7L1729,853.2z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
BIN
packages/anastasis-webui/src/assets/logo.jpeg
Normal file
BIN
packages/anastasis-webui/src/assets/logo.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
@ -1,12 +1,15 @@
|
|||||||
import { FunctionalComponent, h } from "preact";
|
import { FunctionalComponent, h } from "preact";
|
||||||
|
import { TranslationProvider } from "../context/translation";
|
||||||
|
|
||||||
import AnastasisClient from "../routes/home";
|
import AnastasisClient from "../pages/home";
|
||||||
|
|
||||||
const App: FunctionalComponent = () => {
|
const App: FunctionalComponent = () => {
|
||||||
return (
|
return (
|
||||||
<div id="preact_root">
|
<TranslationProvider>
|
||||||
<AnastasisClient />
|
<div id="app" class="has-navbar-fixed-top">
|
||||||
</div>
|
<AnastasisClient />
|
||||||
|
</div>
|
||||||
|
</TranslationProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2021 Taler Systems S.A.
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import langIcon from '../../assets/icons/languageicon.svg';
|
||||||
|
import { useTranslationContext } from "../../context/translation";
|
||||||
|
import { strings as messages } from '../../i18n/strings'
|
||||||
|
|
||||||
|
type LangsNames = {
|
||||||
|
[P in keyof typeof messages]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const names: LangsNames = {
|
||||||
|
es: 'Español [es]',
|
||||||
|
en: 'English [en]',
|
||||||
|
fr: 'Français [fr]',
|
||||||
|
de: 'Deutsch [de]',
|
||||||
|
sv: 'Svenska [sv]',
|
||||||
|
it: 'Italiano [it]',
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLangName(s: keyof LangsNames | string) {
|
||||||
|
if (names[s]) return names[s]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LangSelector(): VNode {
|
||||||
|
const [updatingLang, setUpdatingLang] = useState(false)
|
||||||
|
const { lang, changeLanguage } = useTranslationContext()
|
||||||
|
|
||||||
|
return <div class="dropdown is-active ">
|
||||||
|
<div class="dropdown-trigger">
|
||||||
|
<button class="button has-tooltip-left"
|
||||||
|
data-tooltip="change language selection"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-controls="dropdown-menu" onClick={() => setUpdatingLang(!updatingLang)}>
|
||||||
|
<div class="icon is-small is-left">
|
||||||
|
<img src={langIcon} />
|
||||||
|
</div>
|
||||||
|
<span>{getLangName(lang)}</span>
|
||||||
|
<div class="icon is-right">
|
||||||
|
<i class="mdi mdi-chevron-down" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{updatingLang && <div class="dropdown-menu" id="dropdown-menu" role="menu">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
{Object.keys(messages)
|
||||||
|
.filter((l) => l !== lang)
|
||||||
|
.map(l => <a key={l} class="dropdown-item" value={l} onClick={() => { changeLanguage(l); setUpdatingLang(false) }}>{getLangName(l)}</a>)}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2021 Taler Systems S.A.
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { h, VNode } from 'preact';
|
||||||
|
import logo from '../../assets/logo.jpeg';
|
||||||
|
import { LangSelector } from './LangSelector';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onMobileMenu: () => void;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NavigationBar({ onMobileMenu, title }: Props): VNode {
|
||||||
|
return (<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<span class="navbar-item" style={{ fontSize: 24, fontWeight: 900 }}>{title}</span>
|
||||||
|
|
||||||
|
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" onClick={(e) => {
|
||||||
|
onMobileMenu()
|
||||||
|
e.stopPropagation()
|
||||||
|
}}>
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar-menu ">
|
||||||
|
<a class="navbar-start is-justify-content-center is-flex-grow-1" href="https://taler.net">
|
||||||
|
<img src={logo} style={{ height: 50, maxHeight: 50 }} />
|
||||||
|
</a>
|
||||||
|
<div class="navbar-end">
|
||||||
|
<div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
|
||||||
|
<LangSelector />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
101
packages/anastasis-webui/src/components/menu/SideBar.tsx
Normal file
101
packages/anastasis-webui/src/components/menu/SideBar.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2021 Taler Systems S.A.
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import { h, VNode } from 'preact';
|
||||||
|
import { Translate } from '../../i18n';
|
||||||
|
import { LangSelector } from './LangSelector';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mobile?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Sidebar({ mobile }: Props): VNode {
|
||||||
|
// const config = useConfigContext();
|
||||||
|
const config = { version: 'none' }
|
||||||
|
const process = { env : { __VERSION__: '0.0.0'}}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside class="aside is-placed-left is-expanded">
|
||||||
|
{mobile && <div class="footer" onClick={(e) => { return e.stopImmediatePropagation() }}>
|
||||||
|
<LangSelector />
|
||||||
|
</div>}
|
||||||
|
<div class="aside-tools">
|
||||||
|
<div class="aside-tools-label">
|
||||||
|
<div><b>Anastasis</b> Reducer</div>
|
||||||
|
<div class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }}>
|
||||||
|
{process.env.__VERSION__} ({config.version})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="menu is-menu-main">
|
||||||
|
<p class="menu-label">
|
||||||
|
<Translate>Back up a secret</Translate>
|
||||||
|
</p>
|
||||||
|
<ul class="menu-list">
|
||||||
|
<li>
|
||||||
|
<div class="has-icon">
|
||||||
|
<span class="icon"><i class="mdi mdi-square-edit-outline" /></span>
|
||||||
|
<span class="menu-item-label"><Translate>Location & Currency</Translate></span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="is-active">
|
||||||
|
<div class="has-icon">
|
||||||
|
<span class="icon"><i class="mdi mdi-cash-register" /></span>
|
||||||
|
<span class="menu-item-label"><Translate>Personal information</Translate></span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="has-icon">
|
||||||
|
<span class="icon"><i class="mdi mdi-shopping" /></span>
|
||||||
|
<span class="menu-item-label"><Translate>Authorization methods</Translate></span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="has-icon">
|
||||||
|
<span class="icon"><i class="mdi mdi-bank" /></span>
|
||||||
|
<span class="menu-item-label"><Translate>Recovery policies</Translate></span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="has-icon">
|
||||||
|
<span class="icon"><i class="mdi mdi-bank" /></span>
|
||||||
|
<span class="menu-item-label"><Translate>Enter secrets</Translate></span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="has-icon">
|
||||||
|
<span class="icon"><i class="mdi mdi-bank" /></span>
|
||||||
|
<span class="menu-item-label"><Translate>Payment (optional)</Translate></span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="has-icon">
|
||||||
|
<span class="icon"><i class="mdi mdi-cash" /></span>
|
||||||
|
<span class="menu-item-label">Backup completed</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
104
packages/anastasis-webui/src/components/menu/index.tsx
Normal file
104
packages/anastasis-webui/src/components/menu/index.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||||
|
import Match from 'preact-router/match';
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
import { NavigationBar } from "./NavigationBar";
|
||||||
|
import { Sidebar } from "./SideBar";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface MenuProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WithTitle({ title, children }: { title: string; children: ComponentChildren }): VNode {
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = `Taler Backoffice: ${title}`
|
||||||
|
}, [title])
|
||||||
|
return <Fragment>{children}</Fragment>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Menu({ title }: MenuProps): VNode {
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
|
|
||||||
|
return <Match>{({ path }: { path: string }) => {
|
||||||
|
const titleWithSubtitle = title // title ? title : (!admin ? getInstanceTitle(path, instance) : getAdminTitle(path, instance))
|
||||||
|
return (<WithTitle title={titleWithSubtitle}>
|
||||||
|
<div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}>
|
||||||
|
<NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={titleWithSubtitle} />
|
||||||
|
|
||||||
|
<Sidebar mobile={mobileOpen} />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</WithTitle>
|
||||||
|
)
|
||||||
|
}}</Match>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotYetReadyAppMenuProps {
|
||||||
|
title: string;
|
||||||
|
onLogout?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotifProps {
|
||||||
|
notification?: Notification;
|
||||||
|
}
|
||||||
|
export function NotificationCard({ notification: n }: NotifProps): VNode | null {
|
||||||
|
if (!n) return null
|
||||||
|
return <div class="notification">
|
||||||
|
<div class="columns is-vcentered">
|
||||||
|
<div class="column is-12">
|
||||||
|
<article class={n.type === 'ERROR' ? "message is-danger" : (n.type === 'WARN' ? "message is-warning" : "message is-info")}>
|
||||||
|
<div class="message-header">
|
||||||
|
<p>{n.message}</p>
|
||||||
|
</div>
|
||||||
|
{n.description &&
|
||||||
|
<div class="message-body">
|
||||||
|
{n.description}
|
||||||
|
</div>}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotYetReadyAppMenu({ onLogout, title }: NotYetReadyAppMenuProps): VNode {
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = `Taler Backoffice: ${title}`
|
||||||
|
}, [title])
|
||||||
|
|
||||||
|
return <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}>
|
||||||
|
<NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={title} />
|
||||||
|
{onLogout && <Sidebar onLogout={onLogout} mobile={mobileOpen} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Notification {
|
||||||
|
message: string;
|
||||||
|
description?: string | VNode;
|
||||||
|
type: MessageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ValueOrFunction<T> = T | ((p: T) => T)
|
||||||
|
export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'
|
||||||
|
|
59
packages/anastasis-webui/src/context/translation.ts
Normal file
59
packages/anastasis-webui/src/context/translation.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
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 { createContext, h, VNode } from 'preact'
|
||||||
|
import { useContext, useEffect } from 'preact/hooks'
|
||||||
|
import { useLang } from '../hooks'
|
||||||
|
import * as jedLib from "jed";
|
||||||
|
import { strings } from "../i18n/strings";
|
||||||
|
|
||||||
|
interface Type {
|
||||||
|
lang: string;
|
||||||
|
handler: any;
|
||||||
|
changeLanguage: (l: string) => void;
|
||||||
|
}
|
||||||
|
const initial = {
|
||||||
|
lang: 'en',
|
||||||
|
handler: null,
|
||||||
|
changeLanguage: () => {
|
||||||
|
// do not change anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const Context = createContext<Type>(initial)
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initial?: string;
|
||||||
|
children: any;
|
||||||
|
forceLang?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TranslationProvider = ({ initial, children, forceLang }: Props): VNode => {
|
||||||
|
const [lang, changeLanguage] = useLang(initial)
|
||||||
|
useEffect(() => {
|
||||||
|
if (forceLang) {
|
||||||
|
changeLanguage(forceLang)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const handler = new jedLib.Jed(strings[lang]);
|
||||||
|
return h(Context.Provider, { value: { lang, handler, changeLanguage }, children });
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTranslationContext = (): Type => useContext(Context);
|
110
packages/anastasis-webui/src/hooks/index.ts
Normal file
110
packages/anastasis-webui/src/hooks/index.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
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 { 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
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBackendURL(url?: string): [string, boolean, StateUpdater<string>, () => void] {
|
||||||
|
const [value, setter] = useNotNullLocalStorage('backend-url', url || calculateRootPath())
|
||||||
|
const [triedToLog, setTriedToLog] = useLocalStorage('tried-login')
|
||||||
|
|
||||||
|
const checkedSetter = (v: ValueOrFunction<string>) => {
|
||||||
|
setTriedToLog('yes')
|
||||||
|
return setter(p => (v instanceof Function ? v(p) : v).replace(/\/$/, ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetBackend = () => {
|
||||||
|
setTriedToLog(undefined)
|
||||||
|
}
|
||||||
|
return [value, !!triedToLog, checkedSetter, resetBackend]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBackendDefaultToken(): [string | undefined, StateUpdater<string | undefined>] {
|
||||||
|
return useLocalStorage('backend-token')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBackendInstanceToken(id: string): [string | undefined, StateUpdater<string | undefined>] {
|
||||||
|
const [token, setToken] = useLocalStorage(`backend-token-${id}`)
|
||||||
|
const [defaultToken, defaultSetToken] = useBackendDefaultToken()
|
||||||
|
|
||||||
|
// instance named 'default' use the default token
|
||||||
|
if (id === 'default') {
|
||||||
|
return [defaultToken, defaultSetToken]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [token, setToken]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLang(initial?: string): [string, StateUpdater<string>] {
|
||||||
|
const browserLang = typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined;
|
||||||
|
const defaultLang = (browserLang || initial || 'en').substring(0, 2)
|
||||||
|
return useNotNullLocalStorage('lang-preference', defaultLang)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 setValue = (value?: string | ((val?: string) => string | undefined)) => {
|
||||||
|
setStoredValue(p => {
|
||||||
|
const toStore = value instanceof Function ? value(p) : value
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
if (!toStore) {
|
||||||
|
window.localStorage.removeItem(key)
|
||||||
|
} else {
|
||||||
|
window.localStorage.setItem(key, toStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toStore
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return [storedValue, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNotNullLocalStorage(key: string, initialValue: string): [string, StateUpdater<string>] {
|
||||||
|
const [storedValue, setStoredValue] = useState<string>((): string => {
|
||||||
|
return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [storedValue, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { BackupStates, getBackupStartState, getRecoveryStartState, RecoveryState
|
|||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
|
|
||||||
const reducerBaseUrl = "http://localhost:5000/";
|
const reducerBaseUrl = "http://localhost:5000/";
|
||||||
let remoteReducer = true;
|
const remoteReducer = true;
|
||||||
|
|
||||||
interface AnastasisState {
|
interface AnastasisState {
|
||||||
reducerState: ReducerState | undefined;
|
reducerState: ReducerState | undefined;
|
||||||
@ -123,7 +123,7 @@ function storageSet(key: string, value: any): void {
|
|||||||
function restoreState(): any {
|
function restoreState(): any {
|
||||||
let state: any;
|
let state: any;
|
||||||
try {
|
try {
|
||||||
let s = storageGet("anastasisReducerState");
|
const s = storageGet("anastasisReducerState");
|
||||||
if (s === "undefined") {
|
if (s === "undefined") {
|
||||||
state = undefined;
|
state = undefined;
|
||||||
} else if (s) {
|
} else if (s) {
|
||||||
|
203
packages/anastasis-webui/src/i18n/index.tsx
Normal file
203
packages/anastasis-webui/src/i18n/index.tsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation helpers for React components and template literals.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports
|
||||||
|
*/
|
||||||
|
import { ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact";
|
||||||
|
|
||||||
|
import { useTranslationContext } from "../context/translation";
|
||||||
|
|
||||||
|
export function useTranslator() {
|
||||||
|
const ctx = useTranslationContext();
|
||||||
|
const jed = ctx.handler
|
||||||
|
return function str(stringSeq: TemplateStringsArray, ...values: any[]): string {
|
||||||
|
const s = toI18nString(stringSeq);
|
||||||
|
if (!s) return s
|
||||||
|
const tr = jed
|
||||||
|
.translate(s)
|
||||||
|
.ifPlural(1, s)
|
||||||
|
.fetch(...values);
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert template strings to a msgid
|
||||||
|
*/
|
||||||
|
function toI18nString(stringSeq: ReadonlyArray<string>): string {
|
||||||
|
let s = "";
|
||||||
|
for (let i = 0; i < stringSeq.length; i++) {
|
||||||
|
s += stringSeq[i];
|
||||||
|
if (i < stringSeq.length - 1) {
|
||||||
|
s += `%${i + 1}$s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface TranslateSwitchProps {
|
||||||
|
target: number;
|
||||||
|
children: ComponentChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyChildren(children: ComponentChildren): string {
|
||||||
|
let n = 1;
|
||||||
|
const ss = (children instanceof Array ? children : [children]).map((c) => {
|
||||||
|
if (typeof c === "string") {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return `%${n++}$s`;
|
||||||
|
});
|
||||||
|
const s = ss.join("").replace(/ +/g, " ").trim();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TranslateProps {
|
||||||
|
children: ComponentChildren;
|
||||||
|
/**
|
||||||
|
* Component that the translated element should be wrapped in.
|
||||||
|
* Defaults to "div".
|
||||||
|
*/
|
||||||
|
wrap?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props to give to the wrapped component.
|
||||||
|
*/
|
||||||
|
wrapProps?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTranslatedChildren(
|
||||||
|
translation: string,
|
||||||
|
children: ComponentChildren,
|
||||||
|
): ComponentChild[] {
|
||||||
|
const tr = translation.split(/%(\d+)\$s/);
|
||||||
|
const childArray = children instanceof Array ? children : [children];
|
||||||
|
// Merge consecutive string children.
|
||||||
|
const placeholderChildren = Array<ComponentChild>();
|
||||||
|
for (let i = 0; i < childArray.length; i++) {
|
||||||
|
const x = childArray[i];
|
||||||
|
if (x === undefined) {
|
||||||
|
continue;
|
||||||
|
} else if (typeof x === "string") {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
placeholderChildren.push(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = Array<ComponentChild>();
|
||||||
|
for (let i = 0; i < tr.length; i++) {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
// Text
|
||||||
|
result.push(tr[i]);
|
||||||
|
} else {
|
||||||
|
const childIdx = Number.parseInt(tr[i],10) - 1;
|
||||||
|
result.push(placeholderChildren[childIdx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate text node children of this component.
|
||||||
|
* If a child component might produce a text node, it must be wrapped
|
||||||
|
* in a another non-text element.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```
|
||||||
|
* <Translate>
|
||||||
|
* Hello. Your score is <span><PlayerScore player={player} /></span>
|
||||||
|
* </Translate>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function Translate({ children }: TranslateProps): VNode {
|
||||||
|
const s = stringifyChildren(children);
|
||||||
|
const ctx = useTranslationContext()
|
||||||
|
const translation: string = ctx.handler.ngettext(s, s, 1);
|
||||||
|
const result = getTranslatedChildren(translation, children)
|
||||||
|
return <Fragment>{result}</Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch translation based on singular or plural based on the target prop.
|
||||||
|
* Should only contain TranslateSingular and TransplatePlural as children.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```
|
||||||
|
* <TranslateSwitch target={n}>
|
||||||
|
* <TranslateSingular>I have {n} apple.</TranslateSingular>
|
||||||
|
* <TranslatePlural>I have {n} apples.</TranslatePlural>
|
||||||
|
* </TranslateSwitch>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function TranslateSwitch({ children, target }: TranslateSwitchProps) {
|
||||||
|
let singular: VNode<TranslationPluralProps> | undefined;
|
||||||
|
let plural: VNode<TranslationPluralProps> | undefined;
|
||||||
|
// const children = this.props.children;
|
||||||
|
if (children) {
|
||||||
|
(children instanceof Array ? children : [children]).forEach((child: any) => {
|
||||||
|
if (child.type === TranslatePlural) {
|
||||||
|
plural = child;
|
||||||
|
}
|
||||||
|
if (child.type === TranslateSingular) {
|
||||||
|
singular = child;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!singular || !plural) {
|
||||||
|
console.error("translation not found");
|
||||||
|
return h("span", {}, ["translation not found"]);
|
||||||
|
}
|
||||||
|
singular.props.target = target;
|
||||||
|
plural.props.target = target;
|
||||||
|
// We're looking up the translation based on the
|
||||||
|
// singular, even if we must use the plural form.
|
||||||
|
return singular;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TranslationPluralProps {
|
||||||
|
children: ComponentChildren;
|
||||||
|
target: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [[TranslateSwitch]].
|
||||||
|
*/
|
||||||
|
export function TranslatePlural({ children, target }: TranslationPluralProps): VNode {
|
||||||
|
const s = stringifyChildren(children);
|
||||||
|
const ctx = useTranslationContext()
|
||||||
|
const translation = ctx.handler.ngettext(s, s, 1);
|
||||||
|
const result = getTranslatedChildren(translation, children);
|
||||||
|
return <Fragment>{result}</Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [[TranslateSwitch]].
|
||||||
|
*/
|
||||||
|
export function TranslateSingular({ children, target }: TranslationPluralProps): VNode {
|
||||||
|
const s = stringifyChildren(children);
|
||||||
|
const ctx = useTranslationContext()
|
||||||
|
const translation = ctx.handler.ngettext(s, s, target);
|
||||||
|
const result = getTranslatedChildren(translation, children);
|
||||||
|
return <Fragment>{result}</Fragment>;
|
||||||
|
|
||||||
|
}
|
27
packages/anastasis-webui/src/i18n/poheader
Normal file
27
packages/anastasis-webui/src/i18n/poheader
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# 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/>
|
||||||
|
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Taler Wallet\n"
|
||||||
|
"Report-Msgid-Bugs-To: taler@gnu.org\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
19
packages/anastasis-webui/src/i18n/strings-prelude
Normal file
19
packages/anastasis-webui/src/i18n/strings-prelude
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*eslint quote-props: ["error", "consistent"]*/
|
||||||
|
export const strings: {[s: string]: any} = {};
|
||||||
|
|
44
packages/anastasis-webui/src/i18n/strings.ts
Normal file
44
packages/anastasis-webui/src/i18n/strings.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*eslint quote-props: ["error", "consistent"]*/
|
||||||
|
export const strings: {[s: string]: any} = {};
|
||||||
|
|
||||||
|
strings['de'] = {
|
||||||
|
"domain": "messages",
|
||||||
|
"locale_data": {
|
||||||
|
"messages": {
|
||||||
|
"": {
|
||||||
|
"domain": "messages",
|
||||||
|
"plural_forms": "nplurals=2; plural=(n != 1);",
|
||||||
|
"lang": ""
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
strings['en'] = {
|
||||||
|
"domain": "messages",
|
||||||
|
"locale_data": {
|
||||||
|
"messages": {
|
||||||
|
"": {
|
||||||
|
"domain": "messages",
|
||||||
|
"plural_forms": "nplurals=2; plural=(n != 1);",
|
||||||
|
"lang": ""
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
26
packages/anastasis-webui/src/i18n/taler-anastasis.pot
Normal file
26
packages/anastasis-webui/src/i18n/taler-anastasis.pot
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# 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/>
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Taler Anastasis\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import './style/index.css';
|
|
||||||
import App from './components/app';
|
import App from './components/app';
|
||||||
|
import './scss/main.scss';
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AnastasisReducerApi, ReducerStateRecovery, ReducerStateBackup } from "../../hooks/use-anastasis-reducer";
|
||||||
|
import { AnastasisClientFrame, withProcessLabel, LabeledInput } from "./index";
|
||||||
|
|
||||||
|
export function AttributeEntryScreen(props: AttributeEntryProps): VNode {
|
||||||
|
const { reducer, reducerState: backupState } = props;
|
||||||
|
const [attrs, setAttrs] = useState<Record<string, string>>(
|
||||||
|
props.reducerState.identity_attributes ?? {}
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
title={withProcessLabel(reducer, "Select Country")}
|
||||||
|
onNext={() => reducer.transition("enter_user_attributes", {
|
||||||
|
identity_attributes: attrs,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{backupState.required_attributes.map((x: any, i: number) => {
|
||||||
|
return (
|
||||||
|
<AttributeEntryField
|
||||||
|
key={i}
|
||||||
|
isFirst={i == 0}
|
||||||
|
setValue={(v: string) => setAttrs({ ...attrs, [x.name]: v })}
|
||||||
|
spec={x}
|
||||||
|
value={attrs[x.name]} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttributeEntryProps {
|
||||||
|
reducer: AnastasisReducerApi;
|
||||||
|
reducerState: ReducerStateRecovery | ReducerStateBackup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeEntryFieldProps {
|
||||||
|
isFirst: boolean;
|
||||||
|
value: string;
|
||||||
|
setValue: (newValue: string) => void;
|
||||||
|
spec: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LabeledInput
|
||||||
|
grabFocus={props.isFirst}
|
||||||
|
label={props.spec.label}
|
||||||
|
bind={[props.value, props.setValue]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import {
|
||||||
|
encodeCrock,
|
||||||
|
stringToBytes
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AuthMethodSetupProps, AnastasisClientFrame, LabeledInput } from "./index";
|
||||||
|
|
||||||
|
export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNav title="Add email authentication">
|
||||||
|
<p>
|
||||||
|
For email authentication, you need to provide an email address. When
|
||||||
|
recovering your secret, you will need to enter the code you receive by
|
||||||
|
email.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<LabeledInput
|
||||||
|
label="Email address"
|
||||||
|
grabFocus
|
||||||
|
bind={[email, setEmail]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => props.cancel()}>Cancel</button>
|
||||||
|
<button
|
||||||
|
onClick={() => props.addAuthMethod({
|
||||||
|
authentication_method: {
|
||||||
|
type: "email",
|
||||||
|
instructions: `Email to ${email}`,
|
||||||
|
challenge: encodeCrock(stringToBytes(email)),
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import {
|
||||||
|
canonicalJson, encodeCrock,
|
||||||
|
stringToBytes
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AuthMethodSetupProps, LabeledInput } from "./index";
|
||||||
|
|
||||||
|
export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode {
|
||||||
|
const [fullName, setFullName] = useState("");
|
||||||
|
const [street, setStreet] = useState("");
|
||||||
|
const [city, setCity] = useState("");
|
||||||
|
const [postcode, setPostcode] = useState("");
|
||||||
|
const [country, setCountry] = useState("");
|
||||||
|
|
||||||
|
const addPostAuth = () => {
|
||||||
|
const challengeJson = {
|
||||||
|
full_name: fullName,
|
||||||
|
street,
|
||||||
|
city,
|
||||||
|
postcode,
|
||||||
|
country,
|
||||||
|
};
|
||||||
|
props.addAuthMethod({
|
||||||
|
authentication_method: {
|
||||||
|
type: "email",
|
||||||
|
instructions: `Letter to address in postal code ${postcode}`,
|
||||||
|
challenge: encodeCrock(stringToBytes(canonicalJson(challengeJson))),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={style.home}>
|
||||||
|
<h1>Add {props.method} authentication</h1>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
For postal letter authentication, you need to provide a postal
|
||||||
|
address. When recovering your secret, you will be asked to enter a
|
||||||
|
code that you will receive in a letter to that address.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<LabeledInput
|
||||||
|
grabFocus
|
||||||
|
label="Full Name"
|
||||||
|
bind={[fullName, setFullName]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LabeledInput label="Street" bind={[street, setStreet]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LabeledInput label="City" bind={[city, setCity]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LabeledInput label="Postal Code" bind={[postcode, setPostcode]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LabeledInput label="Country" bind={[country, setCountry]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => props.cancel()}>Cancel</button>
|
||||||
|
<button onClick={() => addPostAuth()}>Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import {
|
||||||
|
encodeCrock,
|
||||||
|
stringToBytes
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AuthMethodSetupProps, AnastasisClientFrame, LabeledInput } from "./index";
|
||||||
|
|
||||||
|
export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode {
|
||||||
|
const [questionText, setQuestionText] = useState("");
|
||||||
|
const [answerText, setAnswerText] = useState("");
|
||||||
|
const addQuestionAuth = (): void => props.addAuthMethod({
|
||||||
|
authentication_method: {
|
||||||
|
type: "question",
|
||||||
|
instructions: questionText,
|
||||||
|
challenge: encodeCrock(stringToBytes(answerText)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNav title="Add Security Question">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
For security question authentication, you need to provide a question
|
||||||
|
and its answer. When recovering your secret, you will be shown the
|
||||||
|
question and you will need to type the answer exactly as you typed it
|
||||||
|
here.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<LabeledInput
|
||||||
|
label="Security question"
|
||||||
|
grabFocus
|
||||||
|
bind={[questionText, setQuestionText]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LabeledInput label="Answer" bind={[answerText, setAnswerText]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => props.cancel()}>Cancel</button>
|
||||||
|
<button onClick={() => addQuestionAuth()}>Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import {
|
||||||
|
encodeCrock,
|
||||||
|
stringToBytes
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState, useRef, useLayoutEffect } from "preact/hooks";
|
||||||
|
import { AuthMethodSetupProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function AuthMethodSmsSetup(props: AuthMethodSetupProps): VNode {
|
||||||
|
const [mobileNumber, setMobileNumber] = useState("");
|
||||||
|
const addSmsAuth = (): void => {
|
||||||
|
props.addAuthMethod({
|
||||||
|
authentication_method: {
|
||||||
|
type: "sms",
|
||||||
|
instructions: `SMS to ${mobileNumber}`,
|
||||||
|
challenge: encodeCrock(stringToBytes(mobileNumber)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNav title="Add SMS authentication">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
For SMS authentication, you need to provide a mobile number. When
|
||||||
|
recovering your secret, you will be asked to enter the code you
|
||||||
|
receive via SMS.
|
||||||
|
</p>
|
||||||
|
<label>
|
||||||
|
Mobile number:{" "}
|
||||||
|
<input
|
||||||
|
value={mobileNumber}
|
||||||
|
ref={inputRef}
|
||||||
|
style={{ display: "block" }}
|
||||||
|
autoFocus
|
||||||
|
onChange={(e) => setMobileNumber((e.target as any).value)}
|
||||||
|
type="text" />
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => props.cancel()}>Cancel</button>
|
||||||
|
<button onClick={() => addSmsAuth()}>Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AuthMethod, ReducerStateBackup } from "anastasis-core";
|
||||||
|
import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer";
|
||||||
|
import { AuthMethodEmailSetup } from "./AuthMethodEmailSetup";
|
||||||
|
import { AuthMethodPostSetup } from "./AuthMethodPostSetup";
|
||||||
|
import { AuthMethodQuestionSetup } from "./AuthMethodQuestionSetup";
|
||||||
|
import { AuthMethodSmsSetup } from "./AuthMethodSmsSetup";
|
||||||
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function AuthenticationEditorScreen(props: AuthenticationEditorProps): VNode {
|
||||||
|
const [selectedMethod, setSelectedMethod] = useState<string | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
const { reducer, backupState } = props;
|
||||||
|
const providers = backupState.authentication_providers!;
|
||||||
|
const authAvailableSet = new Set<string>();
|
||||||
|
for (const provKey of Object.keys(providers)) {
|
||||||
|
const p = providers[provKey];
|
||||||
|
if ("http_status" in p && (!("error_code" in p)) && p.methods) {
|
||||||
|
for (const meth of p.methods) {
|
||||||
|
authAvailableSet.add(meth.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedMethod) {
|
||||||
|
const cancel = (): void => setSelectedMethod(undefined);
|
||||||
|
const addMethod = (args: any): void => {
|
||||||
|
reducer.transition("add_authentication", args);
|
||||||
|
setSelectedMethod(undefined);
|
||||||
|
};
|
||||||
|
const methodMap: Record<
|
||||||
|
string, (props: AuthMethodSetupProps) => h.JSX.Element
|
||||||
|
> = {
|
||||||
|
sms: AuthMethodSmsSetup,
|
||||||
|
question: AuthMethodQuestionSetup,
|
||||||
|
email: AuthMethodEmailSetup,
|
||||||
|
post: AuthMethodPostSetup,
|
||||||
|
};
|
||||||
|
const AuthSetup = methodMap[selectedMethod] ?? AuthMethodNotImplemented;
|
||||||
|
return (
|
||||||
|
<AuthSetup
|
||||||
|
cancel={cancel}
|
||||||
|
addAuthMethod={addMethod}
|
||||||
|
method={selectedMethod} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function MethodButton(props: { method: string; label: string }): VNode {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
disabled={!authAvailableSet.has(props.method)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedMethod(props.method);
|
||||||
|
reducer.dismissError();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const configuredAuthMethods: AuthMethod[] = backupState.authentication_methods ?? [];
|
||||||
|
const haveMethodsConfigured = configuredAuthMethods.length;
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame title="Backup: Configure Authentication Methods">
|
||||||
|
<div>
|
||||||
|
<MethodButton method="sms" label="SMS" />
|
||||||
|
<MethodButton method="email" label="Email" />
|
||||||
|
<MethodButton method="question" label="Question" />
|
||||||
|
<MethodButton method="post" label="Physical Mail" />
|
||||||
|
<MethodButton method="totp" label="TOTP" />
|
||||||
|
<MethodButton method="iban" label="IBAN" />
|
||||||
|
</div>
|
||||||
|
<h2>Configured authentication methods</h2>
|
||||||
|
{haveMethodsConfigured ? (
|
||||||
|
configuredAuthMethods.map((x, i) => {
|
||||||
|
return (
|
||||||
|
<p key={i}>
|
||||||
|
{x.type} ({x.instructions}){" "}
|
||||||
|
<button
|
||||||
|
onClick={() => reducer.transition("delete_authentication", {
|
||||||
|
authentication_method: i,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<p>No authentication methods configured yet.</p>
|
||||||
|
)}
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthMethodSetupProps {
|
||||||
|
method: string;
|
||||||
|
addAuthMethod: (x: any) => void;
|
||||||
|
cancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthMethodNotImplemented(props: AuthMethodSetupProps): VNode {
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNav title={`Add ${props.method} authentication`}>
|
||||||
|
<p>This auth method is not implemented yet, please choose another one.</p>
|
||||||
|
<button onClick={() => props.cancel()}>Cancel</button>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthenticationEditorProps {
|
||||||
|
reducer: AnastasisReducerApi;
|
||||||
|
backupState: ReducerStateBackup;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { BackupReducerProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function BackupFinishedScreen(props: BackupReducerProps): VNode {
|
||||||
|
return (<AnastasisClientFrame hideNext title="Backup finished">
|
||||||
|
<p>
|
||||||
|
Your backup of secret "{props.backupState.secret_name ?? "??"}" was
|
||||||
|
successful.
|
||||||
|
</p>
|
||||||
|
<p>The backup is stored by the following providers:</p>
|
||||||
|
<ul>
|
||||||
|
{Object.keys(props.backupState.success_details!).map((x, i) => {
|
||||||
|
const sd = props.backupState.success_details![x];
|
||||||
|
return (
|
||||||
|
<li key={i}>
|
||||||
|
{x} (Policy version {sd.policy_version})
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<button onClick={() => props.reducer.reset()}>Back to start</button>
|
||||||
|
</AnastasisClientFrame>);
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { RecoveryReducerProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function ChallengeOverviewScreen(props: RecoveryReducerProps): VNode {
|
||||||
|
const { recoveryState, reducer } = props;
|
||||||
|
const policies = recoveryState.recovery_information!.policies;
|
||||||
|
const chArr = recoveryState.recovery_information!.challenges;
|
||||||
|
const challenges: {
|
||||||
|
[uuid: string]: {
|
||||||
|
type: string;
|
||||||
|
instructions: string;
|
||||||
|
cost: string;
|
||||||
|
};
|
||||||
|
} = {};
|
||||||
|
for (const ch of chArr) {
|
||||||
|
challenges[ch.uuid] = {
|
||||||
|
type: ch.type,
|
||||||
|
cost: ch.cost,
|
||||||
|
instructions: ch.instructions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame title="Recovery: Solve challenges">
|
||||||
|
<h2>Policies</h2>
|
||||||
|
{policies.map((x, i) => {
|
||||||
|
return (
|
||||||
|
<div key={i}>
|
||||||
|
<h3>Policy #{i + 1}</h3>
|
||||||
|
{x.map((x, j) => {
|
||||||
|
const ch = challenges[x.uuid];
|
||||||
|
const feedback = recoveryState.challenge_feedback?.[x.uuid];
|
||||||
|
return (
|
||||||
|
<div key={j}
|
||||||
|
style={{
|
||||||
|
borderLeft: "2px solid gray",
|
||||||
|
paddingLeft: "0.5em",
|
||||||
|
borderRadius: "0.5em",
|
||||||
|
marginTop: "0.5em",
|
||||||
|
marginBottom: "0.5em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h4>
|
||||||
|
{ch.type} ({ch.instructions})
|
||||||
|
</h4>
|
||||||
|
<p>Status: {feedback?.state ?? "unknown"}</p>
|
||||||
|
{feedback?.state !== "solved" ? (
|
||||||
|
<button
|
||||||
|
onClick={() => reducer.transition("select_challenge", {
|
||||||
|
uuid: x.uuid,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Solve
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { CommonReducerProps, AnastasisClientFrame, withProcessLabel } from "./index";
|
||||||
|
|
||||||
|
export function ContinentSelectionScreen(props: CommonReducerProps): VNode {
|
||||||
|
const { reducer, reducerState } = props;
|
||||||
|
const sel = (x: string): void => reducer.transition("select_continent", { continent: x });
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
hideNext
|
||||||
|
title={withProcessLabel(reducer, "Select Continent")}
|
||||||
|
>
|
||||||
|
{reducerState.continents.map((x: any) => (
|
||||||
|
<button onClick={() => sel(x.name)} key={x.name}>
|
||||||
|
{x.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { CommonReducerProps, AnastasisClientFrame, withProcessLabel } from "./index";
|
||||||
|
|
||||||
|
export function CountrySelectionScreen(props: CommonReducerProps): VNode {
|
||||||
|
const { reducer, reducerState } = props;
|
||||||
|
const sel = (x: any): void => reducer.transition("select_country", {
|
||||||
|
country_code: x.code,
|
||||||
|
currencies: [x.currency],
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
hideNext
|
||||||
|
title={withProcessLabel(reducer, "Select Country")}
|
||||||
|
>
|
||||||
|
{reducerState.countries.map((x: any) => (
|
||||||
|
<button onClick={() => sel(x)} key={x.name}>
|
||||||
|
{x.name} ({x.currency})
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { BackupReducerProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function PoliciesPayingScreen(props: BackupReducerProps): VNode {
|
||||||
|
const payments = props.backupState.policy_payment_requests ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNext title="Backup: Recovery Document Payments">
|
||||||
|
<p>
|
||||||
|
Some of the providers require a payment to store the encrypted
|
||||||
|
recovery document.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{payments.map((x, i) => {
|
||||||
|
return (
|
||||||
|
<li key={i}>
|
||||||
|
{x.provider}: {x.payto}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<button onClick={() => props.reducer.transition("pay", {})}>
|
||||||
|
Check payment status now
|
||||||
|
</button>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
bytesToString,
|
||||||
|
decodeCrock
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { RecoveryReducerProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function RecoveryFinishedScreen(props: RecoveryReducerProps): VNode {
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame title="Recovery Finished" hideNext>
|
||||||
|
<h1>Recovery Finished</h1>
|
||||||
|
<p>
|
||||||
|
Secret: {bytesToString(decodeCrock(props.recoveryState.core_secret?.value!))}
|
||||||
|
</p>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { BackupReducerProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function ReviewPoliciesScreen(props: BackupReducerProps): VNode {
|
||||||
|
const { reducer, backupState } = props;
|
||||||
|
const authMethods = backupState.authentication_methods!;
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame title="Backup: Review Recovery Policies">
|
||||||
|
{backupState.policies?.map((p, i) => {
|
||||||
|
const policyName = p.methods
|
||||||
|
.map((x, i) => authMethods[x.authentication_method].type)
|
||||||
|
.join(" + ");
|
||||||
|
return (
|
||||||
|
<div key={i}>
|
||||||
|
{/* <div key={i} class={style.policy}> */}
|
||||||
|
<h3>
|
||||||
|
Policy #{i + 1}: {policyName}
|
||||||
|
</h3>
|
||||||
|
Required Authentications:
|
||||||
|
<ul>
|
||||||
|
{p.methods.map((x, i) => {
|
||||||
|
const m = authMethods[x.authentication_method];
|
||||||
|
return (
|
||||||
|
<li key={i}>
|
||||||
|
{m.type} ({m.instructions}) at provider {x.provider}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => reducer.transition("delete_policy", { policy_index: i })}
|
||||||
|
>
|
||||||
|
Delete Policy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import {
|
||||||
|
encodeCrock,
|
||||||
|
stringToBytes
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { BackupReducerProps, AnastasisClientFrame, LabeledInput } from "./index";
|
||||||
|
|
||||||
|
export function SecretEditorScreen(props: BackupReducerProps): VNode {
|
||||||
|
const { reducer } = props;
|
||||||
|
const [secretName, setSecretName] = useState(
|
||||||
|
props.backupState.secret_name ?? ""
|
||||||
|
);
|
||||||
|
const [secretValue, setSecretValue] = useState(
|
||||||
|
props.backupState.core_secret?.value ?? "" ?? ""
|
||||||
|
);
|
||||||
|
const secretNext = (): void => {
|
||||||
|
reducer.runTransaction(async (tx) => {
|
||||||
|
await tx.transition("enter_secret_name", {
|
||||||
|
name: secretName,
|
||||||
|
});
|
||||||
|
await tx.transition("enter_secret", {
|
||||||
|
secret: {
|
||||||
|
value: encodeCrock(stringToBytes(secretValue)),
|
||||||
|
mime: "text/plain",
|
||||||
|
},
|
||||||
|
expiration: {
|
||||||
|
t_ms: new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await tx.transition("next", {});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
title="Backup: Provide secret"
|
||||||
|
onNext={() => secretNext()}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<LabeledInput
|
||||||
|
label="Secret Name:"
|
||||||
|
grabFocus
|
||||||
|
bind={[secretName, setSecretName]} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LabeledInput
|
||||||
|
label="Secret Value:"
|
||||||
|
bind={[secretValue, setSecretValue]} />
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { RecoveryReducerProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function SecretSelectionScreen(props: RecoveryReducerProps): VNode {
|
||||||
|
const { reducer, recoveryState } = props;
|
||||||
|
const [selectingVersion, setSelectingVersion] = useState<boolean>(false);
|
||||||
|
const [otherVersion, setOtherVersion] = useState<number>(
|
||||||
|
recoveryState.recovery_document?.version ?? 0
|
||||||
|
);
|
||||||
|
const recoveryDocument = recoveryState.recovery_document!;
|
||||||
|
const [otherProvider, setOtherProvider] = useState<string>("");
|
||||||
|
function selectVersion(p: string, n: number): void {
|
||||||
|
reducer.runTransaction(async (tx) => {
|
||||||
|
await tx.transition("change_version", {
|
||||||
|
version: n,
|
||||||
|
provider_url: p,
|
||||||
|
});
|
||||||
|
setSelectingVersion(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (selectingVersion) {
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNav title="Recovery: Select secret">
|
||||||
|
<p>Select a different version of the secret</p>
|
||||||
|
<select onChange={(e) => setOtherProvider((e.target as any).value)}>
|
||||||
|
{Object.keys(recoveryState.authentication_providers ?? {}).map(
|
||||||
|
(x, i) => (
|
||||||
|
<option key={i} selected={x === recoveryDocument.provider_url} value={x}>
|
||||||
|
{x}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
value={otherVersion}
|
||||||
|
onChange={(e) => setOtherVersion(Number((e.target as HTMLInputElement).value))}
|
||||||
|
type="number" />
|
||||||
|
<button onClick={() => selectVersion(otherProvider, otherVersion)}>
|
||||||
|
Use this version
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => selectVersion(otherProvider, 0)}>
|
||||||
|
Use latest version
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => setSelectingVersion(false)}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame title="Recovery: Select secret">
|
||||||
|
<p>Provider: {recoveryDocument.provider_url}</p>
|
||||||
|
<p>Secret version: {recoveryDocument.version}</p>
|
||||||
|
<p>Secret name: {recoveryDocument.version}</p>
|
||||||
|
<button onClick={() => setSelectingVersion(true)}>
|
||||||
|
Select different secret
|
||||||
|
</button>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
22
packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx
Normal file
22
packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AnastasisClientFrame, LabeledInput } from "./index";
|
||||||
|
import { SolveEntryProps } from "./SolveScreen";
|
||||||
|
|
||||||
|
export function SolveEmailEntry(props: SolveEntryProps): VNode {
|
||||||
|
const [answer, setAnswer] = useState("");
|
||||||
|
const { reducer, challenge, feedback } = props;
|
||||||
|
const next = (): void => reducer.transition("solve_challenge", {
|
||||||
|
answer,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
title="Recovery: Solve challenge"
|
||||||
|
onNext={() => next()}
|
||||||
|
>
|
||||||
|
<p>Feedback: {JSON.stringify(feedback)}</p>
|
||||||
|
<p>{challenge.instructions}</p>
|
||||||
|
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
22
packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx
Normal file
22
packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AnastasisClientFrame, LabeledInput } from "./index";
|
||||||
|
import { SolveEntryProps } from "./SolveScreen";
|
||||||
|
|
||||||
|
export function SolvePostEntry(props: SolveEntryProps): VNode {
|
||||||
|
const [answer, setAnswer] = useState("");
|
||||||
|
const { reducer, challenge, feedback } = props;
|
||||||
|
const next = (): void => reducer.transition("solve_challenge", {
|
||||||
|
answer,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
title="Recovery: Solve challenge"
|
||||||
|
onNext={() => next()}
|
||||||
|
>
|
||||||
|
<p>Feedback: {JSON.stringify(feedback)}</p>
|
||||||
|
<p>{challenge.instructions}</p>
|
||||||
|
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AnastasisClientFrame, LabeledInput } from "./index";
|
||||||
|
import { SolveEntryProps } from "./SolveScreen";
|
||||||
|
|
||||||
|
export function SolveQuestionEntry(props: SolveEntryProps): VNode {
|
||||||
|
const [answer, setAnswer] = useState("");
|
||||||
|
const { reducer, challenge, feedback } = props;
|
||||||
|
const next = (): void => reducer.transition("solve_challenge", {
|
||||||
|
answer,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
title="Recovery: Solve challenge"
|
||||||
|
onNext={() => next()}
|
||||||
|
>
|
||||||
|
<p>Feedback: {JSON.stringify(feedback)}</p>
|
||||||
|
<p>Question: {challenge.instructions}</p>
|
||||||
|
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
41
packages/anastasis-webui/src/pages/home/SolveScreen.tsx
Normal file
41
packages/anastasis-webui/src/pages/home/SolveScreen.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { AnastasisReducerApi, ChallengeFeedback, ChallengeInfo } from "../../hooks/use-anastasis-reducer";
|
||||||
|
import { SolveEmailEntry } from "./SolveEmailEntry";
|
||||||
|
import { SolvePostEntry } from "./SolvePostEntry";
|
||||||
|
import { SolveQuestionEntry } from "./SolveQuestionEntry";
|
||||||
|
import { SolveSmsEntry } from "./SolveSmsEntry";
|
||||||
|
import { SolveUnsupportedEntry } from "./SolveUnsupportedEntry";
|
||||||
|
import { RecoveryReducerProps } from "./index";
|
||||||
|
|
||||||
|
export function SolveScreen(props: RecoveryReducerProps): VNode {
|
||||||
|
const chArr = props.recoveryState.recovery_information!.challenges;
|
||||||
|
const challengeFeedback = props.recoveryState.challenge_feedback ?? {};
|
||||||
|
const selectedUuid = props.recoveryState.selected_challenge_uuid!;
|
||||||
|
const challenges: {
|
||||||
|
[uuid: string]: ChallengeInfo;
|
||||||
|
} = {};
|
||||||
|
for (const ch of chArr) {
|
||||||
|
challenges[ch.uuid] = ch;
|
||||||
|
}
|
||||||
|
const selectedChallenge = challenges[selectedUuid];
|
||||||
|
const dialogMap: Record<string, (p: SolveEntryProps) => h.JSX.Element> = {
|
||||||
|
question: SolveQuestionEntry,
|
||||||
|
sms: SolveSmsEntry,
|
||||||
|
email: SolveEmailEntry,
|
||||||
|
post: SolvePostEntry,
|
||||||
|
};
|
||||||
|
const SolveDialog = dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
|
||||||
|
return (
|
||||||
|
<SolveDialog
|
||||||
|
challenge={selectedChallenge}
|
||||||
|
reducer={props.reducer}
|
||||||
|
feedback={challengeFeedback[selectedUuid]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SolveEntryProps {
|
||||||
|
reducer: AnastasisReducerApi;
|
||||||
|
challenge: ChallengeInfo;
|
||||||
|
feedback?: ChallengeFeedback;
|
||||||
|
}
|
||||||
|
|
22
packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx
Normal file
22
packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AnastasisClientFrame, LabeledInput } from "./index";
|
||||||
|
import { SolveEntryProps } from "./SolveScreen";
|
||||||
|
|
||||||
|
export function SolveSmsEntry(props: SolveEntryProps): VNode {
|
||||||
|
const [answer, setAnswer] = useState("");
|
||||||
|
const { reducer, challenge, feedback } = props;
|
||||||
|
const next = (): void => reducer.transition("solve_challenge", {
|
||||||
|
answer,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
title="Recovery: Solve challenge"
|
||||||
|
onNext={() => next()}
|
||||||
|
>
|
||||||
|
<p>Feedback: {JSON.stringify(feedback)}</p>
|
||||||
|
<p>{challenge.instructions}</p>
|
||||||
|
<LabeledInput label="Answer" grabFocus bind={[answer, setAnswer]} />
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
import { SolveEntryProps } from "./SolveScreen";
|
||||||
|
|
||||||
|
export function SolveUnsupportedEntry(props: SolveEntryProps): VNode {
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNext title="Recovery: Solve challenge">
|
||||||
|
<p>{JSON.stringify(props.challenge)}</p>
|
||||||
|
<p>Challenge not supported.</p>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
14
packages/anastasis-webui/src/pages/home/StartScreen.tsx
Normal file
14
packages/anastasis-webui/src/pages/home/StartScreen.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer";
|
||||||
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function StartScreen(props: { reducer: AnastasisReducerApi; }): VNode {
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNav title="Home">
|
||||||
|
<button autoFocus onClick={() => props.reducer.startBackup()}>
|
||||||
|
Backup
|
||||||
|
</button>
|
||||||
|
<button onClick={() => props.reducer.startRecover()}>Recover</button>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { BackupReducerProps, AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
export function TruthsPayingScreen(props: BackupReducerProps): VNode {
|
||||||
|
const payments = props.backupState.payments ?? [];
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
hideNext
|
||||||
|
title="Backup: Authentication Storage Payments"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Some of the providers require a payment to store the encrypted
|
||||||
|
authentication information.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{payments.map((x, i) => {
|
||||||
|
return <li key={i}>{x}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<button onClick={() => props.reducer.transition("pay", {})}>
|
||||||
|
Check payment status now
|
||||||
|
</button>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
248
packages/anastasis-webui/src/pages/home/index.tsx
Normal file
248
packages/anastasis-webui/src/pages/home/index.tsx
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import {
|
||||||
|
ComponentChildren, createContext,
|
||||||
|
Fragment, FunctionalComponent, h, VNode
|
||||||
|
} from "preact";
|
||||||
|
import { useContext, useLayoutEffect, useRef } from "preact/hooks";
|
||||||
|
import { Menu } from "../../components/menu";
|
||||||
|
import {
|
||||||
|
BackupStates, RecoveryStates,
|
||||||
|
ReducerStateBackup,
|
||||||
|
ReducerStateRecovery,
|
||||||
|
} from "anastasis-core";
|
||||||
|
import {
|
||||||
|
AnastasisReducerApi,
|
||||||
|
useAnastasisReducer
|
||||||
|
} from "../../hooks/use-anastasis-reducer";
|
||||||
|
import { AttributeEntryScreen } from "./AttributeEntryScreen";
|
||||||
|
import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen";
|
||||||
|
import { BackupFinishedScreen } from "./BackupFinishedScreen";
|
||||||
|
import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen";
|
||||||
|
import { ContinentSelectionScreen } from "./ContinentSelectionScreen";
|
||||||
|
import { CountrySelectionScreen } from "./CountrySelectionScreen";
|
||||||
|
import { PoliciesPayingScreen } from "./PoliciesPayingScreen";
|
||||||
|
import { RecoveryFinishedScreen } from "./RecoveryFinishedScreen";
|
||||||
|
import { ReviewPoliciesScreen } from "./ReviewPoliciesScreen";
|
||||||
|
import { SecretEditorScreen } from "./SecretEditorScreen";
|
||||||
|
import { SecretSelectionScreen } from "./SecretSelectionScreen";
|
||||||
|
import { SolveScreen } from "./SolveScreen";
|
||||||
|
import { StartScreen } from "./StartScreen";
|
||||||
|
import { TruthsPayingScreen } from "./TruthsPayingScreen";
|
||||||
|
|
||||||
|
const WithReducer = createContext<AnastasisReducerApi | undefined>(undefined);
|
||||||
|
|
||||||
|
function isBackup(reducer: AnastasisReducerApi): boolean {
|
||||||
|
return !!reducer.currentReducerState?.backup_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonReducerProps {
|
||||||
|
reducer: AnastasisReducerApi;
|
||||||
|
reducerState: ReducerStateBackup | ReducerStateRecovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withProcessLabel(reducer: AnastasisReducerApi, text: string): string {
|
||||||
|
if (isBackup(reducer)) {
|
||||||
|
return `Backup: ${text}`;
|
||||||
|
}
|
||||||
|
return `Recovery: ${text}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BackupReducerProps {
|
||||||
|
reducer: AnastasisReducerApi;
|
||||||
|
backupState: ReducerStateBackup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecoveryReducerProps {
|
||||||
|
reducer: AnastasisReducerApi;
|
||||||
|
recoveryState: ReducerStateRecovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnastasisClientFrameProps {
|
||||||
|
onNext?(): void;
|
||||||
|
title: string;
|
||||||
|
children: ComponentChildren;
|
||||||
|
/**
|
||||||
|
* Should back/next buttons be provided?
|
||||||
|
*/
|
||||||
|
hideNav?: boolean;
|
||||||
|
/**
|
||||||
|
* Hide only the "next" button.
|
||||||
|
*/
|
||||||
|
hideNext?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
|
||||||
|
const reducer = useContext(WithReducer);
|
||||||
|
if (!reducer) {
|
||||||
|
return <p>Fatal: Reducer must be in context.</p>;
|
||||||
|
}
|
||||||
|
const next = (): void => {
|
||||||
|
if (props.onNext) {
|
||||||
|
props.onNext();
|
||||||
|
} else {
|
||||||
|
reducer.transition("next", {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleKeyPress = (e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>): void => {
|
||||||
|
console.log("Got key press", e.key);
|
||||||
|
// FIXME: By default, "next" action should be executed here
|
||||||
|
};
|
||||||
|
return (<Fragment>
|
||||||
|
<Menu title="Anastasis" />
|
||||||
|
<section class="section">
|
||||||
|
<div onKeyPress={(e) => handleKeyPress(e)}> {/* class={style.home} */}
|
||||||
|
<button onClick={() => reducer.reset()}>Reset session</button>
|
||||||
|
<h1>{props.title}</h1>
|
||||||
|
<ErrorBanner reducer={reducer} />
|
||||||
|
{props.children}
|
||||||
|
{!props.hideNav ? (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => reducer.back()}>Back</button>
|
||||||
|
{!props.hideNext ? (
|
||||||
|
<button onClick={next}>Next</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnastasisClient: FunctionalComponent = () => {
|
||||||
|
const reducer = useAnastasisReducer();
|
||||||
|
return (
|
||||||
|
<WithReducer.Provider value={reducer}>
|
||||||
|
<AnastasisClientImpl />
|
||||||
|
</WithReducer.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnastasisClientImpl: FunctionalComponent = () => {
|
||||||
|
const reducer = useContext(WithReducer)!;
|
||||||
|
const reducerState = reducer.currentReducerState;
|
||||||
|
if (!reducerState) {
|
||||||
|
return <StartScreen reducer={reducer} />;
|
||||||
|
}
|
||||||
|
console.log("state", reducer.currentReducerState);
|
||||||
|
|
||||||
|
if (
|
||||||
|
reducerState.backup_state === BackupStates.ContinentSelecting ||
|
||||||
|
reducerState.recovery_state === RecoveryStates.ContinentSelecting
|
||||||
|
) {
|
||||||
|
return <ContinentSelectionScreen reducer={reducer} reducerState={reducerState} />;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
reducerState.backup_state === BackupStates.CountrySelecting ||
|
||||||
|
reducerState.recovery_state === RecoveryStates.CountrySelecting
|
||||||
|
) {
|
||||||
|
return <CountrySelectionScreen reducer={reducer} reducerState={reducerState} />;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
reducerState.backup_state === BackupStates.UserAttributesCollecting ||
|
||||||
|
reducerState.recovery_state === RecoveryStates.UserAttributesCollecting
|
||||||
|
) {
|
||||||
|
return <AttributeEntryScreen reducer={reducer} reducerState={reducerState} />;
|
||||||
|
}
|
||||||
|
if (reducerState.backup_state === BackupStates.AuthenticationsEditing) {
|
||||||
|
return (
|
||||||
|
<AuthenticationEditorScreen backupState={reducerState} reducer={reducer} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (reducerState.backup_state === BackupStates.PoliciesReviewing) {
|
||||||
|
return <ReviewPoliciesScreen reducer={reducer} backupState={reducerState} />;
|
||||||
|
}
|
||||||
|
if (reducerState.backup_state === BackupStates.SecretEditing) {
|
||||||
|
return <SecretEditorScreen reducer={reducer} backupState={reducerState} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reducerState.backup_state === BackupStates.BackupFinished) {
|
||||||
|
const backupState: ReducerStateBackup = reducerState;
|
||||||
|
return <BackupFinishedScreen reducer={reducer} backupState={backupState} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reducerState.backup_state === BackupStates.TruthsPaying) {
|
||||||
|
return <TruthsPayingScreen reducer={reducer} backupState={reducerState} />
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reducerState.backup_state === BackupStates.PoliciesPaying) {
|
||||||
|
const backupState: ReducerStateBackup = reducerState;
|
||||||
|
return <PoliciesPayingScreen reducer={reducer} backupState={backupState} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reducerState.recovery_state === RecoveryStates.SecretSelecting) {
|
||||||
|
return <SecretSelectionScreen reducer={reducer} recoveryState={reducerState} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reducerState.recovery_state === RecoveryStates.ChallengeSelecting) {
|
||||||
|
return <ChallengeOverviewScreen reducer={reducer} recoveryState={reducerState} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reducerState.recovery_state === RecoveryStates.ChallengeSolving) {
|
||||||
|
return <SolveScreen reducer={reducer} recoveryState={reducerState} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reducerState.recovery_state === RecoveryStates.RecoveryFinished) {
|
||||||
|
return <RecoveryFinishedScreen reducer={reducer} recoveryState={reducerState} />
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("unknown state", reducer.currentReducerState);
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame hideNav title="Bug">
|
||||||
|
<p>Bug: Unknown state.</p>
|
||||||
|
<button onClick={() => reducer.reset()}>Reset</button>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
interface LabeledInputProps {
|
||||||
|
label: string;
|
||||||
|
grabFocus?: boolean;
|
||||||
|
bind: [string, (x: string) => void];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LabeledInput(props: LabeledInputProps): VNode {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (props.grabFocus) {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
}, [props.grabFocus]);
|
||||||
|
return (
|
||||||
|
<label>
|
||||||
|
{props.label}
|
||||||
|
<input
|
||||||
|
value={props.bind[0]}
|
||||||
|
onChange={(e) => props.bind[1]((e.target as HTMLInputElement).value)}
|
||||||
|
ref={inputRef}
|
||||||
|
style={{ display: "block" }}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface ErrorBannerProps {
|
||||||
|
reducer: AnastasisReducerApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a dismissable error banner if there is a current error.
|
||||||
|
*/
|
||||||
|
function ErrorBanner(props: ErrorBannerProps): VNode | null {
|
||||||
|
const currentError = props.reducer.currentError;
|
||||||
|
if (currentError) {
|
||||||
|
return (
|
||||||
|
<div id="error"> {/* style.error */}
|
||||||
|
<p>Error: {JSON.stringify(currentError)}</p>
|
||||||
|
<button onClick={() => props.reducer.dismissError()}>
|
||||||
|
Dismiss Error
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnastasisClient;
|
@ -1,10 +1,9 @@
|
|||||||
import { FunctionalComponent, h } from 'preact';
|
import { FunctionalComponent, h } from 'preact';
|
||||||
import { Link } from 'preact-router/match';
|
import { Link } from 'preact-router/match';
|
||||||
import style from './style.css';
|
|
||||||
|
|
||||||
const Notfound: FunctionalComponent = () => {
|
const Notfound: FunctionalComponent = () => {
|
||||||
return (
|
return (
|
||||||
<div class={style.notfound}>
|
<div>
|
||||||
<h1>Error 404</h1>
|
<h1>Error 404</h1>
|
||||||
<p>That page doesn't exist.</p>
|
<p>That page doesn't exist.</p>
|
||||||
<Link href="/">
|
<Link href="/">
|
@ -1,6 +1,5 @@
|
|||||||
import { FunctionalComponent, h } from 'preact';
|
import { FunctionalComponent, h } from 'preact';
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
import style from './style.css';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: string;
|
user: string;
|
||||||
@ -27,7 +26,7 @@ const Profile: FunctionalComponent<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.profile}>
|
<div>
|
||||||
<h1>Profile: {user}</h1>
|
<h1>Profile: {user}</h1>
|
||||||
<p>This is the user profile for a user named {user}.</p>
|
<p>This is the user profile for a user named {user}.</p>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
71
packages/anastasis-webui/src/scss/DurationPicker.scss
Normal file
71
packages/anastasis-webui/src/scss/DurationPicker.scss
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
.rdp-picker {
|
||||||
|
display: flex;
|
||||||
|
height: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.rdp-picker {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-masked-div {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 175px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-column-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-column {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-reticule {
|
||||||
|
border: 0;
|
||||||
|
border-top: 2px solid rgba(109, 202, 236, 1);
|
||||||
|
height: 2px;
|
||||||
|
position: absolute;
|
||||||
|
width: 80%;
|
||||||
|
margin: 0;
|
||||||
|
z-index: 100;
|
||||||
|
left: 50%;
|
||||||
|
-webkit-transform: translateX(-50%);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-text-overlay {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 35px;
|
||||||
|
font-size: 20px;
|
||||||
|
left: 50%;
|
||||||
|
-webkit-transform: translateX(-50%);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-cell div {
|
||||||
|
font-size: 17px;
|
||||||
|
color: gray;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 35px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-center {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
186
packages/anastasis-webui/src/scss/_aside.scss
Normal file
186
packages/anastasis-webui/src/scss/_aside.scss
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
html {
|
||||||
|
&.has-aside-left {
|
||||||
|
&.has-aside-expanded {
|
||||||
|
nav.navbar,
|
||||||
|
body {
|
||||||
|
padding-left: $aside-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aside.is-placed-left {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aside.aside.is-expanded {
|
||||||
|
width: $aside-width;
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
@include icon-with-update-mark($aside-icon-width);
|
||||||
|
|
||||||
|
span.menu-item-label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.is-active {
|
||||||
|
ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
background-color: $body-background-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aside.aside {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 40;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: $aside-box-shadow;
|
||||||
|
background: $aside-background-color;
|
||||||
|
|
||||||
|
.aside-tools {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
background-color: $aside-tools-background-color;
|
||||||
|
color: $aside-tools-color;
|
||||||
|
line-height: $navbar-height;
|
||||||
|
height: $navbar-height;
|
||||||
|
padding-left: $default-padding * 0.5;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: $default-padding * 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
&.has-dropdown-icon {
|
||||||
|
position: relative;
|
||||||
|
padding-right: $aside-icon-width;
|
||||||
|
|
||||||
|
.dropdown-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: $size-base * 0.5;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
display: none;
|
||||||
|
border-left: 0;
|
||||||
|
background-color: darken($base-color, 2.5%);
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0 0 $default-padding * 0.5;
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
padding: $default-padding * 0.5 0 $default-padding * 0.5
|
||||||
|
$default-padding * 0.5;
|
||||||
|
font-size: $aside-submenu-font-size;
|
||||||
|
|
||||||
|
&.has-icon {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
&.is-active {
|
||||||
|
&:not(:hover) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-label {
|
||||||
|
padding: 0 $default-padding * 0.5;
|
||||||
|
margin-top: $default-padding * 0.5;
|
||||||
|
margin-bottom: $default-padding * 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include touch {
|
||||||
|
nav.navbar {
|
||||||
|
@include transition(margin-left);
|
||||||
|
}
|
||||||
|
aside.aside {
|
||||||
|
@include transition(left);
|
||||||
|
}
|
||||||
|
html.has-aside-mobile-transition {
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
body,
|
||||||
|
nav.navbar {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
aside.aside {
|
||||||
|
width: $aside-mobile-width;
|
||||||
|
display: block;
|
||||||
|
left: $aside-mobile-width * -1;
|
||||||
|
|
||||||
|
.image {
|
||||||
|
img {
|
||||||
|
max-width: $aside-mobile-width * 0.33;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
li.is-active {
|
||||||
|
ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
background-color: $body-background-color;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
@include icon-with-update-mark($aside-icon-width);
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
span.menu-item-label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div.has-aside-mobile-expanded {
|
||||||
|
nav.navbar {
|
||||||
|
margin-left: $aside-mobile-width;
|
||||||
|
}
|
||||||
|
aside.aside {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
packages/anastasis-webui/src/scss/_card.scss
Normal file
69
packages/anastasis-webui/src/scss/_card.scss
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
.card:not(:last-child) {
|
||||||
|
margin-bottom: $default-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: $radius-large;
|
||||||
|
border: $card-border;
|
||||||
|
|
||||||
|
&.has-table {
|
||||||
|
.card-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.b-table {
|
||||||
|
border-radius: $radius-large;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-card-widget {
|
||||||
|
.card-content {
|
||||||
|
padding: $default-padding * .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
border-bottom: 1px solid $base-color-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
hr {
|
||||||
|
margin-left: $card-content-padding * -1;
|
||||||
|
margin-right: $card-content-padding * -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-widget-icon {
|
||||||
|
.icon {
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-widget-label {
|
||||||
|
.subtitle {
|
||||||
|
color: $grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
254
packages/anastasis-webui/src/scss/_custom-calendar.scss
Normal file
254
packages/anastasis-webui/src/scss/_custom-calendar.scss
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary-color: #3298dc;
|
||||||
|
|
||||||
|
--primary-text-color-dark: rgba(0,0,0,.87);
|
||||||
|
--secondary-text-color-dark: rgba(0,0,0,.57);
|
||||||
|
--disabled-text-color-dark: rgba(0,0,0,.13);
|
||||||
|
|
||||||
|
--primary-text-color-light: rgba(255,255,255,.87);
|
||||||
|
--secondary-text-color-light: rgba(255,255,255,.57);
|
||||||
|
--disabled-text-color-light: rgba(255,255,255,.13);
|
||||||
|
|
||||||
|
--font-stack: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
|
--primary-card-color: #fff;
|
||||||
|
--primary-background-color: #f2f2f2;
|
||||||
|
|
||||||
|
--box-shadow-lvl-1: 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||||
|
--box-shadow-lvl-2: 0 3px 6px rgba(0, 0, 0, 0.16),
|
||||||
|
0 3px 6px rgba(0, 0, 0, 0.23);
|
||||||
|
--box-shadow-lvl-3: 0 10px 20px rgba(0, 0, 0, 0.19),
|
||||||
|
0 6px 6px rgba(0, 0, 0, 0.23);
|
||||||
|
--box-shadow-lvl-4: 0 14px 28px rgba(0, 0, 0, 0.25),
|
||||||
|
0 10px 10px rgba(0, 0, 0, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.datePicker {
|
||||||
|
text-align: left;
|
||||||
|
background: var(--primary-card-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
z-index: 200;
|
||||||
|
position: fixed;
|
||||||
|
height: auto;
|
||||||
|
max-height: 90vh;
|
||||||
|
width: 90vw;
|
||||||
|
max-width: 448px;
|
||||||
|
transform-origin: top left;
|
||||||
|
transition: transform .22s ease-in-out, opacity .22s ease-in-out;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0) translate(-50%, -50%);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&.datePicker--opened {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datePicker--titles {
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
padding: 24px;
|
||||||
|
height: 100px;
|
||||||
|
background: var(--primary-color);
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: rgba(255,255,255,.57);
|
||||||
|
font-size: 18px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
padding: 20px;
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
top: -9px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
font-size: 26px;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--disabled-text-color-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datePicker--scroll {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(90vh - 56px - 100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datePicker--calendar {
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
.datePicker--dayNames {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
// there's probably a better way to do this, but wanted to try out CSS grid
|
||||||
|
grid-template-columns: calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7);
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--secondary-text-color-dark);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 42px;
|
||||||
|
display: inline-grid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datePicker--days {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
text-align: center;
|
||||||
|
grid-template-columns: calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7);
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--primary-text-color-dark);
|
||||||
|
line-height: 42px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-grid;
|
||||||
|
transition: color .22s;
|
||||||
|
height: 42px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
height: 42px;
|
||||||
|
width: 42px;
|
||||||
|
left: calc(50% - 21px);
|
||||||
|
background: var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: transform .22s, opacity .22s;
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled=true] {
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.datePicker--today {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.datePicker--selected {
|
||||||
|
color: rgba(255,255,255,.87);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datePicker--selectYear {
|
||||||
|
padding: 0 20px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
max-height: 362px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 20px auto;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
font-size: 42px;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.datePicker--actions {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border: none;
|
||||||
|
margin-left: 8px;
|
||||||
|
min-width: 64px;
|
||||||
|
line-height: 36px;
|
||||||
|
background-color: transparent;
|
||||||
|
appearance: none;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background-color .13s;
|
||||||
|
|
||||||
|
&:hover, &:focus {
|
||||||
|
outline: none;
|
||||||
|
background-color: var(--disabled-text-color-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datePicker--background {
|
||||||
|
z-index: 199;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0,0,0,.52);
|
||||||
|
animation: fadeIn .22s forwards;
|
||||||
|
}
|
35
packages/anastasis-webui/src/scss/_footer.scss
Normal file
35
packages/anastasis-webui/src/scss/_footer.scss
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
footer.footer {
|
||||||
|
.logo {
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: $footer-logo-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mobile {
|
||||||
|
.footer-copyright {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
64
packages/anastasis-webui/src/scss/_form.scss
Normal file
64
packages/anastasis-webui/src/scss/_form.scss
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
.field {
|
||||||
|
&.has-check {
|
||||||
|
.field-body {
|
||||||
|
margin-top: $default-padding * .125;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
.mdi-24px.mdi-set, .mdi-24px.mdi:before {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.upload {
|
||||||
|
.upload-draggable {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input, .textarea, select {
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:focus, &:active {
|
||||||
|
box-shadow: none!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input[type=checkbox]+.check:before {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch, .b-checkbox.checkbox {
|
||||||
|
input[type=checkbox] {
|
||||||
|
&:focus + .check, &:focus:checked + .check {
|
||||||
|
box-shadow: none!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-checkbox.checkbox input[type=checkbox], .b-radio.radio input[type=radio] {
|
||||||
|
&+.check {
|
||||||
|
border: $checkbox-border;
|
||||||
|
}
|
||||||
|
}
|
55
packages/anastasis-webui/src/scss/_hero-bar.scss
Normal file
55
packages/anastasis-webui/src/scss/_hero-bar.scss
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
section.hero.is-hero-bar {
|
||||||
|
background-color: $hero-bar-background;
|
||||||
|
border-bottom: $light-border;
|
||||||
|
|
||||||
|
.hero-body {
|
||||||
|
padding: $default-padding;
|
||||||
|
|
||||||
|
.level-item {
|
||||||
|
&.is-hero-avatar-item {
|
||||||
|
margin-right: $default-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > .level {
|
||||||
|
margin-bottom: $default-padding * .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle + p {
|
||||||
|
margin-top: $default-padding * .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
&.is-hero-button {
|
||||||
|
background-color: rgba($white, .5);
|
||||||
|
font-weight: 300;
|
||||||
|
@include transition(background-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
packages/anastasis-webui/src/scss/_loading.scss
Normal file
51
packages/anastasis-webui/src/scss/_loading.scss
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
.lds-ring {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
.lds-ring div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
margin: 8px;
|
||||||
|
border: 8px solid black;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||||
|
border-color: black transparent transparent transparent;
|
||||||
|
}
|
||||||
|
.lds-ring div:nth-child(1) {
|
||||||
|
animation-delay: -0.45s;
|
||||||
|
}
|
||||||
|
.lds-ring div:nth-child(2) {
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
.lds-ring div:nth-child(3) {
|
||||||
|
animation-delay: -0.15s;
|
||||||
|
}
|
||||||
|
@keyframes lds-ring {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
24
packages/anastasis-webui/src/scss/_main-section.scss
Normal file
24
packages/anastasis-webui/src/scss/_main-section.scss
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
section.section.is-main-section {
|
||||||
|
padding-top: $default-padding;
|
||||||
|
}
|
50
packages/anastasis-webui/src/scss/_misc.scss
Normal file
50
packages/anastasis-webui/src/scss/_misc.scss
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
.is-user-avatar {
|
||||||
|
&.has-max-width {
|
||||||
|
max-width: $size-base * 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-aligned-center {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: $radius-rounded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.has-update-mark {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $icon-update-mark-size;
|
||||||
|
height: $icon-update-mark-size;
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: 1px;
|
||||||
|
background-color: $icon-update-mark-color;
|
||||||
|
border-radius: $radius-rounded;
|
||||||
|
}
|
||||||
|
}
|
34
packages/anastasis-webui/src/scss/_mixins.scss
Normal file
34
packages/anastasis-webui/src/scss/_mixins.scss
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
@mixin transition($t) {
|
||||||
|
transition: $t 250ms ease-in-out 50ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin icon-with-update-mark ($icon-base-width) {
|
||||||
|
.icon {
|
||||||
|
width: $icon-base-width;
|
||||||
|
|
||||||
|
&.has-update-mark:after {
|
||||||
|
right: ($icon-base-width / 2) - .85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
packages/anastasis-webui/src/scss/_modal.scss
Normal file
35
packages/anastasis-webui/src/scss/_modal.scss
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
.modal-card {
|
||||||
|
width: $modal-card-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-foot {
|
||||||
|
background-color: $modal-card-foot-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mobile {
|
||||||
|
.modal .animation-content .modal-card {
|
||||||
|
width: $modal-card-width-mobile;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
144
packages/anastasis-webui/src/scss/_nav-bar.scss
Normal file
144
packages/anastasis-webui/src/scss/_nav-bar.scss
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
nav.navbar {
|
||||||
|
box-shadow: $navbar-box-shadow;
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
&.has-user-avatar {
|
||||||
|
.is-user-avatar {
|
||||||
|
margin-right: $default-padding * .5;
|
||||||
|
display: inline-flex;
|
||||||
|
width: $navbar-avatar-size;
|
||||||
|
height: $navbar-avatar-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-divider {
|
||||||
|
border-right: $navbar-divider-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-left-space {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-dropdown {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
.navbar-link {
|
||||||
|
padding-right: $navbar-item-h-padding;
|
||||||
|
padding-left: $navbar-item-h-padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-control {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control {
|
||||||
|
.input {
|
||||||
|
color: $navbar-input-color;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $navbar-input-placeholder-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include touch {
|
||||||
|
nav.navbar {
|
||||||
|
display: flex;
|
||||||
|
padding-right: 0;
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&.is-right {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
&.no-left-space-touch {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu {
|
||||||
|
position: absolute;
|
||||||
|
width: 100vw;
|
||||||
|
padding-top: 0;
|
||||||
|
top: $navbar-height;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
.icon:first-child {
|
||||||
|
margin-right: $default-padding * .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-dropdown {
|
||||||
|
>.navbar-link {
|
||||||
|
background-color: $white-ter;
|
||||||
|
.icon:last-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-user-avatar {
|
||||||
|
>.navbar-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: $default-padding * .5;
|
||||||
|
padding-bottom: $default-padding * .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
nav.navbar {
|
||||||
|
.navbar-item {
|
||||||
|
padding-right: $navbar-item-h-padding;
|
||||||
|
padding-left: $navbar-item-h-padding;
|
||||||
|
|
||||||
|
&:not(.is-desktop-icon-only) {
|
||||||
|
.icon:first-child {
|
||||||
|
margin-right: $default-padding * .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.is-desktop-icon-only {
|
||||||
|
span:not(.icon) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
173
packages/anastasis-webui/src/scss/_table.scss
Normal file
173
packages/anastasis-webui/src/scss/_table.scss
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
table.table {
|
||||||
|
thead {
|
||||||
|
th {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
&.checkbox-cell {
|
||||||
|
.b-checkbox.checkbox:not(.button) {
|
||||||
|
margin-right: 0;
|
||||||
|
width: 20px;
|
||||||
|
|
||||||
|
.control-label {
|
||||||
|
display: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
.image {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: $table-avatar-size;
|
||||||
|
height: $table-avatar-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-progress-col {
|
||||||
|
min-width: 5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-table {
|
||||||
|
.table {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This stylizes buefy's pagination */
|
||||||
|
.table-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper + .level {
|
||||||
|
padding: $notification-padding;
|
||||||
|
padding-left: $card-content-padding;
|
||||||
|
padding-right: $card-content-padding;
|
||||||
|
margin: 0;
|
||||||
|
border-top: $base-color-light;
|
||||||
|
background: $notification-background-color;
|
||||||
|
|
||||||
|
.pagination-link {
|
||||||
|
background: $button-background-color;
|
||||||
|
color: $button-color;
|
||||||
|
border-color: $button-border-color;
|
||||||
|
|
||||||
|
&.is-current {
|
||||||
|
border-color: $button-active-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-previous, .pagination-next, .pagination-link {
|
||||||
|
border-color: $button-border-color;
|
||||||
|
color: $base-color;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mobile {
|
||||||
|
.card {
|
||||||
|
&.has-table {
|
||||||
|
.b-table {
|
||||||
|
.table-wrapper + .level {
|
||||||
|
.level-left + .level-right {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.has-mobile-sort-spaced {
|
||||||
|
.b-table {
|
||||||
|
.field.table-mobile-sort {
|
||||||
|
padding-top: $default-padding * .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.b-table {
|
||||||
|
.field.table-mobile-sort {
|
||||||
|
padding: 0 $default-padding * .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper.has-mobile-cards {
|
||||||
|
tr {
|
||||||
|
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1);
|
||||||
|
margin-bottom: 3px!important;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
&.is-progress-col {
|
||||||
|
span, progress {
|
||||||
|
display: flex;
|
||||||
|
width: 45%;
|
||||||
|
align-items: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.checkbox-cell, &.is-image-cell {
|
||||||
|
border-bottom: 0!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.checkbox-cell, &.is-actions-cell {
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-no-head-mobile {
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-progress-col {
|
||||||
|
progress {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-image-cell {
|
||||||
|
.image {
|
||||||
|
width: $table-avatar-size-mobile;
|
||||||
|
height: auto;
|
||||||
|
margin: 0 auto $default-padding * .25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
packages/anastasis-webui/src/scss/_theme-default.scss
Normal file
136
packages/anastasis-webui/src/scss/_theme-default.scss
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* We'll need some initial vars to use here */
|
||||||
|
@import "node_modules/bulma/sass/utilities/initial-variables";
|
||||||
|
|
||||||
|
/* Base: Size */
|
||||||
|
$size-base: 1rem;
|
||||||
|
$default-padding: $size-base * 1.5;
|
||||||
|
|
||||||
|
/* Default font */
|
||||||
|
$family-sans-serif: "Nunito", sans-serif;
|
||||||
|
|
||||||
|
/* Base color */
|
||||||
|
$base-color: #2e323a;
|
||||||
|
$base-color-light: rgba(24, 28, 33, 0.06);
|
||||||
|
|
||||||
|
/* General overrides */
|
||||||
|
$primary: $turquoise;
|
||||||
|
$body-background-color: #f8f8f8;
|
||||||
|
$link: $blue;
|
||||||
|
$link-visited: $purple;
|
||||||
|
$light-border: 1px solid $base-color-light;
|
||||||
|
$hr-height: 1px;
|
||||||
|
|
||||||
|
/* NavBar: specifics */
|
||||||
|
$navbar-input-color: $grey-darker;
|
||||||
|
$navbar-input-placeholder-color: $grey-lighter;
|
||||||
|
$navbar-box-shadow: 0 1px 0 rgba(24, 28, 33, 0.04);
|
||||||
|
$navbar-divider-border: 1px solid rgba($grey-lighter, 0.25);
|
||||||
|
$navbar-item-h-padding: $default-padding * 0.75;
|
||||||
|
$navbar-avatar-size: 1.75rem;
|
||||||
|
|
||||||
|
/* Aside: Bulma override */
|
||||||
|
$menu-item-radius: 0;
|
||||||
|
$menu-list-link-padding: $size-base * 0.5 0;
|
||||||
|
$menu-label-color: lighten($base-color, 25%);
|
||||||
|
$menu-item-color: lighten($base-color, 30%);
|
||||||
|
$menu-item-hover-color: $white;
|
||||||
|
$menu-item-hover-background-color: darken($base-color, 3.5%);
|
||||||
|
$menu-item-active-color: $white;
|
||||||
|
$menu-item-active-background-color: darken($base-color, 2.5%);
|
||||||
|
|
||||||
|
/* Aside: specifics */
|
||||||
|
$aside-width: $size-base * 14;
|
||||||
|
$aside-mobile-width: $size-base * 15;
|
||||||
|
$aside-icon-width: $size-base * 3;
|
||||||
|
$aside-submenu-font-size: $size-base * 0.95;
|
||||||
|
$aside-box-shadow: none;
|
||||||
|
$aside-background-color: $base-color;
|
||||||
|
$aside-tools-background-color: darken($aside-background-color, 10%);
|
||||||
|
$aside-tools-color: $white;
|
||||||
|
|
||||||
|
/* Title Bar: specifics */
|
||||||
|
$title-bar-color: $grey;
|
||||||
|
$title-bar-active-color: $black-ter;
|
||||||
|
|
||||||
|
/* Hero Bar: specifics */
|
||||||
|
$hero-bar-background: $white;
|
||||||
|
|
||||||
|
/* Card: Bulma override */
|
||||||
|
$card-shadow: none;
|
||||||
|
$card-header-shadow: none;
|
||||||
|
|
||||||
|
/* Card: specifics */
|
||||||
|
$card-border: 1px solid $base-color-light;
|
||||||
|
$card-header-border-bottom-color: $base-color-light;
|
||||||
|
|
||||||
|
/* Table: Bulma override */
|
||||||
|
$table-cell-border: 1px solid $white-bis;
|
||||||
|
|
||||||
|
/* Table: specifics */
|
||||||
|
$table-avatar-size: $size-base * 1.5;
|
||||||
|
$table-avatar-size-mobile: 25vw;
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
$checkbox-border: 1px solid $base-color;
|
||||||
|
|
||||||
|
/* Modal card: Bulma override */
|
||||||
|
$modal-card-head-background-color: $white-ter;
|
||||||
|
$modal-card-title-size: $size-base;
|
||||||
|
$modal-card-body-padding: $default-padding 20px;
|
||||||
|
$modal-card-head-border-bottom: 1px solid $white-ter;
|
||||||
|
$modal-card-foot-border-top: 0;
|
||||||
|
|
||||||
|
/* Modal card: specifics */
|
||||||
|
$modal-card-width: 80vw;
|
||||||
|
$modal-card-width-mobile: 90vw;
|
||||||
|
$modal-card-foot-background-color: $white-ter;
|
||||||
|
|
||||||
|
/* Notification: Bulma override */
|
||||||
|
$notification-padding: $default-padding * 0.75 $default-padding;
|
||||||
|
|
||||||
|
/* Footer: Bulma override */
|
||||||
|
$footer-background-color: $white;
|
||||||
|
$footer-padding: $default-padding * 0.33 $default-padding;
|
||||||
|
|
||||||
|
/* Footer: specifics */
|
||||||
|
$footer-logo-height: $size-base * 2;
|
||||||
|
|
||||||
|
/* Progress: Bulma override */
|
||||||
|
$progress-bar-background-color: $grey-lighter;
|
||||||
|
|
||||||
|
/* Icon: specifics */
|
||||||
|
$icon-update-mark-size: $size-base * 0.5;
|
||||||
|
$icon-update-mark-color: $yellow;
|
||||||
|
|
||||||
|
$input-disabled-border-color: $grey-lighter;
|
||||||
|
$table-row-hover-background-color: hsl(0, 0%, 80%);
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
div {
|
||||||
|
border-radius: $menu-item-radius;
|
||||||
|
color: $menu-item-color;
|
||||||
|
display: block;
|
||||||
|
padding: $menu-list-link-padding;
|
||||||
|
}
|
||||||
|
}
|
25
packages/anastasis-webui/src/scss/_tiles.scss
Normal file
25
packages/anastasis-webui/src/scss/_tiles.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
.is-tiles-wrapper {
|
||||||
|
margin-bottom: $default-padding;
|
||||||
|
}
|
50
packages/anastasis-webui/src/scss/_title-bar.scss
Normal file
50
packages/anastasis-webui/src/scss/_title-bar.scss
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
section.section.is-title-bar {
|
||||||
|
padding: $default-padding;
|
||||||
|
border-bottom: $light-border;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 $default-padding * .5 0 0;
|
||||||
|
font-size: $default-padding;
|
||||||
|
color: $title-bar-color;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: inline-block;
|
||||||
|
content: '/';
|
||||||
|
padding-left: $default-padding * .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
font-weight: 900;
|
||||||
|
color: $title-bar-active-color;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
packages/anastasis-webui/src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf
Normal file
BIN
packages/anastasis-webui/src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf
Normal file
Binary file not shown.
22
packages/anastasis-webui/src/scss/fonts/nunito.css
Normal file
22
packages/anastasis-webui/src/scss/fonts/nunito.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Nunito';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(./XRXV3I6Li01BKofINeaE.ttf) format('truetype');
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3
packages/anastasis-webui/src/scss/icons/materialdesignicons-4.9.95.min.css
vendored
Normal file
3
packages/anastasis-webui/src/scss/icons/materialdesignicons-4.9.95.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
29
packages/anastasis-webui/src/scss/libs/_all.scss
Normal file
29
packages/anastasis-webui/src/scss/libs/_all.scss
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
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 "node_modules/bulma-radio/bulma-radio";
|
||||||
|
// @import "node_modules/bulma-responsive-tables/bulma-responsive-tables";
|
||||||
|
@import "node_modules/bulma-checkbox/bulma-checkbox";
|
||||||
|
// @import "node_modules/bulma-switch-control/bulma-switch-control";
|
||||||
|
// @import "node_modules/bulma-upload-control/bulma-upload-control";
|
||||||
|
|
||||||
|
/* Bulma */
|
||||||
|
@import "node_modules/bulma/bulma";
|
191
packages/anastasis-webui/src/scss/main.scss
Normal file
191
packages/anastasis-webui/src/scss/main.scss
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Theme style (colors & sizes) */
|
||||||
|
@import "theme-default";
|
||||||
|
|
||||||
|
/* Core Libs & Lib configs */
|
||||||
|
@import "libs/all";
|
||||||
|
|
||||||
|
/* Mixins */
|
||||||
|
@import "mixins";
|
||||||
|
|
||||||
|
/* Theme components */
|
||||||
|
@import "nav-bar";
|
||||||
|
@import "aside";
|
||||||
|
@import "title-bar";
|
||||||
|
@import "hero-bar";
|
||||||
|
@import "card";
|
||||||
|
@import "table";
|
||||||
|
@import "tiles";
|
||||||
|
@import "form";
|
||||||
|
@import "main-section";
|
||||||
|
@import "modal";
|
||||||
|
@import "footer";
|
||||||
|
@import "misc";
|
||||||
|
@import "custom-calendar";
|
||||||
|
@import "loading";
|
||||||
|
|
||||||
|
@import "fonts/nunito.css";
|
||||||
|
@import "icons/materialdesignicons-4.9.95.min.css";
|
||||||
|
|
||||||
|
$tooltip-color: red;
|
||||||
|
|
||||||
|
@import "../../node_modules/@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css";
|
||||||
|
// @import "../../node_modules/bulma-timeline/dist/css/bulma-timeline.min.css";
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline .timeline-item .timeline-content {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline .timeline-item:last-child::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline .timeline-item .timeline-marker {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: absolute;
|
||||||
|
width: 60%;
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-right: 10%;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast > .message {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
opacity: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
&.is-loading {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
&:after {
|
||||||
|
// @include loader;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 2.5em);
|
||||||
|
left: calc(50% - 2.5em);
|
||||||
|
width: 5em;
|
||||||
|
height: 5em;
|
||||||
|
border-width: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:indeterminate + .check {
|
||||||
|
background: red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-sticky {
|
||||||
|
position: sticky;
|
||||||
|
right: 0px;
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-sticky .buttons {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table.is-striped tbody tr:not(.is-selected):nth-child(even) .right-sticky {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover .right-sticky {
|
||||||
|
background-color: hsl(0, 0%, 80%);
|
||||||
|
}
|
||||||
|
.table.is-striped tbody tr:nth-child(even):hover .right-sticky {
|
||||||
|
background-color: hsl(0, 0%, 95%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-full-size {
|
||||||
|
height: calc(100% - 3rem);
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 14rem);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-full-size .column .card {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include touch {
|
||||||
|
.content-full-size {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column.is-half {
|
||||||
|
flex: none;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:read-only {
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip]:before {
|
||||||
|
max-width: 15rem;
|
||||||
|
width: max-content;
|
||||||
|
text-align: left;
|
||||||
|
transition: opacity 0.1s linear 1s;
|
||||||
|
// transform: inherit !important;
|
||||||
|
white-space: pre-wrap !important;
|
||||||
|
font-weight: normal;
|
||||||
|
// position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon[data-tooltip]:before {
|
||||||
|
transition: none;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[data-tooltip] {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[data-tooltip]::before {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-body > p {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-body > p.warning {
|
||||||
|
background-color: #fffbdd;
|
||||||
|
border: solid 1px #f2e9bf;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title><% preact.title %></title>
|
<title><% preact.title %></title>
|
||||||
|
196
pnpm-lock.yaml
196
pnpm-lock.yaml
@ -31,42 +31,56 @@ importers:
|
|||||||
|
|
||||||
packages/anastasis-webui:
|
packages/anastasis-webui:
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@creativebulma/bulma-tooltip': ^1.2.0
|
||||||
'@gnu-taler/taler-util': workspace:^0.8.3
|
'@gnu-taler/taler-util': workspace:^0.8.3
|
||||||
'@types/enzyme': ^3.10.5
|
'@types/enzyme': ^3.10.5
|
||||||
'@types/jest': ^26.0.8
|
'@types/jest': ^26.0.8
|
||||||
'@typescript-eslint/eslint-plugin': ^2.25.0
|
'@typescript-eslint/eslint-plugin': ^2.25.0
|
||||||
'@typescript-eslint/parser': ^2.25.0
|
'@typescript-eslint/parser': ^2.25.0
|
||||||
anastasis-core: workspace:^0.0.1
|
anastasis-core: workspace:^0.0.1
|
||||||
|
bulma: ^0.9.3
|
||||||
|
bulma-checkbox: ^1.1.1
|
||||||
|
bulma-radio: ^1.1.1
|
||||||
enzyme: ^3.11.0
|
enzyme: ^3.11.0
|
||||||
enzyme-adapter-preact-pure: ^3.1.0
|
enzyme-adapter-preact-pure: ^3.1.0
|
||||||
eslint: ^6.8.0
|
eslint: ^6.8.0
|
||||||
eslint-config-preact: ^1.1.1
|
eslint-config-preact: ^1.1.1
|
||||||
|
jed: 1.1.1
|
||||||
jest: ^26.2.2
|
jest: ^26.2.2
|
||||||
jest-preset-preact: ^4.0.2
|
jest-preset-preact: ^4.0.2
|
||||||
preact: ^10.3.1
|
preact: ^10.3.1
|
||||||
preact-cli: ^3.2.2
|
preact-cli: ^3.2.2
|
||||||
preact-render-to-string: ^5.1.4
|
preact-render-to-string: ^5.1.4
|
||||||
preact-router: ^3.2.1
|
preact-router: ^3.2.1
|
||||||
|
sass: ^1.32.13
|
||||||
|
sass-loader: ^10.1.1
|
||||||
sirv-cli: ^1.0.0-next.3
|
sirv-cli: ^1.0.0-next.3
|
||||||
typescript: ^3.7.5
|
typescript: ^3.7.5
|
||||||
dependencies:
|
dependencies:
|
||||||
'@gnu-taler/taler-util': link:../taler-util
|
'@gnu-taler/taler-util': link:../taler-util
|
||||||
anastasis-core: link:../anastasis-core
|
anastasis-core: link:../anastasis-core
|
||||||
|
jed: 1.1.1
|
||||||
preact: 10.5.14
|
preact: 10.5.14
|
||||||
preact-render-to-string: 5.1.19_preact@10.5.14
|
preact-render-to-string: 5.1.19_preact@10.5.14
|
||||||
preact-router: 3.2.1_preact@10.5.14
|
preact-router: 3.2.1_preact@10.5.14
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@creativebulma/bulma-tooltip': 1.2.0
|
||||||
'@types/enzyme': 3.10.9
|
'@types/enzyme': 3.10.9
|
||||||
'@types/jest': 26.0.24
|
'@types/jest': 26.0.24
|
||||||
'@typescript-eslint/eslint-plugin': 2.34.0_2b015b1c4b7c4a3ed9a197dc233b1a35
|
'@typescript-eslint/eslint-plugin': 2.34.0_2b015b1c4b7c4a3ed9a197dc233b1a35
|
||||||
'@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.10
|
'@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.10
|
||||||
|
bulma: 0.9.3
|
||||||
|
bulma-checkbox: 1.1.1
|
||||||
|
bulma-radio: 1.1.1
|
||||||
enzyme: 3.11.0
|
enzyme: 3.11.0
|
||||||
enzyme-adapter-preact-pure: 3.1.0_enzyme@3.11.0+preact@10.5.14
|
enzyme-adapter-preact-pure: 3.1.0_enzyme@3.11.0+preact@10.5.14
|
||||||
eslint: 6.8.0
|
eslint: 6.8.0
|
||||||
eslint-config-preact: 1.1.4_eslint@6.8.0+typescript@3.9.10
|
eslint-config-preact: 1.1.4_eslint@6.8.0+typescript@3.9.10
|
||||||
jest: 26.6.3
|
jest: 26.6.3
|
||||||
jest-preset-preact: 4.0.2_9b3f24ae35a87c3c82fffbe3fdf70e1e
|
jest-preset-preact: 4.0.2_9b3f24ae35a87c3c82fffbe3fdf70e1e
|
||||||
preact-cli: 3.2.2_517d24bd855b57d7e424aceed04e063b
|
preact-cli: 3.2.2_8d1b4ee21ca5a56b4aabd4a3e659b2d7
|
||||||
|
sass: 1.43.2
|
||||||
|
sass-loader: 10.2.0_sass@1.43.2
|
||||||
sirv-cli: 1.0.14
|
sirv-cli: 1.0.14
|
||||||
typescript: 3.9.10
|
typescript: 3.9.10
|
||||||
|
|
||||||
@ -3570,6 +3584,10 @@ packages:
|
|||||||
arrify: 1.0.1
|
arrify: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@creativebulma/bulma-tooltip/1.2.0:
|
||||||
|
resolution: {integrity: sha512-ooImbeXEBxf77cttbzA7X5rC5aAWm9UsXIGViFOnsqB+6M944GkB28S5R4UWRqjFd2iW4zGEkEifAU+q43pt2w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@emotion/cache/10.0.29:
|
/@emotion/cache/10.0.29:
|
||||||
resolution: {integrity: sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==}
|
resolution: {integrity: sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4607,7 +4625,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 0.0.39
|
'@types/estree': 0.0.39
|
||||||
estree-walker: 1.0.1
|
estree-walker: 1.0.1
|
||||||
picomatch: 2.3.0
|
picomatch: 2.2.2
|
||||||
rollup: 2.56.2
|
rollup: 2.56.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@ -8365,6 +8383,22 @@ packages:
|
|||||||
resolution: {integrity: sha1-y5T662HIaWRR2zZTThQi+U8K7og=}
|
resolution: {integrity: sha1-y5T662HIaWRR2zZTThQi+U8K7og=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/bulma-checkbox/1.1.1:
|
||||||
|
resolution: {integrity: sha512-16aTRbXQBCdfk8nrWSVJCasD28FudeVF+G+mZfMJc2N/xTcU4XXjzQ6Iya1neKOgXkXQMx9nJOH2n8H7LRztNg==}
|
||||||
|
dependencies:
|
||||||
|
bulma: 0.9.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/bulma-radio/1.1.1:
|
||||||
|
resolution: {integrity: sha512-aIHuMbpBGyZYx8KxbQRdjIy/0M9WHWz5VyxMggwxmCadnN0gd7gC/G96WUy9mhaoIfo9yX/Cf8pKQNinKH+w7w==}
|
||||||
|
dependencies:
|
||||||
|
bulma: 0.9.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/bulma/0.9.3:
|
||||||
|
resolution: {integrity: sha512-0d7GNW1PY4ud8TWxdNcP6Cc8Bu7MxcntD/RRLGWuiw/s0a9P+XlH/6QoOIrmbj6o8WWJzJYhytiu9nFjTszk1g==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/bytes/3.0.0:
|
/bytes/3.0.0:
|
||||||
resolution: {integrity: sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=}
|
resolution: {integrity: sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@ -10784,18 +10818,18 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7
|
eslint: ^3 || ^4 || ^5 || ^6 || ^7
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.3
|
array-includes: 3.1.2
|
||||||
array.prototype.flatmap: 1.2.4
|
array.prototype.flatmap: 1.2.4
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 6.8.0
|
eslint: 6.8.0
|
||||||
has: 1.0.3
|
has: 1.0.3
|
||||||
jsx-ast-utils: 3.2.0
|
jsx-ast-utils: 3.2.0
|
||||||
object.entries: 1.1.4
|
object.entries: 1.1.3
|
||||||
object.fromentries: 2.0.4
|
object.fromentries: 2.0.3
|
||||||
object.values: 1.1.4
|
object.values: 1.1.2
|
||||||
prop-types: 15.7.2
|
prop-types: 15.7.2
|
||||||
resolve: 1.20.0
|
resolve: 1.19.0
|
||||||
string.prototype.matchall: 4.0.5
|
string.prototype.matchall: 4.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-react/7.22.0_eslint@7.18.0:
|
/eslint-plugin-react/7.22.0_eslint@7.18.0:
|
||||||
@ -16852,6 +16886,116 @@ packages:
|
|||||||
- webpack-command
|
- webpack-command
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/preact-cli/3.2.2_8d1b4ee21ca5a56b4aabd4a3e659b2d7:
|
||||||
|
resolution: {integrity: sha512-42aUanAb/AqHHvnfb/IwJw9UhY5iuHkGRBv3TrTsQMrq0Ee8Z84r+HS8wjGI0aHHb0R8tnHI0hhllWgmNhjB/Q==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
less-loader: ^7.3.0
|
||||||
|
preact: '*'
|
||||||
|
preact-render-to-string: '*'
|
||||||
|
sass-loader: ^10.2.0
|
||||||
|
stylus-loader: ^4.3.3
|
||||||
|
peerDependenciesMeta:
|
||||||
|
less-loader:
|
||||||
|
optional: true
|
||||||
|
sass-loader:
|
||||||
|
optional: true
|
||||||
|
stylus-loader:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/core': 7.15.0
|
||||||
|
'@babel/plugin-proposal-class-properties': 7.14.5_@babel+core@7.15.0
|
||||||
|
'@babel/plugin-proposal-decorators': 7.14.5_@babel+core@7.15.0
|
||||||
|
'@babel/plugin-proposal-object-rest-spread': 7.14.7_@babel+core@7.15.0
|
||||||
|
'@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.15.0
|
||||||
|
'@babel/plugin-transform-object-assign': 7.14.5_@babel+core@7.15.0
|
||||||
|
'@babel/plugin-transform-react-jsx': 7.14.9_@babel+core@7.15.0
|
||||||
|
'@babel/preset-env': 7.15.0_@babel+core@7.15.0
|
||||||
|
'@babel/preset-typescript': 7.15.0_@babel+core@7.15.0
|
||||||
|
'@preact/async-loader': 3.0.1_preact@10.5.14
|
||||||
|
'@prefresh/babel-plugin': 0.4.1
|
||||||
|
'@prefresh/webpack': 3.3.2_b4d84c08f02729896cbfdece19209372
|
||||||
|
autoprefixer: 10.3.1_postcss@8.3.6
|
||||||
|
babel-esm-plugin: 0.9.0_webpack@4.46.0
|
||||||
|
babel-loader: 8.2.2_be352a5a80662835a7707f972edfcfde
|
||||||
|
babel-plugin-macros: 3.1.0
|
||||||
|
babel-plugin-transform-react-remove-prop-types: 0.4.24
|
||||||
|
browserlist: 1.0.1
|
||||||
|
browserslist: 4.16.8
|
||||||
|
compression-webpack-plugin: 6.1.1_webpack@4.46.0
|
||||||
|
console-clear: 1.1.1
|
||||||
|
copy-webpack-plugin: 6.4.1_webpack@4.46.0
|
||||||
|
critters-webpack-plugin: 2.5.0
|
||||||
|
cross-spawn-promise: 0.10.2
|
||||||
|
css-loader: 5.2.7_webpack@4.46.0
|
||||||
|
ejs-loader: 0.5.0
|
||||||
|
envinfo: 7.8.1
|
||||||
|
esm: 3.2.25
|
||||||
|
fast-async: 6.3.8
|
||||||
|
file-loader: 6.2.0_webpack@4.46.0
|
||||||
|
fork-ts-checker-webpack-plugin: 4.1.6
|
||||||
|
get-port: 5.1.1
|
||||||
|
gittar: 0.1.1
|
||||||
|
glob: 7.1.7
|
||||||
|
html-webpack-exclude-assets-plugin: 0.0.7
|
||||||
|
html-webpack-plugin: 3.2.0_webpack@4.46.0
|
||||||
|
ip: 1.1.5
|
||||||
|
isomorphic-unfetch: 3.1.0
|
||||||
|
kleur: 4.1.4
|
||||||
|
loader-utils: 2.0.0
|
||||||
|
mini-css-extract-plugin: 1.6.2_webpack@4.46.0
|
||||||
|
minimatch: 3.0.4
|
||||||
|
native-url: 0.3.4
|
||||||
|
optimize-css-assets-webpack-plugin: 6.0.1_webpack@4.46.0
|
||||||
|
ora: 5.4.1
|
||||||
|
pnp-webpack-plugin: 1.7.0_typescript@4.4.3
|
||||||
|
postcss: 8.3.6
|
||||||
|
postcss-load-config: 3.1.0
|
||||||
|
postcss-loader: 4.3.0_postcss@8.3.6+webpack@4.46.0
|
||||||
|
preact: 10.5.14
|
||||||
|
preact-render-to-string: 5.1.19_preact@10.5.14
|
||||||
|
progress-bar-webpack-plugin: 2.1.0_webpack@4.46.0
|
||||||
|
promise-polyfill: 8.2.0
|
||||||
|
prompts: 2.4.1
|
||||||
|
raw-loader: 4.0.2_webpack@4.46.0
|
||||||
|
react-refresh: 0.10.0
|
||||||
|
rimraf: 3.0.2
|
||||||
|
sade: 1.7.4
|
||||||
|
sass-loader: 10.2.0_sass@1.43.2
|
||||||
|
size-plugin: 3.0.0_webpack@4.46.0
|
||||||
|
source-map: 0.7.3
|
||||||
|
stack-trace: 0.0.10
|
||||||
|
style-loader: 2.0.0_webpack@4.46.0
|
||||||
|
terser-webpack-plugin: 4.2.3_webpack@4.46.0
|
||||||
|
typescript: 4.4.3
|
||||||
|
update-notifier: 5.1.0
|
||||||
|
url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
|
||||||
|
validate-npm-package-name: 3.0.0
|
||||||
|
webpack: 4.46.0
|
||||||
|
webpack-bundle-analyzer: 4.4.2
|
||||||
|
webpack-dev-server: 3.11.2_webpack@4.46.0
|
||||||
|
webpack-fix-style-only-entries: 0.6.1
|
||||||
|
webpack-merge: 5.8.0
|
||||||
|
webpack-plugin-replace: 1.2.0
|
||||||
|
which: 2.0.2
|
||||||
|
workbox-cacheable-response: 6.2.4
|
||||||
|
workbox-core: 6.2.4
|
||||||
|
workbox-precaching: 6.2.4
|
||||||
|
workbox-routing: 6.2.4
|
||||||
|
workbox-strategies: 6.2.4
|
||||||
|
workbox-webpack-plugin: 6.2.4_webpack@4.46.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/babel__core'
|
||||||
|
- bufferutil
|
||||||
|
- debug
|
||||||
|
- supports-color
|
||||||
|
- ts-node
|
||||||
|
- utf-8-validate
|
||||||
|
- webpack-cli
|
||||||
|
- webpack-command
|
||||||
|
dev: true
|
||||||
|
|
||||||
/preact-render-to-string/5.1.19_preact@10.5.14:
|
/preact-render-to-string/5.1.19_preact@10.5.14:
|
||||||
resolution: {integrity: sha512-bj8sn/oytIKO6RtOGSS/1+5CrQyRSC99eLUnEVbqUa6MzJX5dYh7wu9bmT0d6lm/Vea21k9KhCQwvr2sYN3rrQ==}
|
resolution: {integrity: sha512-bj8sn/oytIKO6RtOGSS/1+5CrQyRSC99eLUnEVbqUa6MzJX5dYh7wu9bmT0d6lm/Vea21k9KhCQwvr2sYN3rrQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -18075,11 +18219,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
rollup: ^2.0.0
|
rollup: ^2.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.14.5
|
'@babel/code-frame': 7.12.13
|
||||||
jest-worker: 26.6.2
|
jest-worker: 26.6.2
|
||||||
rollup: 2.56.2
|
rollup: 2.56.2
|
||||||
serialize-javascript: 4.0.0
|
serialize-javascript: 4.0.0
|
||||||
terser: 5.7.1
|
terser: 5.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rollup/2.37.1:
|
/rollup/2.37.1:
|
||||||
@ -18188,6 +18332,38 @@ packages:
|
|||||||
walker: 1.0.7
|
walker: 1.0.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/sass-loader/10.2.0_sass@1.43.2:
|
||||||
|
resolution: {integrity: sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==}
|
||||||
|
engines: {node: '>= 10.13.0'}
|
||||||
|
peerDependencies:
|
||||||
|
fibers: '>= 3.1.0'
|
||||||
|
node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0
|
||||||
|
sass: ^1.3.0
|
||||||
|
webpack: ^4.36.0 || ^5.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
fibers:
|
||||||
|
optional: true
|
||||||
|
node-sass:
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
klona: 2.0.4
|
||||||
|
loader-utils: 2.0.0
|
||||||
|
neo-async: 2.6.2
|
||||||
|
sass: 1.43.2
|
||||||
|
schema-utils: 3.1.1
|
||||||
|
semver: 7.3.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/sass/1.43.2:
|
||||||
|
resolution: {integrity: sha512-DncYhjl3wBaPMMJR0kIUaH3sF536rVrOcqqVGmTZHQRRzj7LQlyGV7Mb8aCKFyILMr5VsPHwRYtyKpnKYlmQSQ==}
|
||||||
|
engines: {node: '>=8.9.0'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
chokidar: 3.5.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/sax/1.2.4:
|
/sax/1.2.4:
|
||||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
Loading…
Reference in New Issue
Block a user