typechecked dynamic path
This commit is contained in:
parent
af7b107f45
commit
5d9390bb34
@ -40,43 +40,94 @@ import settingsIcon from "./svg/settings_black_24dp.svg";
|
||||
* @author sebasjm
|
||||
*/
|
||||
|
||||
export enum Pages {
|
||||
welcome = "/welcome",
|
||||
type PageLocation<DynamicPart extends object> = {
|
||||
pattern: string;
|
||||
(params: DynamicPart): string;
|
||||
};
|
||||
|
||||
balance = "/balance",
|
||||
balance_history = "/balance/history/:currency?",
|
||||
balance_manual_withdraw = "/balance/manual-withdraw/:currency?",
|
||||
balance_deposit = "/balance/deposit/:currency",
|
||||
balance_transaction = "/balance/transaction/:tid",
|
||||
|
||||
dev = "/dev",
|
||||
|
||||
backup = "/backup",
|
||||
backup_provider_detail = "/backup/provider/:pid",
|
||||
backup_provider_add = "/backup/provider/add",
|
||||
|
||||
settings = "/settings",
|
||||
settings_exchange_add = "/settings/exchange/add/:currency?",
|
||||
|
||||
cta = "/cta/:action",
|
||||
cta_pay = "/cta/pay",
|
||||
cta_refund = "/cta/refund",
|
||||
cta_tips = "/cta/tip",
|
||||
cta_withdraw = "/cta/withdraw",
|
||||
cta_deposit = "/cta/deposit",
|
||||
function replaceAll(
|
||||
pattern: string,
|
||||
vars: Record<string, string>,
|
||||
values: Record<string, any>,
|
||||
): string {
|
||||
let result = pattern;
|
||||
for (const v in vars) {
|
||||
result = result.replace(vars[v], values[v]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function pageDefinition<T extends object>(pattern: string): PageLocation<T> {
|
||||
const patternParams = pattern.match(/(:[\w?]*)/g);
|
||||
if (!patternParams)
|
||||
throw Error(
|
||||
`page definition pattern ${pattern} doesn't have any parameter`,
|
||||
);
|
||||
|
||||
const vars = patternParams.reduce((prev, cur) => {
|
||||
const pName = cur.match(/(\w+)/g);
|
||||
|
||||
//skip things like :? in the path pattern
|
||||
if (!pName || !pName[0]) return prev;
|
||||
const name = pName[0];
|
||||
return { ...prev, [name]: cur };
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
const f = (values: T): string => replaceAll(pattern, vars, values);
|
||||
f.pattern = pattern;
|
||||
return f;
|
||||
}
|
||||
|
||||
export const Pages = {
|
||||
welcome: "/welcome",
|
||||
balance: "/balance",
|
||||
balanceHistory: pageDefinition<{ currency?: string }>(
|
||||
"/balance/history/:currency?",
|
||||
),
|
||||
balanceManualWithdraw: pageDefinition<{ currency?: string }>(
|
||||
"/balance/manual-withdraw/:currency?",
|
||||
),
|
||||
balanceDeposit: pageDefinition<{ currency: string }>(
|
||||
"/balance/deposit/:currency",
|
||||
),
|
||||
balanceTransaction: pageDefinition<{ tid: string }>(
|
||||
"/balance/transaction/:tid",
|
||||
),
|
||||
dev: "/dev",
|
||||
|
||||
backup: "/backup",
|
||||
backupProviderDetail: pageDefinition<{ pid: string }>(
|
||||
"/backup/provider/:pid",
|
||||
),
|
||||
backupProviderAdd: "/backup/provider/add",
|
||||
|
||||
settings: "/settings",
|
||||
settingsExchangeAdd: pageDefinition<{ currency?: string }>(
|
||||
"/settings/exchange/add/:currency?",
|
||||
),
|
||||
|
||||
cta: pageDefinition<{ action: string }>("/cta/:action"),
|
||||
ctaPay: "/cta/pay",
|
||||
ctaRefund: "/cta/refund",
|
||||
ctaTips: "/cta/tip",
|
||||
ctaWithdraw: "/cta/withdraw",
|
||||
ctaDeposit: "/cta/deposit",
|
||||
};
|
||||
|
||||
export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<NavigationHeader>
|
||||
<a href="/balance" class={path.startsWith("/balance") ? "active" : ""}>
|
||||
<a
|
||||
href={Pages.balance}
|
||||
class={path.startsWith("/balance") ? "active" : ""}
|
||||
>
|
||||
<i18n.Translate>Balance</i18n.Translate>
|
||||
</a>
|
||||
<a href="/backup" class={path.startsWith("/backup") ? "active" : ""}>
|
||||
<a href={Pages.backup} class={path.startsWith("/backup") ? "active" : ""}>
|
||||
<i18n.Translate>Backup</i18n.Translate>
|
||||
</a>
|
||||
<a href="/settings">
|
||||
<a href={Pages.settings}>
|
||||
<SvgIcon
|
||||
title={i18n.str`Settings`}
|
||||
dangerouslySetInnerHTML={{ __html: settingsIcon }}
|
||||
@ -92,21 +143,27 @@ export function WalletNavBar({ path = "" }: { path?: string }): VNode {
|
||||
return (
|
||||
<NavigationHeaderHolder>
|
||||
<NavigationHeader>
|
||||
<a href="/balance" class={path.startsWith("/balance") ? "active" : ""}>
|
||||
<a
|
||||
href={Pages.balance}
|
||||
class={path.startsWith("/balance") ? "active" : ""}
|
||||
>
|
||||
<i18n.Translate>Balance</i18n.Translate>
|
||||
</a>
|
||||
<a href="/backup" class={path.startsWith("/backup") ? "active" : ""}>
|
||||
<a
|
||||
href={Pages.backup}
|
||||
class={path.startsWith("/backup") ? "active" : ""}
|
||||
>
|
||||
<i18n.Translate>Backup</i18n.Translate>
|
||||
</a>
|
||||
|
||||
<JustInDevMode>
|
||||
<a href="/dev" class={path.startsWith("/dev") ? "active" : ""}>
|
||||
<a href={Pages.dev} class={path.startsWith("/dev") ? "active" : ""}>
|
||||
<i18n.Translate>Dev</i18n.Translate>
|
||||
</a>
|
||||
</JustInDevMode>
|
||||
|
||||
<a
|
||||
href="/settings"
|
||||
href={Pages.settings}
|
||||
class={path.startsWith("/settings") ? "active" : ""}
|
||||
>
|
||||
<i18n.Translate>Settings</i18n.Translate>
|
||||
|
@ -14,7 +14,7 @@ import Banner from "./Banner.js";
|
||||
import { Time } from "./Time.js";
|
||||
|
||||
interface Props extends JSX.HTMLAttributes {
|
||||
goToTransaction: (id: string) => void;
|
||||
goToTransaction: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export function PendingTransactions({ goToTransaction }: Props): VNode {
|
||||
@ -46,7 +46,7 @@ export function PendingTransactionsView({
|
||||
transactions,
|
||||
goToTransaction,
|
||||
}: {
|
||||
goToTransaction: (id: string) => void;
|
||||
goToTransaction: (id: string) => Promise<void>;
|
||||
transactions: Transaction[];
|
||||
}): VNode {
|
||||
return (
|
||||
|
@ -120,7 +120,7 @@ function TransactionLayout(props: TransactionLayoutProps): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<HistoryRow
|
||||
href={Pages.balance_transaction.replace(":tid", props.id)}
|
||||
href={Pages.balanceTransaction({ tid: props.id })}
|
||||
style={{
|
||||
backgroundColor: props.pending ? "lightcyan" : "inherit",
|
||||
alignItems: "center",
|
||||
|
@ -45,8 +45,9 @@ function CheckTalerActionComponent(): VNode {
|
||||
const [talerActionUrl] = useTalerActionURL();
|
||||
|
||||
useEffect(() => {
|
||||
if (talerActionUrl)
|
||||
route(Pages.cta.replace(":action", encodeURIComponent(talerActionUrl)));
|
||||
if (talerActionUrl) {
|
||||
route(Pages.cta({ action: encodeURIComponent(talerActionUrl) }));
|
||||
}
|
||||
}, [talerActionUrl]);
|
||||
|
||||
return <Fragment />;
|
||||
@ -60,8 +61,8 @@ export function Application(): VNode {
|
||||
{({ devMode }: { devMode: boolean }) => (
|
||||
<IoCProviderForRuntime>
|
||||
<PendingTransactions
|
||||
goToTransaction={(txId: string) =>
|
||||
redirectTo(Pages.balance_transaction.replace(":tid", txId))
|
||||
goToTransaction={(tid: string) =>
|
||||
redirectTo(Pages.balanceTransaction({ tid }))
|
||||
}
|
||||
/>
|
||||
<Match>
|
||||
@ -74,24 +75,18 @@ export function Application(): VNode {
|
||||
path={Pages.balance}
|
||||
component={BalancePage}
|
||||
goToWalletManualWithdraw={() =>
|
||||
redirectTo(
|
||||
Pages.balance_manual_withdraw.replace(":currency?", ""),
|
||||
)
|
||||
redirectTo(Pages.balanceManualWithdraw({}))
|
||||
}
|
||||
goToWalletDeposit={(currency: string) =>
|
||||
redirectTo(
|
||||
Pages.balance_deposit.replace(":currency", currency),
|
||||
)
|
||||
redirectTo(Pages.balanceDeposit({ currency }))
|
||||
}
|
||||
goToWalletHistory={(currency: string) =>
|
||||
redirectTo(
|
||||
Pages.balance_history.replace(":currency?", currency),
|
||||
)
|
||||
redirectTo(Pages.balanceHistory({ currency }))
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.cta}
|
||||
path={Pages.cta.pattern}
|
||||
component={function Action({ action }: { action: string }) {
|
||||
const [, setDismissed] = useTalerActionURL();
|
||||
|
||||
@ -110,37 +105,37 @@ export function Application(): VNode {
|
||||
<Route
|
||||
path={Pages.backup}
|
||||
component={BackupPage}
|
||||
onAddProvider={() => redirectTo(Pages.backup_provider_add)}
|
||||
onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.backup_provider_detail}
|
||||
path={Pages.backupProviderDetail.pattern}
|
||||
component={ProviderDetailPage}
|
||||
onBack={() => redirectTo(Pages.backup)}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.balance_transaction}
|
||||
path={Pages.balanceTransaction.pattern}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.balance_manual_withdraw}
|
||||
path={Pages.balanceManualWithdraw.pattern}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.balance_deposit}
|
||||
path={Pages.balanceDeposit.pattern}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.balance_history}
|
||||
path={Pages.balanceHistory.pattern}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.backup_provider_add}
|
||||
path={Pages.backupProviderAdd}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route path={Pages.settings} component={RedirectToWalletPage} />
|
||||
<Route
|
||||
path={Pages.settings_exchange_add}
|
||||
path={Pages.settingsExchangeAdd.pattern}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route path={Pages.dev} component={RedirectToWalletPage} />
|
||||
|
@ -93,10 +93,8 @@ export function Application(): VNode {
|
||||
}}
|
||||
>
|
||||
<PendingTransactions
|
||||
goToTransaction={(txId: string) =>
|
||||
redirectTo(
|
||||
Pages.balance_transaction.replace(":tid", txId),
|
||||
)
|
||||
goToTransaction={(tid: string) =>
|
||||
redirectTo(Pages.balanceTransaction({ tid }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@ -122,50 +120,37 @@ export function Application(): VNode {
|
||||
*/}
|
||||
|
||||
<Route
|
||||
path={Pages.balance_history}
|
||||
path={Pages.balanceHistory.pattern}
|
||||
component={HistoryPage}
|
||||
goToWalletDeposit={(currency: string) =>
|
||||
redirectTo(
|
||||
Pages.balance_deposit.replace(":currency", currency),
|
||||
)
|
||||
redirectTo(Pages.balanceDeposit({ currency }))
|
||||
}
|
||||
goToWalletManualWithdraw={(currency?: string) =>
|
||||
redirectTo(
|
||||
Pages.balance_manual_withdraw.replace(
|
||||
":currency?",
|
||||
currency || "",
|
||||
),
|
||||
)
|
||||
redirectTo(Pages.balanceManualWithdraw({ currency }))
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.balance_transaction}
|
||||
path={Pages.balanceTransaction.pattern}
|
||||
component={TransactionPage}
|
||||
goToWalletHistory={(currency?: string) =>
|
||||
redirectTo(
|
||||
Pages.balance_history.replace(":currency?", currency || ""),
|
||||
)
|
||||
redirectTo(Pages.balanceHistory({ currency }))
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.balance_manual_withdraw}
|
||||
path={Pages.balanceManualWithdraw.pattern}
|
||||
component={ManualWithdrawPage}
|
||||
onCancel={() => redirectTo(Pages.balance)}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Pages.balance_deposit}
|
||||
path={Pages.balanceDeposit.pattern}
|
||||
component={DepositPage}
|
||||
onCancel={(currency: string) => {
|
||||
redirectTo(
|
||||
Pages.balance_history.replace(":currency?", currency),
|
||||
);
|
||||
redirectTo(Pages.balanceHistory({ currency }));
|
||||
}}
|
||||
onSuccess={(currency: string) => {
|
||||
redirectTo(
|
||||
Pages.balance_history.replace(":currency?", currency),
|
||||
);
|
||||
redirectTo(Pages.balanceHistory({ currency }));
|
||||
setGlobalNotification(
|
||||
<i18n.Translate>
|
||||
All done, your transaction is in progress
|
||||
@ -184,15 +169,15 @@ export function Application(): VNode {
|
||||
<Route
|
||||
path={Pages.backup}
|
||||
component={BackupPage}
|
||||
onAddProvider={() => redirectTo(Pages.backup_provider_add)}
|
||||
onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.backup_provider_detail}
|
||||
path={Pages.backupProviderDetail.pattern}
|
||||
component={ProviderDetailPage}
|
||||
onBack={() => redirectTo(Pages.backup)}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.backup_provider_add}
|
||||
path={Pages.backupProviderAdd}
|
||||
component={ProviderAddPage}
|
||||
onBack={() => redirectTo(Pages.backup)}
|
||||
/>
|
||||
@ -201,7 +186,7 @@ export function Application(): VNode {
|
||||
* SETTINGS
|
||||
*/}
|
||||
<Route
|
||||
path={Pages.settings_exchange_add}
|
||||
path={Pages.settingsExchangeAdd.pattern}
|
||||
component={ExchangeAddPage}
|
||||
onBack={() => redirectTo(Pages.balance)}
|
||||
/>
|
||||
@ -216,22 +201,17 @@ export function Application(): VNode {
|
||||
* CALL TO ACTION
|
||||
*/}
|
||||
<Route
|
||||
path={Pages.cta_pay}
|
||||
path={Pages.ctaPay}
|
||||
component={PayPage}
|
||||
goToWalletManualWithdraw={(currency?: string) =>
|
||||
redirectTo(
|
||||
Pages.balance_manual_withdraw.replace(
|
||||
":currency?",
|
||||
currency || "",
|
||||
),
|
||||
)
|
||||
redirectTo(Pages.balanceManualWithdraw({ currency }))
|
||||
}
|
||||
goBack={() => redirectTo(Pages.balance)}
|
||||
/>
|
||||
<Route path={Pages.cta_refund} component={RefundPage} />
|
||||
<Route path={Pages.cta_tips} component={TipPage} />
|
||||
<Route path={Pages.cta_withdraw} component={WithdrawPage} />
|
||||
<Route path={Pages.cta_deposit} component={DepositPageCTA} />
|
||||
<Route path={Pages.ctaRefund} component={RefundPage} />
|
||||
<Route path={Pages.ctaTips} component={TipPage} />
|
||||
<Route path={Pages.ctaWithdraw} component={WithdrawPage} />
|
||||
<Route path={Pages.ctaDeposit} component={DepositPageCTA} />
|
||||
|
||||
{/**
|
||||
* NOT FOUND
|
||||
@ -240,13 +220,13 @@ export function Application(): VNode {
|
||||
<Route
|
||||
path={Pages.balance}
|
||||
component={Redirect}
|
||||
to={Pages.balance_history.replace(":currency?", "")}
|
||||
to={Pages.balanceHistory({})}
|
||||
/>
|
||||
|
||||
<Route
|
||||
default
|
||||
component={Redirect}
|
||||
to={Pages.balance_history.replace(":currency?", "")}
|
||||
to={Pages.balanceHistory({})}
|
||||
/>
|
||||
</Router>
|
||||
</WalletBox>
|
||||
@ -268,5 +248,7 @@ function Redirect({ to }: { to: string }): null {
|
||||
}
|
||||
|
||||
function shouldShowPendingOperations(path: string): boolean {
|
||||
// FIXME: replace includes with a match API like preact router does
|
||||
// [Pages.balanceHistory, Pages.dev, Pages.settings, Pages.backup]
|
||||
return ["/balance/history/", "/dev", "/settings", "/backup"].includes(path);
|
||||
}
|
||||
|
@ -167,10 +167,9 @@ function BackupLayout(props: TransactionLayoutProps): VNode {
|
||||
<RowBorderGray>
|
||||
<div style={{ color: !props.active ? "grey" : undefined }}>
|
||||
<a
|
||||
href={Pages.backup_provider_detail.replace(
|
||||
":pid",
|
||||
encodeURIComponent(props.id),
|
||||
)}
|
||||
href={Pages.backupProviderDetail({
|
||||
pid: encodeURIComponent(props.id),
|
||||
})}
|
||||
>
|
||||
<span>{props.title}</span>
|
||||
</a>
|
||||
|
@ -165,10 +165,7 @@ export function CreateManualWithdraw({
|
||||
</i18n.Translate>
|
||||
</BoldLight>
|
||||
<LinkPrimary
|
||||
href={Pages.settings_exchange_add.replace(
|
||||
":currency?",
|
||||
initialCurrency,
|
||||
)}
|
||||
href={Pages.settingsExchangeAdd({ currency: initialCurrency })}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
<i18n.Translate>Add Exchange</i18n.Translate>
|
||||
@ -194,7 +191,7 @@ export function CreateManualWithdraw({
|
||||
<i18n.Translate>No exchange configured</i18n.Translate>
|
||||
</BoldLight>
|
||||
<LinkPrimary
|
||||
href={Pages.settings_exchange_add.replace(":currency?", "")}
|
||||
href={Pages.settingsExchangeAdd({})}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
<i18n.Translate>Add Exchange</i18n.Translate>
|
||||
@ -242,7 +239,7 @@ export function CreateManualWithdraw({
|
||||
</Input>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<LinkPrimary
|
||||
href={Pages.settings_exchange_add.replace(":currency?", "")}
|
||||
href={Pages.settingsExchangeAdd({})}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
<i18n.Translate>Add Exchange</i18n.Translate>
|
||||
|
@ -156,9 +156,6 @@ export function View({
|
||||
[exchange_name: string]: CalculatedCoinfInfo[];
|
||||
},
|
||||
);
|
||||
function Item({ children }: any) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
|
@ -178,9 +178,7 @@ export function SettingsView({
|
||||
)}
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div />
|
||||
<LinkPrimary
|
||||
href={Pages.settings_exchange_add.replace(":currency?", "")}
|
||||
>
|
||||
<LinkPrimary href={Pages.settingsExchangeAdd({})}>
|
||||
<i18n.Translate>Add an exchange</i18n.Translate>
|
||||
</LinkPrimary>
|
||||
</div>
|
||||
|
@ -352,10 +352,9 @@ export function TransactionView({
|
||||
<td>
|
||||
{<Amount value={r.amountEffective} />}{" "}
|
||||
<a
|
||||
href={Pages.balance_transaction.replace(
|
||||
":tid",
|
||||
r.transactionId,
|
||||
)}
|
||||
href={Pages.balanceTransaction({
|
||||
tid: r.transactionId,
|
||||
})}
|
||||
>
|
||||
was refunded
|
||||
</a>{" "}
|
||||
@ -556,10 +555,9 @@ export function TransactionView({
|
||||
title={<i18n.Translate>Original order ID</i18n.Translate>}
|
||||
text={
|
||||
<a
|
||||
href={Pages.balance_transaction.replace(
|
||||
":tid",
|
||||
transaction.refundedTransactionId,
|
||||
)}
|
||||
href={Pages.balanceTransaction({
|
||||
tid: transaction.refundedTransactionId,
|
||||
})}
|
||||
>
|
||||
{transaction.info.orderId}
|
||||
</a>
|
||||
|
Loading…
Reference in New Issue
Block a user