add template from merchant backoffice
This commit is contained in:
parent
269022a526
commit
5883d42d80
@ -23,15 +23,20 @@
|
||||
"dependencies": {
|
||||
"@gnu-taler/taler-util": "workspace:^0.8.3",
|
||||
"anastasis-core": "workspace:^0.0.1",
|
||||
"jed": "1.1.1",
|
||||
"preact": "^10.3.1",
|
||||
"preact-render-to-string": "^5.1.4",
|
||||
"preact-router": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@creativebulma/bulma-tooltip": "^1.2.0",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/jest": "^26.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^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-adapter-preact-pure": "^3.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
@ -39,6 +44,8 @@
|
||||
"jest": "^26.2.2",
|
||||
"jest-preset-preact": "^4.0.2",
|
||||
"preact-cli": "^3.2.2",
|
||||
"sass": "^1.32.13",
|
||||
"sass-loader": "^10.1.1",
|
||||
"sirv-cli": "^1.0.0-next.3",
|
||||
"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 { TranslationProvider } from "../context/translation";
|
||||
|
||||
import AnastasisClient from "../routes/home";
|
||||
import AnastasisClient from "../pages/home";
|
||||
|
||||
const App: FunctionalComponent = () => {
|
||||
return (
|
||||
<div id="preact_root">
|
||||
<AnastasisClient />
|
||||
</div>
|
||||
<TranslationProvider>
|
||||
<div id="app" class="has-navbar-fixed-top">
|
||||
<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";
|
||||
|
||||
const reducerBaseUrl = "http://localhost:5000/";
|
||||
let remoteReducer = true;
|
||||
const remoteReducer = true;
|
||||
|
||||
interface AnastasisState {
|
||||
reducerState: ReducerState | undefined;
|
||||
@ -123,7 +123,7 @@ function storageSet(key: string, value: any): void {
|
||||
function restoreState(): any {
|
||||
let state: any;
|
||||
try {
|
||||
let s = storageGet("anastasisReducerState");
|
||||
const s = storageGet("anastasisReducerState");
|
||||
if (s === "undefined") {
|
||||
state = undefined;
|
||||
} 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 './scss/main.scss';
|
||||
|
||||
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 { Link } from 'preact-router/match';
|
||||
import style from './style.css';
|
||||
|
||||
const Notfound: FunctionalComponent = () => {
|
||||
return (
|
||||
<div class={style.notfound}>
|
||||
<div>
|
||||
<h1>Error 404</h1>
|
||||
<p>That page doesn't exist.</p>
|
||||
<Link href="/">
|
@ -1,6 +1,5 @@
|
||||
import { FunctionalComponent, h } from 'preact';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import style from './style.css';
|
||||
|
||||
interface Props {
|
||||
user: string;
|
||||
@ -27,7 +26,7 @@ const Profile: FunctionalComponent<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={style.profile}>
|
||||
<div>
|
||||
<h1>Profile: {user}</h1>
|
||||
<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>
|
||||
<html lang="en">
|
||||
<html lang="en" class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><% preact.title %></title>
|
||||
|
196
pnpm-lock.yaml
196
pnpm-lock.yaml
@ -31,42 +31,56 @@ importers:
|
||||
|
||||
packages/anastasis-webui:
|
||||
specifiers:
|
||||
'@creativebulma/bulma-tooltip': ^1.2.0
|
||||
'@gnu-taler/taler-util': workspace:^0.8.3
|
||||
'@types/enzyme': ^3.10.5
|
||||
'@types/jest': ^26.0.8
|
||||
'@typescript-eslint/eslint-plugin': ^2.25.0
|
||||
'@typescript-eslint/parser': ^2.25.0
|
||||
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-adapter-preact-pure: ^3.1.0
|
||||
eslint: ^6.8.0
|
||||
eslint-config-preact: ^1.1.1
|
||||
jed: 1.1.1
|
||||
jest: ^26.2.2
|
||||
jest-preset-preact: ^4.0.2
|
||||
preact: ^10.3.1
|
||||
preact-cli: ^3.2.2
|
||||
preact-render-to-string: ^5.1.4
|
||||
preact-router: ^3.2.1
|
||||
sass: ^1.32.13
|
||||
sass-loader: ^10.1.1
|
||||
sirv-cli: ^1.0.0-next.3
|
||||
typescript: ^3.7.5
|
||||
dependencies:
|
||||
'@gnu-taler/taler-util': link:../taler-util
|
||||
anastasis-core: link:../anastasis-core
|
||||
jed: 1.1.1
|
||||
preact: 10.5.14
|
||||
preact-render-to-string: 5.1.19_preact@10.5.14
|
||||
preact-router: 3.2.1_preact@10.5.14
|
||||
devDependencies:
|
||||
'@creativebulma/bulma-tooltip': 1.2.0
|
||||
'@types/enzyme': 3.10.9
|
||||
'@types/jest': 26.0.24
|
||||
'@typescript-eslint/eslint-plugin': 2.34.0_2b015b1c4b7c4a3ed9a197dc233b1a35
|
||||
'@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-adapter-preact-pure: 3.1.0_enzyme@3.11.0+preact@10.5.14
|
||||
eslint: 6.8.0
|
||||
eslint-config-preact: 1.1.4_eslint@6.8.0+typescript@3.9.10
|
||||
jest: 26.6.3
|
||||
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
|
||||
typescript: 3.9.10
|
||||
|
||||
@ -3570,6 +3584,10 @@ packages:
|
||||
arrify: 1.0.1
|
||||
dev: true
|
||||
|
||||
/@creativebulma/bulma-tooltip/1.2.0:
|
||||
resolution: {integrity: sha512-ooImbeXEBxf77cttbzA7X5rC5aAWm9UsXIGViFOnsqB+6M944GkB28S5R4UWRqjFd2iW4zGEkEifAU+q43pt2w==}
|
||||
dev: true
|
||||
|
||||
/@emotion/cache/10.0.29:
|
||||
resolution: {integrity: sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==}
|
||||
dependencies:
|
||||
@ -4607,7 +4625,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/estree': 0.0.39
|
||||
estree-walker: 1.0.1
|
||||
picomatch: 2.3.0
|
||||
picomatch: 2.2.2
|
||||
rollup: 2.56.2
|
||||
dev: true
|
||||
|
||||
@ -8365,6 +8383,22 @@ packages:
|
||||
resolution: {integrity: sha1-y5T662HIaWRR2zZTThQi+U8K7og=}
|
||||
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:
|
||||
resolution: {integrity: sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=}
|
||||
engines: {node: '>= 0.8'}
|
||||
@ -10784,18 +10818,18 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7
|
||||
dependencies:
|
||||
array-includes: 3.1.3
|
||||
array-includes: 3.1.2
|
||||
array.prototype.flatmap: 1.2.4
|
||||
doctrine: 2.1.0
|
||||
eslint: 6.8.0
|
||||
has: 1.0.3
|
||||
jsx-ast-utils: 3.2.0
|
||||
object.entries: 1.1.4
|
||||
object.fromentries: 2.0.4
|
||||
object.values: 1.1.4
|
||||
object.entries: 1.1.3
|
||||
object.fromentries: 2.0.3
|
||||
object.values: 1.1.2
|
||||
prop-types: 15.7.2
|
||||
resolve: 1.20.0
|
||||
string.prototype.matchall: 4.0.5
|
||||
resolve: 1.19.0
|
||||
string.prototype.matchall: 4.0.3
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-react/7.22.0_eslint@7.18.0:
|
||||
@ -16852,6 +16886,116 @@ packages:
|
||||
- webpack-command
|
||||
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:
|
||||
resolution: {integrity: sha512-bj8sn/oytIKO6RtOGSS/1+5CrQyRSC99eLUnEVbqUa6MzJX5dYh7wu9bmT0d6lm/Vea21k9KhCQwvr2sYN3rrQ==}
|
||||
peerDependencies:
|
||||
@ -18075,11 +18219,11 @@ packages:
|
||||
peerDependencies:
|
||||
rollup: ^2.0.0
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.14.5
|
||||
'@babel/code-frame': 7.12.13
|
||||
jest-worker: 26.6.2
|
||||
rollup: 2.56.2
|
||||
serialize-javascript: 4.0.0
|
||||
terser: 5.7.1
|
||||
terser: 5.4.0
|
||||
dev: true
|
||||
|
||||
/rollup/2.37.1:
|
||||
@ -18188,6 +18332,38 @@ packages:
|
||||
walker: 1.0.7
|
||||
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:
|
||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||
dev: true
|
||||
|
Loading…
Reference in New Issue
Block a user