diff options
14 files changed, 150 insertions, 93 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 4b26c38b7..4a937d855 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -59,16 +59,16 @@ export function AttributeEntryScreen(): VNode { <ConfirmModal active onCancel={() => setAskUserIfSure(false)} - description="You can not forget what you had entered" + description="The values in the form must be correct" label="I am sure" cancelLabel="Wait, I want to check" onConfirm={() => reducer.transition("enter_user_attributes", { identity_attributes: attrs, - })} + }).then(() => setAskUserIfSure(false) )} > You personal information is used to define the location where your secret will be safely stored. If you forget what you have entered or - if there is a misspell you will be unable to recover your secret again. + if there is a misspell you will be unable to recover your secret. </ConfirmModal> ) : null} diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx index 0789ee6ad..c3ff7e746 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx @@ -49,13 +49,13 @@ export const WithDetails = createExample(TestedComponent, { ...reducerStatesExample.backupFinished, secret_name: "super_secret", success_details: { - "http://anastasis.net": { + "https://anastasis.demo.taler.net/": { policy_expiration: { t_ms: "never", }, policy_version: 0, }, - "http://taler.net": { + "https://kudos.demo.anastasis.lu/": { policy_expiration: { t_ms: new Date().getTime() + 60 * 60 * 24 * 1000, }, diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx index 825ec5dc0..129f1e9e4 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx @@ -1,3 +1,4 @@ +import { AuthenticationProviderStatusOk } from "anastasis-core"; import { format } from "date-fns"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; @@ -15,33 +16,28 @@ export function BackupFinishedScreen(): VNode { return <div>invalid state</div>; } const details = reducer.currentReducerState.success_details; + const providers = reducer.currentReducerState.authentication_providers ?? {} return ( - <AnastasisClientFrame hideNav title="Backup finished"> - {reducer.currentReducerState.secret_name ? ( - <p> - Your backup of secret{" "} - <b>"{reducer.currentReducerState.secret_name}"</b> was successful. - </p> - ) : ( - <p>Your secret was successfully backed up.</p> - )} + <AnastasisClientFrame hideNav title="Backup success!"> + <p>Your backup is complete.</p> {details && ( <div class="block"> <p>The backup is stored by the following providers:</p> - {Object.keys(details).map((x, i) => { - const sd = details[x]; + {Object.keys(details).map((url, i) => { + const sd = details[url]; + const p = providers[url] as AuthenticationProviderStatusOk return ( <div key={i} class="box"> - {x} + <a href={url} target="_blank" rel="noreferrer">{p.business_name}</a> <p> version {sd.policy_version} {sd.policy_expiration.t_ms !== "never" ? ` expires at: ${format( - sd.policy_expiration.t_ms, - "dd-MM-yyyy", - )}` + new Date(sd.policy_expiration.t_ms), + "dd-MM-yyyy", + )}` : " without expiration date"} </p> </div> @@ -49,17 +45,6 @@ export function BackupFinishedScreen(): VNode { })} </div> )} - <div - style={{ - marginTop: "2em", - display: "flex", - justifyContent: "space-between", - }} - > - <button class="button" onClick={() => reducer.back()}> - Back - </button> - </div> </AnastasisClientFrame> ); } diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index c6de00e98..d0c9b2f5d 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -3,6 +3,7 @@ import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; import { authMethods, KnownAuthMethods } from "./authMethod"; +import { AsyncButton } from "../../components/AsyncButton"; function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { const { feedback } = props; @@ -150,13 +151,15 @@ export function ChallengeOverviewScreen(): VNode { id: string; feedback?: ChallengeFeedback; }): VNode { - function selectChallenge(): void { - if (reducer) reducer.transition("select_challenge", { uuid: id }); + async function selectChallenge(): Promise<void> { + if (reducer) { + return reducer.transition("select_challenge", { uuid: id }); + } } if (!feedback) { return ( <div> - <button + <AsyncButton class="button" disabled={ atLeastThereIsOnePolicySolved && !policy.isPolicySolved @@ -164,7 +167,7 @@ export function ChallengeOverviewScreen(): VNode { onClick={selectChallenge} > Solve - </button> + </AsyncButton> </div> ); } @@ -178,7 +181,7 @@ export function ChallengeOverviewScreen(): VNode { case ChallengeFeedbackStatus.Payment: return ( <div> - <button + <AsyncButton class="button" disabled={ atLeastThereIsOnePolicySolved && !policy.isPolicySolved @@ -186,13 +189,13 @@ export function ChallengeOverviewScreen(): VNode { onClick={selectChallenge} > Pay - </button> + </AsyncButton> </div> ); case ChallengeFeedbackStatus.Redirect: return ( <div> - <button + <AsyncButton class="button" disabled={ atLeastThereIsOnePolicySolved && !policy.isPolicySolved @@ -200,7 +203,7 @@ export function ChallengeOverviewScreen(): VNode { onClick={selectChallenge} > Go to {feedback.redirect_url} - </button> + </AsyncButton> </div> ); case ChallengeFeedbackStatus.Solved: @@ -212,7 +215,7 @@ export function ChallengeOverviewScreen(): VNode { default: return ( <div> - <button + <AsyncButton class="button" disabled={ atLeastThereIsOnePolicySolved && !policy.isPolicySolved @@ -220,7 +223,7 @@ export function ChallengeOverviewScreen(): VNode { onClick={selectChallenge} > Solve - </button> + </AsyncButton> </div> ); } diff --git a/packages/anastasis-webui/src/pages/home/ConfirmModal.tsx b/packages/anastasis-webui/src/pages/home/ConfirmModal.tsx index cab70de63..e3561d892 100644 --- a/packages/anastasis-webui/src/pages/home/ConfirmModal.tsx +++ b/packages/anastasis-webui/src/pages/home/ConfirmModal.tsx @@ -1,10 +1,11 @@ import { ComponentChildren, h, VNode } from "preact"; +import { AsyncButton } from "../../components/AsyncButton"; export interface ConfirmModelProps { active?: boolean; description?: string; onCancel?: () => void; - onConfirm?: () => void; + onConfirm?: () => Promise<void>; label?: string; cancelLabel?: string; children?: ComponentChildren; @@ -33,13 +34,13 @@ export function ConfirmModal({ {cancelLabel} </button> <div class="buttons is-right" style={{ width: "100%" }}> - <button + <AsyncButton class={danger ? "button is-danger " : "button is-info "} disabled={disabled} onClick={onConfirm} > {label} - </button> + </AsyncButton> </div> </footer> </div> diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx index 11ae09d4f..dba2aa446 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx @@ -1,10 +1,18 @@ import { bytesToString, decodeCrock } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; +import { useEffect, useState } from "preact/hooks"; +import { QR } from "../../components/QR"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function RecoveryFinishedScreen(): VNode { const reducer = useAnastasisContext(); + const [copied, setCopied] = useState(false) + useEffect(() => { + setTimeout(() => { + setCopied(false) + },1000) + }, [copied]) if (!reducer) { return <div>no reducer in context</div>; @@ -15,6 +23,7 @@ export function RecoveryFinishedScreen(): VNode { ) { return <div>invalid state</div>; } + const secretName = reducer.currentReducerState.recovery_document?.secret_name; const encodedSecret = reducer.currentReducerState.core_secret; if (!encodedSecret) { return ( @@ -35,19 +44,33 @@ export function RecoveryFinishedScreen(): VNode { ); } const secret = bytesToString(decodeCrock(encodedSecret.value)); + const contentURI = `data:${encodedSecret.mime},${secret}` + // const fileName = encodedSecret['filename'] + // data:plain/text;base64,asdasd return ( - <AnastasisClientFrame title="Recovery Finished" hideNav> - <p>Your secret: {secret}</p> - <div - style={{ - marginTop: "2em", - display: "flex", - justifyContent: "space-between", - }} - > - <button class="button" onClick={() => reducer.back()}> - Back + <AnastasisClientFrame title="Recovery Success" hideNav> + <h2 class="subtitle">Your secret was recovered</h2> + {secretName && <p class="block"> + <b>Secret name:</b> {secretName} + </p>} + <div class="block buttons" disabled={copied}> + <button class="button" onClick={() => { + navigator.clipboard.writeText(secret); + setCopied(true) + }}> + { !copied ? 'Copy' : 'Copied'} </button> + <a class="button is-info" download="secret.txt" href={contentURI}> + <div class="icon is-small "> + <i class="mdi mdi-download" /> + </div> + <span> + Save as + </span> + </a> + </div> + <div class="block"> + <QR text={secret} /> </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index 4000f9bfe..7e517abfe 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -1,3 +1,4 @@ +import { AuthenticationProviderStatus, AuthenticationProviderStatusOk } from "anastasis-core"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../components/AsyncButton"; @@ -38,15 +39,13 @@ export function SecretSelectionScreen(): VNode { }); } - const providerList = Object.keys( - reducer.currentReducerState.authentication_providers ?? {}, - ); + const provs = reducer.currentReducerState.authentication_providers ?? {}; const recoveryDocument = reducer.currentReducerState.recovery_document; if (!recoveryDocument) { return ( <ChooseAnotherProviderScreen - providers={providerList} + providers={provs} selected="" onChange={(newProv) => doSelectVersion(newProv, 0)} /> @@ -56,7 +55,7 @@ export function SecretSelectionScreen(): VNode { if (selectingVersion) { return ( <SelectOtherVersionProviderScreen - providers={providerList} + providers={provs} provider={recoveryDocument.provider_url} version={recoveryDocument.version} onCancel={() => setSelectingVersion(false)} @@ -69,12 +68,15 @@ export function SecretSelectionScreen(): VNode { return <AddingProviderScreen onCancel={() => setManageProvider(false)} />; } + const provierInfo = provs[recoveryDocument.provider_url] as AuthenticationProviderStatusOk return ( <AnastasisClientFrame title="Recovery: Select secret"> <div class="columns"> <div class="column"> <div class="box" style={{ border: "2px solid green" }}> - <h1 class="subtitle">{recoveryDocument.provider_url}</h1> + <h1 class="subtitle"> + {provierInfo.business_name} + </h1> <div class="block"> {currentVersion === 0 ? ( <p>Set to recover the latest version</p> @@ -111,7 +113,7 @@ function ChooseAnotherProviderScreen({ onChange, }: { selected: string; - providers: string[]; + providers: { [url: string]: AuthenticationProviderStatus }; onChange: (prov: string) => void; }): VNode { return ( @@ -132,11 +134,13 @@ function ChooseAnotherProviderScreen({ {" "} Choose a provider{" "} </option> - {providers.map((prov) => ( - <option key={prov} value={prov}> - {prov} + {Object.keys(providers).map((url) => { + const p = providers[url] + if (!("methods" in p)) return null + return <option key={url} value={url}> + {p.business_name} </option> - ))} + })} </select> <div class="icon is-small is-left"> <i class="mdi mdi-earth" /> @@ -158,20 +162,21 @@ function SelectOtherVersionProviderScreen({ onCancel: () => void; provider: string; version: number; - providers: string[]; + providers: { [url: string]: AuthenticationProviderStatus }; onConfirm: (prov: string, v: number) => Promise<void>; }): VNode { const [otherProvider, setOtherProvider] = useState<string>(provider); const [otherVersion, setOtherVersion] = useState( version > 0 ? String(version) : "", ); + const otherProviderInfo = providers[otherProvider] as AuthenticationProviderStatusOk return ( <AnastasisClientFrame hideNav title="Recovery: Select secret"> <div class="columns"> <div class="column"> <div class="box"> - <h1 class="subtitle">Provider {otherProvider}</h1> + <h1 class="subtitle">Provider {otherProviderInfo.business_name}</h1> <div class="block"> {version === 0 ? ( <p>Set to recover the latest version</p> @@ -193,11 +198,13 @@ function SelectOtherVersionProviderScreen({ {" "} Choose a provider{" "} </option> - {providers.map((prov) => ( - <option key={prov} value={prov}> - {prov} + {Object.keys(providers).map((url) => { + const p = providers[url] + if (!("methods" in p)) return null + return <option key={url} value={url}> + {p.business_name} </option> - ))} + })} </select> <div class="icon is-small is-left"> <i class="mdi mdi-earth" /> @@ -242,7 +249,6 @@ function SelectOtherVersionProviderScreen({ </div> </div> </div> - <div class="column">.</div> </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx index ff6c51d1c..1e0b6f18a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx @@ -10,6 +10,7 @@ import { AuthMethodSolveProps } from "./index"; export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { const [answer, setAnswer] = useState(""); + const [expanded, setExpanded] = useState(false) const reducer = useAnastasisContext(); if (!reducer) { @@ -86,14 +87,30 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( - <AnastasisClientFrame hideNav title="Add email authentication"> + <AnastasisClientFrame hideNav title="Email challenge"> <SolveOverviewFeedbackDisplay feedback={feedback} /> <p> - An email has been sent to "<b>{selectedChallenge.instructions}</b>". - Type the code below. - <b>Here we need to add the code "{selectedUuid}"</b> + An email has been sent to "<b>{selectedChallenge.instructions}</b>". The + message has and identification code and recovery code that starts with "<b>A-</b>". + Wait the message to arrive and the enter the recovery code below. </p> - <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + {!expanded ? <p> + The identification code in the email should start with "{selectedUuid.substring(0, 10)}" + <span class="icon has-tooltip-top" data-tooltip="click to expand" onClick={() => setExpanded(e => !e)}> + <i class="mdi mdi-information" /> + </span> + </p> + : <p> + The identification code in the email is "{selectedUuid}" + <span class="icon has-tooltip-top" data-tooltip="click to show less code" onClick={() => setExpanded(e => !e)}> + <i class="mdi mdi-information" /> + </span> + </p>} + <TextInput label="Answer" + grabFocus + bind={[answer, setAnswer]} + placeholder="A-1234567812345678" + /> <div style={{ diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx index 46cf0502c..5cff7bf01 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx @@ -86,7 +86,7 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( - <AnastasisClientFrame hideNav title="Add email authentication"> + <AnastasisClientFrame hideNav title="IBAN Challenge"> <SolveOverviewFeedbackDisplay feedback={feedback} /> <p>Send a wire transfer to the address,</p> <button class="button">Check</button> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx index ee001ebe9..97e768cbe 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx @@ -86,7 +86,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( - <AnastasisClientFrame hideNav title="Add email authentication"> + <AnastasisClientFrame hideNav title="Postal Challenge"> <SolveOverviewFeedbackDisplay feedback={feedback} /> <p>Wait for the answer</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx index 222789507..b4896eac8 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx @@ -86,9 +86,15 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( - <AnastasisClientFrame hideNav title="Add email authentication"> + <AnastasisClientFrame hideNav title="Question challenge"> <SolveOverviewFeedbackDisplay feedback={feedback} /> - <p>Answer the question please</p> + <p> + In this challenge you need to provide the answer for the next question: + </p> + <pre> + {selectedChallenge.instructions} + </pre> + <p>Type the answer below</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx index 8ee4d600a..67049df12 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx @@ -11,6 +11,7 @@ import { AuthMethodSolveProps } from "./index"; export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { const [answer, setAnswer] = useState(""); + const [expanded, setExpanded] = useState(false) const reducer = useAnastasisContext(); if (!reducer) { return ( @@ -86,13 +87,30 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( - <AnastasisClientFrame hideNav title="Add email authentication"> + <AnastasisClientFrame hideNav title="SMS Challenge"> <SolveOverviewFeedbackDisplay feedback={feedback} /> <p> - An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type - the code below + An sms has been sent to "<b>{selectedChallenge.instructions}</b>". The + message has and identification code and recovery code that starts with "<b>A-</b>". + Wait the message to arrive and the enter the recovery code below. </p> - <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + {!expanded ? <p> + The identification code in the SMS should start with "{selectedUuid.substring(0, 10)}" + <span class="icon has-tooltip-top" data-tooltip="click to expand" onClick={() => setExpanded(e => !e)}> + <i class="mdi mdi-information" /> + </span> + </p> + : <p> + The identification code in the SMS is "{selectedUuid}" + <span class="icon has-tooltip-top" data-tooltip="click to show less code" onClick={() => setExpanded(e => !e)}> + <i class="mdi mdi-information" /> + </span> + </p>} + <TextInput label="Answer" + grabFocus + bind={[answer, setAnswer]} + placeholder="A-1234567812345678" + /> <div style={{ diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx index 98c2e51df..e9fa7012f 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx @@ -86,7 +86,7 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { feedback?.state === ChallengeFeedbackStatus.TruthUnknown; return ( - <AnastasisClientFrame hideNav title="Add email authentication"> + <AnastasisClientFrame hideNav title="TOTP Challenge"> <SolveOverviewFeedbackDisplay feedback={feedback} /> <p>enter the totp solution</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> @@ -110,3 +110,4 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { </AnastasisClientFrame> ); } +// NKE8 VD857T X033X6RG WEGPYP6D70 Q7YE XN8D2 ZN79SCN 231B4QK0 diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx index 04cc8c921..a8f6c3101 100644 --- a/packages/anastasis-webui/src/utils/index.tsx +++ b/packages/anastasis-webui/src/utils/index.tsx @@ -101,7 +101,7 @@ const base = { "http://localhost:8086/": { http_status: 200, annual_fee: "COL:0", - business_name: "ana", + business_name: "Anastasis Local", currency: "COL", liability_limit: "COL:10", methods: [ @@ -125,7 +125,7 @@ const base = { "https://kudos.demo.anastasis.lu/": { http_status: 200, annual_fee: "COL:0", - business_name: "ana", + business_name: "Anastasis Kudo", currency: "COL", liability_limit: "COL:10", methods: [ @@ -145,7 +145,7 @@ const base = { "https://anastasis.demo.taler.net/": { http_status: 200, annual_fee: "COL:0", - business_name: "ana", + business_name: "Anastasis Demo", currency: "COL", liability_limit: "COL:10", methods: [ @@ -180,9 +180,6 @@ const base = { hint: "request to provider failed", }, }, - // expiration: { - // d_ms: 1792525051855 // check t_ms - // }, } as Partial<ReducerState>; export const reducerStatesExample = { |