anastasis-webui: ui tweaks

This commit is contained in:
Florian Dold 2021-11-08 16:51:23 +01:00
parent 16662b194d
commit 6a0c5263bb
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 190 additions and 87 deletions

View File

@ -109,13 +109,23 @@ export * from "./challenge-feedback-types.js";
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 continents: ContinentInfo[] = [];
for (const country of anastasisData.countriesList.countries) {
if (continentSet.has(country.continent)) {
continue;
}
if (opts.requireProvider && !currenciesWithProvider.has(country.currency)) {
// Country's currency doesn't have any providers => skip
continue;
}
continentSet.add(country.continent);
continents.push({
...{ name_i18n: country.continent_i18n },
@ -148,9 +158,18 @@ export class ReducerError extends Error {
* Get countries for a continent, abort with ReducerError
* 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(
(x) => x.continent === continent,
(x) =>
x.continent === continent &&
(!opts.requireProvider || currenciesWithProvider.has(x.currency)),
);
if (countries.length <= 0) {
throw new ReducerError({
@ -164,14 +183,18 @@ function getCountries(continent: string): CountryInfo[] {
export async function getBackupStartState(): Promise<ReducerStateBackup> {
return {
backup_state: BackupStates.ContinentSelecting,
continents: getContinents(),
continents: getContinents({
requireProvider: true,
}),
};
}
export async function getRecoveryStartState(): Promise<ReducerStateRecovery> {
return {
recovery_state: RecoveryStates.ContinentSelecting,
continents: getContinents(),
continents: getContinents({
requireProvider: true,
}),
};
}
@ -1073,7 +1096,9 @@ async function backupSelectContinent(
state: ReducerStateBackup,
args: ActionArgsSelectContinent,
): Promise<ReducerStateBackup | ReducerStateError> {
const countries = getCountries(args.continent);
const countries = getCountries(args.continent, {
requireProvider: true,
});
if (countries.length <= 0) {
return {
code: TalerErrorCode.ANASTASIS_REDUCER_INPUT_INVALID,
@ -1092,7 +1117,9 @@ async function recoverySelectContinent(
state: ReducerStateRecovery,
args: ActionArgsSelectContinent,
): Promise<ReducerStateRecovery | ReducerStateError> {
const countries = getCountries(args.continent);
const countries = getCountries(args.continent, {
requireProvider: true,
});
return {
...state,
recovery_state: RecoveryStates.CountrySelecting,

View File

@ -15,13 +15,13 @@
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { h, VNode } from 'preact';
import logo from '../../assets/logo.jpeg';
import { LangSelector } from './LangSelector';
import { h, VNode } from "preact";
import logo from "../../assets/logo.jpeg";
import { LangSelector } from "./LangSelector";
interface Props {
onMobileMenu: () => void;
@ -29,27 +29,51 @@ interface Props {
}
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>
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
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" />
</a>
</div>
<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 ">
<div class="navbar-end">
<div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
{/* <LangSelector /> */}
<div class="navbar-menu ">
<div class="navbar-end">
<div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
{/* <LangSelector /> */}
</div>
</div>
</div>
</div>
</nav>
</nav>
);
}
}

View File

@ -44,9 +44,9 @@ export function Sidebar({ mobile }: Props): VNode {
</div>} */}
<div class="aside-tools">
<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 }}>
{process.env.__VERSION__} ({config.version})
Version {process.env.__VERSION__} ({config.version})
</div>
</div>
</div>

View File

@ -15,41 +15,53 @@
*/
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 { NavigationBar } from "./NavigationBar";
import { Sidebar } from "./SideBar";
interface MenuProps {
title: string;
}
function WithTitle({ title, children }: { title: string; children: ComponentChildren }): VNode {
function WithTitle({
title,
children,
}: {
title: string;
children: ComponentChildren;
}): VNode {
useEffect(() => {
document.title = `Taler Backoffice: ${title}`
}, [title])
return <Fragment>{children}</Fragment>
document.title = `${title}`;
}, [title]);
return <Fragment>{children}</Fragment>;
}
export function Menu({ title }: MenuProps): VNode {
const [mobileOpen, setMobileOpen] = useState(false)
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>
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 {
@ -60,37 +72,56 @@ interface NotYetReadyAppMenuProps {
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>
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>
</div>
);
}
export function NotYetReadyAppMenu({ onLogout, title }: NotYetReadyAppMenuProps): VNode {
const [mobileOpen, setMobileOpen] = useState(false)
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 mobile={mobileOpen} />}
</div>
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 mobile={mobileOpen} />}
</div>
);
}
export interface Notification {
@ -99,6 +130,5 @@ export interface Notification {
type: MessageType;
}
export type ValueOrFunction<T> = T | ((p: T) => T)
export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'
export type ValueOrFunction<T> = T | ((p: T) => T);
export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS";

View File

@ -123,13 +123,35 @@ export function ContinentSelectionScreen(): VNode {
</div>
<div class="column is-two-third">
<p>
Your location will help us to determine which personal information
to ask you for the next step.
Your choice will help us with asking the right information to unique
identify you when you want to recover your backed up secrets.
</p>
<p>
You should choose the country that issued most of your long-term
legal documents or personal identifiers.
Choose the country that issued most of your long-term legal
documents or personal identifiers.
</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>
</AnastasisClientFrame>