anastasis-webui: ui tweaks
This commit is contained in:
parent
16662b194d
commit
6a0c5263bb
@ -109,13 +109,23 @@ export * from "./challenge-feedback-types.js";
|
|||||||
|
|
||||||
const logger = new Logger("anastasis-core:index.ts");
|
const logger = new Logger("anastasis-core:index.ts");
|
||||||
|
|
||||||
function getContinents(): ContinentInfo[] {
|
function getContinents(
|
||||||
|
opts: { requireProvider?: boolean } = {},
|
||||||
|
): ContinentInfo[] {
|
||||||
|
const currenciesWithProvider = new Set<string>();
|
||||||
|
anastasisData.providersList.anastasis_provider.forEach((x) => {
|
||||||
|
currenciesWithProvider.add(x.currency);
|
||||||
|
});
|
||||||
const continentSet = new Set<string>();
|
const continentSet = new Set<string>();
|
||||||
const continents: ContinentInfo[] = [];
|
const continents: ContinentInfo[] = [];
|
||||||
for (const country of anastasisData.countriesList.countries) {
|
for (const country of anastasisData.countriesList.countries) {
|
||||||
if (continentSet.has(country.continent)) {
|
if (continentSet.has(country.continent)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (opts.requireProvider && !currenciesWithProvider.has(country.currency)) {
|
||||||
|
// Country's currency doesn't have any providers => skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
continentSet.add(country.continent);
|
continentSet.add(country.continent);
|
||||||
continents.push({
|
continents.push({
|
||||||
...{ name_i18n: country.continent_i18n },
|
...{ name_i18n: country.continent_i18n },
|
||||||
@ -148,9 +158,18 @@ export class ReducerError extends Error {
|
|||||||
* Get countries for a continent, abort with ReducerError
|
* Get countries for a continent, abort with ReducerError
|
||||||
* exception when continent doesn't exist.
|
* exception when continent doesn't exist.
|
||||||
*/
|
*/
|
||||||
function getCountries(continent: string): CountryInfo[] {
|
function getCountries(
|
||||||
|
continent: string,
|
||||||
|
opts: { requireProvider?: boolean } = {},
|
||||||
|
): CountryInfo[] {
|
||||||
|
const currenciesWithProvider = new Set<string>();
|
||||||
|
anastasisData.providersList.anastasis_provider.forEach((x) => {
|
||||||
|
currenciesWithProvider.add(x.currency);
|
||||||
|
});
|
||||||
const countries = anastasisData.countriesList.countries.filter(
|
const countries = anastasisData.countriesList.countries.filter(
|
||||||
(x) => x.continent === continent,
|
(x) =>
|
||||||
|
x.continent === continent &&
|
||||||
|
(!opts.requireProvider || currenciesWithProvider.has(x.currency)),
|
||||||
);
|
);
|
||||||
if (countries.length <= 0) {
|
if (countries.length <= 0) {
|
||||||
throw new ReducerError({
|
throw new ReducerError({
|
||||||
@ -164,14 +183,18 @@ function getCountries(continent: string): CountryInfo[] {
|
|||||||
export async function getBackupStartState(): Promise<ReducerStateBackup> {
|
export async function getBackupStartState(): Promise<ReducerStateBackup> {
|
||||||
return {
|
return {
|
||||||
backup_state: BackupStates.ContinentSelecting,
|
backup_state: BackupStates.ContinentSelecting,
|
||||||
continents: getContinents(),
|
continents: getContinents({
|
||||||
|
requireProvider: true,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRecoveryStartState(): Promise<ReducerStateRecovery> {
|
export async function getRecoveryStartState(): Promise<ReducerStateRecovery> {
|
||||||
return {
|
return {
|
||||||
recovery_state: RecoveryStates.ContinentSelecting,
|
recovery_state: RecoveryStates.ContinentSelecting,
|
||||||
continents: getContinents(),
|
continents: getContinents({
|
||||||
|
requireProvider: true,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1073,7 +1096,9 @@ async function backupSelectContinent(
|
|||||||
state: ReducerStateBackup,
|
state: ReducerStateBackup,
|
||||||
args: ActionArgsSelectContinent,
|
args: ActionArgsSelectContinent,
|
||||||
): Promise<ReducerStateBackup | ReducerStateError> {
|
): Promise<ReducerStateBackup | ReducerStateError> {
|
||||||
const countries = getCountries(args.continent);
|
const countries = getCountries(args.continent, {
|
||||||
|
requireProvider: true,
|
||||||
|
});
|
||||||
if (countries.length <= 0) {
|
if (countries.length <= 0) {
|
||||||
return {
|
return {
|
||||||
code: TalerErrorCode.ANASTASIS_REDUCER_INPUT_INVALID,
|
code: TalerErrorCode.ANASTASIS_REDUCER_INPUT_INVALID,
|
||||||
@ -1092,7 +1117,9 @@ async function recoverySelectContinent(
|
|||||||
state: ReducerStateRecovery,
|
state: ReducerStateRecovery,
|
||||||
args: ActionArgsSelectContinent,
|
args: ActionArgsSelectContinent,
|
||||||
): Promise<ReducerStateRecovery | ReducerStateError> {
|
): Promise<ReducerStateRecovery | ReducerStateError> {
|
||||||
const countries = getCountries(args.continent);
|
const countries = getCountries(args.continent, {
|
||||||
|
requireProvider: true,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
recovery_state: RecoveryStates.CountrySelecting,
|
recovery_state: RecoveryStates.CountrySelecting,
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from 'preact';
|
import { h, VNode } from "preact";
|
||||||
import logo from '../../assets/logo.jpeg';
|
import logo from "../../assets/logo.jpeg";
|
||||||
import { LangSelector } from './LangSelector';
|
import { LangSelector } from "./LangSelector";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onMobileMenu: () => void;
|
onMobileMenu: () => void;
|
||||||
@ -29,14 +29,38 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NavigationBar({ onMobileMenu, title }: Props): VNode {
|
export function NavigationBar({ onMobileMenu, title }: Props): VNode {
|
||||||
return (<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
|
return (
|
||||||
|
<nav
|
||||||
|
class="navbar is-fixed-top"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="main navigation"
|
||||||
|
>
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<span class="navbar-item" style={{ fontSize: 24, fontWeight: 900 }}>{title}</span>
|
<span class="navbar-item" style={{ fontSize: 24, fontWeight: 900 }}>
|
||||||
|
{title}
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" onClick={(e) => {
|
</span>
|
||||||
onMobileMenu()
|
<a
|
||||||
e.stopPropagation()
|
href="mailto:contact@anastasis.lu"
|
||||||
}}>
|
style={{ alignSelf: "center", padding: "0.5em" }}
|
||||||
|
>
|
||||||
|
Contact us
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://bugs.anastasis.li/"
|
||||||
|
style={{ alignSelf: "center", padding: "0.5em" }}
|
||||||
|
>
|
||||||
|
Report a bug
|
||||||
|
</a>
|
||||||
|
<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" />
|
<span aria-hidden="true" />
|
||||||
<span aria-hidden="true" />
|
<span aria-hidden="true" />
|
||||||
|
@ -44,9 +44,9 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
</div>} */}
|
</div>} */}
|
||||||
<div class="aside-tools">
|
<div class="aside-tools">
|
||||||
<div class="aside-tools-label">
|
<div class="aside-tools-label">
|
||||||
<div><b>Anastasis</b> Reducer</div>
|
<div><b>Anastasis</b></div>
|
||||||
<div class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }}>
|
<div class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }}>
|
||||||
{process.env.__VERSION__} ({config.version})
|
Version {process.env.__VERSION__} ({config.version})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,41 +15,53 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||||
import Match from 'preact-router/match';
|
import Match from "preact-router/match";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { NavigationBar } from "./NavigationBar";
|
import { NavigationBar } from "./NavigationBar";
|
||||||
import { Sidebar } from "./SideBar";
|
import { Sidebar } from "./SideBar";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface MenuProps {
|
interface MenuProps {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function WithTitle({ title, children }: { title: string; children: ComponentChildren }): VNode {
|
function WithTitle({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
children: ComponentChildren;
|
||||||
|
}): VNode {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `Taler Backoffice: ${title}`
|
document.title = `${title}`;
|
||||||
}, [title])
|
}, [title]);
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment>{children}</Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Menu({ title }: MenuProps): VNode {
|
export function Menu({ title }: MenuProps): VNode {
|
||||||
const [mobileOpen, setMobileOpen] = useState(false)
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|
||||||
return <Match>{({ path }: { path: string }) => {
|
return (
|
||||||
const titleWithSubtitle = title // title ? title : (!admin ? getInstanceTitle(path, instance) : getAdminTitle(path, instance))
|
<Match>
|
||||||
return (<WithTitle title={titleWithSubtitle}>
|
{({ path }: { path: string }) => {
|
||||||
<div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}>
|
const titleWithSubtitle = title; // title ? title : (!admin ? getInstanceTitle(path, instance) : getAdminTitle(path, instance))
|
||||||
<NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={titleWithSubtitle} />
|
return (
|
||||||
|
<WithTitle title={titleWithSubtitle}>
|
||||||
|
<div
|
||||||
|
class={mobileOpen ? "has-aside-mobile-expanded" : ""}
|
||||||
|
onClick={() => setMobileOpen(false)}
|
||||||
|
>
|
||||||
|
<NavigationBar
|
||||||
|
onMobileMenu={() => setMobileOpen(!mobileOpen)}
|
||||||
|
title={titleWithSubtitle}
|
||||||
|
/>
|
||||||
|
|
||||||
<Sidebar mobile={mobileOpen} />
|
<Sidebar mobile={mobileOpen} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</WithTitle>
|
</WithTitle>
|
||||||
)
|
);
|
||||||
}}</Match>
|
}}
|
||||||
|
</Match>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotYetReadyAppMenuProps {
|
interface NotYetReadyAppMenuProps {
|
||||||
@ -60,37 +72,56 @@ interface NotYetReadyAppMenuProps {
|
|||||||
interface NotifProps {
|
interface NotifProps {
|
||||||
notification?: Notification;
|
notification?: Notification;
|
||||||
}
|
}
|
||||||
export function NotificationCard({ notification: n }: NotifProps): VNode | null {
|
export function NotificationCard({
|
||||||
if (!n) return null
|
notification: n,
|
||||||
return <div class="notification">
|
}: NotifProps): VNode | null {
|
||||||
|
if (!n) return null;
|
||||||
|
return (
|
||||||
|
<div class="notification">
|
||||||
<div class="columns is-vcentered">
|
<div class="columns is-vcentered">
|
||||||
<div class="column is-12">
|
<div class="column is-12">
|
||||||
<article class={n.type === 'ERROR' ? "message is-danger" : (n.type === 'WARN' ? "message is-warning" : "message is-info")}>
|
<article
|
||||||
|
class={
|
||||||
|
n.type === "ERROR"
|
||||||
|
? "message is-danger"
|
||||||
|
: n.type === "WARN"
|
||||||
|
? "message is-warning"
|
||||||
|
: "message is-info"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="message-header">
|
<div class="message-header">
|
||||||
<p>{n.message}</p>
|
<p>{n.message}</p>
|
||||||
</div>
|
</div>
|
||||||
{n.description &&
|
{n.description && <div class="message-body">{n.description}</div>}
|
||||||
<div class="message-body">
|
|
||||||
{n.description}
|
|
||||||
</div>}
|
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotYetReadyAppMenu({ onLogout, title }: NotYetReadyAppMenuProps): VNode {
|
export function NotYetReadyAppMenu({
|
||||||
const [mobileOpen, setMobileOpen] = useState(false)
|
onLogout,
|
||||||
|
title,
|
||||||
|
}: NotYetReadyAppMenuProps): VNode {
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `Taler Backoffice: ${title}`
|
document.title = `Taler Backoffice: ${title}`;
|
||||||
}, [title])
|
}, [title]);
|
||||||
|
|
||||||
return <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}>
|
return (
|
||||||
<NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={title} />
|
<div
|
||||||
|
class={mobileOpen ? "has-aside-mobile-expanded" : ""}
|
||||||
|
onClick={() => setMobileOpen(false)}
|
||||||
|
>
|
||||||
|
<NavigationBar
|
||||||
|
onMobileMenu={() => setMobileOpen(!mobileOpen)}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
{onLogout && <Sidebar mobile={mobileOpen} />}
|
{onLogout && <Sidebar mobile={mobileOpen} />}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
@ -99,6 +130,5 @@ export interface Notification {
|
|||||||
type: MessageType;
|
type: MessageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ValueOrFunction<T> = T | ((p: T) => T)
|
export type ValueOrFunction<T> = T | ((p: T) => T);
|
||||||
export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'
|
export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS";
|
||||||
|
|
||||||
|
@ -123,13 +123,35 @@ export function ContinentSelectionScreen(): VNode {
|
|||||||
</div>
|
</div>
|
||||||
<div class="column is-two-third">
|
<div class="column is-two-third">
|
||||||
<p>
|
<p>
|
||||||
Your location will help us to determine which personal information
|
Your choice will help us with asking the right information to unique
|
||||||
to ask you for the next step.
|
identify you when you want to recover your backed up secrets.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
You should choose the country that issued most of your long-term
|
Choose the country that issued most of your long-term legal
|
||||||
legal documents or personal identifiers.
|
documents or personal identifiers.
|
||||||
</p>
|
</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: "1px solid gray",
|
||||||
|
borderRadius: "0.5em",
|
||||||
|
backgroundColor: "#fbfcbd",
|
||||||
|
padding: "0.5em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
If you just want to try out Anastasis, we recomment that you
|
||||||
|
choose <b>Testcontinent</b> with <b>Demoland</b>. For this special
|
||||||
|
country, you will be asked for a simple number and not real,
|
||||||
|
personal identifiable information.
|
||||||
|
</p>
|
||||||
|
{/*
|
||||||
|
<p>
|
||||||
|
Because of the diversity of personally identifying information in
|
||||||
|
different countries and cultures, we do not support all countries
|
||||||
|
yet. If you want to improve the supported countries,{" "}
|
||||||
|
<a href="mailto:contact@anastasis.lu">contact us</a>.
|
||||||
|
</p> */}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
|
Loading…
Reference in New Issue
Block a user