add testing to web components
This commit is contained in:
parent
716da3246b
commit
6d06b52605
@ -37,6 +37,8 @@ echo compile
|
||||
build_css &
|
||||
build_js src/main.ts &
|
||||
build_js src/main.test.ts &
|
||||
for file in $(find src/ -name test.ts); do build_js $file; done &
|
||||
wait -n
|
||||
wait -n
|
||||
wait -n
|
||||
wait -n
|
||||
|
@ -9,12 +9,14 @@
|
||||
"dev": "./dev.mjs",
|
||||
"prepare": "pnpm compile",
|
||||
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
||||
"test": "mocha --enable-source-maps 'dist/**/*.test.js'",
|
||||
"test": "mocha --enable-source-maps 'dist/**/*test.js'",
|
||||
"pretty": "prettier --write src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gnu-taler/anastasis-core": "workspace:*",
|
||||
"@gnu-taler/taler-util": "workspace:*",
|
||||
"@types/chai": "^4.3.0",
|
||||
"chai": "^4.3.6",
|
||||
"date-fns": "2.28.0",
|
||||
"jed": "1.1.1",
|
||||
"preact": "^10.5.15",
|
||||
|
21
packages/anastasis-webui/src/components/InvalidState.tsx
Normal file
21
packages/anastasis-webui/src/components/InvalidState.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export default function InvalidState(): VNode {
|
||||
return <div>invalid state</div>;
|
||||
}
|
21
packages/anastasis-webui/src/components/NoReducer.tsx
Normal file
21
packages/anastasis-webui/src/components/NoReducer.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export default function NoReducer(): VNode {
|
||||
return <div>no reducer</div>;
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { ReducerState } from "@gnu-taler/anastasis-core";
|
||||
import { createExample, reducerStatesExample } from "../../utils/index.js";
|
||||
import { AddingProviderScreen as TestedComponent } from "./AddingProviderScreen.js";
|
||||
|
||||
export default {
|
||||
title: "Pages/ManageProvider",
|
||||
component: TestedComponent,
|
||||
args: {
|
||||
order: 1,
|
||||
},
|
||||
argTypes: {
|
||||
onUpdate: { action: "onUpdate" },
|
||||
onBack: { action: "onBack" },
|
||||
},
|
||||
};
|
||||
|
||||
export const NewProvider = createExample(TestedComponent, {
|
||||
...reducerStatesExample.authEditing,
|
||||
} as ReducerState);
|
||||
|
||||
export const NewProviderWithoutProviderList = createExample(TestedComponent, {
|
||||
...reducerStatesExample.authEditing,
|
||||
authentication_providers: {},
|
||||
} as ReducerState);
|
||||
|
||||
export const NewSmsProvider = createExample(
|
||||
TestedComponent,
|
||||
{
|
||||
...reducerStatesExample.authEditing,
|
||||
} as ReducerState,
|
||||
{ providerType: "sms" },
|
||||
);
|
||||
|
||||
export const NewIBANProvider = createExample(
|
||||
TestedComponent,
|
||||
{
|
||||
...reducerStatesExample.authEditing,
|
||||
} as ReducerState,
|
||||
{ providerType: "iban" },
|
||||
);
|
@ -1,367 +0,0 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
import {
|
||||
AuthenticationProviderStatus,
|
||||
AuthenticationProviderStatusError,
|
||||
AuthenticationProviderStatusOk,
|
||||
} from "@gnu-taler/anastasis-core";
|
||||
import { h, VNode } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { TextInput } from "../../components/fields/TextInput.js";
|
||||
import { useAnastasisContext } from "../../context/anastasis.js";
|
||||
import { authMethods, KnownAuthMethods } from "./authMethod/index.js";
|
||||
import { AnastasisClientFrame } from "./index.js";
|
||||
|
||||
interface Props {
|
||||
providerType?: KnownAuthMethods;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
async function testProvider(
|
||||
url: string,
|
||||
expectedMethodType?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(new URL("config", url).href);
|
||||
const json = await response.json().catch((d) => ({}));
|
||||
if (!("methods" in json) || !Array.isArray(json.methods)) {
|
||||
throw Error(
|
||||
"This provider doesn't have authentication method. Check the provider URL",
|
||||
);
|
||||
}
|
||||
if (!expectedMethodType) {
|
||||
return;
|
||||
}
|
||||
let found = false;
|
||||
for (let i = 0; i < json.methods.length && !found; i++) {
|
||||
found = json.methods[i].type === expectedMethodType;
|
||||
}
|
||||
if (!found) {
|
||||
throw Error(
|
||||
`This provider does not support authentication method ${expectedMethodType}`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
const error =
|
||||
e instanceof Error
|
||||
? Error(
|
||||
`There was an error testing this provider, try another one. ${e.message}`,
|
||||
)
|
||||
: Error(`There was an error testing this provider, try another one.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function AddingProviderScreen({ providerType, onCancel }: Props): VNode {
|
||||
const reducer = useAnastasisContext();
|
||||
|
||||
const [providerURL, setProviderURL] = useState("");
|
||||
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const [testing, setTesting] = useState(false);
|
||||
|
||||
const providerLabel = providerType
|
||||
? authMethods[providerType].label
|
||||
: undefined;
|
||||
|
||||
const allAuthProviders =
|
||||
!reducer ||
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.reducer_type === "error" ||
|
||||
!reducer.currentReducerState.authentication_providers
|
||||
? {}
|
||||
: reducer.currentReducerState.authentication_providers;
|
||||
|
||||
const authProvidersByStatus = Object.keys(allAuthProviders).reduce(
|
||||
(prev, url) => {
|
||||
const p = allAuthProviders[url];
|
||||
if (
|
||||
providerLabel &&
|
||||
p.status === "ok" &&
|
||||
p.methods.findIndex((m) => m.type === providerType) !== -1
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
const others = prev[p.status] ? prev[p.status] : [];
|
||||
others.push({ ...p, url });
|
||||
return {
|
||||
...prev,
|
||||
[p.status]: others,
|
||||
};
|
||||
},
|
||||
{} as Record<
|
||||
AuthenticationProviderStatus["status"],
|
||||
(AuthenticationProviderStatus & { url: string })[]
|
||||
>,
|
||||
);
|
||||
const authProviders = authProvidersByStatus["ok"].map((p) => p.url);
|
||||
|
||||
console.log("rodos", allAuthProviders);
|
||||
//FIXME: move this timeout logic into a hook
|
||||
const timeout = useRef<number | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
if (timeout) window.clearTimeout(timeout.current);
|
||||
timeout.current = window.setTimeout(async () => {
|
||||
const url = providerURL.endsWith("/") ? providerURL : providerURL + "/";
|
||||
if (!providerURL || authProviders.includes(url)) return;
|
||||
try {
|
||||
setTesting(true);
|
||||
await testProvider(url, providerType);
|
||||
setError("");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) setError(e.message);
|
||||
}
|
||||
setTesting(false);
|
||||
}, 200);
|
||||
}, [providerURL, reducer]);
|
||||
|
||||
async function addProvider(provider_url: string): Promise<void> {
|
||||
await reducer?.transition("add_provider", { provider_url });
|
||||
onCancel();
|
||||
}
|
||||
function deleteProvider(provider_url: string): void {
|
||||
reducer?.transition("delete_provider", { provider_url });
|
||||
}
|
||||
|
||||
if (!reducer) {
|
||||
return <div>no reducer in context</div>;
|
||||
}
|
||||
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
!("authentication_providers" in reducer.currentReducerState)
|
||||
) {
|
||||
return <div>invalid state</div>;
|
||||
}
|
||||
|
||||
let errors = !providerURL ? "Add provider URL" : undefined;
|
||||
let url: string | undefined;
|
||||
try {
|
||||
url = new URL("", providerURL).href;
|
||||
} catch {
|
||||
errors = "Check the URL";
|
||||
}
|
||||
if (!!error && !errors) {
|
||||
errors = error;
|
||||
}
|
||||
if (!errors && authProviders.includes(url!)) {
|
||||
errors = "That provider is already known";
|
||||
}
|
||||
|
||||
return (
|
||||
<AnastasisClientFrame
|
||||
hideNav
|
||||
title="Backup: Manage providers"
|
||||
hideNext={errors}
|
||||
>
|
||||
<div>
|
||||
{!providerLabel ? (
|
||||
<p>Add a provider url</p>
|
||||
) : (
|
||||
<p>Add a provider url for a {providerLabel} service</p>
|
||||
)}
|
||||
<div class="container">
|
||||
<TextInput
|
||||
label="Provider URL"
|
||||
placeholder="https://provider.com"
|
||||
grabFocus
|
||||
error={errors}
|
||||
bind={[providerURL, setProviderURL]}
|
||||
/>
|
||||
</div>
|
||||
<p class="block">Example: https://kudos.demo.anastasis.lu</p>
|
||||
{testing && <p class="has-text-info">Testing</p>}
|
||||
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
marginTop: "2em",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<button class="button" onClick={onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<span data-tooltip={errors}>
|
||||
<button
|
||||
class="button is-info"
|
||||
disabled={error !== "" || testing}
|
||||
onClick={() => addProvider(url!)}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{authProviders.length > 0 ? (
|
||||
!providerLabel ? (
|
||||
<p class="subtitle">Current providers</p>
|
||||
) : (
|
||||
<p class="subtitle">
|
||||
Current providers for {providerLabel} service
|
||||
</p>
|
||||
)
|
||||
) : !providerLabel ? (
|
||||
<p class="subtitle">No known providers, add one.</p>
|
||||
) : (
|
||||
<p class="subtitle">No known providers for {providerLabel} service</p>
|
||||
)}
|
||||
|
||||
{authProviders.map((k) => {
|
||||
const p = allAuthProviders[k] as AuthenticationProviderStatusOk;
|
||||
return (
|
||||
<TableRow key={k} url={k} info={p} onDelete={deleteProvider} />
|
||||
);
|
||||
})}
|
||||
{authProvidersByStatus["error"]?.map((k) => {
|
||||
const p = k as AuthenticationProviderStatusError;
|
||||
return (
|
||||
<TableRowError
|
||||
key={k}
|
||||
url={k.url}
|
||||
info={p}
|
||||
onDelete={deleteProvider}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AnastasisClientFrame>
|
||||
);
|
||||
}
|
||||
function TableRow({
|
||||
url,
|
||||
info,
|
||||
onDelete,
|
||||
}: {
|
||||
onDelete: (s: string) => void;
|
||||
url: string;
|
||||
info: AuthenticationProviderStatusOk;
|
||||
}): VNode {
|
||||
const [status, setStatus] = useState("checking");
|
||||
useEffect(function () {
|
||||
testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url)
|
||||
.then(function () {
|
||||
setStatus("responding");
|
||||
})
|
||||
.catch(function () {
|
||||
setStatus("failed to contact");
|
||||
});
|
||||
});
|
||||
return (
|
||||
<div
|
||||
class="box"
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<div>
|
||||
<div class="subtitle">{url}</div>
|
||||
<dl>
|
||||
<dt>
|
||||
<b>Business Name</b>
|
||||
</dt>
|
||||
<dd>{info.business_name}</dd>
|
||||
<dt>
|
||||
<b>Supported methods</b>
|
||||
</dt>
|
||||
<dd>{info.methods.map((m) => m.type).join(",")}</dd>
|
||||
<dt>
|
||||
<b>Maximum storage</b>
|
||||
</dt>
|
||||
<dd>{info.storage_limit_in_megabytes} Mb</dd>
|
||||
<dt>
|
||||
<b>Status</b>
|
||||
</dt>
|
||||
<dd>{status}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<button class="button is-danger" onClick={() => onDelete(url)}>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TableRowError({
|
||||
url,
|
||||
info,
|
||||
onDelete,
|
||||
}: {
|
||||
onDelete: (s: string) => void;
|
||||
url: string;
|
||||
info: AuthenticationProviderStatusError;
|
||||
}): VNode {
|
||||
const [status, setStatus] = useState("checking");
|
||||
useEffect(function () {
|
||||
testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url)
|
||||
.then(function () {
|
||||
setStatus("responding");
|
||||
})
|
||||
.catch(function () {
|
||||
setStatus("failed to contact");
|
||||
});
|
||||
});
|
||||
return (
|
||||
<div
|
||||
class="box"
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<div>
|
||||
<div class="subtitle">{url}</div>
|
||||
<dl>
|
||||
<dt>
|
||||
<b>Error</b>
|
||||
</dt>
|
||||
<dd>{info.hint}</dd>
|
||||
<dt>
|
||||
<b>Code</b>
|
||||
</dt>
|
||||
<dd>{info.code}</dd>
|
||||
<dt>
|
||||
<b>Status</b>
|
||||
</dt>
|
||||
<dd>{status}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<button class="button is-danger" onClick={() => onDelete(url)}>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
import { AuthenticationProviderStatus } from "@gnu-taler/anastasis-core";
|
||||
import InvalidState from "../../../components/InvalidState.js";
|
||||
import NoReducer from "../../../components/NoReducer.js";
|
||||
import { compose, StateViewMap } from "../../../utils/index.js";
|
||||
import useComponentState from "./state.js";
|
||||
import { WithoutProviderType, WithProviderType } from "./views.js";
|
||||
|
||||
export type AuthProvByStatusMap = Record<
|
||||
AuthenticationProviderStatus["status"],
|
||||
(AuthenticationProviderStatus & { url: string })[]
|
||||
>
|
||||
|
||||
export type State = NoReducer | InvalidState | WithType | WithoutType;
|
||||
|
||||
export interface NoReducer {
|
||||
status: "no-reducer";
|
||||
}
|
||||
export interface InvalidState {
|
||||
status: "invalid-state";
|
||||
}
|
||||
|
||||
interface CommonProps {
|
||||
addProvider?: () => Promise<void>;
|
||||
deleteProvider: (url: string) => Promise<void>;
|
||||
authProvidersByStatus: AuthProvByStatusMap;
|
||||
error: string | undefined;
|
||||
onCancel: () => Promise<void>;
|
||||
testing: boolean;
|
||||
setProviderURL: (url: string) => Promise<void>;
|
||||
providerURL: string;
|
||||
errors: string | undefined;
|
||||
}
|
||||
|
||||
export interface WithType extends CommonProps {
|
||||
status: "with-type";
|
||||
providerLabel: string;
|
||||
}
|
||||
export interface WithoutType extends CommonProps {
|
||||
status: "without-type";
|
||||
}
|
||||
|
||||
const map: StateViewMap<State> = {
|
||||
"no-reducer": NoReducer,
|
||||
"invalid-state": InvalidState,
|
||||
"with-type": WithProviderType,
|
||||
"without-type": WithoutProviderType,
|
||||
};
|
||||
|
||||
export default compose("AddingProviderScreen", useComponentState, map)
|
||||
|
||||
|
||||
export async function testProvider(
|
||||
url: string,
|
||||
expectedMethodType?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(new URL("config", url).href);
|
||||
const json = await response.json().catch((d) => ({}));
|
||||
if (!("methods" in json) || !Array.isArray(json.methods)) {
|
||||
throw Error(
|
||||
"This provider doesn't have authentication method. Check the provider URL",
|
||||
);
|
||||
}
|
||||
if (!expectedMethodType) {
|
||||
return;
|
||||
}
|
||||
let found = false;
|
||||
for (let i = 0; i < json.methods.length && !found; i++) {
|
||||
found = json.methods[i].type === expectedMethodType;
|
||||
}
|
||||
if (!found) {
|
||||
throw Error(
|
||||
`This provider does not support authentication method ${expectedMethodType}`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
const error =
|
||||
e instanceof Error
|
||||
? Error(
|
||||
`There was an error testing this provider, try another one. ${e.message}`,
|
||||
)
|
||||
: Error(`There was an error testing this provider, try another one.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useAnastasisContext } from "../../../context/anastasis.js";
|
||||
import { authMethods, KnownAuthMethods } from "../authMethod/index.jsx";
|
||||
import { AuthProvByStatusMap, State, testProvider } from "./index.js";
|
||||
|
||||
interface Props {
|
||||
providerType?: KnownAuthMethods;
|
||||
onCancel: () => Promise<void>;
|
||||
}
|
||||
|
||||
export default function useComponentState({ providerType, onCancel }: Props): State {
|
||||
const reducer = useAnastasisContext();
|
||||
|
||||
const [providerURL, setProviderURL] = useState("");
|
||||
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const [testing, setTesting] = useState(false);
|
||||
|
||||
const providerLabel = providerType
|
||||
? authMethods[providerType].label
|
||||
: undefined;
|
||||
|
||||
const allAuthProviders =
|
||||
!reducer ||
|
||||
!reducer.currentReducerState ||
|
||||
reducer.currentReducerState.reducer_type === "error" ||
|
||||
!reducer.currentReducerState.authentication_providers
|
||||
? {}
|
||||
: reducer.currentReducerState.authentication_providers;
|
||||
|
||||
const authProvidersByStatus = Object.keys(allAuthProviders).reduce(
|
||||
(prev, url) => {
|
||||
const p = allAuthProviders[url];
|
||||
if (
|
||||
providerLabel &&
|
||||
p.status === "ok" &&
|
||||
p.methods.findIndex((m) => m.type === providerType) !== -1
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
prev[p.status].push({ ...p, url });
|
||||
return prev;
|
||||
},
|
||||
{ "not-contacted": [], disabled: [], error: [], ok: [] } as AuthProvByStatusMap,
|
||||
);
|
||||
const authProviders = authProvidersByStatus["ok"].map((p) => p.url);
|
||||
|
||||
//FIXME: move this timeout logic into a hook
|
||||
const timeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
if (timeout.current) clearTimeout(timeout.current);
|
||||
timeout.current = setTimeout(async () => {
|
||||
const url = providerURL.endsWith("/") ? providerURL : providerURL + "/";
|
||||
if (!providerURL || authProviders.includes(url)) return;
|
||||
try {
|
||||
setTesting(true);
|
||||
await testProvider(url, providerType);
|
||||
setError("");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) setError(e.message);
|
||||
}
|
||||
setTesting(false);
|
||||
}, 200);
|
||||
}, [providerURL, reducer]);
|
||||
|
||||
if (!reducer) {
|
||||
return {
|
||||
status: "no-reducer",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
!reducer.currentReducerState ||
|
||||
!("authentication_providers" in reducer.currentReducerState)
|
||||
) {
|
||||
return {
|
||||
status: "invalid-state",
|
||||
};
|
||||
}
|
||||
|
||||
const addProvider = async (provider_url: string): Promise<void> => {
|
||||
await reducer.transition("add_provider", { provider_url });
|
||||
onCancel();
|
||||
}
|
||||
const deleteProvider = async (provider_url: string): Promise<void> => {
|
||||
reducer.transition("delete_provider", { provider_url });
|
||||
}
|
||||
|
||||
let errors = !providerURL ? "Add provider URL" : undefined;
|
||||
let url: string | undefined;
|
||||
try {
|
||||
url = new URL("", providerURL).href;
|
||||
} catch {
|
||||
errors = "Check the URL";
|
||||
}
|
||||
const _url = url
|
||||
|
||||
if (!!error && !errors) {
|
||||
errors = error;
|
||||
}
|
||||
if (!errors && authProviders.includes(url!)) {
|
||||
errors = "That provider is already known";
|
||||
}
|
||||
|
||||
const commonState = {
|
||||
addProvider: !_url ? undefined : async () => addProvider(_url),
|
||||
deleteProvider: async (url: string) => deleteProvider(url),
|
||||
allAuthProviders,
|
||||
authProvidersByStatus,
|
||||
onCancel,
|
||||
providerURL,
|
||||
testing,
|
||||
setProviderURL: async (s: string) => setProviderURL(s),
|
||||
errors,
|
||||
error,
|
||||
}
|
||||
|
||||
if (!providerLabel) {
|
||||
return {
|
||||
status: "without-type",
|
||||
...commonState
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
status: "with-type",
|
||||
providerLabel,
|
||||
...commonState
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { AuthenticationProviderStatusOk } from "@gnu-taler/anastasis-core";
|
||||
import { createExampleWithoutAnastasis } from "../../../utils/index.jsx";
|
||||
import { WithoutProviderType, WithProviderType } from "./views.jsx";
|
||||
|
||||
export default {
|
||||
title: "Pages/ManageProvider",
|
||||
args: {
|
||||
order: 1,
|
||||
},
|
||||
argTypes: {
|
||||
onUpdate: { action: "onUpdate" },
|
||||
onBack: { action: "onBack" },
|
||||
},
|
||||
};
|
||||
|
||||
export const NewProvider = createExampleWithoutAnastasis(WithoutProviderType, {
|
||||
authProvidersByStatus: {
|
||||
ok: [
|
||||
{
|
||||
business_name: "X provider",
|
||||
status: "ok",
|
||||
storage_limit_in_megabytes: 5,
|
||||
methods: [
|
||||
{
|
||||
type: "question",
|
||||
usage_fee: "KUDOS:1",
|
||||
},
|
||||
],
|
||||
url: "",
|
||||
} as AuthenticationProviderStatusOk & { url: string },
|
||||
],
|
||||
"not-contacted": [],
|
||||
disabled: [],
|
||||
error: [],
|
||||
},
|
||||
});
|
||||
|
||||
export const NewProviderWithoutProviderList = createExampleWithoutAnastasis(
|
||||
WithoutProviderType,
|
||||
{
|
||||
authProvidersByStatus: {
|
||||
ok: [],
|
||||
"not-contacted": [],
|
||||
disabled: [],
|
||||
error: [],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const NewSmsProvider = createExampleWithoutAnastasis(WithProviderType, {
|
||||
authProvidersByStatus: {
|
||||
ok: [],
|
||||
"not-contacted": [],
|
||||
disabled: [],
|
||||
error: [],
|
||||
},
|
||||
providerLabel: "sms",
|
||||
});
|
||||
|
||||
export const NewIBANProvider = createExampleWithoutAnastasis(WithProviderType, {
|
||||
authProvidersByStatus: {
|
||||
ok: [],
|
||||
"not-contacted": [],
|
||||
disabled: [],
|
||||
error: [],
|
||||
},
|
||||
providerLabel: "IBAN",
|
||||
});
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { expect } from "chai";
|
||||
import { mountHook } from "../../../test-utils.js";
|
||||
import useComponentState from "./state.js";
|
||||
|
||||
describe("AddingProviderScreen states", () => {
|
||||
it("should have status 'no-balance' when balance is empty", async () => {
|
||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||
mountHook(() =>
|
||||
useComponentState({ onCancel: async () => { null } }),
|
||||
);
|
||||
|
||||
{
|
||||
const { status } = getLastResultOrThrow();
|
||||
expect(status).equal("no-reducer");
|
||||
}
|
||||
|
||||
await assertNoPendingUpdate();
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,304 @@
|
||||
/*
|
||||
This file is part of GNU Anastasis
|
||||
(C) 2021-2022 Anastasis SARL
|
||||
|
||||
GNU Anastasis is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Anastasis 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
import {
|
||||
AuthenticationProviderStatusError,
|
||||
AuthenticationProviderStatusOk,
|
||||
} from "@gnu-taler/anastasis-core";
|
||||
import { h, VNode } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { TextInput } from "../../../components/fields/TextInput.js";
|
||||
import { AnastasisClientFrame } from "../index.js";
|
||||
import { testProvider, WithoutType, WithType } from "./index.js";
|
||||
|
||||
export function WithProviderType(props: WithType): VNode {
|
||||
return (
|
||||
<AnastasisClientFrame
|
||||
hideNav
|
||||
title="Backup: Manage providers1"
|
||||
hideNext={props.errors}
|
||||
>
|
||||
<div>
|
||||
<p>Add a provider url for a {props.providerLabel} service</p>
|
||||
<div class="container">
|
||||
<TextInput
|
||||
label="Provider URL"
|
||||
placeholder="https://provider.com"
|
||||
grabFocus
|
||||
error={props.errors}
|
||||
bind={[props.providerURL, props.setProviderURL]}
|
||||
/>
|
||||
</div>
|
||||
<p class="block">Example: https://kudos.demo.anastasis.lu</p>
|
||||
{props.testing && <p class="has-text-info">Testing</p>}
|
||||
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
marginTop: "2em",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<button class="button" onClick={props.onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<span data-tooltip={props.errors}>
|
||||
<button
|
||||
class="button is-info"
|
||||
disabled={props.error !== "" || props.testing}
|
||||
onClick={props.addProvider}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{props.authProvidersByStatus["ok"].length > 0 ? (
|
||||
<p class="subtitle">
|
||||
Current providers for {props.providerLabel} service
|
||||
</p>
|
||||
) : (
|
||||
<p class="subtitle">
|
||||
No known providers for {props.providerLabel} service
|
||||
</p>
|
||||
)}
|
||||
|
||||
{props.authProvidersByStatus["ok"].map((k, i) => {
|
||||
const p = k as AuthenticationProviderStatusOk;
|
||||
return (
|
||||
<TableRow
|
||||
key={i}
|
||||
url={k.url}
|
||||
info={p}
|
||||
onDelete={props.deleteProvider}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<p class="subtitle">Providers with errors</p>
|
||||
{props.authProvidersByStatus["error"].map((k, i) => {
|
||||
const p = k as AuthenticationProviderStatusError;
|
||||
return (
|
||||
<TableRowError
|
||||
key={i}
|
||||
url={k.url}
|
||||
info={p}
|
||||
onDelete={props.deleteProvider}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AnastasisClientFrame>
|
||||
);
|
||||
}
|
||||
|
||||
export function WithoutProviderType(props: WithoutType): VNode {
|
||||
return (
|
||||
<AnastasisClientFrame
|
||||
hideNav
|
||||
title="Backup: Manage providers2"
|
||||
hideNext={props.errors}
|
||||
>
|
||||
<div>
|
||||
<p>Add a provider url</p>
|
||||
<div class="container">
|
||||
<TextInput
|
||||
label="Provider URL"
|
||||
placeholder="https://provider.com"
|
||||
grabFocus
|
||||
error={props.errors}
|
||||
bind={[props.providerURL, props.setProviderURL]}
|
||||
/>
|
||||
</div>
|
||||
<p class="block">Example: https://kudos.demo.anastasis.lu</p>
|
||||
{props.testing && <p class="has-text-info">Testing</p>}
|
||||
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
marginTop: "2em",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<button class="button" onClick={props.onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<span data-tooltip={props.errors}>
|
||||
<button
|
||||
class="button is-info"
|
||||
disabled={props.error !== "" || props.testing}
|
||||
onClick={props.addProvider}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{props.authProvidersByStatus["ok"].length > 0 ? (
|
||||
<p class="subtitle">Current providers</p>
|
||||
) : (
|
||||
<p class="subtitle">No known providers, add one.</p>
|
||||
)}
|
||||
|
||||
{props.authProvidersByStatus["ok"].map((k, i) => {
|
||||
const p = k as AuthenticationProviderStatusOk;
|
||||
return (
|
||||
<TableRow
|
||||
key={i}
|
||||
url={k.url}
|
||||
info={p}
|
||||
onDelete={props.deleteProvider}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<p class="subtitle">Providers with errors</p>
|
||||
{props.authProvidersByStatus["error"].map((k, i) => {
|
||||
const p = k as AuthenticationProviderStatusError;
|
||||
return (
|
||||
<TableRowError
|
||||
key={i}
|
||||
url={k.url}
|
||||
info={p}
|
||||
onDelete={props.deleteProvider}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AnastasisClientFrame>
|
||||
);
|
||||
}
|
||||
|
||||
function TableRow({
|
||||
url,
|
||||
info,
|
||||
onDelete,
|
||||
}: {
|
||||
onDelete: (s: string) => Promise<void>;
|
||||
url: string;
|
||||
info: AuthenticationProviderStatusOk;
|
||||
}): VNode {
|
||||
const [status, setStatus] = useState("checking");
|
||||
useEffect(function () {
|
||||
testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url)
|
||||
.then(function () {
|
||||
setStatus("responding");
|
||||
})
|
||||
.catch(function () {
|
||||
setStatus("failed to contact");
|
||||
});
|
||||
});
|
||||
return (
|
||||
<div
|
||||
class="box"
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<div>
|
||||
<div class="subtitle">{url}</div>
|
||||
<dl>
|
||||
<dt>
|
||||
<b>Business Name</b>
|
||||
</dt>
|
||||
<dd>{info.business_name}</dd>
|
||||
<dt>
|
||||
<b>Supported methods</b>
|
||||
</dt>
|
||||
<dd>{info.methods.map((m) => m.type).join(",")}</dd>
|
||||
<dt>
|
||||
<b>Maximum storage</b>
|
||||
</dt>
|
||||
<dd>{info.storage_limit_in_megabytes} Mb</dd>
|
||||
<dt>
|
||||
<b>Status</b>
|
||||
</dt>
|
||||
<dd>{status}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<button class="button is-danger" onClick={() => onDelete(url)}>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TableRowError({
|
||||
url,
|
||||
info,
|
||||
onDelete,
|
||||
}: {
|
||||
onDelete: (s: string) => void;
|
||||
url: string;
|
||||
info: AuthenticationProviderStatusError;
|
||||
}): VNode {
|
||||
const [status, setStatus] = useState("checking");
|
||||
useEffect(function () {
|
||||
testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url)
|
||||
.then(function () {
|
||||
setStatus("responding");
|
||||
})
|
||||
.catch(function () {
|
||||
setStatus("failed to contact");
|
||||
});
|
||||
});
|
||||
return (
|
||||
<div
|
||||
class="box"
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<div>
|
||||
<div class="subtitle">{url}</div>
|
||||
<dl>
|
||||
<dt>
|
||||
<b>Error</b>
|
||||
</dt>
|
||||
<dd>{info.hint}</dd>
|
||||
<dt>
|
||||
<b>Code</b>
|
||||
</dt>
|
||||
<dd>{info.code}</dd>
|
||||
<dt>
|
||||
<b>Status</b>
|
||||
</dt>
|
||||
<dd>{status}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<button class="button is-danger" onClick={() => onDelete(url)}>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -17,7 +17,7 @@ import { AuthMethod, ReducerStateBackup } from "@gnu-taler/anastasis-core";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAnastasisContext } from "../../context/anastasis.js";
|
||||
import { AddingProviderScreen } from "./AddingProviderScreen.js";
|
||||
import AddingProviderScreen from "./AddingProviderScreen/index.js";
|
||||
import {
|
||||
authMethods,
|
||||
AuthMethodSetupProps,
|
||||
@ -84,7 +84,7 @@ export function AuthenticationEditorScreen(): VNode {
|
||||
if (manageProvider !== undefined) {
|
||||
return (
|
||||
<AddingProviderScreen
|
||||
onCancel={() => setManageProvider(undefined)}
|
||||
onCancel={async () => setManageProvider(undefined)}
|
||||
providerType={
|
||||
isKnownAuthMethods(manageProvider) ? manageProvider : undefined
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import { useEffect, useState } from "preact/hooks";
|
||||
import { AsyncButton } from "../../components/AsyncButton.js";
|
||||
import { PhoneNumberInput } from "../../components/fields/NumberInput.js";
|
||||
import { useAnastasisContext } from "../../context/anastasis.js";
|
||||
import { AddingProviderScreen } from "./AddingProviderScreen.js";
|
||||
import AddingProviderScreen from "./AddingProviderScreen/index.js";
|
||||
import { AnastasisClientFrame } from "./index.js";
|
||||
|
||||
export function SecretSelectionScreen(): VNode {
|
||||
@ -54,7 +54,9 @@ export function SecretSelectionScreen(): VNode {
|
||||
const recoveryDocument = reducer.currentReducerState.recovery_document;
|
||||
|
||||
if (manageProvider) {
|
||||
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
|
||||
return (
|
||||
<AddingProviderScreen onCancel={async () => setManageProvider(false)} />
|
||||
);
|
||||
}
|
||||
|
||||
if (reducer.discoveryState.state === "none") {
|
||||
@ -220,7 +222,9 @@ export function OldSecretSelectionScreen(): VNode {
|
||||
}
|
||||
|
||||
if (manageProvider) {
|
||||
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
|
||||
return (
|
||||
<AddingProviderScreen onCancel={async () => setManageProvider(false)} />
|
||||
);
|
||||
}
|
||||
|
||||
const providerInfo = provs[
|
||||
|
@ -19,7 +19,7 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import * as a23 from "./AddingProviderScreen.stories.js";
|
||||
import * as a23 from "./AddingProviderScreen/stories.js";
|
||||
import * as a28 from "./AttributeEntryScreen.stories.js";
|
||||
import * as a18 from "./AuthenticationEditorScreen.stories.js";
|
||||
import * as a8 from "./authMethod/AuthMethodEmailSetup.stories.js";
|
||||
|
@ -168,9 +168,9 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
|
||||
window.removeEventListener("popstate", browserOnBackButton);
|
||||
};
|
||||
}, []);
|
||||
if (!reducer) {
|
||||
return <p>Fatal: Reducer must be in context.</p>;
|
||||
}
|
||||
// if (!reducer) {
|
||||
// return <p>Fatal: Reducer must be in context.</p>;
|
||||
// }
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -21,13 +21,26 @@ import {
|
||||
ReducerState,
|
||||
ReducerStateRecovery,
|
||||
} from "@gnu-taler/anastasis-core";
|
||||
import { FunctionalComponent, h, VNode } from "preact";
|
||||
import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
|
||||
import { AnastasisProvider } from "../context/anastasis.js";
|
||||
|
||||
const noop = async (): Promise<void> => {
|
||||
return;
|
||||
};
|
||||
|
||||
export function createExampleWithoutAnastasis<Props>(
|
||||
Component: FunctionalComponent<Props>,
|
||||
props: Partial<Props> | (() => Partial<Props>),
|
||||
): ComponentChildren {
|
||||
//FIXME: props are evaluated on build time
|
||||
// in some cases we want to evaluated the props on render time so we can get some relative timestamp
|
||||
// check how we can build evaluatedProps in render time
|
||||
const evaluatedProps = typeof props === "function" ? props() : props;
|
||||
const Render = (args: any): VNode => h(Component, args);
|
||||
Render.args = evaluatedProps;
|
||||
return Render;
|
||||
}
|
||||
|
||||
export function createExample<Props>(
|
||||
Component: FunctionalComponent<Props>,
|
||||
currentReducerState?: ReducerState,
|
||||
@ -293,3 +306,24 @@ export const reducerStatesExample = {
|
||||
backup_state: BackupStates.TruthsPaying,
|
||||
} as ReducerState,
|
||||
};
|
||||
|
||||
export type StateFunc<S> = (p: S) => VNode;
|
||||
|
||||
export type StateViewMap<StateType extends { status: string }> = {
|
||||
[S in StateType as S["status"]]: StateFunc<S>;
|
||||
};
|
||||
|
||||
export function compose<SType extends { status: string }, PType>(
|
||||
name: string,
|
||||
hook: (p: PType) => SType,
|
||||
vs: StateViewMap<SType>,
|
||||
): (p: PType) => VNode {
|
||||
const Component = (p: PType): VNode => {
|
||||
const state = hook(p);
|
||||
const s = state.status as unknown as SType["status"];
|
||||
const c = vs[s] as unknown as StateFunc<SType>;
|
||||
return c(state);
|
||||
};
|
||||
Component.name = `${name}`;
|
||||
return Component;
|
||||
}
|
||||
|
@ -1,4 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import linaria from '@linaria/esbuild'
|
||||
import esbuild from 'esbuild'
|
||||
@ -61,7 +76,7 @@ export const buildConfig = {
|
||||
],
|
||||
format: 'iife',
|
||||
platform: 'browser',
|
||||
sourcemap: true,
|
||||
sourcemap: true,
|
||||
jsxFactory: 'h',
|
||||
jsxFragment: 'Fragment',
|
||||
// define: {
|
||||
|
@ -56,10 +56,12 @@ importers:
|
||||
'@creativebulma/bulma-tooltip': ^1.2.0
|
||||
'@gnu-taler/anastasis-core': workspace:*
|
||||
'@gnu-taler/taler-util': workspace:*
|
||||
'@types/chai': ^4.3.0
|
||||
'@types/mocha': ^9.0.0
|
||||
bulma: ^0.9.3
|
||||
bulma-checkbox: ^1.1.1
|
||||
bulma-radio: ^1.1.1
|
||||
chai: ^4.3.6
|
||||
chokidar: ^3.5.3
|
||||
date-fns: 2.28.0
|
||||
eslint-plugin-header: ^3.1.1
|
||||
@ -76,6 +78,8 @@ importers:
|
||||
dependencies:
|
||||
'@gnu-taler/anastasis-core': link:../anastasis-core
|
||||
'@gnu-taler/taler-util': link:../taler-util
|
||||
'@types/chai': 4.3.0
|
||||
chai: 4.3.6
|
||||
date-fns: 2.28.0
|
||||
jed: 1.1.1
|
||||
preact: 10.6.5
|
||||
@ -3488,7 +3492,6 @@ packages:
|
||||
|
||||
/@types/chai/4.3.0:
|
||||
resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==}
|
||||
dev: true
|
||||
|
||||
/@types/chrome/0.0.176:
|
||||
resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
|
||||
@ -4330,7 +4333,6 @@ packages:
|
||||
|
||||
/assertion-error/1.1.0:
|
||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||
dev: true
|
||||
|
||||
/assign-symbols/1.0.0:
|
||||
resolution: {integrity: sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=}
|
||||
@ -5208,7 +5210,6 @@ packages:
|
||||
loupe: 2.3.4
|
||||
pathval: 1.1.1
|
||||
type-detect: 4.0.8
|
||||
dev: true
|
||||
|
||||
/chalk/0.4.0:
|
||||
resolution: {integrity: sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=}
|
||||
@ -5264,8 +5265,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/check-error/1.0.2:
|
||||
resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=}
|
||||
dev: true
|
||||
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
|
||||
|
||||
/chokidar/2.1.8:
|
||||
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
|
||||
@ -6242,7 +6242,6 @@ packages:
|
||||
engines: {node: '>=0.12'}
|
||||
dependencies:
|
||||
type-detect: 4.0.8
|
||||
dev: true
|
||||
|
||||
/deep-equal/1.1.1:
|
||||
resolution: {integrity: sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==}
|
||||
@ -7773,8 +7772,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/get-func-name/2.0.0:
|
||||
resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=}
|
||||
dev: true
|
||||
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
|
||||
|
||||
/get-intrinsic/1.1.1:
|
||||
resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
|
||||
@ -9312,7 +9310,6 @@ packages:
|
||||
resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
|
||||
dependencies:
|
||||
get-func-name: 2.0.0
|
||||
dev: true
|
||||
|
||||
/lower-case/1.1.4:
|
||||
resolution: {integrity: sha1-miyr0bno4K6ZOkv31YdcOcQujqw=}
|
||||
@ -10453,7 +10450,6 @@ packages:
|
||||
|
||||
/pathval/1.1.1:
|
||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||
dev: true
|
||||
|
||||
/pbkdf2/3.1.2:
|
||||
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
|
||||
@ -13165,7 +13161,6 @@ packages:
|
||||
/type-detect/4.0.8:
|
||||
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/type-fest/0.13.1:
|
||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||
|
Loading…
Reference in New Issue
Block a user