From 0388d31d364139d0a3999126b06d8ac850117ab9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 Sep 2023 00:39:00 -0300 Subject: account page --- packages/demobank-ui/src/components/CopyButton.tsx | 60 ++++++++ .../demobank-ui/src/components/ErrorLoading.tsx | 29 ++++ packages/demobank-ui/src/components/Routing.tsx | 109 +++++++++++++ .../src/components/ShowInputErrorLabel.tsx | 29 ++++ packages/demobank-ui/src/components/app.tsx | 2 +- packages/demobank-ui/src/pages/AccountPage.tsx | 170 --------------------- .../demobank-ui/src/pages/AccountPage/index.ts | 91 +++++++++++ .../demobank-ui/src/pages/AccountPage/state.ts | 87 +++++++++++ .../demobank-ui/src/pages/AccountPage/stories.tsx | 29 ++++ packages/demobank-ui/src/pages/AccountPage/test.ts | 32 ++++ .../demobank-ui/src/pages/AccountPage/views.tsx | 74 +++++++++ packages/demobank-ui/src/pages/AdminPage.tsx | 2 +- packages/demobank-ui/src/pages/BusinessAccount.tsx | 2 +- packages/demobank-ui/src/pages/HomePage.tsx | 2 +- packages/demobank-ui/src/pages/LoginForm.tsx | 2 +- .../src/pages/PaytoWireTransferForm.tsx | 2 +- .../demobank-ui/src/pages/RegistrationPage.tsx | 2 +- packages/demobank-ui/src/pages/Routing.tsx | 110 ------------- .../demobank-ui/src/pages/ShowInputErrorLabel.tsx | 29 ---- .../demobank-ui/src/pages/WalletWithdrawForm.tsx | 2 +- .../src/pages/WithdrawalConfirmationQuestion.tsx | 2 +- 21 files changed, 549 insertions(+), 318 deletions(-) create mode 100644 packages/demobank-ui/src/components/CopyButton.tsx create mode 100644 packages/demobank-ui/src/components/ErrorLoading.tsx create mode 100644 packages/demobank-ui/src/components/Routing.tsx create mode 100644 packages/demobank-ui/src/components/ShowInputErrorLabel.tsx delete mode 100644 packages/demobank-ui/src/pages/AccountPage.tsx create mode 100644 packages/demobank-ui/src/pages/AccountPage/index.ts create mode 100644 packages/demobank-ui/src/pages/AccountPage/state.ts create mode 100644 packages/demobank-ui/src/pages/AccountPage/stories.tsx create mode 100644 packages/demobank-ui/src/pages/AccountPage/test.ts create mode 100644 packages/demobank-ui/src/pages/AccountPage/views.tsx delete mode 100644 packages/demobank-ui/src/pages/Routing.tsx delete mode 100644 packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/src/components/CopyButton.tsx b/packages/demobank-ui/src/components/CopyButton.tsx new file mode 100644 index 000000000..c61083074 --- /dev/null +++ b/packages/demobank-ui/src/components/CopyButton.tsx @@ -0,0 +1,60 @@ +import { h, VNode } from "preact"; +import { useEffect, useState } from "preact/hooks"; + + + +export function CopyIcon(): VNode { + return ( + + + + + ) + }; + + export function CopiedIcon(): VNode { + return ( + + + + ) + }; + +export function CopyButton({ getContent }: { getContent: () => string }): VNode { + const [copied, setCopied] = useState(false); + function copyText(): void { + navigator.clipboard.writeText(getContent() || ""); + setCopied(true); + } + useEffect(() => { + if (copied) { + setTimeout(() => { + setCopied(false); + }, 1000); + } + }, [copied]); + + if (!copied) { + return ( + + ); + } + return ( +
+ +
+ ); + } \ No newline at end of file diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx new file mode 100644 index 000000000..fbc4ffceb --- /dev/null +++ b/packages/demobank-ui/src/components/ErrorLoading.tsx @@ -0,0 +1,29 @@ +/* +/* + 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 + */ + +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { h, VNode } from "preact"; + +export function ErrorLoading(): VNode { + const { i18n } = useTranslationContext() + return ( +
+ Could not complete the request + +
+ ); +} diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx new file mode 100644 index 000000000..d5ea44e10 --- /dev/null +++ b/packages/demobank-ui/src/components/Routing.tsx @@ -0,0 +1,109 @@ +/* + 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 + */ + +import { createHashHistory } from "history"; +import { VNode, h } from "preact"; +import { Route, Router, route } from "preact-router"; +import { useEffect } from "preact/hooks"; +import { BankFrame } from "../pages/BankFrame.js"; +import { BusinessAccount } from "../pages/BusinessAccount.js"; +import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js"; +import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js"; +import { RegistrationPage } from "../pages/RegistrationPage.js"; + +export function Routing(): VNode { + const history = createHashHistory(); + + return ( + { + route("/business"); + }} + > + + ( + { + route("/account"); + }} + onLoadNotOk={() => { + route("/account"); + }} + /> + )} + /> + } + /> + ( + { + route("/account"); + }} + /> + )} + /> + ( + { + route(`/operation/${wopid}`); + }} + onRegister={() => { + route("/register"); + }} + /> + )} + /> + ( + { + route("/account"); + }} + onRegister={() => { + route("/register"); + }} + onLoadNotOk={() => { + route("/account"); + }} + /> + )} + /> + + + + ); +} + +function Redirect({ to }: { to: string }): VNode { + useEffect(() => { + route(to, true); + }, []); + return
being redirected to {to}
; +} + +export function assertUnreachable(x: never): never { + throw new Error("Didn't expect to get here"); +} diff --git a/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx b/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx new file mode 100644 index 000000000..dacffe20a --- /dev/null +++ b/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx @@ -0,0 +1,29 @@ +/* + 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 + */ + +import { Fragment, h, VNode } from "preact"; + +export function ShowInputErrorLabel({ + isDirty, + message, +}: { + message: string | undefined; + isDirty: boolean; +}): VNode { + if (message && isDirty) + return
{message}
; + return ; +} diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index ea86da518..1f7034bc5 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -23,7 +23,7 @@ import { FunctionalComponent, h } from "preact"; import { SWRConfig } from "swr"; import { BackendStateProvider } from "../context/backend.js"; import { strings } from "../i18n/strings.js"; -import { Routing } from "../pages/Routing.js"; +import { Routing } from "./Routing.js"; const WITH_LOCAL_STORAGE_CACHE = false; diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx b/packages/demobank-ui/src/pages/AccountPage.tsx deleted file mode 100644 index 820c59984..000000000 --- a/packages/demobank-ui/src/pages/AccountPage.tsx +++ /dev/null @@ -1,170 +0,0 @@ -/* - 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 - */ - -import { Amounts, HttpStatusCode, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; -import { - ErrorType, - HttpResponsePaginated, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; -import { Fragment, VNode, h } from "preact"; -import { Transactions } from "../components/Transactions/index.js"; -import { useBackendContext } from "../context/backend.js"; -import { useAccountDetails } from "../hooks/access.js"; -import { LoginForm } from "./LoginForm.js"; -import { PaymentOptions } from "./PaymentOptions.js"; -import { notifyError } from "../hooks/notification.js"; -import { useEffect, useState } from "preact/hooks"; - -interface Props { - account: string; - onLoadNotOk: ( - error: HttpResponsePaginated, - ) => VNode; -} - -export const CopyIcon = (): VNode => ( - - - - -); - -export const CopiedIcon = (): VNode => ( - - - -); - -function CopyButton({ getContent }: { getContent: () => string }): VNode { - const [copied, setCopied] = useState(false); - function copyText(): void { - navigator.clipboard.writeText(getContent() || ""); - setCopied(true); - } - useEffect(() => { - if (copied) { - setTimeout(() => { - setCopied(false); - }, 1000); - } - }, [copied]); - - if (!copied) { - return ( - - ); - } - return ( -
- -
- ); -} - - -/** - * Query account information and show QR code if there is pending withdrawal - */ -export function AccountPage({ account, onLoadNotOk }: Props): VNode { - const result = useAccountDetails(account); - const backend = useBackendContext(); - const { i18n } = useTranslationContext(); - - if (!result.ok) { - if (result.loading || result.type === ErrorType.TIMEOUT) { - return onLoadNotOk(result); - } - //logout if there is any error, not if loading - backend.logOut(); - if (result.status === HttpStatusCode.NotFound) { - notifyError({ - title: i18n.str`Username or account label "${account}" not found`, - }); - return ; - } - return onLoadNotOk(result); - } - - const { data } = result; - const balance = Amounts.parseOrThrow(data.balance.amount); - const debitThreshold = Amounts.parseOrThrow(data.debitThreshold); - const payto = parsePaytoUri(data.paytoUri); - if (!payto || !payto.isKnown || payto.targetType !== "iban") { - return ( -
Payto from server is not valid "{data.paytoUri}"
- ); - } - const balanceIsDebit = data.balance.credit_debit_indicator == "debit"; - const limit = balanceIsDebit - ? Amounts.sub(debitThreshold, balance).amount - : Amounts.add(balance, debitThreshold).amount; - return ( - -
-

- - Welcome, {account} ({payto.iban})! stringifyPaytoUri(payto)} /> - -

-
- -
-
-

{i18n.str`Bank account balance`}

- {!balance ? ( -
- Waiting server response... -
- ) : ( -
- {balanceIsDebit ? - : null} - {`${Amounts.stringifyValue(balance)}`} -   - {`${balance.currency}`} -
- )} -
-
-
-
-

{i18n.str`Payments`}

- -
-
- -
-
-

{i18n.str`Latest transactions`}

- -
-
-
- ); -} diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts new file mode 100644 index 000000000..28fb7cb0c --- /dev/null +++ b/packages/demobank-ui/src/pages/AccountPage/index.ts @@ -0,0 +1,91 @@ +/* + 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 + */ + +import { HttpError, HttpResponseOk, HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser"; +import { AbsoluteTime, AmountJson, PaytoUriIBAN } from "@gnu-taler/taler-util"; +import { Loading } from "../../components/Loading.js"; +import { useComponentState } from "./state.js"; +import { ReadyView, InvalidIbanView} from "./views.js"; +import { VNode } from "preact"; +import { LoginForm } from "../LoginForm.js"; +import { ErrorLoading } from "../../components/ErrorLoading.js"; + +export interface Props { + account: string; + onLoadNotOk: ( + error: HttpResponsePaginated, + ) => VNode; +} + +export type State = State.Loading | State.LoadingError | State.Ready | State.InvalidIban | State.UserNotFound; + +export namespace State { + export interface Loading { + status: "loading"; + error: undefined; + } + + export interface LoadingError { + status: "loading-error"; + error: HttpError; + } + + export interface BaseInfo { + error: undefined; + } + + export interface Ready extends BaseInfo { + status: "ready"; + error: undefined; + account: string, + payto: PaytoUriIBAN, + balance: AmountJson, + balanceIsDebit: boolean, + limit: AmountJson, + } + + export interface InvalidIban { + status: "invalid-iban", + error: HttpResponseOk; + } + + export interface UserNotFound { + status: "error-user-not-found", + error: HttpError; + onRegister?: () => void; + } +} + +export interface Transaction { + negative: boolean; + counterpart: string; + when: AbsoluteTime; + amount: AmountJson | undefined; + subject: string; +} + +const viewMapping: utils.StateViewMap = { + loading: Loading, + "error-user-not-found": LoginForm, + "invalid-iban": InvalidIbanView, + "loading-error": ErrorLoading, + ready: ReadyView, +}; + +export const AccountPage = utils.compose( + (p: Props) => useComponentState(p), + viewMapping, +); diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts new file mode 100644 index 000000000..bc59c9374 --- /dev/null +++ b/packages/demobank-ui/src/pages/AccountPage/state.ts @@ -0,0 +1,87 @@ +/* + 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 + */ + +import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util"; +import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useBackendContext } from "../../context/backend.js"; +import { useAccountDetails } from "../../hooks/access.js"; +import { notifyError } from "../../hooks/notification.js"; +import { Props, State } from "./index.js"; + +export function useComponentState({ account, onLoadNotOk }: Props): State { + const result = useAccountDetails(account); + const backend = useBackendContext(); + const { i18n } = useTranslationContext(); + + if (result.loading) { + return { + status: "loading", + error: undefined, + }; + } + + if (!result.ok) { + if (result.loading || result.type === ErrorType.TIMEOUT) { + return { + status: "loading-error", + error: result, + }; + } + //logout if there is any error, not if loading + backend.logOut(); + if (result.status === HttpStatusCode.NotFound) { + notifyError({ + title: i18n.str`Username or account label "${account}" not found`, + }); + return { + status: "error-user-not-found", + error: result, + }; + } + return { + status: "loading-error", + error: result, + }; + } + + const { data } = result; + const balance = Amounts.parseOrThrow(data.balance.amount); + const debitThreshold = Amounts.parseOrThrow(data.debitThreshold); + const payto = parsePaytoUri(data.paytoUri); + + if (!payto || !payto.isKnown || payto.targetType !== "iban") { + return { + status: "invalid-iban", + error: result + }; + } + + const balanceIsDebit = data.balance.credit_debit_indicator == "debit"; + const limit = balanceIsDebit + ? Amounts.sub(debitThreshold, balance).amount + : Amounts.add(balance, debitThreshold).amount; + + + return { + status: "ready", + error: undefined, + account, + balance, + balanceIsDebit, + limit, + payto + }; +} diff --git a/packages/demobank-ui/src/pages/AccountPage/stories.tsx b/packages/demobank-ui/src/pages/AccountPage/stories.tsx new file mode 100644 index 000000000..f3828a5d6 --- /dev/null +++ b/packages/demobank-ui/src/pages/AccountPage/stories.tsx @@ -0,0 +1,29 @@ +/* + 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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import * as tests from "@gnu-taler/web-util/testing"; +import { ReadyView } from "./views.js"; + +export default { + title: "account page", +}; + +export const Ready = tests.createExample(ReadyView, {}); diff --git a/packages/demobank-ui/src/pages/AccountPage/test.ts b/packages/demobank-ui/src/pages/AccountPage/test.ts new file mode 100644 index 000000000..588b84c35 --- /dev/null +++ b/packages/demobank-ui/src/pages/AccountPage/test.ts @@ -0,0 +1,32 @@ +/* + 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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import * as tests from "@gnu-taler/web-util/testing"; +import { SwrMockEnvironment } from "@gnu-taler/web-util/testing"; +import { expect } from "chai"; +import { CASHOUT_API_EXAMPLE } from "../../endpoints.js"; +import { Props } from "./index.js"; +import { useComponentState } from "./state.js"; + +describe("Account states", () => { + it("should do some tests", async () => { + }); +}); diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx new file mode 100644 index 000000000..b476759b4 --- /dev/null +++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx @@ -0,0 +1,74 @@ +/* + 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 + */ + +import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, h, VNode } from "preact"; +import { Transactions } from "../../components/Transactions/index.js"; +import { PaymentOptions } from "../PaymentOptions.js"; +import { State } from "./index.js"; +import { CopyButton } from "../../components/CopyButton.js"; + +export function InvalidIbanView({error}:State.InvalidIban) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); +} + +export function ReadyView({ account, balance, balanceIsDebit, limit, payto }: State.Ready): VNode<{}> { + const { i18n } = useTranslationContext(); + return +
+

+ + Welcome, {account} ({payto.iban})! stringifyPaytoUri(payto)} /> + +

+
+ +
+
+

{i18n.str`Bank account balance`}

+ {!balance ? ( +
+ Waiting server response... +
+ ) : ( +
+ {balanceIsDebit ? - : null} + {`${Amounts.stringifyValue(balance)}`} +   + {`${balance.currency}`} +
+ )} +
+
+
+
+

{i18n.str`Payments`}

+ +
+
+ +
+
+

{i18n.str`Latest transactions`}

+ +
+
+
; +} + diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx index ce0feebce..73a4f9ca3 100644 --- a/packages/demobank-ui/src/pages/AdminPage.tsx +++ b/packages/demobank-ui/src/pages/AdminPage.tsx @@ -43,7 +43,7 @@ import { ErrorBannerFloat } from "./BankFrame.js"; import { ShowCashoutDetails } from "./BusinessAccount.js"; import { handleNotOkResult } from "./HomePage.js"; import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js"; -import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { ErrorMessage, notifyInfo } from "../hooks/notification.js"; const charset = diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx index d9aa8fa36..2faf83a1c 100644 --- a/packages/demobank-ui/src/pages/BusinessAccount.tsx +++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx @@ -44,7 +44,7 @@ import { import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js"; import { ErrorBannerFloat } from "./BankFrame.js"; import { LoginForm } from "./LoginForm.js"; -import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { handleNotOkResult } from "./HomePage.js"; import { ErrorMessage, notifyInfo } from "../hooks/notification.js"; import { Amount } from "./WalletWithdrawForm.js"; diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx index 93a9bdfae..86e511284 100644 --- a/packages/demobank-ui/src/pages/HomePage.tsx +++ b/packages/demobank-ui/src/pages/HomePage.tsx @@ -32,7 +32,7 @@ import { useBackendContext } from "../context/backend.js"; import { getInitialBackendBaseURL } from "../hooks/backend.js"; import { notifyError, notifyInfo } from "../hooks/notification.js"; import { useSettings } from "../hooks/settings.js"; -import { AccountPage } from "./AccountPage.js"; +import { AccountPage } from "./AccountPage/index.js"; import { AdminPage } from "./AdminPage.js"; import { LoginForm } from "./LoginForm.js"; import { WithdrawalQRCode } from "./WithdrawalQRCode.js"; diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index d2cb1bd8e..f0ae97d60 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -25,7 +25,7 @@ import { bankUiSettings } from "../settings.js"; import { undefinedIfEmpty } from "../utils.js"; import { ErrorBannerFloat } from "./BankFrame.js"; import { USERNAME_REGEX } from "./RegistrationPage.js"; -import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; /** * Collect and submit login data. diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index d8c1644b1..d16dc70f8 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -36,7 +36,7 @@ import { undefinedIfEmpty, validateIBAN, } from "../utils.js"; -import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; const logger = new Logger("PaytoWireTransferForm"); diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index ded48564f..e52a5b11b 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -25,7 +25,7 @@ import { useTestingAPI } from "../hooks/access.js"; import { notifyError } from "../hooks/notification.js"; import { bankUiSettings } from "../settings.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; -import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; const logger = new Logger("RegistrationPage"); diff --git a/packages/demobank-ui/src/pages/Routing.tsx b/packages/demobank-ui/src/pages/Routing.tsx deleted file mode 100644 index f176c73db..000000000 --- a/packages/demobank-ui/src/pages/Routing.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - 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 - */ - -import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { createHashHistory } from "history"; -import { VNode, h } from "preact"; -import { Route, Router, route } from "preact-router"; -import { useEffect, useMemo, useState } from "preact/hooks"; -import { BankFrame } from "./BankFrame.js"; -import { BusinessAccount } from "./BusinessAccount.js"; -import { HomePage, WithdrawalOperationPage } from "./HomePage.js"; -import { PublicHistoriesPage } from "./PublicHistoriesPage.js"; -import { RegistrationPage } from "./RegistrationPage.js"; - -export function Routing(): VNode { - const history = createHashHistory(); - - return ( - { - route("/business"); - }} - > - - ( - { - route("/account"); - }} - onLoadNotOk={() => { - route("/account"); - }} - /> - )} - /> - } - /> - ( - { - route("/account"); - }} - /> - )} - /> - ( - { - route(`/operation/${wopid}`); - }} - onRegister={() => { - route("/register"); - }} - /> - )} - /> - ( - { - route("/account"); - }} - onRegister={() => { - route("/register"); - }} - onLoadNotOk={() => { - route("/account"); - }} - /> - )} - /> - - - - ); -} - -function Redirect({ to }: { to: string }): VNode { - useEffect(() => { - route(to, true); - }, []); - return
being redirected to {to}
; -} - -export function assertUnreachable(x: never): never { - throw new Error("Didn't expect to get here"); -} diff --git a/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx b/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx deleted file mode 100644 index dacffe20a..000000000 --- a/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - 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 - */ - -import { Fragment, h, VNode } from "preact"; - -export function ShowInputErrorLabel({ - isDirty, - message, -}: { - message: string | undefined; - isDirty: boolean; -}): VNode { - if (message && isDirty) - return
{message}
; - return ; -} diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index 4c4a38e57..83be99d6f 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -30,7 +30,7 @@ import { useEffect, useRef, useState } from "preact/hooks"; import { useAccessAPI } from "../hooks/access.js"; import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; -import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { forwardRef } from "preact/compat"; const logger = new Logger("WalletWithdrawForm"); diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index cdb612155..2fa8e51b5 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -28,7 +28,7 @@ import { useMemo, useState } from "preact/hooks"; import { useAccessAnonAPI } from "../hooks/access.js"; import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; -import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; const logger = new Logger("WithdrawalConfirmationQuestion"); -- cgit v1.2.3 From 58debefbe0456ce203877f1cc417c42f4abb0685 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 21 Sep 2023 17:56:29 +0200 Subject: wallet-core,harness: towards corebank API instead of fakebank/nexus API --- packages/demobank-ui/src/hooks/access.ts | 1 + packages/taler-harness/src/harness/harness.ts | 11 +- packages/taler-harness/src/harness/helpers.ts | 4 +- .../taler-harness/src/harness/libeufin-apis.ts | 775 --------------- packages/taler-harness/src/harness/libeufin.ts | 1047 -------------------- packages/taler-harness/src/index.ts | 4 +- .../test-age-restrictions-merchant.ts | 4 +- .../src/integrationtests/test-bank-api.ts | 4 +- .../integrationtests/test-exchange-management.ts | 4 +- .../taler-harness/src/integrationtests/test-kyc.ts | 4 +- .../test-libeufin-api-bankaccount.ts | 108 -- .../test-libeufin-api-bankconnection.ts | 56 -- .../test-libeufin-api-facade-bad-request.ts | 68 -- .../integrationtests/test-libeufin-api-facade.ts | 70 -- .../test-libeufin-api-permissions.ts | 65 -- .../test-libeufin-api-sandbox-camt.ts | 76 -- .../test-libeufin-api-sandbox-transactions.ts | 69 -- .../test-libeufin-api-scheduling.ts | 106 -- .../integrationtests/test-libeufin-api-users.ts | 63 -- .../integrationtests/test-libeufin-bad-gateway.ts | 75 -- .../src/integrationtests/test-libeufin-basic.ts | 317 ------ .../src/integrationtests/test-libeufin-c5x.ts | 147 --- .../test-libeufin-facade-anastasis.ts | 179 ---- .../integrationtests/test-libeufin-keyrotation.ts | 82 -- .../test-libeufin-nexus-balance.ts | 117 --- .../test-libeufin-refund-multiple-users.ts | 104 -- .../src/integrationtests/test-libeufin-refund.ts | 101 -- .../test-libeufin-sandbox-wire-transfer-cli.ts | 85 -- .../src/integrationtests/test-libeufin-tutorial.ts | 130 --- .../src/integrationtests/test-payment-fault.ts | 4 +- .../src/integrationtests/test-tipping.ts | 4 +- .../integrationtests/test-wallet-notifications.ts | 4 +- .../integrationtests/test-withdrawal-abort-bank.ts | 4 +- .../test-withdrawal-bank-integrated.ts | 4 +- .../integrationtests/test-withdrawal-fakebank.ts | 5 +- .../src/integrationtests/test-withdrawal-fees.ts | 4 +- .../src/integrationtests/test-withdrawal-manual.ts | 4 +- .../src/integrationtests/testrunner.ts | 38 - packages/taler-util/src/bank-api-client.ts | 126 ++- packages/taler-util/src/wallet-types.ts | 8 +- packages/taler-wallet-core/src/dbless.ts | 4 +- .../taler-wallet-core/src/operations/testing.ts | 21 +- packages/taler-wallet-core/src/wallet.ts | 18 +- 43 files changed, 166 insertions(+), 3958 deletions(-) delete mode 100644 packages/taler-harness/src/harness/libeufin-apis.ts delete mode 100644 packages/taler-harness/src/harness/libeufin.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-camt.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-bad-gateway.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-basic.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-keyrotation.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-refund-multiple-users.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-refund.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts delete mode 100644 packages/taler-harness/src/integrationtests/test-libeufin-tutorial.ts (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts index b8b6ab899..61f458e51 100644 --- a/packages/demobank-ui/src/hooks/access.ts +++ b/packages/demobank-ui/src/hooks/access.ts @@ -124,6 +124,7 @@ export function useTestingAPI(): TestingAPI { const register = async ( data: SandboxBackend.Access.BankRegistrationRequest, ): Promise> => { + // FIXME: This API is deprecated. The normal account registration API should be used instead. const res = await noAuthRequest(`access-api/testing/register`, { method: "POST", data, diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 0c3c367af..edb0071c8 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -28,7 +28,7 @@ import { AccountAddDetails, AmountJson, Amounts, - BankAccessApiClient, + TalerCorebankApiClient, Configuration, CoreApiResponse, Duration, @@ -650,8 +650,7 @@ export class FakebankService } get bankAccessApiBaseUrl(): string { - let url = new URL("taler-bank-access/", this.baseUrl); - return url.href; + return this.baseUrl; } async createExchangeAccount( @@ -666,7 +665,7 @@ export class FakebankService accountName: accountName, accountPassword: password, accountPaytoUri: getPayto(accountName), - wireGatewayApiBaseUrl: `http://localhost:${this.bankConfig.httpPort}/taler-wire-gateway/${accountName}/`, + wireGatewayApiBaseUrl: `http://localhost:${this.bankConfig.httpPort}/accounts/${accountName}/taler-wire-gateway/`, }; } @@ -691,14 +690,14 @@ export class FakebankService "bank", ); await this.pingUntilAvailable(); - const bankClient = new BankAccessApiClient(this.bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(this.bankAccessApiBaseUrl); for (const acc of this.accounts) { await bankClient.registerAccount(acc.accountName, acc.accountPassword); } } async pingUntilAvailable(): Promise { - const url = `http://localhost:${this.bankConfig.httpPort}/taler-bank-integration/config`; + const url = `http://localhost:${this.bankConfig.httpPort}/config`; await pingProc(this.proc, url, "bank"); } } diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index 9892e600b..0a864cad3 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -25,7 +25,7 @@ */ import { AmountString, - BankAccessApiClient, + TalerCorebankApiClient, ConfirmPayResultType, Duration, Logger, @@ -560,7 +560,7 @@ export async function withdrawViaBankV2( ): Promise { const { walletClient: wallet, bank, exchange, amount } = p; - const bankClient = new BankAccessApiClient(bank.bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl); const user = await bankClient.createRandomBankUser(); const wop = await bankClient.createWithdrawalOperation(user.username, amount); diff --git a/packages/taler-harness/src/harness/libeufin-apis.ts b/packages/taler-harness/src/harness/libeufin-apis.ts deleted file mode 100644 index 0193f9252..000000000 --- a/packages/taler-harness/src/harness/libeufin-apis.ts +++ /dev/null @@ -1,775 +0,0 @@ -/** - * This file defines most of the API calls offered - * by Nexus and Sandbox. They don't have state, - * therefore got moved away from libeufin.ts where - * the services get actually started and managed. - */ - -import { URL } from "@gnu-taler/taler-util"; -import { - createPlatformHttpLib, - makeBasicAuthHeader, -} from "@gnu-taler/taler-util/http"; -import { - LibeufinNexusTransactions, - LibeufinSandboxAdminBankAccountBalance, - NexusBankConnections, - NexusFacadeListResponse, - NexusGetPermissionsResponse, - NexusNewTransactionsInfo, - NexusTask, - NexusTaskCollection, - NexusUserResponse, -} from "./libeufin.js"; - -export interface LibeufinSandboxServiceInterface { - baseUrl: string; -} - -export interface LibeufinNexusServiceInterface { - baseUrl: string; -} - -export interface CreateEbicsSubscriberRequest { - hostID: string; - userID: string; - partnerID: string; - systemID?: string; -} - -export interface BankAccountInfo { - iban: string; - bic: string; - name: string; - label: string; -} - -export interface CreateEbicsBankConnectionRequest { - name: string; // connection name. - ebicsURL: string; - hostID: string; - userID: string; - partnerID: string; - systemID?: string; -} - -export interface UpdateNexusUserRequest { - newPassword: string; -} - -export interface NexusAuth { - auth: { - username: string; - password: string; - }; -} - -export interface PostNexusTaskRequest { - name: string; - cronspec: string; - type: string; // fetch | submit - params: - | { - level: string; // report | statement | all - rangeType: string; // all | since-last | previous-days | latest - } - | {}; -} - -export interface CreateNexusUserRequest { - username: string; - password: string; -} - -export interface PostNexusPermissionRequest { - action: "revoke" | "grant"; - permission: { - subjectType: string; - subjectId: string; - resourceType: string; - resourceId: string; - permissionName: string; - }; -} - -export interface CreateAnastasisFacadeRequest { - name: string; - connectionName: string; - accountName: string; - currency: string; - reserveTransferLevel: "report" | "statement" | "notification"; -} - -export interface CreateTalerWireGatewayFacadeRequest { - name: string; - connectionName: string; - accountName: string; - currency: string; - reserveTransferLevel: "report" | "statement" | "notification"; -} - -export interface SandboxAccountTransactions { - payments: { - accountLabel: string; - creditorIban: string; - creditorBic?: string; - creditorName: string; - debtorIban: string; - debtorBic: string; - debtorName: string; - amount: string; - currency: string; - subject: string; - date: string; - creditDebitIndicator: "debit" | "credit"; - accountServicerReference: string; - }[]; -} - -export interface DeleteBankConnectionRequest { - bankConnectionId: string; -} - -export interface SimulateIncomingTransactionRequest { - debtorIban: string; - debtorBic: string; - debtorName: string; - - /** - * Subject / unstructured remittance info. - */ - subject: string; - - /** - * Decimal amount without currency. - */ - amount: string; -} - -export interface CreateEbicsBankAccountRequest { - subscriber: { - hostID: string; - partnerID: string; - userID: string; - systemID?: string; - }; - // IBAN - iban: string; - // BIC - bic: string; - // human name - name: string; - label: string; -} - -export interface LibeufinSandboxAddIncomingRequest { - creditorIban: string; - creditorBic: string; - creditorName: string; - debtorIban: string; - debtorBic: string; - debtorName: string; - subject: string; - amount: string; - currency: string; - uid: string; - direction: string; -} - -const libeufinHarnessHttpLib = createPlatformHttpLib(); - -/** - * APIs spread across Legacy and Access, it is therefore - * the "base URL" relative to which API every call addresses. - */ -export namespace LibeufinSandboxApi { - // Creates one bank account via the Access API. - // Need the /demobanks/$id/access-api as the base URL - export async function createDemobankAccount( - username: string, - password: string, - libeufinSandboxService: LibeufinSandboxServiceInterface, - iban: string | null = null, - ): Promise { - let url = new URL("testing/register", libeufinSandboxService.baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: { - username: username, - password: password, - iban: iban, - }, - }); - } - // Need /demobanks/$id as the base URL - export async function createDemobankEbicsSubscriber( - req: CreateEbicsSubscriberRequest, - demobankAccountLabel: string, - libeufinSandboxService: LibeufinSandboxServiceInterface, - username: string = "admin", - password: string = "secret", - ): Promise { - // baseUrl should already be pointed to one demobank. - let url = new URL("ebics/subscribers", libeufinSandboxService.baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: { - userID: req.userID, - hostID: req.hostID, - partnerID: req.partnerID, - demobankAccountLabel: demobankAccountLabel, - }, - }); - } - - export async function rotateKeys( - libeufinSandboxService: LibeufinSandboxServiceInterface, - hostID: string, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL(`admin/ebics/hosts/${hostID}/rotate-keys`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: {}, - }); - } - export async function createEbicsHost( - libeufinSandboxService: LibeufinSandboxServiceInterface, - hostID: string, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL("admin/ebics/hosts", baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: { - hostID, - ebicsVersion: "2.5", - }, - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - } - - export async function createBankAccount( - libeufinSandboxService: LibeufinSandboxServiceInterface, - req: BankAccountInfo, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL(`admin/bank-accounts/${req.label}`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: req, - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - } - - /** - * This function is useless. It creates a Ebics subscriber - * but never gives it a bank account. To be removed - */ - export async function createEbicsSubscriber( - libeufinSandboxService: LibeufinSandboxServiceInterface, - req: CreateEbicsSubscriberRequest, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL("admin/ebics/subscribers", baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: req, - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - } - - /** - * Create a new bank account and associate it to - * a existing EBICS subscriber. - */ - export async function createEbicsBankAccount( - libeufinSandboxService: LibeufinSandboxServiceInterface, - req: CreateEbicsBankAccountRequest, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL("admin/ebics/bank-accounts", baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: req, - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - } - - export async function simulateIncomingTransaction( - libeufinSandboxService: LibeufinSandboxServiceInterface, - accountLabel: string, - req: SimulateIncomingTransactionRequest, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL( - `admin/bank-accounts/${accountLabel}/simulate-incoming-transaction`, - baseUrl, - ); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - body: req, - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - } - - export async function getAccountTransactions( - libeufinSandboxService: LibeufinSandboxServiceInterface, - accountLabel: string, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL( - `admin/bank-accounts/${accountLabel}/transactions`, - baseUrl, - ); - const res = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return (await res.json()) as SandboxAccountTransactions; - } - - export async function getCamt053( - libeufinSandboxService: LibeufinSandboxServiceInterface, - accountLabel: string, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL("admin/payments/camt", baseUrl); - return await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: { - bankaccount: accountLabel, - type: 53, - }, - }); - } - - export async function getAccountInfoWithBalance( - libeufinSandboxService: LibeufinSandboxServiceInterface, - accountLabel: string, - ): Promise { - const baseUrl = libeufinSandboxService.baseUrl; - let url = new URL(`admin/bank-accounts/${accountLabel}`, baseUrl); - const res = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return res.json(); - } -} - -export namespace LibeufinNexusApi { - export async function getAllConnections( - nexus: LibeufinNexusServiceInterface, - ): Promise { - let url = new URL("bank-connections", nexus.baseUrl); - const res = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return res.json(); - } - - export async function deleteBankConnection( - libeufinNexusService: LibeufinNexusServiceInterface, - req: DeleteBankConnectionRequest, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL("bank-connections/delete-connection", baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: req, - }); - } - - export async function createEbicsBankConnection( - libeufinNexusService: LibeufinNexusServiceInterface, - req: CreateEbicsBankConnectionRequest, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL("bank-connections", baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: { - source: "new", - type: "ebics", - name: req.name, - data: { - ebicsURL: req.ebicsURL, - hostID: req.hostID, - userID: req.userID, - partnerID: req.partnerID, - systemID: req.systemID, - }, - }, - }); - } - - export async function getBankAccount( - libeufinNexusService: LibeufinNexusServiceInterface, - accountName: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`bank-accounts/${accountName}`, baseUrl); - const resp = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return resp.json(); - } - - export async function submitInitiatedPayment( - libeufinNexusService: LibeufinNexusServiceInterface, - accountName: string, - paymentId: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `bank-accounts/${accountName}/payment-initiations/${paymentId}/submit`, - baseUrl, - ); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: {}, - }); - } - - export async function fetchAccounts( - libeufinNexusService: LibeufinNexusServiceInterface, - connectionName: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `bank-connections/${connectionName}/fetch-accounts`, - baseUrl, - ); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: {}, - }); - } - - export async function importConnectionAccount( - libeufinNexusService: LibeufinNexusServiceInterface, - connectionName: string, - offeredAccountId: string, - nexusBankAccountId: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `bank-connections/${connectionName}/import-account`, - baseUrl, - ); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: { - offeredAccountId, - nexusBankAccountId, - }, - }); - } - - export async function connectBankConnection( - libeufinNexusService: LibeufinNexusServiceInterface, - connectionName: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`bank-connections/${connectionName}/connect`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: {}, - }); - } - - export async function getPaymentInitiations( - libeufinNexusService: LibeufinNexusServiceInterface, - accountName: string, - username: string = "admin", - password: string = "test", - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `/bank-accounts/${accountName}/payment-initiations`, - baseUrl, - ); - let response = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - const respJson = await response.json(); - console.log( - `Payment initiations of: ${accountName}`, - JSON.stringify(respJson, null, 2), - ); - } - - // Uses the Anastasis API to get a list of transactions. - export async function getAnastasisTransactions( - libeufinNexusService: LibeufinNexusServiceInterface, - anastasisBaseUrl: string, - // FIXME: Nail down type! - params: {}, // of the request: {delta: 5, ..} - username: string = "admin", - password: string = "test", - ): Promise { - let url = new URL("history/incoming", anastasisBaseUrl); - for (const [k, v] of Object.entries(params)) { - url.searchParams.set(k, String(v)); - } - let response = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return response.json(); - } - - // FIXME: this function should return some structured - // object that represents a history. - export async function getAccountTransactions( - libeufinNexusService: LibeufinNexusServiceInterface, - accountName: string, - username: string = "admin", - password: string = "test", - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/bank-accounts/${accountName}/transactions`, baseUrl); - let response = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return response.json(); - } - - export async function fetchTransactions( - libeufinNexusService: LibeufinNexusServiceInterface, - accountName: string, - rangeType: string = "all", - level: string = "report", - username: string = "admin", - password: string = "test", - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `/bank-accounts/${accountName}/fetch-transactions`, - baseUrl, - ); - const resp = await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: { - rangeType: rangeType, - level: level, - }, - }); - return resp.json(); - } - - export async function changePassword( - libeufinNexusService: LibeufinNexusServiceInterface, - username: string, - req: UpdateNexusUserRequest, - auth: NexusAuth, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/users/${username}/password`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: req, - }); - } - - export async function getUser( - libeufinNexusService: LibeufinNexusServiceInterface, - auth: NexusAuth, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/user`, baseUrl); - const resp = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return resp.json(); - } - - export async function createUser( - libeufinNexusService: LibeufinNexusServiceInterface, - req: CreateNexusUserRequest, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/users`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: req, - }); - } - - export async function getAllPermissions( - libeufinNexusService: LibeufinNexusServiceInterface, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/permissions`, baseUrl); - const resp = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return resp.json(); - } - - export async function postPermission( - libeufinNexusService: LibeufinNexusServiceInterface, - req: PostNexusPermissionRequest, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/permissions`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: req, - }); - } - - export async function getAllTasks( - libeufinNexusService: LibeufinNexusServiceInterface, - bankAccountName: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl); - const resp = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return resp.json(); - } - - export async function getTask( - libeufinNexusService: LibeufinNexusServiceInterface, - bankAccountName: string, - // When void, the request returns the list of all the - // tasks under this bank account. - taskName: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `/bank-accounts/${bankAccountName}/schedule/${taskName}`, - baseUrl, - ); - if (taskName) url = new URL(taskName, `${url.href}/`); - const resp = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - return resp.json(); - } - - export async function deleteTask( - libeufinNexusService: LibeufinNexusServiceInterface, - bankAccountName: string, - taskName: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `/bank-accounts/${bankAccountName}/schedule/${taskName}`, - baseUrl, - ); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "DELETE", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - } - - export async function postTask( - libeufinNexusService: LibeufinNexusServiceInterface, - bankAccountName: string, - req: PostNexusTaskRequest, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: req, - }); - } - - export async function deleteFacade( - libeufinNexusService: LibeufinNexusServiceInterface, - facadeName: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`facades/${facadeName}`, baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "DELETE", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - } - - export async function getAllFacades( - libeufinNexusService: LibeufinNexusServiceInterface, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL("facades", baseUrl); - const resp = await libeufinHarnessHttpLib.fetch(url.href, { - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - }); - // FIXME: Just return validated, typed response here! - return resp.json(); - } - - export async function createAnastasisFacade( - libeufinNexusService: LibeufinNexusServiceInterface, - req: CreateAnastasisFacadeRequest, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL("facades", baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: { - name: req.name, - type: "anastasis", - config: { - bankAccount: req.accountName, - bankConnection: req.connectionName, - currency: req.currency, - reserveTransferLevel: req.reserveTransferLevel, - }, - }, - }); - } - - export async function createTwgFacade( - libeufinNexusService: LibeufinNexusServiceInterface, - req: CreateTalerWireGatewayFacadeRequest, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL("facades", baseUrl); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: { - name: req.name, - type: "taler-wire-gateway", - config: { - bankAccount: req.accountName, - bankConnection: req.connectionName, - currency: req.currency, - reserveTransferLevel: req.reserveTransferLevel, - }, - }, - }); - } - - export async function submitAllPaymentInitiations( - libeufinNexusService: LibeufinNexusServiceInterface, - accountId: string, - ): Promise { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL( - `/bank-accounts/${accountId}/submit-all-payment-initiations`, - baseUrl, - ); - await libeufinHarnessHttpLib.fetch(url.href, { - method: "POST", - headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, - body: {}, - }); - } -} diff --git a/packages/taler-harness/src/harness/libeufin.ts b/packages/taler-harness/src/harness/libeufin.ts deleted file mode 100644 index caeea85ae..000000000 --- a/packages/taler-harness/src/harness/libeufin.ts +++ /dev/null @@ -1,1047 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * This file defines euFin test logic that needs state - * and that depends on the main harness.ts. The other - * definitions - mainly helper functions to call RESTful - * APIs - moved to libeufin-apis.ts. That enables harness.ts - * to depend on such API calls, in contrast to the previous - * situation where harness.ts had to include this file causing - * a circular dependency. */ - -/** - * Imports. - */ -import { AmountString, Logger } from "@gnu-taler/taler-util"; -import { - DbInfo, - GlobalTestState, - ProcessWrapper, - getRandomIban, - pingProc, - runCommand, - setupDb, - sh, -} from "../harness/harness.js"; -import { - CreateAnastasisFacadeRequest, - CreateEbicsBankAccountRequest, - CreateEbicsBankConnectionRequest, - CreateNexusUserRequest, - CreateTalerWireGatewayFacadeRequest, - LibeufinNexusApi, - LibeufinSandboxApi, - LibeufinSandboxServiceInterface, - PostNexusPermissionRequest, -} from "../harness/libeufin-apis.js"; - -const logger = new Logger("libeufin.ts"); - -export { LibeufinNexusApi, LibeufinSandboxApi }; - -export interface LibeufinServices { - libeufinSandbox: LibeufinSandboxService; - libeufinNexus: LibeufinNexusService; - commonDb: DbInfo; -} - -export interface LibeufinSandboxConfig { - httpPort: number; - databaseJdbcUri: string; -} - -export interface LibeufinNexusConfig { - httpPort: number; - databaseJdbcUri: string; -} - -export interface LibeufinNexusMoneyMovement { - amount: string; - creditDebitIndicator: string; - details: { - debtor: { - name: string; - }; - debtorAccount: { - iban: string; - }; - debtorAgent: { - bic: string; - }; - creditor: { - name: string; - }; - creditorAccount: { - iban: string; - }; - creditorAgent: { - bic: string; - }; - endToEndId: string; - unstructuredRemittanceInformation: string; - }; -} - -export interface LibeufinNexusBatches { - batchTransactions: Array; -} - -export interface LibeufinNexusTransaction { - amount: string; - creditDebitIndicator: string; - status: string; - bankTransactionCode: string; - valueDate: string; - bookingDate: string; - accountServicerRef: string; - batches: Array; -} - -export interface LibeufinNexusTransactions { - transactions: Array; -} - -export interface LibeufinCliDetails { - nexusUrl: string; - sandboxUrl: string; - nexusDatabaseUri: string; - sandboxDatabaseUri: string; - nexusUser: LibeufinNexusUser; -} - -export interface LibeufinEbicsSubscriberDetails { - hostId: string; - partnerId: string; - userId: string; -} - -export interface LibeufinEbicsConnectionDetails { - subscriberDetails: LibeufinEbicsSubscriberDetails; - ebicsUrl: string; - connectionName: string; -} - -export interface LibeufinBankAccountDetails { - currency: string; - iban: string; - bic: string; - personName: string; - accountName: string; -} - -export interface LibeufinNexusUser { - username: string; - password: string; -} - -export interface LibeufinBackupFileDetails { - passphrase: string; - outputFile: string; - connectionName: string; -} - -export interface LibeufinKeyLetterDetails { - outputFile: string; - connectionName: string; -} - -export interface LibeufinBankAccountImportDetails { - offeredBankAccountName: string; - nexusBankAccountName: string; - connectionName: string; -} - -export interface LibeufinPreparedPaymentDetails { - creditorIban: string; - creditorBic: string; - creditorName: string; - subject: string; - amount: string; - currency: string; - nexusBankAccountName: string; -} - -export interface NexusBankConnection { - // connection type. For example "ebics". - type: string; - - // connection name as given by the user at - // the moment of creation. - name: string; -} - -export interface NexusBankConnections { - bankConnections: NexusBankConnection[]; -} - -export interface FacadeShowInfo { - // Name of the facade, same as the "fcid" parameter. - name: string; - - // Type of the facade. - // For example, "taler-wire-gateway". - type: string; - - // Bas URL of the facade. - baseUrl: string; - - // details depending on the facade type. - config: any; -} - -export interface FetchParams { - // Because transactions are delivered by banks in "batches", - // then every batch can have different qualities. This value - // lets the request specify which type of batch ought to be - // returned. Currently, the following two type are supported: - // - // 'report': typically includes only non booked transactions. - // 'statement': typically includes only booked transactions. - level: "report" | "statement" | "all"; - - // This type indicates the time range of the query. - // It allows the following values: - // - // 'latest': retrieves the last transactions from the bank. - // If there are older unread transactions, those will *not* - // be downloaded. - // - // 'all': retrieves all the transactions from the bank, - // until the oldest. - // - // 'previous-days': currently *not* implemented, it will allow - // the request to download transactions from - // today until N days before. - // - // 'since-last': retrieves all the transactions since the last - // time one was downloaded. - // - rangeType: "latest" | "all" | "previous-days" | "since-last"; -} - -export interface NexusTask { - // The resource being impacted by this operation. - // Typically a (Nexus) bank account being fetched - // or whose payments are submitted. In this cases, - // this value is the "bank-account" constant. - resourceType: string; - // Name of the resource. In case of "bank-account", that - // is the name under which the bank account was imported - // from the bank. - resourceId: string; - // Task name, equals 'taskId' - taskName: string; - // Values allowed are "fetch" or "submit". - taskType: string; - // FIXME: describe. - taskCronSpec: string; - // Only meaningful for "fetch" types. - taskParams: FetchParams; - // Timestamp in seconds when the next iteration will run. - nextScheduledExecutionSec: number; - // Timestamp in seconds when the previous iteration ran. - prevScheduledExecutionSec: number; -} - -export interface NexusNewTransactionsInfo { - // How many transactions are new to Nexus. - newTransactions: number; - // How many transactions got downloaded by the request. - // Note that a transaction can be downloaded multiple - // times but only counts as new once. - downloadedTransactions: number; -} - - -export interface NexusUserResponse { - // User name - username: string; - - // Is this a super user? - superuser: boolean; -} - -export interface NexusTaskShortInfo { - cronspec: string; - type: "fetch" | "submit"; - params: FetchParams; -} - -export interface NexusTaskCollection { - // This field can contain *multiple* objects of the type sampled below. - schedule: { - [taskName: string]: NexusTaskShortInfo; - }; -} - -export interface NexusFacadeListResponse { - facades: FacadeShowInfo[]; -} - -export interface LibeufinSandboxAdminBankAccountBalance { - // Balance in the $currency:$amount format. - balance: AmountString; - // IBAN of the bank account identified by $accountLabel - iban: string; - // BIC of the bank account identified by $accountLabel - bic: string; - // Mentions $accountLabel - label: string; -} - -export interface LibeufinPermission { - subjectType: string; - subjectId: string; - resourceType: string; - resourceId: string; - permissionName: string; -} - -export interface NexusGetPermissionsResponse { - permissions: LibeufinPermission[]; -} - -export class LibeufinSandboxService implements LibeufinSandboxServiceInterface { - static async create( - gc: GlobalTestState, - sandboxConfig: LibeufinSandboxConfig, - ): Promise { - return new LibeufinSandboxService(gc, sandboxConfig); - } - - sandboxProc: ProcessWrapper | undefined; - globalTestState: GlobalTestState; - - constructor( - gc: GlobalTestState, - private sandboxConfig: LibeufinSandboxConfig, - ) { - this.globalTestState = gc; - } - - get baseUrl(): string { - return `http://localhost:${this.sandboxConfig.httpPort}/`; - } - - async start(): Promise { - await sh( - this.globalTestState, - "libeufin-sandbox-config", - "libeufin-sandbox config default", - { - ...process.env, - LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxConfig.databaseJdbcUri, - }, - ); - - this.sandboxProc = this.globalTestState.spawnService( - "libeufin-sandbox", - ["serve", "--port", `${this.sandboxConfig.httpPort}`], - "libeufin-sandbox", - { - ...process.env, - LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxConfig.databaseJdbcUri, - LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret", - }, - ); - } - - async c53tick(): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-sandbox-c53tick", - "libeufin-sandbox camt053tick", - { - ...process.env, - LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxConfig.databaseJdbcUri, - }, - ); - return stdout; - } - - async makeTransaction( - debit: string, - credit: string, - amount: string, // $currency:x.y - subject: string, - ): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-sandbox-maketransfer", - `libeufin-sandbox make-transaction --debit-account=${debit} --credit-account=${credit} ${amount} "${subject}"`, - { - ...process.env, - LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxConfig.databaseJdbcUri, - }, - ); - return stdout; - } - - async pingUntilAvailable(): Promise { - const url = this.baseUrl; - await pingProc(this.sandboxProc, url, "libeufin-sandbox"); - } -} - -export class LibeufinNexusService { - static async create( - gc: GlobalTestState, - nexusConfig: LibeufinNexusConfig, - ): Promise { - return new LibeufinNexusService(gc, nexusConfig); - } - - nexusProc: ProcessWrapper | undefined; - globalTestState: GlobalTestState; - - constructor(gc: GlobalTestState, private nexusConfig: LibeufinNexusConfig) { - this.globalTestState = gc; - } - - get baseUrl(): string { - return `http://localhost:${this.nexusConfig.httpPort}/`; - } - - async start(): Promise { - await runCommand( - this.globalTestState, - "libeufin-nexus-superuser", - "libeufin-nexus", - ["superuser", "admin", "--password", "test"], - { - ...process.env, - LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusConfig.databaseJdbcUri, - }, - ); - - this.nexusProc = this.globalTestState.spawnService( - "libeufin-nexus", - ["serve", "--port", `${this.nexusConfig.httpPort}`], - "libeufin-nexus", - { - ...process.env, - LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusConfig.databaseJdbcUri, - }, - ); - } - - async pingUntilAvailable(): Promise { - const url = `${this.baseUrl}config`; - await pingProc(this.nexusProc, url, "libeufin-nexus"); - } - - async createNexusSuperuser(details: LibeufinNexusUser): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-nexus", - `libeufin-nexus superuser ${details.username} --password=${details.password}`, - { - ...process.env, - LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusConfig.databaseJdbcUri, - }, - ); - console.log(stdout); - } -} - -export interface TwgAddIncomingRequest { - amount: string; - reserve_pub: string; - debit_account: string; -} - -/** - * The bundle aims at minimizing the amount of input - * data that is required to initialize a new user + Ebics - * connection. - */ -export class NexusUserBundle { - userReq: CreateNexusUserRequest; - connReq: CreateEbicsBankConnectionRequest; - anastasisReq: CreateAnastasisFacadeRequest; - twgReq: CreateTalerWireGatewayFacadeRequest; - twgTransferPermission: PostNexusPermissionRequest; - twgHistoryPermission: PostNexusPermissionRequest; - twgAddIncomingPermission: PostNexusPermissionRequest; - localAccountName: string; - remoteAccountName: string; - - constructor(salt: string, ebicsURL: string) { - this.userReq = { - username: `username-${salt}`, - password: `password-${salt}`, - }; - - this.connReq = { - name: `connection-${salt}`, - ebicsURL: ebicsURL, - hostID: `ebicshost,${salt}`, - partnerID: `ebicspartner,${salt}`, - userID: `ebicsuser,${salt}`, - }; - - this.twgReq = { - currency: "EUR", - name: `twg-${salt}`, - reserveTransferLevel: "report", - accountName: `local-account-${salt}`, - connectionName: `connection-${salt}`, - }; - this.anastasisReq = { - currency: "EUR", - name: `anastasis-${salt}`, - reserveTransferLevel: "report", - accountName: `local-account-${salt}`, - connectionName: `connection-${salt}`, - }; - this.remoteAccountName = `remote-account-${salt}`; - this.localAccountName = `local-account-${salt}`; - this.twgTransferPermission = { - action: "grant", - permission: { - subjectId: `username-${salt}`, - subjectType: "user", - resourceType: "facade", - resourceId: `twg-${salt}`, - permissionName: "facade.talerWireGateway.transfer", - }, - }; - this.twgHistoryPermission = { - action: "grant", - permission: { - subjectId: `username-${salt}`, - subjectType: "user", - resourceType: "facade", - resourceId: `twg-${salt}`, - permissionName: "facade.talerWireGateway.history", - }, - }; - } -} - -/** - * The bundle aims at minimizing the amount of input - * data that is required to initialize a new Sandbox - * customer, associating their bank account with a Ebics - * subscriber. - */ -export class SandboxUserBundle { - ebicsBankAccount: CreateEbicsBankAccountRequest; - constructor(salt: string) { - this.ebicsBankAccount = { - bic: "BELADEBEXXX", - iban: getRandomIban(), - label: `remote-account-${salt}`, - name: `Taler Exchange: ${salt}`, - subscriber: { - hostID: `ebicshost,${salt}`, - partnerID: `ebicspartner,${salt}`, - userID: `ebicsuser,${salt}`, - }, - }; - } -} - -export class LibeufinCli { - cliDetails: LibeufinCliDetails; - globalTestState: GlobalTestState; - - constructor(gc: GlobalTestState, cd: LibeufinCliDetails) { - this.globalTestState = gc; - this.cliDetails = cd; - } - - env(): any { - return { - ...process.env, - LIBEUFIN_SANDBOX_URL: this.cliDetails.sandboxUrl, - LIBEUFIN_SANDBOX_USERNAME: "admin", - LIBEUFIN_SANDBOX_PASSWORD: "secret", - }; - } - - async checkSandbox(): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-checksandbox", - "libeufin-cli sandbox check", - this.env(), - ); - } - - async registerBankCustomer( - username: string, - password: string, - ): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-registercustomer", - "libeufin-cli sandbox demobank register --name='Test Customer'", - { - ...process.env, - LIBEUFIN_SANDBOX_URL: this.cliDetails.sandboxUrl + "/demobanks/default", - LIBEUFIN_SANDBOX_USERNAME: username, - LIBEUFIN_SANDBOX_PASSWORD: password, - }, - ); - console.log(stdout); - } - - async createEbicsHost(hostId: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-createebicshost", - `libeufin-cli sandbox ebicshost create --host-id=${hostId}`, - this.env(), - ); - console.log(stdout); - } - - async createEbicsSubscriber( - details: LibeufinEbicsSubscriberDetails, - ): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-createebicssubscriber", - "libeufin-cli sandbox ebicssubscriber create" + - ` --host-id=${details.hostId}` + - ` --partner-id=${details.partnerId}` + - ` --user-id=${details.userId}`, - this.env(), - ); - console.log(stdout); - } - - async createEbicsBankAccount( - sd: LibeufinEbicsSubscriberDetails, - bankAccountDetails: LibeufinBankAccountDetails, - ): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-createebicsbankaccount", - "libeufin-cli sandbox ebicsbankaccount create" + - ` --iban=${bankAccountDetails.iban}` + - ` --bic=${bankAccountDetails.bic}` + - ` --person-name='${bankAccountDetails.personName}'` + - ` --account-name=${bankAccountDetails.accountName}` + - ` --ebics-host-id=${sd.hostId}` + - ` --ebics-partner-id=${sd.partnerId}` + - ` --ebics-user-id=${sd.userId}`, - this.env(), - ); - console.log(stdout); - } - - async generateTransactions(accountName: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-generatetransactions", - `libeufin-cli sandbox bankaccount generate-transactions ${accountName}`, - this.env(), - ); - console.log(stdout); - } - - async showSandboxTransactions(accountName: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-showsandboxtransactions", - `libeufin-cli sandbox bankaccount transactions ${accountName}`, - this.env(), - ); - console.log(stdout); - } - - async createEbicsConnection( - connectionDetails: LibeufinEbicsConnectionDetails, - ): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-createebicsconnection", - `libeufin-cli connections new-ebics-connection` + - ` --ebics-url=${connectionDetails.ebicsUrl}` + - ` --host-id=${connectionDetails.subscriberDetails.hostId}` + - ` --partner-id=${connectionDetails.subscriberDetails.partnerId}` + - ` --ebics-user-id=${connectionDetails.subscriberDetails.userId}` + - ` ${connectionDetails.connectionName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async createBackupFile(details: LibeufinBackupFileDetails): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-createbackupfile", - `libeufin-cli connections export-backup` + - ` --passphrase=${details.passphrase}` + - ` --output-file=${details.outputFile}` + - ` ${details.connectionName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async createKeyLetter(details: LibeufinKeyLetterDetails): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-createkeyletter", - `libeufin-cli connections get-key-letter` + - ` ${details.connectionName} ${details.outputFile}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async connect(connectionName: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-connect", - `libeufin-cli connections connect ${connectionName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async downloadBankAccounts(connectionName: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-downloadbankaccounts", - `libeufin-cli connections download-bank-accounts ${connectionName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async listOfferedBankAccounts(connectionName: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-listofferedbankaccounts", - `libeufin-cli connections list-offered-bank-accounts ${connectionName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async importBankAccount( - importDetails: LibeufinBankAccountImportDetails, - ): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-importbankaccount", - "libeufin-cli connections import-bank-account" + - ` --offered-account-id=${importDetails.offeredBankAccountName}` + - ` --nexus-bank-account-id=${importDetails.nexusBankAccountName}` + - ` ${importDetails.connectionName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async fetchTransactions(bankAccountName: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-fetchtransactions", - `libeufin-cli accounts fetch-transactions ${bankAccountName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async transactions(bankAccountName: string): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-transactions", - `libeufin-cli accounts transactions ${bankAccountName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async preparePayment(details: LibeufinPreparedPaymentDetails): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-preparepayment", - `libeufin-cli accounts prepare-payment` + - ` --creditor-iban=${details.creditorIban}` + - ` --creditor-bic=${details.creditorBic}` + - ` --creditor-name='${details.creditorName}'` + - ` --payment-subject='${details.subject}'` + - ` --payment-amount=${details.currency}:${details.amount}` + - ` ${details.nexusBankAccountName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async submitPayment( - details: LibeufinPreparedPaymentDetails, - paymentUuid: string, - ): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-submitpayments", - `libeufin-cli accounts submit-payments` + - ` --payment-uuid=${paymentUuid}` + - ` ${details.nexusBankAccountName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async newAnastasisFacade(req: NewAnastasisFacadeReq): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-new-anastasis-facade", - `libeufin-cli facades new-anastasis-facade` + - ` --currency ${req.currency}` + - ` --facade-name ${req.facadeName}` + - ` ${req.connectionName} ${req.accountName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async newTalerWireGatewayFacade(req: NewTalerWireGatewayReq): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-new-taler-wire-gateway-facade", - `libeufin-cli facades new-taler-wire-gateway-facade` + - ` --currency ${req.currency}` + - ` --facade-name ${req.facadeName}` + - ` ${req.connectionName} ${req.accountName}`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } - - async listFacades(): Promise { - const stdout = await sh( - this.globalTestState, - "libeufin-cli-facades-list", - `libeufin-cli facades list`, - { - ...process.env, - LIBEUFIN_NEXUS_URL: this.cliDetails.nexusUrl, - LIBEUFIN_NEXUS_USERNAME: this.cliDetails.nexusUser.username, - LIBEUFIN_NEXUS_PASSWORD: this.cliDetails.nexusUser.password, - }, - ); - console.log(stdout); - } -} - -interface NewAnastasisFacadeReq { - facadeName: string; - connectionName: string; - accountName: string; - currency: string; -} - -interface NewTalerWireGatewayReq { - facadeName: string; - connectionName: string; - accountName: string; - currency: string; -} - -/** - * Launch Nexus and Sandbox AND creates users / facades / bank accounts / - * .. all that's required to start making bank traffic. - */ -export async function launchLibeufinServices( - t: GlobalTestState, - nexusUserBundle: NexusUserBundle[], - sandboxUserBundle: SandboxUserBundle[] = [], - withFacades: string[] = [], // takes only "twg" and/or "anastasis" -): Promise { - const db = await setupDb(t); - - const libeufinSandbox = await LibeufinSandboxService.create(t, { - httpPort: 5010, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - }); - - await libeufinSandbox.start(); - await libeufinSandbox.pingUntilAvailable(); - - const libeufinNexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - - await libeufinNexus.start(); - await libeufinNexus.pingUntilAvailable(); - console.log("Libeufin services launched!"); - - for (let sb of sandboxUserBundle) { - await LibeufinSandboxApi.createEbicsHost( - libeufinSandbox, - sb.ebicsBankAccount.subscriber.hostID, - ); - await LibeufinSandboxApi.createEbicsSubscriber( - libeufinSandbox, - sb.ebicsBankAccount.subscriber, - ); - await LibeufinSandboxApi.createDemobankAccount( - sb.ebicsBankAccount.label, - "password-unused", - { baseUrl: libeufinSandbox.baseUrl + "/demobanks/default/access-api/" }, - ); - await LibeufinSandboxApi.createDemobankEbicsSubscriber( - sb.ebicsBankAccount.subscriber, - sb.ebicsBankAccount.label, - { baseUrl: libeufinSandbox.baseUrl + "/demobanks/default/" }, - ); - } - console.log("Sandbox user(s) / account(s) / subscriber(s): created"); - - for (let nb of nexusUserBundle) { - await LibeufinNexusApi.createEbicsBankConnection(libeufinNexus, nb.connReq); - await LibeufinNexusApi.connectBankConnection( - libeufinNexus, - nb.connReq.name, - ); - await LibeufinNexusApi.fetchAccounts(libeufinNexus, nb.connReq.name); - await LibeufinNexusApi.importConnectionAccount( - libeufinNexus, - nb.connReq.name, - nb.remoteAccountName, - nb.localAccountName, - ); - await LibeufinNexusApi.createUser(libeufinNexus, nb.userReq); - for (let facade of withFacades) { - switch (facade) { - case "twg": - await LibeufinNexusApi.createTwgFacade(libeufinNexus, nb.twgReq); - await LibeufinNexusApi.postPermission( - libeufinNexus, - nb.twgTransferPermission, - ); - await LibeufinNexusApi.postPermission( - libeufinNexus, - nb.twgHistoryPermission, - ); - break; - case "anastasis": - await LibeufinNexusApi.createAnastasisFacade( - libeufinNexus, - nb.anastasisReq, - ); - } - } - } - console.log( - "Nexus user(s) / connection(s) / facade(s) / permission(s): created", - ); - - return { - commonDb: db, - libeufinNexus: libeufinNexus, - libeufinSandbox: libeufinSandbox, - }; -} - -/** - * Helper function that searches a payment among - * a list, as returned by Nexus. The key is just - * the payment subject. - */ -export function findNexusPayment( - key: string, - payments: LibeufinNexusTransactions, -): LibeufinNexusMoneyMovement | void { - let transactions = payments["transactions"]; - for (let i = 0; i < transactions.length; i++) { - //FIXME: last line won't compile with the current definition of the type - //@ts-ignore - let batches = transactions[i]["camtData"]["batches"]; - for (let y = 0; y < batches.length; y++) { - let movements = batches[y]["batchTransactions"]; - for (let z = 0; z < movements.length; z++) { - let movement = movements[z]; - if (movement["details"]["unstructuredRemittanceInformation"] == key) - return movement; - } - } - } -} diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index f5d4fd2c2..8ace45a89 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -20,7 +20,7 @@ import { addPaytoQueryParams, Amounts, - BankAccessApiClient, + TalerCorebankApiClient, Configuration, decodeCrock, j2s, @@ -236,7 +236,7 @@ deploymentCli console.log(tipReserveResp); - const bankAccessApiClient = new BankAccessApiClient( + const bankAccessApiClient = new TalerCorebankApiClient( args.tipTopup.bankAccessUrl, { auth: { diff --git a/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts b/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts index 7f936a479..90b08724f 100644 --- a/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts +++ b/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts @@ -27,7 +27,7 @@ import { withdrawViaBankV2, } from "../harness/helpers.js"; import { - BankAccessApiClient, + TalerCorebankApiClient, MerchantApiClient, WireGatewayApiClient, } from "@gnu-taler/taler-util"; @@ -179,7 +179,7 @@ export async function runAgeRestrictionsMerchantTest(t: GlobalTestState) { // Pay with coin from tipping { - const bankClient = new BankAccessApiClient(bank.bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl); const mbu = await bankClient.createRandomBankUser(); const tipReserveResp = await merchantClient.createTippingReserve({ exchange_url: exchange.baseUrl, diff --git a/packages/taler-harness/src/integrationtests/test-bank-api.ts b/packages/taler-harness/src/integrationtests/test-bank-api.ts index a13ff63c7..740e89c30 100644 --- a/packages/taler-harness/src/integrationtests/test-bank-api.ts +++ b/packages/taler-harness/src/integrationtests/test-bank-api.ts @@ -18,7 +18,7 @@ * Imports. */ import { - BankAccessApiClient, + TalerCorebankApiClient, CreditDebitIndicator, WireGatewayApiClient, createEddsaKeyPair, @@ -99,7 +99,7 @@ export async function runBankApiTest(t: GlobalTestState) { console.log("setup done!"); - const bankClient = new BankAccessApiClient(bank.bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl); const bankUser = await bankClient.registerAccount("user1", "pw1"); diff --git a/packages/taler-harness/src/integrationtests/test-exchange-management.ts b/packages/taler-harness/src/integrationtests/test-exchange-management.ts index 9338a8988..329012e42 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-management.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-management.ts @@ -18,7 +18,7 @@ * Imports. */ import { - BankAccessApiClient, + TalerCorebankApiClient, ExchangesListResponse, TalerErrorCode, URL, @@ -263,7 +263,7 @@ export async function runExchangeManagementTest( // Create withdrawal operation - const bankClient = new BankAccessApiClient(bank.bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl); const user = await bankClient.createRandomBankUser(); const wop = await bankClient.createWithdrawalOperation( diff --git a/packages/taler-harness/src/integrationtests/test-kyc.ts b/packages/taler-harness/src/integrationtests/test-kyc.ts index 1f7358b66..2b2b57183 100644 --- a/packages/taler-harness/src/integrationtests/test-kyc.ts +++ b/packages/taler-harness/src/integrationtests/test-kyc.ts @@ -18,7 +18,7 @@ * Imports. */ import { - BankAccessApiClient, + TalerCorebankApiClient, Duration, j2s, Logger, @@ -302,7 +302,7 @@ export async function runKycTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - const bankClient = new BankAccessApiClient(bank.bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl); const amount = "TESTKUDOS:20"; const user = await bankClient.createRandomBankUser(); diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts deleted file mode 100644 index e5e3dfe64..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - LibeufinNexusApi, - LibeufinNexusService, - LibeufinSandboxService, - LibeufinSandboxApi, - findNexusPayment, -} from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinApiBankaccountTest(t: GlobalTestState) { - const nexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - await nexus.start(); - await nexus.pingUntilAvailable(); - - await LibeufinNexusApi.createUser(nexus, { - username: "one", - password: "testing-the-bankaccount-api", - }); - const sandbox = await LibeufinSandboxService.create(t, { - httpPort: 5012, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - }); - await sandbox.start(); - await sandbox.pingUntilAvailable(); - await LibeufinSandboxApi.createEbicsHost(sandbox, "mock"); - await LibeufinSandboxApi.createDemobankAccount( - "mock", - "password-unused", - { baseUrl: sandbox.baseUrl + "/demobanks/default/access-api/" }, - "DE71500105179674997361", - ); - await LibeufinSandboxApi.createDemobankEbicsSubscriber( - { - hostID: "mock", - partnerID: "mock", - userID: "mock", - }, - "mock", - { baseUrl: sandbox.baseUrl + "/demobanks/default/" }, - ); - await LibeufinNexusApi.createEbicsBankConnection(nexus, { - name: "bankaccount-api-test-connection", - ebicsURL: "http://localhost:5012/ebicsweb", - hostID: "mock", - userID: "mock", - partnerID: "mock", - }); - await LibeufinNexusApi.connectBankConnection( - nexus, - "bankaccount-api-test-connection", - ); - await LibeufinNexusApi.fetchAccounts( - nexus, - "bankaccount-api-test-connection", - ); - - await LibeufinNexusApi.importConnectionAccount( - nexus, - "bankaccount-api-test-connection", - "mock", - "local-mock", - ); - await LibeufinSandboxApi.simulateIncomingTransaction( - sandbox, - "mock", // creditor bankaccount label - { - debtorIban: "DE84500105176881385584", - debtorBic: "BELADEBEXXX", - debtorName: "mock2", - amount: "EUR:1", - subject: "mock subject", - }, - ); - await LibeufinNexusApi.fetchTransactions(nexus, "local-mock"); - let transactions = await LibeufinNexusApi.getAccountTransactions( - nexus, - "local-mock", - ); - let el = findNexusPayment("mock subject", transactions); - t.assertTrue(el instanceof Object); -} - -runLibeufinApiBankaccountTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts deleted file mode 100644 index 243500dc9..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { LibeufinNexusApi, LibeufinNexusService } from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinApiBankconnectionTest(t: GlobalTestState) { - const nexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - await nexus.start(); - await nexus.pingUntilAvailable(); - - await LibeufinNexusApi.createUser(nexus, { - username: "one", - password: "testing-the-bankconnection-api", - }); - - await LibeufinNexusApi.createEbicsBankConnection(nexus, { - name: "bankconnection-api-test-connection", - ebicsURL: "http://localhost:5012/ebicsweb", - hostID: "mock", - userID: "mock", - partnerID: "mock", - }); - - let connections = await LibeufinNexusApi.getAllConnections(nexus); - t.assertTrue(connections.bankConnections.length == 1); - - await LibeufinNexusApi.deleteBankConnection(nexus, { - bankConnectionId: "bankconnection-api-test-connection", - }); - connections = await LibeufinNexusApi.getAllConnections(nexus); - t.assertTrue(connections.bankConnections.length == 0); -} -runLibeufinApiBankconnectionTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts deleted file mode 100644 index 27cc81588..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { URL } from "@gnu-taler/taler-util"; -import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; -import { - launchLibeufinServices, - NexusUserBundle, - SandboxUserBundle, -} from "../harness/libeufin.js"; -import { - createPlatformHttpLib, - makeBasicAuthHeader, -} from "@gnu-taler/taler-util/http"; - -export async function runLibeufinApiFacadeBadRequestTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus], - [user01sandbox], - ["twg"], - ); - console.log("malformed facade"); - const baseUrl = libeufinServices.libeufinNexus.baseUrl; - let url = new URL("facades", baseUrl); - let resp = await harnessHttpLib.fetch(url.href, { - method: "POST", - body: { - name: "malformed-facade", - type: "taler-wire-gateway", - config: {}, // malformation here. - }, - headers: { - Authorization: makeBasicAuthHeader("admin", "test"), - }, - }); - t.assertTrue(resp.status == 400); -} - -runLibeufinApiFacadeBadRequestTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts deleted file mode 100644 index a819dd481..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - SandboxUserBundle, - NexusUserBundle, - launchLibeufinServices, - LibeufinNexusApi, -} from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinApiFacadeTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus], - [user01sandbox], - ["twg"], - ); - let resp = await LibeufinNexusApi.getAllFacades( - libeufinServices.libeufinNexus, - ); - // check that original facade shows up. - t.assertTrue(resp.facades[0].name == user01nexus.twgReq["name"]); - - const twgBaseUrl: string = resp.facades[0]["baseUrl"]; - t.assertTrue(typeof twgBaseUrl === "string"); - t.assertTrue(twgBaseUrl.startsWith("http://")); - t.assertTrue(twgBaseUrl.endsWith("/")); - - // delete it. - await LibeufinNexusApi.deleteFacade( - libeufinServices.libeufinNexus, - user01nexus.twgReq["name"], - ); - resp = await LibeufinNexusApi.getAllFacades(libeufinServices.libeufinNexus); - t.assertTrue(!resp.hasOwnProperty("facades")); -} - -runLibeufinApiFacadeTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts deleted file mode 100644 index 56443c20a..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - NexusUserBundle, - LibeufinNexusApi, - LibeufinNexusService, -} from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinApiPermissionsTest(t: GlobalTestState) { - const nexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - await nexus.start(); - await nexus.pingUntilAvailable(); - - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - - await LibeufinNexusApi.createUser(nexus, user01nexus.userReq); - await LibeufinNexusApi.postPermission( - nexus, - user01nexus.twgTransferPermission, - ); - let transferPermission = await LibeufinNexusApi.getAllPermissions(nexus); - let element = transferPermission["permissions"].pop(); - t.assertTrue(!!element); - t.assertTrue( - element["permissionName"] == "facade.talerwiregateway.transfer" && - element["subjectId"] == "username-01", - ); - let denyTransfer = user01nexus.twgTransferPermission; - - // Now revoke permission. - denyTransfer["action"] = "revoke"; - await LibeufinNexusApi.postPermission(nexus, denyTransfer); - - transferPermission = await LibeufinNexusApi.getAllPermissions(nexus); - t.assertTrue(transferPermission["permissions"].length == 0); -} - -runLibeufinApiPermissionsTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-camt.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-camt.ts deleted file mode 100644 index 22b411dc2..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-camt.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - LibeufinSandboxApi, - LibeufinSandboxService, -} from "../harness/libeufin.js"; - -// This test only checks that LibEuFin doesn't fail when -// it generates Camt statements - no assertions take place. -// Furthermore, it prints the Camt.053 being generated. -export async function runLibeufinApiSandboxCamtTest(t: GlobalTestState) { - const sandbox = await LibeufinSandboxService.create(t, { - httpPort: 5012, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - }); - await sandbox.start(); - await sandbox.pingUntilAvailable(); - - await LibeufinSandboxApi.createDemobankAccount( - "mock-account-0", - "password-unused", - { baseUrl: sandbox.baseUrl + "/demobanks/default/access-api/" }, - ); - await LibeufinSandboxApi.createDemobankAccount( - "mock-account-1", - "password-unused", - { baseUrl: sandbox.baseUrl + "/demobanks/default/access-api/" }, - ); - await sandbox.makeTransaction( - "mock-account-0", - "mock-account-1", - "EUR:1", - "+1", - ); - await sandbox.makeTransaction( - "mock-account-0", - "mock-account-1", - "EUR:1", - "+1", - ); - await sandbox.makeTransaction( - "mock-account-0", - "mock-account-1", - "EUR:1", - "+1", - ); - await sandbox.makeTransaction( - "mock-account-1", - "mock-account-0", - "EUR:5", - "minus 5", - ); - await sandbox.c53tick(); - let ret = await LibeufinSandboxApi.getCamt053(sandbox, "mock-account-1"); - console.log(ret); -} -runLibeufinApiSandboxCamtTest.experimental = true; -runLibeufinApiSandboxCamtTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts deleted file mode 100644 index 6cfc55aa6..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - LibeufinSandboxApi, - LibeufinSandboxService, -} from "../harness/libeufin.js"; - -export async function runLibeufinApiSandboxTransactionsTest( - t: GlobalTestState, -) { - const sandbox = await LibeufinSandboxService.create(t, { - httpPort: 5012, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - }); - await sandbox.start(); - await sandbox.pingUntilAvailable(); - await LibeufinSandboxApi.createDemobankAccount( - "mock-account", - "password-unused", - { baseUrl: sandbox.baseUrl + "/demobanks/default/access-api/" }, - "DE71500105179674997361", - ); - await LibeufinSandboxApi.simulateIncomingTransaction( - sandbox, - "mock-account", - { - debtorIban: "DE84500105176881385584", - debtorBic: "BELADEBEXXX", - debtorName: "mock2", - subject: "mock subject", - amount: "EUR:1", - }, - ); - await LibeufinSandboxApi.simulateIncomingTransaction( - sandbox, - "mock-account", - { - debtorIban: "DE84500105176881385584", - debtorBic: "BELADEBEXXX", - debtorName: "mock2", - subject: "mock subject 2", - amount: "EUR:1.1", - }, - ); - let ret = await LibeufinSandboxApi.getAccountInfoWithBalance( - sandbox, - "mock-account", - ); - t.assertAmountEquals(ret.balance, "EUR:2.1"); -} -runLibeufinApiSandboxTransactionsTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts deleted file mode 100644 index 15ed2ab78..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - launchLibeufinServices, - LibeufinNexusApi, - LibeufinNexusService, - NexusUserBundle, - SandboxUserBundle, -} from "../harness/libeufin.js"; - -/** - * Test Nexus scheduling API. It creates a task, check whether it shows - * up, then deletes it, and check if it's gone. Ideally, a check over the - * _liveliness_ of a scheduled task should happen. - */ -export async function runLibeufinApiSchedulingTest(t: GlobalTestState) { - const nexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - await nexus.start(); - await nexus.pingUntilAvailable(); - - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - await launchLibeufinServices(t, [user01nexus], [user01sandbox]); - await LibeufinNexusApi.postTask(nexus, user01nexus.localAccountName, { - name: "test-task", - cronspec: "* * *", - type: "fetch", - params: { - level: "all", - rangeType: "all", - }, - }); - let resp = await LibeufinNexusApi.getTask( - nexus, - user01nexus.localAccountName, - "test-task", - ); - t.assertTrue(resp.taskName == "test-task"); - await LibeufinNexusApi.deleteTask( - nexus, - user01nexus.localAccountName, - "test-task", - ); - try { - await LibeufinNexusApi.getTask( - nexus, - user01nexus.localAccountName, - "test-task", - ); - } catch (err: any) { - t.assertTrue(err.response.status == 404); - } - - // Same with submit task. - await LibeufinNexusApi.postTask(nexus, user01nexus.localAccountName, { - name: "test-task", - cronspec: "* * *", - type: "submit", - params: {}, - }); - resp = await LibeufinNexusApi.getTask( - nexus, - user01nexus.localAccountName, - "test-task", - ); - t.assertTrue(resp.taskName == "test-task"); - await LibeufinNexusApi.deleteTask( - nexus, - user01nexus.localAccountName, - "test-task", - ); - try { - await LibeufinNexusApi.getTask( - nexus, - user01nexus.localAccountName, - "test-task", - ); - } catch (err: any) { - t.assertTrue(err.response.status == 404); - } -} -runLibeufinApiSchedulingTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts deleted file mode 100644 index 662b22bbe..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { LibeufinNexusApi, LibeufinNexusService } from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinApiUsersTest(t: GlobalTestState) { - const nexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - await nexus.start(); - await nexus.pingUntilAvailable(); - - await LibeufinNexusApi.createUser(nexus, { - username: "one", - password: "will-be-changed", - }); - - await LibeufinNexusApi.changePassword( - nexus, - "one", - { - newPassword: "got-changed", - }, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); - - let resp = await LibeufinNexusApi.getUser(nexus, { - auth: { - username: "one", - password: "got-changed", - }, - }); - console.log(resp); - t.assertTrue(resp["username"] == "one" && !resp["superuser"]); -} - -runLibeufinApiUsersTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-bad-gateway.ts b/packages/taler-harness/src/integrationtests/test-libeufin-bad-gateway.ts deleted file mode 100644 index 1187d923b..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-bad-gateway.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState, delayMs } from "../harness/harness.js"; -import { - NexusUserBundle, - LibeufinNexusApi, - LibeufinNexusService, - LibeufinSandboxService, -} from "../harness/libeufin.js"; - -/** - * Testing how Nexus reacts when the Sandbox is unreachable. - * Typically, because the user specified a wrong EBICS endpoint. - */ -export async function runLibeufinBadGatewayTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/not-found", // the EBICS endpoint at Sandbox - ); - - // Start Nexus - const libeufinNexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - await libeufinNexus.start(); - await libeufinNexus.pingUntilAvailable(); - - // Start Sandbox - const libeufinSandbox = await LibeufinSandboxService.create(t, { - httpPort: 5010, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - }); - await libeufinSandbox.start(); - await libeufinSandbox.pingUntilAvailable(); - - // Connecting to a non-existent Sandbox endpoint. - await LibeufinNexusApi.createEbicsBankConnection( - libeufinNexus, - user01nexus.connReq, - ); - - // 502 Bad Gateway expected. - try { - await LibeufinNexusApi.connectBankConnection( - libeufinNexus, - user01nexus.connReq.name, - ); - } catch (e: any) { - t.assertTrue(e.response.status == 502); - return; - } - t.assertTrue(false); -} -runLibeufinBadGatewayTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-basic.ts b/packages/taler-harness/src/integrationtests/test-libeufin-basic.ts deleted file mode 100644 index d87278197..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-basic.ts +++ /dev/null @@ -1,317 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { - AbsoluteTime, - Duration, - MerchantContractTerms, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { - DbInfo, - ExchangeService, - GlobalTestState, - HarnessExchangeBankAccount, - MerchantService, - WalletClient, - setupDb, -} from "../harness/harness.js"; -import { - createWalletDaemonWithClient, - makeTestPaymentV2, -} from "../harness/helpers.js"; -import { - LibeufinNexusApi, - LibeufinNexusService, - LibeufinSandboxApi, - LibeufinSandboxService, -} from "../harness/libeufin.js"; - -const exchangeIban = "DE71500105179674997361"; -const customerIban = "DE84500105176881385584"; -const customerBic = "BELADEBEXXX"; -const merchantIban = "DE42500105171245624648"; - -export interface LibeufinTestEnvironment { - commonDb: DbInfo; - exchange: ExchangeService; - exchangeBankAccount: HarnessExchangeBankAccount; - merchant: MerchantService; - walletClient: WalletClient; - libeufinSandbox: LibeufinSandboxService; - libeufinNexus: LibeufinNexusService; -} - -/** - * Create a Taler environment with LibEuFin and an EBICS account. - */ -export async function createLibeufinTestEnvironment( - t: GlobalTestState, - coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("EUR")), -): Promise { - const db = await setupDb(t); - - const libeufinSandbox = await LibeufinSandboxService.create(t, { - httpPort: 5010, - databaseJdbcUri: db.connStr, - }); - - await libeufinSandbox.start(); - await libeufinSandbox.pingUntilAvailable(); - - const libeufinNexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: db.connStr, - }); - - await libeufinNexus.start(); - await libeufinNexus.pingUntilAvailable(); - - await LibeufinSandboxApi.createEbicsHost(libeufinSandbox, "host01"); - // Subscriber and bank Account for the exchange - await LibeufinSandboxApi.createDemobankAccount( - "exchangeacct", - "password-unused", - { baseUrl: libeufinSandbox.baseUrl + "/demobanks/default/access-api/" }, - exchangeIban, - ); - await LibeufinSandboxApi.createDemobankEbicsSubscriber( - { - hostID: "host01", - partnerID: "partner01", - userID: "user01", - }, - "exchangeacct", - { baseUrl: libeufinSandbox.baseUrl + "/demobanks/default/" }, - ); - - await LibeufinSandboxApi.createDemobankAccount( - "merchantacct", - "password-unused", - { baseUrl: libeufinSandbox.baseUrl + "/demobanks/default/access-api/" }, - merchantIban, - ); - await LibeufinSandboxApi.createDemobankEbicsSubscriber( - { - hostID: "host01", - partnerID: "partner02", - userID: "user02", - }, - "merchantacct", - { baseUrl: libeufinSandbox.baseUrl + "/demobanks/default/" }, - ); - - await LibeufinNexusApi.createEbicsBankConnection(libeufinNexus, { - name: "myconn", - ebicsURL: "http://localhost:5010/ebicsweb", - hostID: "host01", - partnerID: "partner01", - userID: "user01", - }); - await LibeufinNexusApi.connectBankConnection(libeufinNexus, "myconn"); - await LibeufinNexusApi.fetchAccounts(libeufinNexus, "myconn"); - await LibeufinNexusApi.importConnectionAccount( - libeufinNexus, - "myconn", - "exchangeacct", - "myacct", - ); - - await LibeufinNexusApi.createTwgFacade(libeufinNexus, { - name: "twg1", - accountName: "myacct", - connectionName: "myconn", - currency: "EUR", - reserveTransferLevel: "report", - }); - - await LibeufinNexusApi.createUser(libeufinNexus, { - username: "twguser", - password: "twgpw", - }); - - await LibeufinNexusApi.postPermission(libeufinNexus, { - action: "grant", - permission: { - subjectType: "user", - subjectId: "twguser", - resourceType: "facade", - resourceId: "twg1", - permissionName: "facade.talerWireGateway.history", - }, - }); - - await LibeufinNexusApi.postPermission(libeufinNexus, { - action: "grant", - permission: { - subjectType: "user", - subjectId: "twguser", - resourceType: "facade", - resourceId: "twg1", - permissionName: "facade.talerWireGateway.transfer", - }, - }); - - const exchange = ExchangeService.create(t, { - name: "testexchange-1", - currency: "EUR", - httpPort: 8081, - database: db.connStr, - }); - - const merchant = await MerchantService.create(t, { - name: "testmerchant-1", - currency: "EUR", - httpPort: 8083, - database: db.connStr, - }); - - const exchangeBankAccount: HarnessExchangeBankAccount = { - accountName: "twguser", - accountPassword: "twgpw", - accountPaytoUri: `payto://iban/${exchangeIban}?receiver-name=Exchange`, - wireGatewayApiBaseUrl: - "http://localhost:5011/facades/twg1/taler-wire-gateway/", - }; - - exchange.addBankAccount("1", exchangeBankAccount); - - exchange.addCoinConfigList(coinConfig); - - await exchange.start(); - await exchange.pingUntilAvailable(); - - merchant.addExchange(exchange); - - await merchant.start(); - await merchant.pingUntilAvailable(); - - await merchant.addInstanceWithWireAccount({ - id: "default", - name: "Default Instance", - paytoUris: [`payto://iban/${merchantIban}?receiver-name=Merchant`], - defaultWireTransferDelay: Duration.toTalerProtocolDuration( - Duration.getZero(), - ), - }); - - console.log("setup done!"); - - const { walletClient } = await createWalletDaemonWithClient(t, { - name: "default", - }); - - return { - commonDb: db, - exchange, - merchant, - walletClient, - exchangeBankAccount, - libeufinNexus, - libeufinSandbox, - }; -} - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinBasicTest(t: GlobalTestState) { - // Set up test environment - - const { walletClient, exchange, merchant, libeufinSandbox, libeufinNexus } = - await createLibeufinTestEnvironment(t); - - await walletClient.call(WalletApiOperation.AddExchange, { - exchangeBaseUrl: exchange.baseUrl, - }); - - const wr = await walletClient.call( - WalletApiOperation.AcceptManualWithdrawal, - { - exchangeBaseUrl: exchange.baseUrl, - amount: "EUR:15", - }, - ); - - const reservePub: string = wr.reservePub; - - await LibeufinSandboxApi.simulateIncomingTransaction( - libeufinSandbox, - "exchangeacct", - { - amount: "EUR:15.00", - debtorBic: customerBic, - debtorIban: customerIban, - debtorName: "Jane Customer", - subject: `Taler Top-up ${reservePub}`, - }, - ); - - await LibeufinNexusApi.fetchTransactions(libeufinNexus, "myacct"); - - await exchange.runWirewatchOnce(); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - - const bal = await walletClient.call(WalletApiOperation.GetBalances, {}); - console.log("balances", JSON.stringify(bal, undefined, 2)); - t.assertAmountEquals(bal.balances[0].available, "EUR:14.7"); - - const order: Partial = { - summary: "Buy me!", - amount: "EUR:5", - fulfillment_url: "taler://fulfillment-success/thx", - wire_transfer_deadline: AbsoluteTime.toProtocolTimestamp( - AbsoluteTime.now(), - ), - }; - - await makeTestPaymentV2(t, { walletClient, merchant, order }); - - await exchange.runAggregatorOnce(); - await exchange.runTransferOnce(); - - await LibeufinNexusApi.submitAllPaymentInitiations(libeufinNexus, "myacct"); - - const exchangeTransactions = await LibeufinSandboxApi.getAccountTransactions( - libeufinSandbox, - "exchangeacct", - ); - - console.log( - "exchange transactions:", - JSON.stringify(exchangeTransactions, undefined, 2), - ); - - t.assertDeepEqual( - exchangeTransactions.payments[0].creditDebitIndicator, - "credit", - ); - t.assertDeepEqual( - exchangeTransactions.payments[1].creditDebitIndicator, - "debit", - ); - t.assertDeepEqual(exchangeTransactions.payments[1].debtorIban, exchangeIban); - t.assertDeepEqual( - exchangeTransactions.payments[1].creditorIban, - merchantIban, - ); -} -runLibeufinBasicTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts b/packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts deleted file mode 100644 index 5097bc4d3..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - launchLibeufinServices, - LibeufinNexusApi, - NexusUserBundle, - SandboxUserBundle, -} from "../harness/libeufin.js"; - -/** - * This test checks how the C52 and C53 coordinate. It'll test - * whether fresh transactions stop showing as C52 after they get - * included in a bank statement. - */ -export async function runLibeufinC5xTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * User saltetd "02". - */ - const user02nexus = new NexusUserBundle( - "02", - "http://localhost:5010/ebicsweb", - ); - const user02sandbox = new SandboxUserBundle("02"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus, user02nexus], - [user01sandbox, user02sandbox], - ["twg"], - ); - - // Check that C52 and C53 have zero entries. - - // C52 - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "report", // level - ); - // C53 - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "statement", // level - ); - const nexusTxs = await LibeufinNexusApi.getAccountTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - t.assertTrue(nexusTxs["transactions"].length == 0); - - // Addressing one payment to user 01 - await libeufinServices.libeufinSandbox.makeTransaction( - user02sandbox.ebicsBankAccount.label, // debit - user01sandbox.ebicsBankAccount.label, // credit - "EUR:10", - "first payment", - ); - - let expectOne = await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "report", // C52 - ); - t.assertTrue(expectOne.newTransactions == 1); - t.assertTrue(expectOne.downloadedTransactions == 1); - - /* Expect zero payments being downloaded because the - * previous request consumed already the one pending - * payment. - */ - let expectZero = await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "report", // C52 - ); - t.assertTrue(expectZero.newTransactions == 0); - t.assertTrue(expectZero.downloadedTransactions == 0); - - /** - * A statement should still account zero payments because - * so far the payment made before is still pending. - */ - expectZero = await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "statement", // C53 - ); - t.assertTrue(expectZero.newTransactions == 0); - t.assertTrue(expectZero.downloadedTransactions == 0); - - /** - * Ticking now. That books any pending transaction. - */ - await libeufinServices.libeufinSandbox.c53tick(); - - /** - * A statement is now expected to download the transaction, - * although that got already ingested along the report - * earlier. Thus the transaction counts as downloaded but - * not as new. - */ - expectOne = await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "statement", // C53 - ); - t.assertTrue(expectOne.downloadedTransactions == 1); - t.assertTrue(expectOne.newTransactions == 0); -} -runLibeufinC5xTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts b/packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts deleted file mode 100644 index 0efd55f44..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - SandboxUserBundle, - NexusUserBundle, - launchLibeufinServices, - LibeufinNexusApi, - LibeufinSandboxApi, -} from "../harness/libeufin.js"; - -/** - * Testing the Anastasis API, offered by the Anastasis facade. - */ -export async function runLibeufinAnastasisFacadeTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus], - [user01sandbox], - ["anastasis"], // create only one Anastasis facade. - ); - let resp = await LibeufinNexusApi.getAllFacades( - libeufinServices.libeufinNexus, - ); - // check that original facade shows up. - t.assertTrue( - resp["facades"][0]["name"] == user01nexus.anastasisReq["name"], - ); -const anastasisBaseUrl: string = resp["facades"][0]["baseUrl"]; - t.assertTrue(typeof anastasisBaseUrl === "string"); - t.assertTrue(anastasisBaseUrl.startsWith("http://")); - t.assertTrue(anastasisBaseUrl.endsWith("/")); - - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - - await LibeufinNexusApi.postPermission(libeufinServices.libeufinNexus, { - action: "grant", - permission: { - subjectId: user01nexus.userReq.username, - subjectType: "user", - resourceType: "facade", - resourceId: user01nexus.anastasisReq.name, - permissionName: "facade.anastasis.history", - }, - }); - - // check if empty. - let txsEmpty = await LibeufinNexusApi.getAnastasisTransactions( - libeufinServices.libeufinNexus, - anastasisBaseUrl, - { delta: 5 }, - ); - - t.assertTrue(txsEmpty.data.incoming_transactions.length == 0); - - LibeufinSandboxApi.simulateIncomingTransaction( - libeufinServices.libeufinSandbox, - user01sandbox.ebicsBankAccount.label, - { - debtorIban: "ES3314655813489414469157", - debtorBic: "BCMAESM1XXX", - debtorName: "Mock Donor", - subject: "Anastasis donation", - amount: "EUR:3", // Sandbox takes currency from its 'config' - }, - ); - - LibeufinSandboxApi.simulateIncomingTransaction( - libeufinServices.libeufinSandbox, - user01sandbox.ebicsBankAccount.label, - { - debtorIban: "ES3314655813489414469157", - debtorBic: "BCMAESM1XXX", - debtorName: "Mock Donor", - subject: "another Anastasis donation", - amount: "EUR:1", // Sandbox takes currency from its "config" - }, - ); - - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - - let txs = await LibeufinNexusApi.getAnastasisTransactions( - libeufinServices.libeufinNexus, - anastasisBaseUrl, - { delta: 5 }, - user01nexus.userReq.username, - user01nexus.userReq.password, - ); - - // check the two payments show up - let txsList = txs.data.incoming_transactions; - t.assertTrue(txsList.length == 2); - t.assertTrue( - [txsList[0].subject, txsList[1].subject].includes("Anastasis donation"), - ); - t.assertTrue( - [txsList[0].subject, txsList[1].subject].includes( - "another Anastasis donation", - ), - ); - t.assertTrue(txsList[0].row_id == 1); - t.assertTrue(txsList[1].row_id == 2); - - LibeufinSandboxApi.simulateIncomingTransaction( - libeufinServices.libeufinSandbox, - user01sandbox.ebicsBankAccount.label, - { - debtorIban: "ES3314655813489414469157", - debtorBic: "BCMAESM1XXX", - debtorName: "Mock Donor", - subject: "last Anastasis donation", - amount: "EUR:10.10", // Sandbox takes currency from its "config" - }, - ); - - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - - let txsLast = await LibeufinNexusApi.getAnastasisTransactions( - libeufinServices.libeufinNexus, - anastasisBaseUrl, - { delta: 5, start: 2 }, - user01nexus.userReq.username, - user01nexus.userReq.password, - ); - console.log( - txsLast.data.incoming_transactions[0].subject == "last Anastasis donation", - ); - - let txsReverse = await LibeufinNexusApi.getAnastasisTransactions( - libeufinServices.libeufinNexus, - anastasisBaseUrl, - { delta: -5, start: 4 }, - user01nexus.userReq.username, - user01nexus.userReq.password, - ); - t.assertTrue(txsReverse.data.incoming_transactions[0].row_id == 3); - t.assertTrue(txsReverse.data.incoming_transactions[1].row_id == 2); - t.assertTrue(txsReverse.data.incoming_transactions[2].row_id == 1); -} - -runLibeufinAnastasisFacadeTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-keyrotation.ts b/packages/taler-harness/src/integrationtests/test-libeufin-keyrotation.ts deleted file mode 100644 index a2c21d5d8..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-keyrotation.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - SandboxUserBundle, - NexusUserBundle, - launchLibeufinServices, - LibeufinSandboxApi, - LibeufinNexusApi, -} from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinKeyrotationTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus], - [user01sandbox], - ); - - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - - /* Rotate the Sandbox keys, and fetch the transactions again */ - await LibeufinSandboxApi.rotateKeys( - libeufinServices.libeufinSandbox, - user01sandbox.ebicsBankAccount.subscriber.hostID, - ); - - try { - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - } catch (e: any) { - /** - * Asserting that Nexus responded with a 500 Internal server - * error, because the bank signed the last response with a new - * key pair that was never downloaded by Nexus. - * - * NOTE: the bank accepted the request addressed to the old - * public key. Should it in this case reject the request even - * before trying to verify it? - */ - t.assertTrue(e.response.status == 500); - // FIXME: uncomment and adapt the following command after #6723 is fixed. - // t.assertTrue(e.response.data.code == 9000); - } -} -runLibeufinKeyrotationTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts b/packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts deleted file mode 100644 index 868f93759..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - SandboxUserBundle, - NexusUserBundle, - launchLibeufinServices, - LibeufinNexusApi, -} from "../harness/libeufin.js"; - -/** - * This test checks how the C52 and C53 coordinate. It'll test - * whether fresh transactions stop showing as C52 after they get - * included in a bank statement. - */ -export async function runLibeufinNexusBalanceTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * User saltetd "02". - */ - const user02nexus = new NexusUserBundle( - "02", - "http://localhost:5010/ebicsweb", - ); - const user02sandbox = new SandboxUserBundle("02"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus, user02nexus], - [user01sandbox, user02sandbox], - ["twg"], - ); - - // user 01 gets 10 - await libeufinServices.libeufinSandbox.makeTransaction( - user02sandbox.ebicsBankAccount.label, // debit - user01sandbox.ebicsBankAccount.label, // credit - "EUR:10", - "first payment", - ); - // user 01 gets another 10 - await libeufinServices.libeufinSandbox.makeTransaction( - user02sandbox.ebicsBankAccount.label, // debit - user01sandbox.ebicsBankAccount.label, // credit - "EUR:10", - "second payment", - ); - - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "report", // level - ); - - // Check that user 01 has 20, via Nexus. - let accountInfo = await LibeufinNexusApi.getBankAccount( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - t.assertAmountEquals(accountInfo.data.lastSeenBalance, "EUR:20"); - - // Booking the first two transactions. - await libeufinServices.libeufinSandbox.c53tick(); - - // user 01 gives 30 - await libeufinServices.libeufinSandbox.makeTransaction( - user01sandbox.ebicsBankAccount.label, - user02sandbox.ebicsBankAccount.label, - "EUR:30", - "third payment", - ); - - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "all", // range - "report", // level - ); - - let accountInfoDebit = await LibeufinNexusApi.getBankAccount( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - t.assertDeepEqual(accountInfoDebit.data.lastSeenBalance, "-EUR:10"); -} - -runLibeufinNexusBalanceTest.suites = ["libeufin"]; -runLibeufinNexusBalanceTest.experimental = true; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-refund-multiple-users.ts b/packages/taler-harness/src/integrationtests/test-libeufin-refund-multiple-users.ts deleted file mode 100644 index 245f34331..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-refund-multiple-users.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState, delayMs } from "../harness/harness.js"; -import { - SandboxUserBundle, - NexusUserBundle, - launchLibeufinServices, - LibeufinSandboxApi, - LibeufinNexusApi, -} from "../harness/libeufin.js"; - -/** - * User 01 expects a refund from user 02, and expectedly user 03 - * should not be involved in the process. - */ -export async function runLibeufinRefundMultipleUsersTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * User saltetd "02" - */ - const user02nexus = new NexusUserBundle( - "02", - "http://localhost:5010/ebicsweb", - ); - const user02sandbox = new SandboxUserBundle("02"); - - /** - * User saltetd "03" - */ - const user03nexus = new NexusUserBundle( - "03", - "http://localhost:5010/ebicsweb", - ); - const user03sandbox = new SandboxUserBundle("03"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus, user02nexus], - [user01sandbox, user02sandbox], - ["twg"], - ); - - // user 01 gets the payment - await libeufinServices.libeufinSandbox.makeTransaction( - user02sandbox.ebicsBankAccount.label, // debit - user01sandbox.ebicsBankAccount.label, // credit - "EUR:1", - "not a public key", - ); - - // user 01 fetches the payments - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - - // user 01 tries to submit the reimbursement, as - // the payment didn't have a valid public key in - // the subject. - await LibeufinNexusApi.submitInitiatedPayment( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - "1", // so far the only one that can exist. - ); - - // user 02 checks whether a reimbursement arrived. - let history = await LibeufinSandboxApi.getAccountTransactions( - libeufinServices.libeufinSandbox, - user02sandbox.ebicsBankAccount["label"], - ); - // reimbursement arrived IFF the total payments are 2: - // 1 the original (faulty) transaction + 1 the reimbursement. - t.assertTrue(history["payments"].length == 2); -} - -runLibeufinRefundMultipleUsersTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-refund.ts b/packages/taler-harness/src/integrationtests/test-libeufin-refund.ts deleted file mode 100644 index d37363bab..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-refund.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState, delayMs } from "../harness/harness.js"; -import { - SandboxUserBundle, - NexusUserBundle, - launchLibeufinServices, - LibeufinSandboxApi, - LibeufinNexusApi, -} from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinRefundTest(t: GlobalTestState) { - /** - * User saltetd "01" - */ - const user01nexus = new NexusUserBundle( - "01", - "http://localhost:5010/ebicsweb", - ); - const user01sandbox = new SandboxUserBundle("01"); - - /** - * User saltetd "02" - */ - const user02nexus = new NexusUserBundle( - "02", - "http://localhost:5010/ebicsweb", - ); - const user02sandbox = new SandboxUserBundle("02"); - - /** - * Launch Sandbox and Nexus. - */ - const libeufinServices = await launchLibeufinServices( - t, - [user01nexus, user02nexus], - [user01sandbox, user02sandbox], - ["twg"], - ); - - // user 02 pays user 01 with a faulty (non Taler) subject. - await libeufinServices.libeufinSandbox.makeTransaction( - user02sandbox.ebicsBankAccount.label, // debit - user01sandbox.ebicsBankAccount.label, // credit - "EUR:1", - "not a public key", - ); - - // The bad payment should be now ingested and prepared as - // a reimbursement. - await LibeufinNexusApi.fetchTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - // Check that the payment arrived at the Nexus. - const nexusTxs = await LibeufinNexusApi.getAccountTransactions( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - ); - t.assertTrue(nexusTxs["transactions"].length == 1); - - // Submit the reimbursement - await LibeufinNexusApi.submitInitiatedPayment( - libeufinServices.libeufinNexus, - user01nexus.localAccountName, - // The initiated payment (= the reimbursement) ID below - // got set by the Taler facade; at this point only one must - // exist. If "1" is not found, a 404 will make this test fail. - "1", - ); - - // user 02 checks whether the reimbursement arrived. - let history = await LibeufinSandboxApi.getAccountTransactions( - libeufinServices.libeufinSandbox, - user02sandbox.ebicsBankAccount["label"], - ); - // 2 payments must exist: 1 the original (faulty) payment + - // 1 the reimbursement. - t.assertTrue(history["payments"].length == 2); -} -runLibeufinRefundTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts b/packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts deleted file mode 100644 index be467e2f1..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - LibeufinSandboxApi, - LibeufinSandboxService, -} from "../harness/libeufin.js"; - -export async function runLibeufinSandboxWireTransferCliTest( - t: GlobalTestState, -) { - const sandbox = await LibeufinSandboxService.create(t, { - httpPort: 5012, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - }); - await sandbox.start(); - await sandbox.pingUntilAvailable(); - await LibeufinSandboxApi.createDemobankAccount( - "mock-account", - "password-unused", - { baseUrl: sandbox.baseUrl + "/demobanks/default/access-api/" }, - "DE71500105179674997361", - ); - await LibeufinSandboxApi.createDemobankAccount( - "mock-account-2", - "password-unused", - { baseUrl: sandbox.baseUrl + "/demobanks/default/access-api/" }, - "DE71500105179674997364", - ); - - await sandbox.makeTransaction( - "mock-account", - "mock-account-2", - "EUR:1", - "one!", - ); - await sandbox.makeTransaction( - "mock-account", - "mock-account-2", - "EUR:1", - "two!", - ); - await sandbox.makeTransaction( - "mock-account", - "mock-account-2", - "EUR:1", - "three!", - ); - await sandbox.makeTransaction( - "mock-account-2", - "mock-account", - "EUR:1", - "Give one back.", - ); - await sandbox.makeTransaction( - "mock-account-2", - "mock-account", - "EUR:0.11", - "Give fraction back.", - ); - let ret = await LibeufinSandboxApi.getAccountInfoWithBalance( - sandbox, - "mock-account-2", - ); - console.log(ret.balance); - t.assertTrue(ret.balance == "EUR:1.89"); -} -runLibeufinSandboxWireTransferCliTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-tutorial.ts b/packages/taler-harness/src/integrationtests/test-libeufin-tutorial.ts deleted file mode 100644 index 496b65ee3..000000000 --- a/packages/taler-harness/src/integrationtests/test-libeufin-tutorial.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - */ - -/** - * Imports. - */ -import { GlobalTestState } from "../harness/harness.js"; -import { - LibeufinNexusService, - LibeufinSandboxService, - LibeufinCli, -} from "../harness/libeufin.js"; - -/** - * Run basic test with LibEuFin. - */ -export async function runLibeufinTutorialTest(t: GlobalTestState) { - // Set up test environment - - const libeufinSandbox = await LibeufinSandboxService.create(t, { - httpPort: 5010, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - }); - - await libeufinSandbox.start(); - await libeufinSandbox.pingUntilAvailable(); - - const libeufinNexus = await LibeufinNexusService.create(t, { - httpPort: 5011, - databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - }); - - const nexusUser = { username: "foo", password: "secret" }; - const libeufinCli = new LibeufinCli(t, { - sandboxUrl: libeufinSandbox.baseUrl, - nexusUrl: libeufinNexus.baseUrl, - sandboxDatabaseUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`, - nexusDatabaseUri: `jdbc:sqlite:${t.testDir}/libeufin-nexus.sqlite3`, - nexusUser: nexusUser, - }); - - const ebicsDetails = { - hostId: "testhost", - partnerId: "partner01", - userId: "user01", - }; - const bankAccountDetails = { - currency: "EUR", - iban: "DE18500105172929531888", - bic: "INGDDEFFXXX", - personName: "Jane Normal", - accountName: "testacct01", - }; - - await libeufinCli.checkSandbox(); - await libeufinCli.createEbicsHost("testhost"); - await libeufinCli.createEbicsSubscriber(ebicsDetails); - await libeufinCli.createEbicsBankAccount(ebicsDetails, bankAccountDetails); - await libeufinCli.generateTransactions(bankAccountDetails.accountName); - - await libeufinNexus.start(); - await libeufinNexus.pingUntilAvailable(); - - await libeufinNexus.createNexusSuperuser(nexusUser); - const connectionDetails = { - subscriberDetails: ebicsDetails, - ebicsUrl: `${libeufinSandbox.baseUrl}ebicsweb`, // FIXME: need appropriate URL concatenation - connectionName: "my-ebics-conn", - }; - await libeufinCli.createEbicsConnection(connectionDetails); - await libeufinCli.createBackupFile({ - passphrase: "secret", - outputFile: `${t.testDir}/connection-backup.json`, - connectionName: connectionDetails.connectionName, - }); - await libeufinCli.createKeyLetter({ - outputFile: `${t.testDir}/letter.pdf`, - connectionName: connectionDetails.connectionName, - }); - await libeufinCli.connect(connectionDetails.connectionName); - await libeufinCli.downloadBankAccounts(connectionDetails.connectionName); - await libeufinCli.listOfferedBankAccounts(connectionDetails.connectionName); - - const bankAccountImportDetails = { - offeredBankAccountName: bankAccountDetails.accountName, - nexusBankAccountName: "at-nexus-testacct01", - connectionName: connectionDetails.connectionName, - }; - - await libeufinCli.importBankAccount(bankAccountImportDetails); - await libeufinSandbox.c53tick(); - await libeufinCli.fetchTransactions( - bankAccountImportDetails.nexusBankAccountName, - ); - await libeufinCli.transactions(bankAccountImportDetails.nexusBankAccountName); - - const paymentDetails = { - creditorIban: "DE42500105171245624648", - creditorBic: "BELADEBEXXX", - creditorName: "Mina Musterfrau", - subject: "Purchase 01234", - amount: "1.0", - currency: "EUR", - nexusBankAccountName: bankAccountImportDetails.nexusBankAccountName, - }; - await libeufinCli.preparePayment(paymentDetails); - await libeufinCli.submitPayment(paymentDetails, "1"); - - await libeufinCli.newTalerWireGatewayFacade({ - accountName: bankAccountImportDetails.nexusBankAccountName, - connectionName: "my-ebics-conn", - currency: "EUR", - facadeName: "my-twg", - }); - await libeufinCli.listFacades(); -} -runLibeufinTutorialTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment-fault.ts b/packages/taler-harness/src/integrationtests/test-payment-fault.ts index e57427fac..ca74a4ad6 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-fault.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-fault.ts @@ -22,7 +22,7 @@ * Imports. */ import { - BankAccessApiClient, + TalerCorebankApiClient, CoreApiResponse, MerchantApiClient, } from "@gnu-taler/taler-util"; @@ -127,7 +127,7 @@ export async function runPaymentFaultTest(t: GlobalTestState) { // Create withdrawal operation - const bankClient = new BankAccessApiClient(bank.bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl); const user = await bankClient.createRandomBankUser(); const wop = await bankClient.createWithdrawalOperation( diff --git a/packages/taler-harness/src/integrationtests/test-tipping.ts b/packages/taler-harness/src/integrationtests/test-tipping.ts index 4140311ab..3d4ea6663 100644 --- a/packages/taler-harness/src/integrationtests/test-tipping.ts +++ b/packages/taler-harness/src/integrationtests/test-tipping.ts @@ -18,7 +18,7 @@ * Imports. */ import { - BankAccessApiClient, + TalerCorebankApiClient, MerchantApiClient, TransactionMajorState, WireGatewayApiClient, @@ -38,7 +38,7 @@ export async function runTippingTest(t: GlobalTestState) { const { walletClient, bank, exchange, merchant, exchangeBankAccount } = await createSimpleTestkudosEnvironmentV2(t); - const bankAccessApiClient = new BankAccessApiClient( + const bankAccessApiClient = new TalerCorebankApiClient( bank.bankAccessApiBaseUrl, ); const mbu = await bankAccessApiClient.createRandomBankUser(); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts index 9a0eb77ae..ae582fe60 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts @@ -18,7 +18,7 @@ * Imports. */ import { - BankAccessApiClient, + TalerCorebankApiClient, Duration, NotificationType, TransactionMajorState, @@ -121,7 +121,7 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { skipDefaults: true, }); - const bankAccessApiClient = new BankAccessApiClient( + const bankAccessApiClient = new TalerCorebankApiClient( bank.bankAccessApiBaseUrl, ); const user = await bankAccessApiClient.createRandomBankUser(); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts index aa5e2b770..4a0dd845b 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { BankAccessApiClient, TalerErrorCode } from "@gnu-taler/taler-util"; +import { TalerCorebankApiClient, TalerErrorCode } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js"; @@ -33,7 +33,7 @@ export async function runWithdrawalAbortBankTest(t: GlobalTestState) { // Create a withdrawal operation - const bankAccessApiClient = new BankAccessApiClient( + const bankAccessApiClient = new TalerCorebankApiClient( bank.bankAccessApiBaseUrl, ); const user = await bankAccessApiClient.createRandomBankUser(); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts index 232b6d7c2..4a2cc7df9 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts @@ -18,7 +18,7 @@ * Imports. */ import { - BankAccessApiClient, + TalerCorebankApiClient, j2s, NotificationType, TransactionMajorState, @@ -41,7 +41,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) { // Create a withdrawal operation - const bankAccessApiClient = new BankAccessApiClient( + const bankAccessApiClient = new TalerCorebankApiClient( bank.bankAccessApiBaseUrl, ); const user = await bankAccessApiClient.createRandomBankUser(); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts index ec6e54e6c..7cd0548a5 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts @@ -54,7 +54,10 @@ export async function runWithdrawalFakebankTest(t: GlobalTestState) { exchange.addBankAccount("1", { accountName: "exchange", accountPassword: "x", - wireGatewayApiBaseUrl: new URL("/exchange/", bank.baseUrl).href, + wireGatewayApiBaseUrl: new URL( + "/accounts/exchange/taler-wire-gateway", + bank.baseUrl, + ).href, accountPaytoUri: "payto://x-taler-bank/localhost/exchange", }); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts index bc2946a18..a5a5a0d99 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { BankAccessApiClient, j2s } from "@gnu-taler/taler-util"; +import { TalerCorebankApiClient, j2s } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig } from "../harness/denomStructures.js"; import { @@ -107,7 +107,7 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) { const amount = "TESTKUDOS:7.5"; - const bankAccessApiClient = new BankAccessApiClient( + const bankAccessApiClient = new TalerCorebankApiClient( bank.bankAccessApiBaseUrl, ); const user = await bankAccessApiClient.createRandomBankUser(); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts index 1d98cd46e..316e3cc18 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts @@ -19,7 +19,7 @@ */ import { AbsoluteTime, - BankAccessApiClient, + TalerCorebankApiClient, Logger, WireGatewayApiClient, j2s, @@ -41,7 +41,7 @@ export async function runWithdrawalManualTest(t: GlobalTestState) { // Create a withdrawal operation - const bankAccessApiClient = new BankAccessApiClient( + const bankAccessApiClient = new TalerCorebankApiClient( bank.bankAccessApiBaseUrl, ); diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index 237d3bf9f..071871837 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -43,25 +43,6 @@ import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js"; import { runFeeRegressionTest } from "./test-fee-regression.js"; import { runForcedSelectionTest } from "./test-forced-selection.js"; import { runKycTest } from "./test-kyc.js"; -import { runLibeufinApiBankaccountTest } from "./test-libeufin-api-bankaccount.js"; -import { runLibeufinApiBankconnectionTest } from "./test-libeufin-api-bankconnection.js"; -import { runLibeufinApiFacadeBadRequestTest } from "./test-libeufin-api-facade-bad-request.js"; -import { runLibeufinApiFacadeTest } from "./test-libeufin-api-facade.js"; -import { runLibeufinApiPermissionsTest } from "./test-libeufin-api-permissions.js"; -import { runLibeufinApiSandboxCamtTest } from "./test-libeufin-api-sandbox-camt.js"; -import { runLibeufinApiSandboxTransactionsTest } from "./test-libeufin-api-sandbox-transactions.js"; -import { runLibeufinApiSchedulingTest } from "./test-libeufin-api-scheduling.js"; -import { runLibeufinApiUsersTest } from "./test-libeufin-api-users.js"; -import { runLibeufinBadGatewayTest } from "./test-libeufin-bad-gateway.js"; -import { runLibeufinBasicTest } from "./test-libeufin-basic.js"; -import { runLibeufinC5xTest } from "./test-libeufin-c5x.js"; -import { runLibeufinAnastasisFacadeTest } from "./test-libeufin-facade-anastasis.js"; -import { runLibeufinKeyrotationTest } from "./test-libeufin-keyrotation.js"; -import { runLibeufinNexusBalanceTest } from "./test-libeufin-nexus-balance.js"; -import { runLibeufinRefundMultipleUsersTest } from "./test-libeufin-refund-multiple-users.js"; -import { runLibeufinRefundTest } from "./test-libeufin-refund.js"; -import { runLibeufinSandboxWireTransferCliTest } from "./test-libeufin-sandbox-wire-transfer-cli.js"; -import { runLibeufinTutorialTest } from "./test-libeufin-tutorial.js"; import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion.js"; import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete.js"; import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls.js"; @@ -144,25 +125,6 @@ const allTests: TestMainFunction[] = [ runKycTest, runExchangePurseTest, runExchangeDepositTest, - runLibeufinAnastasisFacadeTest, - runLibeufinApiBankaccountTest, - runLibeufinApiBankconnectionTest, - runLibeufinApiFacadeBadRequestTest, - runLibeufinApiFacadeTest, - runLibeufinApiPermissionsTest, - runLibeufinApiSandboxCamtTest, - runLibeufinApiSandboxTransactionsTest, - runLibeufinApiSchedulingTest, - runLibeufinApiUsersTest, - runLibeufinBadGatewayTest, - runLibeufinBasicTest, - runLibeufinC5xTest, - runLibeufinKeyrotationTest, - runLibeufinNexusBalanceTest, - runLibeufinRefundMultipleUsersTest, - runLibeufinRefundTest, - runLibeufinSandboxWireTransferCliTest, - runLibeufinTutorialTest, runMerchantExchangeConfusionTest, runMerchantInstancesDeleteTest, runMerchantInstancesTest, diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts index cc4123500..164cd333d 100644 --- a/packages/taler-util/src/bank-api-client.ts +++ b/packages/taler-util/src/bank-api-client.ts @@ -146,11 +146,87 @@ export class WireGatewayApiClient { } } +export interface ChallengeContactData { + // E-Mail address + email?: string; + + // Phone number. + phone?: string; +} + +export interface Balance { + amount: AmountString; + credit_debit_indicator: "credit" | "debit"; +} + +export interface RegisterAccountRequest { + // Username + username: string; + + // Password. + password: string; + + // Legal name of the account owner + name: string; + + // Defaults to false. + is_public?: boolean; + + // Is this a taler exchange account? + // If true: + // - incoming transactions to the account that do not + // have a valid reserve public key are automatically + // - the account provides the taler-wire-gateway-api endpoints + // Defaults to false. + is_taler_exchange?: boolean; + + // Addresses where to send the TAN for transactions. + // Currently only used for cashouts. + // If missing, cashouts will fail. + // In the future, might be used for other transactions + // as well. + challenge_contact_data?: ChallengeContactData; + + // 'payto' address pointing a bank account + // external to the libeufin-bank. + // Payments will be sent to this bank account + // when the user wants to convert the local currency + // back to fiat currency outside libeufin-bank. + cashout_payto_uri?: string; + + // Internal payto URI of this bank account. + // Used mostly for testing. + internal_payto_uri?: string; +} + +export interface AccountData { + // Legal name of the account owner. + name: string; + + // Available balance on the account. + balance: Balance; + + // payto://-URI of the account. + payto_uri: string; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: AmountString; + + contact_data?: ChallengeContactData; + + // 'payto' address pointing the bank account + // where to send cashouts. This field is optional + // because not all the accounts are required to participate + // in the merchants' circuit. One example is the exchange: + // that never cashouts. Registering these accounts can + // be done via the access API. + cashout_payto_uri?: string; +} + /** - * This API look like it belongs to harness - * but it will be nice to have in utils to be used by others + * Client for the Taler corebank API. */ -export class BankAccessApiClient { +export class TalerCorebankApiClient { httpLib: HttpRequestLibrary; constructor( @@ -215,23 +291,22 @@ export class BankAccessApiClient { return await readSuccessResponseJsonOrThrow(resp, codecForAny()); } - async registerAccount( - username: string, - password: string, - options: { - iban?: string; - } = {}, - ): Promise { - const url = new URL("testing/register", this.baseUrl); + /** + * Register a new account and return information about it. + * + * This is a helper, as it does both the registration and the + * account info query. + */ + async registerAccount(username: string, password: string): Promise { + const url = new URL("accounts", this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", body: { username, password, - iban: options?.iban, + name: username, }, }); - let paytoUri = `payto://x-taler-bank/localhost/${username}`; if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) { logger.error(`${j2s(await resp.json())}`); throw TalerError.fromDetail( @@ -241,31 +316,24 @@ export class BankAccessApiClient { }, ); } - try { - // Pybank has no body, thus this might throw. - const respJson = await resp.json(); - // LibEuFin demobank returns payto URI in response - if (respJson.paytoUri) { - paytoUri = respJson.paytoUri; - } - } catch (e) { - // Do nothing - } + const infoUrl = new URL(`accounts/${username}`, this.baseUrl); + const infoResp = await this.httpLib.fetch(infoUrl.href); + // FIXME: Validate! + const acctInfo: AccountData = await readSuccessResponseJsonOrThrow( + infoResp, + codecForAny(), + ); return { password, username, - accountPaytoUri: paytoUri, + accountPaytoUri: acctInfo.payto_uri, }; } async createRandomBankUser(): Promise { const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); - // FIXME: This is just a temporary workaround, because demobank is running out of short IBANs - const iban = generateIban("DE", 15); - return await this.registerAccount(username, password, { - iban, - }); + return await this.registerAccount(username, password); } async createWithdrawalOperation( diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index f7bd3d120..87985fa2a 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -366,7 +366,7 @@ export const codecForAmountResponse = (): Codec => .property("rawAmount", codecForAmountString()) .build("AmountResponse"); -export interface Balance { +export interface WalletBalance { scopeInfo: ScopeInfo; available: AmountString; pendingIncoming: AmountString; @@ -458,11 +458,11 @@ export type ScopeInfoAuditor = { export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; export interface BalancesResponse { - balances: Balance[]; + balances: WalletBalance[]; } -export const codecForBalance = (): Codec => - buildCodecForObject() +export const codecForBalance = (): Codec => + buildCodecForObject() .property("scopeInfo", codecForAny()) // FIXME .property("available", codecForString()) .property("hasPendingTransactions", codecForBoolean()) diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 4d2fa5cd4..1684977d5 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -31,7 +31,7 @@ import { AmountJson, Amounts, AmountString, - BankAccessApiClient, + TalerCorebankApiClient, codecForAny, codecForBankWithdrawalOperationPostResponse, codecForBatchDepositSuccess, @@ -118,7 +118,7 @@ export async function topupReserveWithDemobank( args: TopupReserveWithDemobankArgs, ) { const { http, bankAccessApiBaseUrl, amount, exchangeInfo, reservePub } = args; - const bankClient = new BankAccessApiClient(bankAccessApiBaseUrl); + const bankClient = new TalerCorebankApiClient(bankAccessApiBaseUrl); const bankUser = await bankClient.createRandomBankUser(); const wopi = await bankClient.createWithdrawalOperation( bankUser.username, diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index f71d842c7..b5840c3a6 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -25,6 +25,7 @@ import { IntegrationTestV2Args, Logger, NotificationType, + RegisterAccountRequest, stringToBytes, TestPayResult, TransactionMajorState, @@ -216,17 +217,25 @@ async function confirmBankWithdrawalUri( async function registerRandomBankUser( http: HttpRequestLibrary, - bankAccessApiBaseUrl: string, + corebankApiBaseUrl: string, ): Promise { - const reqUrl = new URL("testing/register", bankAccessApiBaseUrl).href; + const reqUrl = new URL("accounts", corebankApiBaseUrl).href; const randId = makeId(8); + const username = `testuser-${randId.toLowerCase()}`; + const password = `testpw-${randId}`; + const bankUser: BankUser = { - // euFin doesn't allow resource names to have upper case letters. - username: `testuser-${randId.toLowerCase()}`, - password: `testpw-${randId}`, + username, + password, + }; + + const userReq: RegisterAccountRequest = { + username, + password, + name: username, }; - const resp = await http.postJson(reqUrl, bankUser); + const resp = await http.fetch(reqUrl, { method: "POST", body: userReq }); await checkSuccessResponseOrThrow(resp); return bankUser; } diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 9091a92bf..2c4f1ba6f 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1513,13 +1513,19 @@ async function dispatchRequestInternal( const components = pt.targetPath.split("/"); const creditorAcct = components[components.length - 1]; logger.info(`making testbank transfer to '${creditorAcct}'`); - const fbReq = await ws.http.postJson( - new URL(`${creditorAcct}/admin/add-incoming`, req.bank).href, + const fbReq = await ws.http.fetch( + new URL( + `accounts/${creditorAcct}/taler-wire-gateway/admin/add-incoming`, + req.bank, + ).href, { - amount: Amounts.stringify(amount), - reserve_pub: wres.reservePub, - debit_account: - "payto://x-taler-bank/localhost/testdebtor?receiver-name=Foo", + method: "POST", + body: { + amount: Amounts.stringify(amount), + reserve_pub: wres.reservePub, + debit_account: + "payto://x-taler-bank/localhost/testdebtor?receiver-name=Foo", + }, }, ); const fbResp = await readSuccessResponseJsonOrThrow(fbReq, codecForAny()); -- cgit v1.2.3 From a5406c5a5dc437e036168eb068db11d88e05bb0f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 Sep 2023 08:31:08 -0300 Subject: some ui --- packages/demobank-ui/build.mjs | 4 +- packages/demobank-ui/dev.mjs | 4 +- packages/demobank-ui/package.json | 4 + packages/demobank-ui/postcss.config.js | 6 + packages/demobank-ui/src/components/CopyButton.tsx | 102 +- packages/demobank-ui/src/components/Routing.tsx | 5 + .../src/components/Transactions/views.tsx | 76 +- packages/demobank-ui/src/components/app.tsx | 1 - packages/demobank-ui/src/hooks/access.ts | 10 +- packages/demobank-ui/src/index.html | 49 +- packages/demobank-ui/src/index.tsx | 2 +- .../demobank-ui/src/pages/AccountPage/views.tsx | 93 +- packages/demobank-ui/src/pages/BankFrame.tsx | 266 +- packages/demobank-ui/src/pages/PaymentOptions.tsx | 100 +- .../src/pages/PaytoWireTransferForm.tsx | 581 +- packages/demobank-ui/src/pages/Test.tsx | 5 + .../demobank-ui/src/pages/WalletWithdrawForm.tsx | 8 +- packages/demobank-ui/src/scss/DurationPicker.scss | 70 - packages/demobank-ui/src/scss/_aside.scss | 128 - packages/demobank-ui/src/scss/_card.scss | 69 - .../demobank-ui/src/scss/_custom-calendar.scss | 263 - packages/demobank-ui/src/scss/_footer.scss | 35 - packages/demobank-ui/src/scss/_form.scss | 71 - packages/demobank-ui/src/scss/_hero-bar.scss | 55 - packages/demobank-ui/src/scss/_loading.scss | 51 - packages/demobank-ui/src/scss/_main-section.scss | 24 - packages/demobank-ui/src/scss/_misc.scss | 50 - packages/demobank-ui/src/scss/_mixins.scss | 34 - packages/demobank-ui/src/scss/_modal.scss | 35 - packages/demobank-ui/src/scss/_nav-bar.scss | 144 - packages/demobank-ui/src/scss/_table.scss | 179 - packages/demobank-ui/src/scss/_theme-default.scss | 136 - packages/demobank-ui/src/scss/_tiles.scss | 24 - packages/demobank-ui/src/scss/_title-bar.scss | 50 - packages/demobank-ui/src/scss/bank.scss | 353 - packages/demobank-ui/src/scss/colors-bank.scss | 31 - packages/demobank-ui/src/scss/demo.scss | 167 - .../src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf | Bin 43752 -> 0 bytes packages/demobank-ui/src/scss/fonts/nunito.css | 22 - .../fonts/materialdesignicons-webfont-4.9.95.eot | Bin 844600 -> 0 bytes .../fonts/materialdesignicons-webfont-4.9.95.ttf | Bin 844380 -> 0 bytes .../fonts/materialdesignicons-webfont-4.9.95.woff | Bin 404384 -> 0 bytes .../fonts/materialdesignicons-webfont-4.9.95.woff2 | Bin 283040 -> 0 bytes .../scss/icons/materialdesignicons-4.9.95.min.css | 15109 ------------------- packages/demobank-ui/src/scss/libs/_all.scss | 29 - packages/demobank-ui/src/scss/main.css | 3 + packages/demobank-ui/src/scss/main.scss | 5 - packages/demobank-ui/src/scss/pure.scss | 1397 -- packages/demobank-ui/src/scss/toggle.scss | 51 - packages/demobank-ui/src/stories.tsx | 2 - packages/demobank-ui/tailwind.config.js | 8 + pnpm-lock.yaml | 65 +- 52 files changed, 820 insertions(+), 19156 deletions(-) create mode 100644 packages/demobank-ui/postcss.config.js create mode 100644 packages/demobank-ui/src/pages/Test.tsx delete mode 100644 packages/demobank-ui/src/scss/DurationPicker.scss delete mode 100644 packages/demobank-ui/src/scss/_aside.scss delete mode 100644 packages/demobank-ui/src/scss/_card.scss delete mode 100644 packages/demobank-ui/src/scss/_custom-calendar.scss delete mode 100644 packages/demobank-ui/src/scss/_footer.scss delete mode 100644 packages/demobank-ui/src/scss/_form.scss delete mode 100644 packages/demobank-ui/src/scss/_hero-bar.scss delete mode 100644 packages/demobank-ui/src/scss/_loading.scss delete mode 100644 packages/demobank-ui/src/scss/_main-section.scss delete mode 100644 packages/demobank-ui/src/scss/_misc.scss delete mode 100644 packages/demobank-ui/src/scss/_mixins.scss delete mode 100644 packages/demobank-ui/src/scss/_modal.scss delete mode 100644 packages/demobank-ui/src/scss/_nav-bar.scss delete mode 100644 packages/demobank-ui/src/scss/_table.scss delete mode 100644 packages/demobank-ui/src/scss/_theme-default.scss delete mode 100644 packages/demobank-ui/src/scss/_tiles.scss delete mode 100644 packages/demobank-ui/src/scss/_title-bar.scss delete mode 100644 packages/demobank-ui/src/scss/bank.scss delete mode 100644 packages/demobank-ui/src/scss/colors-bank.scss delete mode 100644 packages/demobank-ui/src/scss/demo.scss delete mode 100644 packages/demobank-ui/src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf delete mode 100644 packages/demobank-ui/src/scss/fonts/nunito.css delete mode 100644 packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.eot delete mode 100644 packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.ttf delete mode 100644 packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff delete mode 100644 packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff2 delete mode 100644 packages/demobank-ui/src/scss/icons/materialdesignicons-4.9.95.min.css delete mode 100644 packages/demobank-ui/src/scss/libs/_all.scss create mode 100644 packages/demobank-ui/src/scss/main.css delete mode 100644 packages/demobank-ui/src/scss/main.scss delete mode 100644 packages/demobank-ui/src/scss/pure.scss delete mode 100644 packages/demobank-ui/src/scss/toggle.scss create mode 100644 packages/demobank-ui/tailwind.config.js (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/build.mjs b/packages/demobank-ui/build.mjs index 22b91803a..64ddc3774 100755 --- a/packages/demobank-ui/build.mjs +++ b/packages/demobank-ui/build.mjs @@ -21,8 +21,8 @@ await build({ type: "production", source: { js: ["src/index.tsx"], - assets: [{base:"src",files:["src/index.html"]}], + assets: [{ base: "src", files: ["src/index.html"] }], }, destination: "./dist/prod", - css: "sass", + css: "postcss", }); diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs index 8b870451b..9c09e5716 100755 --- a/packages/demobank-ui/dev.mjs +++ b/packages/demobank-ui/dev.mjs @@ -24,11 +24,11 @@ const build = initializeDev({ type: "development", source: { js: devEntryPoints, - assets: [{base:"src",files:["src/index.html"]}], + assets: [{ base: "src", files: ["src/index.html"] }], }, destination: "./dist/dev", public: "/app", - css: "sass", + css: "postcss", }); await build(); diff --git a/packages/demobank-ui/package.json b/packages/demobank-ui/package.json index 8b999aeed..744cb4180 100644 --- a/packages/demobank-ui/package.json +++ b/packages/demobank-ui/package.json @@ -46,6 +46,9 @@ "devDependencies": { "@creativebulma/bulma-tooltip": "^1.2.0", "@gnu-taler/pogen": "^0.0.5", + "@tailwindcss/forms": "^0.5.3", + "@tailwindcss/typography": "^0.5.9", + "autoprefixer": "^10.4.14", "@types/chai": "^4.3.0", "@types/history": "^4.7.8", "@types/mocha": "^10.0.1", @@ -62,6 +65,7 @@ "po2json": "^0.4.5", "preact-render-to-string": "^5.2.6", "sass": "1.56.1", + "tailwindcss": "^3.3.2", "typescript": "5.2.2" }, "pogen": { diff --git a/packages/demobank-ui/postcss.config.js b/packages/demobank-ui/postcss.config.js new file mode 100644 index 000000000..2e7af2b7f --- /dev/null +++ b/packages/demobank-ui/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/packages/demobank-ui/src/components/CopyButton.tsx b/packages/demobank-ui/src/components/CopyButton.tsx index c61083074..97ccbf2bf 100644 --- a/packages/demobank-ui/src/components/CopyButton.tsx +++ b/packages/demobank-ui/src/components/CopyButton.tsx @@ -4,57 +4,57 @@ import { useEffect, useState } from "preact/hooks"; export function CopyIcon(): VNode { - return ( - - - - - ) - }; - - export function CopiedIcon(): VNode { - return ( - - - - ) - }; - -export function CopyButton({ getContent }: { getContent: () => string }): VNode { - const [copied, setCopied] = useState(false); - function copyText(): void { - navigator.clipboard.writeText(getContent() || ""); - setCopied(true); - } - useEffect(() => { - if (copied) { - setTimeout(() => { - setCopied(false); - }, 1000); - } - }, [copied]); - - if (!copied) { - return ( - - ); + return ( + + + + + ) +}; + +export function CopiedIcon(): VNode { + return ( + + + + ) +}; + +export function CopyButton({ getContent }: { getContent: () => string }): VNode { + const [copied, setCopied] = useState(false); + function copyText(): void { + navigator.clipboard.writeText(getContent() || ""); + setCopied(true); + } + useEffect(() => { + if (copied) { + setTimeout(() => { + setCopied(false); + }, 1000); } + }, [copied]); + + if (!copied) { return ( -
- -
+ ); - } \ No newline at end of file + } + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx index d5ea44e10..2c532e863 100644 --- a/packages/demobank-ui/src/components/Routing.tsx +++ b/packages/demobank-ui/src/components/Routing.tsx @@ -23,6 +23,7 @@ import { BusinessAccount } from "../pages/BusinessAccount.js"; import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js"; import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js"; import { RegistrationPage } from "../pages/RegistrationPage.js"; +import { Test } from "../pages/Test.js"; export function Routing(): VNode { const history = createHashHistory(); @@ -34,6 +35,10 @@ export function Routing(): VNode { }} > + ( diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx index 34d078c16..f4a78e516 100644 --- a/packages/demobank-ui/src/components/Transactions/views.tsx +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -33,42 +33,46 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode { export function ReadyView({ transactions }: State.Ready): VNode { const { i18n } = useTranslationContext(); return ( -
- - - - - - - - - - - {transactions.map((item, idx) => { - return ( - - - - - - - ); - })} - -
{i18n.str`Date`}{i18n.str`Amount`}{i18n.str`Counterpart`}{i18n.str`Subject`}
- {item.when.t_ms === "never" - ? "" - : format(item.when.t_ms, "dd/MM/yyyy HH:mm:ss")} - - {item.negative ? "-" : ""} - {item.amount ? ( - `${Amounts.stringifyValue(item.amount)} ${ - item.amount.currency - }` - ) : ( - <invalid value> - )} - {item.counterpart}{item.subject}
+
+
+
+

Latest transactions

+
+
+
+ + + + + + + + + + + {transactions.map((item, idx) => { + return ( + + + + + + + ); + })} + +
{i18n.str`Date`}{i18n.str`Amount`}{i18n.str`Counterpart`}{i18n.str`Subject`}
+
{item.when.t_ms === "never" + ? "" + : format(item.when.t_ms, "dd/MM/yyyy HH:mm:ss")}
+
{item.negative ? "-" : ""} + {item.amount ? ( + `${Amounts.stringifyValue(item.amount)} ${item.amount.currency + }` + ) : ( + <{i18n.str`invalid value`}> + )}{item.counterpart}{item.subject}
+
); } diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index 1f7034bc5..22752ab78 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -24,7 +24,6 @@ import { SWRConfig } from "swr"; import { BackendStateProvider } from "../context/backend.js"; import { strings } from "../i18n/strings.js"; import { Routing } from "./Routing.js"; - const WITH_LOCAL_STORAGE_CACHE = false; /** diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts index 61f458e51..af8381135 100644 --- a/packages/demobank-ui/src/hooks/access.ts +++ b/packages/demobank-ui/src/hooks/access.ts @@ -365,7 +365,15 @@ export function useTransactions( RequestError >( [`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE], - paginatedFetcher, + paginatedFetcher, { + refreshInterval: 0, + refreshWhenHidden: false, + refreshWhenOffline: false, + // revalidateOnMount: false, + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + } ); const [lastAfter, setLastAfter] = useState< diff --git a/packages/demobank-ui/src/index.html b/packages/demobank-ui/src/index.html index e21e1fccc..315985648 100644 --- a/packages/demobank-ui/src/index.html +++ b/packages/demobank-ui/src/index.html @@ -16,27 +16,28 @@ @author Sebastian Javier Marchano --> - - - - - - - - - - - Demobank - - - - - - - -
- - + + + + + + + + + + + + Demobank + + + + + + + + +
+ + + \ No newline at end of file diff --git a/packages/demobank-ui/src/index.tsx b/packages/demobank-ui/src/index.tsx index 2e0f740fe..b7d69fd2d 100644 --- a/packages/demobank-ui/src/index.tsx +++ b/packages/demobank-ui/src/index.tsx @@ -16,7 +16,7 @@ import App from "./components/app.js"; import { h, render } from "preact"; -import "./scss/main.scss"; +import "./scss/main.css" const app = document.getElementById("app"); diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx index b476759b4..f2cbbba6c 100644 --- a/packages/demobank-ui/src/pages/AccountPage/views.tsx +++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx @@ -21,54 +21,67 @@ import { Transactions } from "../../components/Transactions/index.js"; import { PaymentOptions } from "../PaymentOptions.js"; import { State } from "./index.js"; import { CopyButton } from "../../components/CopyButton.js"; +import { bankUiSettings } from "../../settings.js"; -export function InvalidIbanView({error}:State.InvalidIban) { - return ( -
Payto from server is not valid "{error.data.paytoUri}"
- ); +export function InvalidIbanView({ error }: State.InvalidIban) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); } -export function ReadyView({ account, balance, balanceIsDebit, limit, payto }: State.Ready): VNode<{}> { +const IS_PUBLIC_ACCOUNT_ENABLED = false + +function ImportantMessage(): VNode { const { i18n } = useTranslationContext(); - return -
-

- - Welcome, {account} ({payto.iban})! stringifyPaytoUri(payto)} /> - -

-
+ return
+
+
+
+
+
+

+ + Welcome, "account" + -

-
-

{i18n.str`Bank account balance`}

- {!balance ? ( -
- Waiting server response... +

+

+ {bankUiSettings.showDemoNav && +

+ {IS_PUBLIC_ACCOUNT_ENABLED ? ( + + This part of the demo shows how a bank that supports Taler + directly would work. In addition to using your own bank + account, you can also see the transaction history of some{" "} + Public Accounts. + + ) : ( + + This part of the demo shows how a bank that supports Taler + directly would work. + + )} +

+ }

+
- ) : ( -
- {balanceIsDebit ? - : null} - {`${Amounts.stringifyValue(balance)}`} -   - {`${balance.currency}`} -
- )} -
- -
-
-

{i18n.str`Payments`}

- +
+
+ +
- +
+
-
-
-

{i18n.str`Latest transactions`}

- -
-
+} + +export function ReadyView({ account, balance, balanceIsDebit, limit, payto }: State.Ready): VNode<{}> { + const { i18n } = useTranslationContext(); + return + + ; } diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index dc61f1302..5b6d95ade 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { Logger, TranslatedString } from "@gnu-taler/taler-util"; +import { Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { StateUpdater, useEffect, useState } from "preact/hooks"; @@ -25,6 +25,7 @@ import { useBusinessAccountDetails } from "../hooks/circuit.js"; import { bankUiSettings } from "../settings.js"; import { useSettings } from "../hooks/settings.js"; import { ErrorMessage, onNotificationUpdate } from "../hooks/notification.js"; +import { CopyButton, CopyIcon } from "../components/CopyButton.js"; const IS_PUBLIC_ACCOUNT_ENABLED = false; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; @@ -70,6 +71,7 @@ export function BankFrame({ const { i18n } = useTranslationContext(); const backend = useBackendContext(); const [settings, updateSettings] = useSettings(); + const [open, setOpen] = useState(false) const demo_sites = []; for (const i in bankUiSettings.demoSites) @@ -79,83 +81,165 @@ export function BankFrame({ , ); - return ( - -
- -
-

- - {bankUiSettings.bankName} - -

- {maybeDemoContent( -

- {IS_PUBLIC_ACCOUNT_ENABLED ? ( - - This part of the demo shows how a bank that supports Taler - directly would work. In addition to using your own bank - account, you can also see the transaction history of some{" "} - Public Accounts. - - ) : ( - - This part of the demo shows how a bank that supports Taler - directly would work. - - )} -

, - )} + return (
+
+
- -
- - {children} -
- -
+ +
+ +
+
+
+ {/* */} + {children} +
+
+
+ +
+
+ + // + //
+ // + //
+ //

+ // + // {bankUiSettings.bankName} + // + //

+ // {maybeDemoContent( + //

+ // {IS_PUBLIC_ACCOUNT_ENABLED ? ( + // + // This part of the demo shows how a bank that supports Taler + // directly would work. In addition to using your own bank + // account, you can also see the transaction history of some{" "} + // Public Accounts. + // + // ) : ( + // + // This part of the demo shows how a bank that supports Taler + // directly would work. + // + // )} + //

, + // )} + //
+ //
+ // + //
+ // + // {children} + //
+ // + //
); } @@ -290,7 +374,7 @@ function TestingTag(): VNode { const testingUrl = localStorage.getItem("bank-base-url"); if (!testingUrl) return ; return ( - +

Testing with {testingUrl}{" "} stop testing - +

); } + +function Footer() { + return ( +
+
+
+

+ You can learn more about GNU Taler on our{" "} + main website. +

+
+
+

+ Copyright © 2014—2022 Taler Systems SA. {versionText}{" "} + +

+
+
+ ); +} + +function WelcomeAccount(): VNode { + const { i18n } = useTranslationContext(); + const account = "Sebastian" + const payto: PaytoUriIBAN = parsePaytoUri("payto://iban/bank.localhost/DE955922") as PaytoUriIBAN + return + Welcome, {account} ({payto.iban})! stringifyPaytoUri(payto)} /> + + +} \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index 3552da7b4..cf3f41deb 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -31,11 +31,77 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode { const { i18n } = useTranslationContext(); const [settings, updateSettings] = useSettings(); - const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">( - "charge-wallet", - ); + const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined); - return ( + return (
+ + Send money to + + +
+ {/* */} + + + + {/* */} + +
+ {tab === "charge-wallet" && ( + { + updateSettings("currentWithdrawalOperationId", id); + }} + /> + )} + {tab === "wire-transfer" && ( + { + notifyInfo(i18n.str`Wire transfer created!`); + }} + /> + )} + +
) + {/* return (
@@ -56,31 +122,7 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode { {i18n.str`Wire transfer`}
- {tab === "charge-wallet" && ( -
-

{i18n.str`Obtain digital cash`}

- { - updateSettings("currentWithdrawalOperationId", id); - }} - /> -
- )} - {tab === "wire-transfer" && ( -
-

{i18n.str`Transfer to bank account`}

- { - notifyInfo(i18n.str`Wire transfer created!`); - }} - /> -
- )}
- ); + ); */} } diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index d16dc70f8..1107360bd 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -17,26 +17,24 @@ import { AmountJson, Amounts, - buildPayto, HttpStatusCode, Logger, - parsePaytoUri, - stringifyPaytoUri, + parsePaytoUri } from "@gnu-taler/taler-util"; import { RequestError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; +import { h, VNode, Fragment } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; -import { notifyError } from "../hooks/notification.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { useAccessAPI } from "../hooks/access.js"; +import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage, undefinedIfEmpty, validateIBAN, } from "../utils.js"; -import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; const logger = new Logger("PaytoWireTransferForm"); @@ -72,293 +70,322 @@ export function PaytoWireTransferForm({ iban: !iban ? i18n.str`Missing IBAN` : !IBAN_REGEX.test(iban) - ? i18n.str`IBAN should have just uppercased letters and numbers` - : validateIBAN(iban, i18n), + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(iban, i18n), subject: !subject ? i18n.str`Missing subject` : undefined, amount: !trimmedAmountStr ? i18n.str`Missing amount` : !parsedAmount - ? i18n.str`Amount is not valid` - : Amounts.isZero(parsedAmount) - ? i18n.str`Should be greater than 0` - : Amounts.cmp(limit, parsedAmount) === -1 - ? i18n.str`balance is not enough` - : undefined, + ? i18n.str`Amount is not valid` + : Amounts.isZero(parsedAmount) + ? i18n.str`Should be greater than 0` + : Amounts.cmp(limit, parsedAmount) === -1 + ? i18n.str`balance is not enough` + : undefined, }); const { createTransaction } = useAccessAPI(); - if (!isRawPayto) - return ( -
-
{ - e.preventDefault(); - }} - autoCapitalize="none" - autoCorrect="off" - > -   - { - setIban(e.currentTarget.value); - }} - /> - -   - { - setSubject(e.currentTarget.value); - }} - /> - -   -
- - { - setAmount(e.currentTarget.value); - }} - /> -
- -

- { - e.preventDefault(); - if (!(iban && subject && amount)) { - return; - } - const ibanPayto = buildPayto("iban", iban, undefined); - ibanPayto.params.message = encodeURIComponent(subject); - const paytoUri = stringifyPaytoUri(ibanPayto); - - try { - await createTransaction({ - paytoUri, - amount: `${limit.currency}:${amount}`, - }); - onSuccess(); - setAmount(undefined); - setIban(undefined); - setSubject(undefined); - } catch (error) { - if (error instanceof RequestError) { - notifyError( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.BadRequest - ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.` - : undefined, - }), - ); - } else { - notifyError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); - } - } - }} - /> - { - e.preventDefault(); - setAmount(undefined); - setIban(undefined); - setSubject(undefined); - }} - /> -

- -

- { - setIsRawPayto(true); - e.preventDefault(); - }} - > - {i18n.str`Want to try the raw payto://-format?`} - -

-
- ); - const parsed = !rawPaytoInput ? undefined : parsePaytoUri(rawPaytoInput); const errorsPayto = undefinedIfEmpty({ rawPaytoInput: !rawPaytoInput ? i18n.str`required` : !parsed - ? i18n.str`does not follow the pattern` - : !parsed.params.amount - ? i18n.str`use the "amount" parameter to specify the amount to be transferred` - : Amounts.parse(parsed.params.amount) === undefined - ? i18n.str`the amount is not valid` - : !parsed.params.message - ? i18n.str`use the "message" parameter to specify a reference text for the transfer` - : !parsed.isKnown || parsed.targetType !== "iban" - ? i18n.str`only "IBAN" target are supported` - : !IBAN_REGEX.test(parsed.iban) - ? i18n.str`IBAN should have just uppercased letters and numbers` - : validateIBAN(parsed.iban, i18n), + ? i18n.str`does not follow the pattern` + : !parsed.params.amount + ? i18n.str`use the "amount" parameter to specify the amount to be transferred` + : Amounts.parse(parsed.params.amount) === undefined + ? i18n.str`the amount is not valid` + : !parsed.params.message + ? i18n.str`use the "message" parameter to specify a reference text for the transfer` + : !parsed.isKnown || parsed.targetType !== "iban" + ? i18n.str`only "IBAN" target are supported` + : !IBAN_REGEX.test(parsed.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(parsed.iban, i18n), }); + // if (!isRawPayto) { + return (
+
+

Transfer details

+
+
+ {/* */} + - return ( -
-

{i18n.str`Transfer money to account identified by payto:// URI:`}

-
{ - e.preventDefault(); - }} - autoCapitalize="none" - autoCorrect="off" - > -

-   - { - rawPaytoInputSetter(e.currentTarget.value); - }} - /> - -
-

- Hint: - - payto://iban/[receiver-iban]?message=[subject]&amount=[ - {limit.currency} - :X.Y] - -
-

-

- { - if (!rawPaytoInput) { - logger.error("Didn't get any raw Payto string!"); - return; - } - try { - await createTransaction({ - paytoUri: rawPaytoInput, - }); - onSuccess(); - rawPaytoInputSetter(undefined); - } catch (error) { - if (error instanceof RequestError) { - notifyError( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.BadRequest - ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.` - : undefined, - }), - ); - } else { - notifyError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); - } - } - }} - /> -

-

- { - setIsRawPayto(false); - }} - > - {i18n.str`Use wire-transfer form?`} - -

-
+ {/* */} + +
+
- ); + +
+
+
+ {!isRawPayto ? + + +
+ +
+ { + setIban(e.currentTarget.value); + }} + /> + +
+

the receiver of the money

+
+ +
+
+ +
+ +
+ + { + setSubject(e.currentTarget.value); + }} + /> + +
+

some text to identify the transfer

+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+
: + +
+ +
+ { + rawPaytoInputSetter(e.currentTarget.value); + }} + /> + +
+
+ +
+ } +
+
+
+ + +
+
+
+ ) + // } + // return ( + //
+ //
{ + // e.preventDefault(); + // }} + // autoCapitalize="none" + // autoCorrect="off" + // > + //   + + //   + + //   + //
+ // + // { + // setAmount(e.currentTarget.value); + // }} + // /> + //
+ // + //

+ // { + // e.preventDefault(); + // if (!(iban && subject && amount)) { + // return; + // } + // const ibanPayto = buildPayto("iban", iban, undefined); + // ibanPayto.params.message = encodeURIComponent(subject); + // const paytoUri = stringifyPaytoUri(ibanPayto); + + // try { + // await createTransaction({ + // paytoUri, + // amount: `${limit.currency}:${amount}`, + // }); + // onSuccess(); + // setAmount(undefined); + // setIban(undefined); + // setSubject(undefined); + // } catch (error) { + // if (error instanceof RequestError) { + // notifyError( + // buildRequestErrorMessage(i18n, error.cause, { + // onClientError: (status) => + // status === HttpStatusCode.BadRequest + // ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.` + // : undefined, + // }), + // ); + // } else { + // notifyError({ + // title: i18n.str`Operation failed, please report`, + // description: + // error instanceof Error + // ? error.message + // : JSON.stringify(error), + // }); + // } + // } + // }} + // /> + // { + // e.preventDefault(); + // setAmount(undefined); + // setIban(undefined); + // setSubject(undefined); + // }} + // /> + //

+ // + //

+ // { + // setIsRawPayto(true); + // e.preventDefault(); + // }} + // > + // {i18n.str`Want to try the raw payto://-format?`} + // + //

+ //
+ // ); + + + + // return ( + //
+ //

{i18n.str`Transfer money to account identified by payto:// URI:`}

+ + //
+ // ); } diff --git a/packages/demobank-ui/src/pages/Test.tsx b/packages/demobank-ui/src/pages/Test.tsx new file mode 100644 index 000000000..874f7fe68 --- /dev/null +++ b/packages/demobank-ui/src/pages/Test.tsx @@ -0,0 +1,5 @@ +import { VNode, h } from "preact"; + +export function Test(): VNode { + return
hola
+} \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index 83be99d6f..da624f61b 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -65,10 +65,10 @@ export function WalletWithdrawForm({ trimmedAmountStr == null ? i18n.str`required` : !parsedAmount - ? i18n.str`invalid` - : Amounts.cmp(limit, parsedAmount) === -1 - ? i18n.str`balance is not enough` - : undefined, + ? i18n.str`invalid` + : Amounts.cmp(limit, parsedAmount) === -1 + ? i18n.str`balance is not enough` + : undefined, }); return ( diff --git a/packages/demobank-ui/src/scss/DurationPicker.scss b/packages/demobank-ui/src/scss/DurationPicker.scss deleted file mode 100644 index aa75b9916..000000000 --- a/packages/demobank-ui/src/scss/DurationPicker.scss +++ /dev/null @@ -1,70 +0,0 @@ -.rdp-picker { - display: flex; - height: 175px; -} - -@media (max-width: 400px) { - .rdp-picker { - width: 250px; - } -} - -.rdp-masked-div { - overflow: hidden; - height: 175px; - position: relative; -} - -.rdp-column-container { - flex-grow: 1; - display: inline-block; -} - -.rdp-column { - position: absolute; - z-index: 0; - width: 100%; -} - -.rdp-reticule { - border: 0; - border-top: 2px solid rgba(109, 202, 236, 1); - height: 2px; - position: absolute; - width: 80%; - margin: 0; - z-index: 100; - left: 50%; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); -} - -.rdp-text-overlay { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - height: 35px; - font-size: 20px; - left: 50%; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); -} - -.rdp-cell div { - font-size: 17px; - color: gray; - font-style: italic; -} - -.rdp-cell { - display: flex; - align-items: center; - justify-content: center; - height: 35px; - font-size: 18px; -} - -.rdp-center { - font-size: 25px; -} diff --git a/packages/demobank-ui/src/scss/_aside.scss b/packages/demobank-ui/src/scss/_aside.scss deleted file mode 100644 index 11809990b..000000000 --- a/packages/demobank-ui/src/scss/_aside.scss +++ /dev/null @@ -1,128 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -html { - &.has-aside-left { - &.has-aside-expanded { - nav.navbar, - body { - padding-left: $aside-width; - } - } - aside.is-placed-left { - display: block; - } - } -} - -aside.aside.is-expanded { - width: $aside-width; - - .menu-list { - @include icon-with-update-mark($aside-icon-width); - - span.menu-item-label { - display: inline-block; - } - - li.is-active { - ul { - display: block; - } - background-color: $body-background-color; - } - } -} - -aside.aside { - display: none; - position: fixed; - top: 0; - left: 0; - z-index: 40; - height: 100vh; - padding: 0; - box-shadow: $aside-box-shadow; - background: $aside-background-color; - - .aside-tools { - display: flex; - flex-direction: row; - width: 100%; - background-color: $aside-tools-background-color; - color: $aside-tools-color; - line-height: $navbar-height; - height: $navbar-height; - padding-left: $default-padding * 0.5; - flex: 1; - - .icon { - margin-right: $default-padding * 0.5; - } - } - - .menu-list { - li { - a { - &.has-dropdown-icon { - position: relative; - padding-right: $aside-icon-width; - - .dropdown-icon { - position: absolute; - top: $size-base * 0.5; - right: 0; - } - } - } - ul { - display: none; - border-left: 0; - background-color: darken($base-color, 2.5%); - padding-left: 0; - margin: 0 0 $default-padding * 0.5; - - li { - a { - padding: $default-padding * 0.5 0 $default-padding * 0.5 - $default-padding * 0.5; - font-size: $aside-submenu-font-size; - - &.has-icon { - padding-left: 0; - } - &.is-active { - &:not(:hover) { - background: transparent; - } - } - } - } - } - } - } - - .menu-label { - padding: 0 $default-padding * 0.5; - margin-top: $default-padding * 0.5; - margin-bottom: $default-padding * 0.5; - } -} diff --git a/packages/demobank-ui/src/scss/_card.scss b/packages/demobank-ui/src/scss/_card.scss deleted file mode 100644 index 3f71aeb6a..000000000 --- a/packages/demobank-ui/src/scss/_card.scss +++ /dev/null @@ -1,69 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -.card:not(:last-child) { - margin-bottom: $default-padding; -} - -.card { - border-radius: $radius-large; - border: $card-border; - - &.has-table { - .card-content { - padding: 0; - } - .b-table { - border-radius: $radius-large; - overflow: hidden; - } - } - - &.is-card-widget { - .card-content { - padding: $default-padding * 0.5; - } - } - - .card-header { - border-bottom: 1px solid $base-color-light; - } - - .card-content { - hr { - margin-left: $card-content-padding * -1; - margin-right: $card-content-padding * -1; - } - } - - .is-widget-icon { - .icon { - width: 5rem; - height: 5rem; - } - } - - .is-widget-label { - .subtitle { - color: $grey; - } - } -} diff --git a/packages/demobank-ui/src/scss/_custom-calendar.scss b/packages/demobank-ui/src/scss/_custom-calendar.scss deleted file mode 100644 index 463cd88d3..000000000 --- a/packages/demobank-ui/src/scss/_custom-calendar.scss +++ /dev/null @@ -1,263 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -:root { - --primary-color: #3298dc; - - --primary-text-color-dark: rgba(0, 0, 0, 0.87); - --secondary-text-color-dark: rgba(0, 0, 0, 0.57); - --disabled-text-color-dark: rgba(0, 0, 0, 0.13); - - --primary-text-color-light: rgba(255, 255, 255, 0.87); - --secondary-text-color-light: rgba(255, 255, 255, 0.57); - --disabled-text-color-light: rgba(255, 255, 255, 0.13); - - --font-stack: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; - - --primary-card-color: #fff; - --primary-background-color: #f2f2f2; - - --box-shadow-lvl-1: 0 1px 3px rgba(0, 0, 0, 0.12), - 0 1px 2px rgba(0, 0, 0, 0.24); - --box-shadow-lvl-2: 0 3px 6px rgba(0, 0, 0, 0.16), - 0 3px 6px rgba(0, 0, 0, 0.23); - --box-shadow-lvl-3: 0 10px 20px rgba(0, 0, 0, 0.19), - 0 6px 6px rgba(0, 0, 0, 0.23); - --box-shadow-lvl-4: 0 14px 28px rgba(0, 0, 0, 0.25), - 0 10px 10px rgba(0, 0, 0, 0.22); -} - -.home .datePicker div { - margin-top: 0px; - margin-bottom: 0px; -} -.datePicker { - text-align: left; - background: var(--primary-card-color); - border-radius: 3px; - z-index: 200; - position: fixed; - height: auto; - max-height: 90vh; - width: 90vw; - max-width: 448px; - transform-origin: top left; - transition: transform 0.22s ease-in-out, opacity 0.22s ease-in-out; - top: 50%; - left: 50%; - opacity: 0; - transform: scale(0) translate(-50%, -50%); - user-select: none; - - &.datePicker--opened { - opacity: 1; - transform: scale(1) translate(-50%, -50%); - } - - .datePicker--titles { - border-top-left-radius: 3px; - border-top-right-radius: 3px; - padding: 24px; - height: 100px; - background: var(--primary-color); - - h2, - h3 { - cursor: pointer; - color: #fff; - line-height: 1; - padding: 0; - margin: 0; - font-size: 32px; - } - - h3 { - color: rgba(255, 255, 255, 0.57); - font-size: 18px; - padding-bottom: 2px; - } - } - - nav { - padding: 20px; - height: 56px; - - h4 { - width: calc(100% - 60px); - text-align: center; - display: inline-block; - padding: 0; - font-size: 14px; - line-height: 24px; - margin: 0; - position: relative; - top: -9px; - color: var(--primary-text-color); - } - - i { - cursor: pointer; - color: var(--secondary-text-color); - font-size: 26px; - user-select: none; - border-radius: 50%; - - &:hover { - background: var(--disabled-text-color-dark); - } - } - } - - .datePicker--scroll { - overflow-y: auto; - max-height: calc(90vh - 56px - 100px); - } - - .datePicker--calendar { - padding: 0 20px; - - .datePicker--dayNames { - width: 100%; - display: grid; - text-align: center; - - // there's probably a better way to do this, but wanted to try out CSS grid - grid-template-columns: - calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) - calc(100% / 7) calc(100% / 7) calc(100% / 7); - - span { - color: var(--secondary-text-color-dark); - font-size: 14px; - line-height: 42px; - display: inline-grid; - } - } - - .datePicker--days { - width: 100%; - display: grid; - text-align: center; - grid-template-columns: - calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) - calc(100% / 7) calc(100% / 7) calc(100% / 7); - - span { - color: var(--primary-text-color-dark); - line-height: 42px; - font-size: 14px; - display: inline-grid; - transition: color 0.22s; - height: 42px; - position: relative; - cursor: pointer; - user-select: none; - border-radius: 50%; - - &::before { - content: ""; - position: absolute; - z-index: -1; - height: 42px; - width: 42px; - left: calc(50% - 21px); - background: var(--primary-color); - border-radius: 50%; - transition: transform 0.22s, opacity 0.22s; - transform: scale(0); - opacity: 0; - } - - &[disabled="true"] { - cursor: unset; - } - - &.datePicker--today { - font-weight: 700; - } - - &.datePicker--selected { - color: rgba(255, 255, 255, 0.87); - - &:before { - transform: scale(1); - opacity: 1; - } - } - } - } - } - - .datePicker--selectYear { - padding: 0 20px; - display: block; - width: 100%; - text-align: center; - max-height: 362px; - - span { - display: block; - width: 100%; - font-size: 24px; - margin: 20px auto; - cursor: pointer; - - &.selected { - font-size: 42px; - color: var(--primary-color); - } - } - } - - div.datePicker--actions { - width: 100%; - padding: 8px; - text-align: right; - - button { - margin-bottom: 0; - font-size: 15px; - cursor: pointer; - color: var(--primary-text-color); - border: none; - margin-left: 8px; - min-width: 64px; - line-height: 36px; - background-color: transparent; - appearance: none; - padding: 0 16px; - border-radius: 3px; - transition: background-color 0.13s; - - &:hover, - &:focus { - outline: none; - background-color: var(--disabled-text-color-dark); - } - } - } -} - -.datePicker--background { - z-index: 199; - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: rgba(0, 0, 0, 0.52); - animation: fadeIn 0.22s forwards; -} diff --git a/packages/demobank-ui/src/scss/_footer.scss b/packages/demobank-ui/src/scss/_footer.scss deleted file mode 100644 index 112522ed8..000000000 --- a/packages/demobank-ui/src/scss/_footer.scss +++ /dev/null @@ -1,35 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -footer.footer { - .logo { - img { - width: auto; - height: $footer-logo-height; - } - } -} - -@include mobile { - .footer-copyright { - text-align: center; - } -} diff --git a/packages/demobank-ui/src/scss/_form.scss b/packages/demobank-ui/src/scss/_form.scss deleted file mode 100644 index 9d93477fd..000000000 --- a/packages/demobank-ui/src/scss/_form.scss +++ /dev/null @@ -1,71 +0,0 @@ -/* - 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -.field { - &.has-check { - .field-body { - margin-top: $default-padding * 0.125; - } - } - .control { - .mdi-24px.mdi-set, - .mdi-24px.mdi:before { - font-size: inherit; - } - } -} -.upload { - .upload-draggable { - display: block; - } -} - -.input, -.textarea, -select { - box-shadow: none; - - &:focus, - &:active { - box-shadow: none !important; - } -} - -.switch input[type="checkbox"] + .check:before { - box-shadow: none; -} - -.switch, -.b-checkbox.checkbox { - input[type="checkbox"] { - &:focus + .check, - &:focus:checked + .check { - box-shadow: none !important; - } - } -} - -.b-checkbox.checkbox input[type="checkbox"], -.b-radio.radio input[type="radio"] { - & + .check { - border: $checkbox-border; - } -} diff --git a/packages/demobank-ui/src/scss/_hero-bar.scss b/packages/demobank-ui/src/scss/_hero-bar.scss deleted file mode 100644 index 31b7e623e..000000000 --- a/packages/demobank-ui/src/scss/_hero-bar.scss +++ /dev/null @@ -1,55 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -section.hero.is-hero-bar { - background-color: $hero-bar-background; - border-bottom: $light-border; - - .hero-body { - padding: $default-padding; - - .level-item { - &.is-hero-avatar-item { - margin-right: $default-padding; - } - - > div > .level { - margin-bottom: $default-padding * 0.5; - } - - .subtitle + p { - margin-top: $default-padding * 0.5; - } - } - - .button { - &.is-hero-button { - background-color: rgba($white, 0.5); - font-weight: 300; - @include transition(background-color); - - &:hover { - background-color: $white; - } - } - } - } -} diff --git a/packages/demobank-ui/src/scss/_loading.scss b/packages/demobank-ui/src/scss/_loading.scss deleted file mode 100644 index d25bf8048..000000000 --- a/packages/demobank-ui/src/scss/_loading.scss +++ /dev/null @@ -1,51 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -.lds-ring { - display: inline-block; - position: relative; - width: 80px; - height: 80px; -} -.lds-ring div { - box-sizing: border-box; - display: block; - position: absolute; - width: 64px; - height: 64px; - margin: 8px; - border: 8px solid black; - border-radius: 50%; - animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; - border-color: black transparent transparent transparent; -} -.lds-ring div:nth-child(1) { - animation-delay: -0.45s; -} -.lds-ring div:nth-child(2) { - animation-delay: -0.3s; -} -.lds-ring div:nth-child(3) { - animation-delay: -0.15s; -} -@keyframes lds-ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/packages/demobank-ui/src/scss/_main-section.scss b/packages/demobank-ui/src/scss/_main-section.scss deleted file mode 100644 index 01edc24bf..000000000 --- a/packages/demobank-ui/src/scss/_main-section.scss +++ /dev/null @@ -1,24 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -section.section.is-main-section { - padding-top: $default-padding; -} diff --git a/packages/demobank-ui/src/scss/_misc.scss b/packages/demobank-ui/src/scss/_misc.scss deleted file mode 100644 index 65bd28dbd..000000000 --- a/packages/demobank-ui/src/scss/_misc.scss +++ /dev/null @@ -1,50 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -.is-user-avatar { - &.has-max-width { - max-width: $size-base * 7; - } - - &.is-aligned-center { - margin: 0 auto; - } - - img { - margin: 0 auto; - border-radius: $radius-rounded; - } -} - -.icon.has-update-mark { - position: relative; - - &:after { - content: ""; - width: $icon-update-mark-size; - height: $icon-update-mark-size; - position: absolute; - top: 1px; - right: 1px; - background-color: $icon-update-mark-color; - border-radius: $radius-rounded; - } -} diff --git a/packages/demobank-ui/src/scss/_mixins.scss b/packages/demobank-ui/src/scss/_mixins.scss deleted file mode 100644 index b52e590e3..000000000 --- a/packages/demobank-ui/src/scss/_mixins.scss +++ /dev/null @@ -1,34 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -@mixin transition($t) { - transition: $t 250ms ease-in-out 50ms; -} - -@mixin icon-with-update-mark($icon-base-width) { - .icon { - width: $icon-base-width; - - &.has-update-mark:after { - right: ($icon-base-width / 2) - 0.85; - } - } -} diff --git a/packages/demobank-ui/src/scss/_modal.scss b/packages/demobank-ui/src/scss/_modal.scss deleted file mode 100644 index b3a31ebf1..000000000 --- a/packages/demobank-ui/src/scss/_modal.scss +++ /dev/null @@ -1,35 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -.modal-card { - width: $modal-card-width; -} - -.modal-card-foot { - background-color: $modal-card-foot-background-color; -} - -@include mobile { - .modal .animation-content .modal-card { - width: $modal-card-width-mobile; - margin: 0 auto; - } -} diff --git a/packages/demobank-ui/src/scss/_nav-bar.scss b/packages/demobank-ui/src/scss/_nav-bar.scss deleted file mode 100644 index c6dd04263..000000000 --- a/packages/demobank-ui/src/scss/_nav-bar.scss +++ /dev/null @@ -1,144 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -nav.navbar { - box-shadow: $navbar-box-shadow; - - .navbar-item { - &.has-user-avatar { - .is-user-avatar { - margin-right: $default-padding * 0.5; - display: inline-flex; - width: $navbar-avatar-size; - height: $navbar-avatar-size; - } - } - - &.has-divider { - border-right: $navbar-divider-border; - } - - &.no-left-space { - padding-left: 0; - } - - &.has-dropdown { - padding-right: 0; - padding-left: 0; - - .navbar-link { - padding-right: $navbar-item-h-padding; - padding-left: $navbar-item-h-padding; - } - } - - &.has-control { - padding-top: 0; - padding-bottom: 0; - } - - .control { - .input { - color: $navbar-input-color; - border: 0; - box-shadow: none; - background: transparent; - - &::placeholder { - color: $navbar-input-placeholder-color; - } - } - } - } -} - -@include touch { - nav.navbar { - display: flex; - padding-right: 0; - - .navbar-brand { - flex: 1; - - &.is-right { - flex: none; - } - } - - .navbar-item { - &.no-left-space-touch { - padding-left: 0; - } - } - - .navbar-menu { - position: absolute; - width: 100vw; - padding-top: 0; - top: $navbar-height; - left: 0; - - .navbar-item { - .icon:first-child { - margin-right: $default-padding * 0.5; - } - - &.has-dropdown { - > .navbar-link { - background-color: $white-ter; - .icon:last-child { - display: none; - } - } - } - - &.has-user-avatar { - > .navbar-link { - display: flex; - align-items: center; - padding-top: $default-padding * 0.5; - padding-bottom: $default-padding * 0.5; - } - } - } - } - } -} - -@include desktop { - nav.navbar { - .navbar-item { - padding-right: $navbar-item-h-padding; - padding-left: $navbar-item-h-padding; - - &:not(.is-desktop-icon-only) { - .icon:first-child { - margin-right: $default-padding * 0.5; - } - } - &.is-desktop-icon-only { - span:not(.icon) { - display: none; - } - } - } - } -} diff --git a/packages/demobank-ui/src/scss/_table.scss b/packages/demobank-ui/src/scss/_table.scss deleted file mode 100644 index b68d50e4f..000000000 --- a/packages/demobank-ui/src/scss/_table.scss +++ /dev/null @@ -1,179 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -table.table { - thead { - th { - border-bottom-width: 1px; - } - } - - td, - th { - &.checkbox-cell { - .b-checkbox.checkbox:not(.button) { - margin-right: 0; - width: 20px; - - .control-label { - display: none; - padding: 0; - } - } - } - } - - td { - .image { - margin: 0 auto; - width: $table-avatar-size; - height: $table-avatar-size; - } - - &.is-progress-col { - min-width: 5rem; - vertical-align: middle; - } - } -} - -.b-table { - .table { - border: 0; - border-radius: 0; - } - - /* This stylizes buefy's pagination */ - .table-wrapper { - margin-bottom: 0; - } - - .table-wrapper + .level { - padding: $notification-padding; - padding-left: $card-content-padding; - padding-right: $card-content-padding; - margin: 0; - border-top: $base-color-light; - background: $notification-background-color; - - .pagination-link { - background: $button-background-color; - color: $button-color; - border-color: $button-border-color; - - &.is-current { - border-color: $button-active-border-color; - } - } - - .pagination-previous, - .pagination-next, - .pagination-link { - border-color: $button-border-color; - color: $base-color; - - &[disabled] { - background-color: transparent; - } - } - } -} - -@include mobile { - .card { - &.has-table { - .b-table { - .table-wrapper + .level { - .level-left + .level-right { - margin-top: 0; - } - } - } - } - &.has-mobile-sort-spaced { - .b-table { - .field.table-mobile-sort { - padding-top: $default-padding * 0.5; - } - } - } - } - .b-table { - .field.table-mobile-sort { - padding: 0 $default-padding * 0.5; - } - - .table-wrapper.has-mobile-cards { - tr { - box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1); - margin-bottom: 3px !important; - } - td { - &.is-progress-col { - span, - progress { - display: flex; - width: 45%; - align-items: center; - align-self: center; - } - } - - &.checkbox-cell, - &.is-image-cell { - border-bottom: 0 !important; - } - - &.checkbox-cell, - &.is-actions-cell { - &:before { - display: none; - } - } - - &.has-no-head-mobile { - &:before { - display: none; - } - - span { - display: block; - width: 100%; - } - - &.is-progress-col { - progress { - width: 100%; - } - } - - &.is-image-cell { - .image { - width: $table-avatar-size-mobile; - height: auto; - margin: 0 auto $default-padding * 0.25; - } - } - } - } - } - } -} diff --git a/packages/demobank-ui/src/scss/_theme-default.scss b/packages/demobank-ui/src/scss/_theme-default.scss deleted file mode 100644 index 538dfd4da..000000000 --- a/packages/demobank-ui/src/scss/_theme-default.scss +++ /dev/null @@ -1,136 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -/* We'll need some initial vars to use here */ -@import "node_modules/bulma/sass/utilities/initial-variables"; - -/* Base: Size */ -$size-base: 1rem; -$default-padding: $size-base * 1.5; - -/* Default font */ -$family-sans-serif: "Nunito", sans-serif; - -/* Base color */ -$base-color: #2e323a; -$base-color-light: rgba(24, 28, 33, 0.06); - -/* General overrides */ -$primary: $turquoise; -$body-background-color: #f8f8f8; -$link: $blue; -$link-visited: $purple; -$light-border: 1px solid $base-color-light; -$hr-height: 1px; - -/* NavBar: specifics */ -$navbar-input-color: $grey-darker; -$navbar-input-placeholder-color: $grey-lighter; -$navbar-box-shadow: 0 1px 0 rgba(24, 28, 33, 0.04); -$navbar-divider-border: 1px solid rgba($grey-lighter, 0.25); -$navbar-item-h-padding: $default-padding * 0.75; -$navbar-avatar-size: 1.75rem; - -/* Aside: Bulma override */ -$menu-item-radius: 0; -$menu-list-link-padding: $size-base * 0.5 0; -$menu-label-color: lighten($base-color, 25%); -$menu-item-color: lighten($base-color, 30%); -$menu-item-hover-color: $white; -$menu-item-hover-background-color: darken($base-color, 3.5%); -$menu-item-active-color: $white; -$menu-item-active-background-color: darken($base-color, 2.5%); - -/* Aside: specifics */ -$aside-width: $size-base * 14; -$aside-mobile-width: $size-base * 15; -$aside-icon-width: $size-base * 3; -$aside-submenu-font-size: $size-base * 0.95; -$aside-box-shadow: none; -$aside-background-color: $base-color; -$aside-tools-background-color: darken($aside-background-color, 10%); -$aside-tools-color: $white; - -/* Title Bar: specifics */ -$title-bar-color: $grey; -$title-bar-active-color: $black-ter; - -/* Hero Bar: specifics */ -$hero-bar-background: $white; - -/* Card: Bulma override */ -$card-shadow: none; -$card-header-shadow: none; - -/* Card: specifics */ -$card-border: 1px solid $base-color-light; -$card-header-border-bottom-color: $base-color-light; - -/* Table: Bulma override */ -$table-cell-border: 1px solid $white-bis; - -/* Table: specifics */ -$table-avatar-size: $size-base * 1.5; -$table-avatar-size-mobile: 25vw; - -/* Form */ -$checkbox-border: 1px solid $base-color; - -/* Modal card: Bulma override */ -$modal-card-head-background-color: $white-ter; -$modal-card-title-size: $size-base; -$modal-card-body-padding: $default-padding 20px; -$modal-card-head-border-bottom: 1px solid $white-ter; -$modal-card-foot-border-top: 0; - -/* Modal card: specifics */ -$modal-card-width: 80vw; -$modal-card-width-mobile: 90vw; -$modal-card-foot-background-color: $white-ter; - -/* Notification: Bulma override */ -$notification-padding: $default-padding * 0.75 $default-padding; - -/* Footer: Bulma override */ -$footer-background-color: $white; -$footer-padding: $default-padding * 0.33 $default-padding; - -/* Footer: specifics */ -$footer-logo-height: $size-base * 2; - -/* Progress: Bulma override */ -$progress-bar-background-color: $grey-lighter; - -/* Icon: specifics */ -$icon-update-mark-size: $size-base * 0.5; -$icon-update-mark-color: $yellow; - -$input-disabled-border-color: $grey-lighter; -$table-row-hover-background-color: hsl(0, 0%, 80%); - -.menu-list { - div { - border-radius: $menu-item-radius; - color: $menu-item-color; - display: block; - padding: $menu-list-link-padding; - } -} diff --git a/packages/demobank-ui/src/scss/_tiles.scss b/packages/demobank-ui/src/scss/_tiles.scss deleted file mode 100644 index e69d995f0..000000000 --- a/packages/demobank-ui/src/scss/_tiles.scss +++ /dev/null @@ -1,24 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -.is-tiles-wrapper { - margin-bottom: $default-padding; -} diff --git a/packages/demobank-ui/src/scss/_title-bar.scss b/packages/demobank-ui/src/scss/_title-bar.scss deleted file mode 100644 index 932f8e65d..000000000 --- a/packages/demobank-ui/src/scss/_title-bar.scss +++ /dev/null @@ -1,50 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -section.section.is-title-bar { - padding: $default-padding; - border-bottom: $light-border; - - ul { - li { - display: inline-block; - padding: 0 $default-padding * 0.5 0 0; - font-size: $default-padding; - color: $title-bar-color; - - &:after { - display: inline-block; - content: "/"; - padding-left: $default-padding * 0.5; - } - - &:last-child { - padding-right: 0; - font-weight: 900; - color: $title-bar-active-color; - - &:after { - display: none; - } - } - } - } -} diff --git a/packages/demobank-ui/src/scss/bank.scss b/packages/demobank-ui/src/scss/bank.scss deleted file mode 100644 index f8de0a984..000000000 --- a/packages/demobank-ui/src/scss/bank.scss +++ /dev/null @@ -1,353 +0,0 @@ -.navcontainer:not(.default-navcontainer) { - margin-bottom: 0 !important; -} - -.abort-button { - margin-left: 2px; - border: 2px solid rgb(0, 120, 231); - color: rgb(0, 120, 231); - font-size: 87%; - margin-top: 1px; - background: white; -} - -div.pages-list { - margin-top: 15px; -} - -.footer { - margin-left: 2em; - margin-right: 2em; -} - -.qr-div, -.login-div, -.register-div { - display: block; - text-align: center; -} - -a.page-number { - color: blue; -} - -a.current-page-number { - color: inherit; - background-color: inherit; -} - -.cancelled { - text-decoration: line-through; -} - -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -/* This CSS code styles the tab */ -.tab { - overflow: hidden; -} - -.top-right { - float: right; -} -.some-space { - display: inline-block; - border: 20px; - margin-right: 15px; - margin-top: 15px; -} - -.tab button { - background-color: lightgray; - color: black; - float: left; - border: none; - outline: none; - cursor: pointer; - padding: 18px 19px; - border: 2px solid #c1c1c1; - transition: 0.5s; - font-weight: bold; -} - -.tab button:hover { - background-color: yellow; - border: 2px solid #c1c1c1; - color: black; -} - -.tab button.active { - background-color: orange; - border: 2px solid #c1c1c1; - color: black; - font-weight: bold; -} - -.tabcontent { - display: none; - padding: 8px 16px; - border: 2px solid #c1c1c1; - width: min-content; -} - -.tabcontent.active { - display: block; -} - -input[type="number"] { - -moz-appearance: textfield; -} - -#transfer-fields { - display: flex; - flex-wrap: wrap; -} - -#id_amount { - width: 6em; - display: inline-block; - border-radius: 4px 0px 0px 4px; -} - -/** - * Amount without the currency, - * placed left to a .currency-indicator. - */ -#main .amount { - width: 6em; - display: inline-block; - border-radius: 4px 0px 0px 4px; -} - -input { - background-color: inherit; -} - -.large-amount { - font-weight: bold; - font-size: xxx-large; -} - -.currency { - font-style: oblique; -} - -/* - * Currency indicator to the right of input fields, - * with non-rounded corners to the left. - */ -#main .currency-indicator { - color: black; - border-radius: 4px 0px 0px 4px; - position: relative; -} - -#main .fieldlabel { - display: block; - padding-bottom: 0.5em; -} - -#main .fieldbox { - margin-right: 1em; - margin-bottom: 0.5em; -} - -#logout-button { - display: block; - width: fit-content; -} - -.register-form > .pure-form, -.login-form > .pure-form { - background: #4a4a4a; - color: #ffffff; - display: inline-block; - text-align: left; - margin-left: auto; - margin-right: auto; - padding: 16px 16px; - border-radius: 8px; - width: min-content; - .formFieldLabel { - margin: 2px 2px; - } - input[type="text"], - input[type="password"] { - border: none; - border-radius: 4px; - background: #6a6a6a; - color: #fefefe; - box-shadow: none; - } - input[placeholder="Password"][type="password"] { - margin-bottom: 8px; - } - .btn-register, - .btn-login { - float: left; - } - .btn-cancel { - float: right; - } - h2 { - margin-top: 0; - margin-bottom: 10px; - } -} - -.challenge-div { - display: block; - text-align: center; -} - -.challenge-form > .pure-form { - background: #4a4a4a; - color: #ffffff; - display: inline-block; - text-align: left; - margin-left: auto; - margin-right: auto; - padding: 16px 16px; - border-radius: 8px; - width: min-content; - .formFieldLabel { - margin: 2px 2px; - } - input[type="text"] { - border: none; - border-radius: 4px; - background: #6a6a6a; - color: #fefefe; - box-shadow: none; - } - .btn-confirm { - float: left; - } - .btn-cancel { - float: right; - } - h2 { - margin-top: 0; - margin-bottom: 10px; - } -} - -.wire-transfer-form > .pure-form, -.payto-form > .pure-form, -.reserve-form > .pure-form { - background: #4a4a4a; - color: #ffffff; - display: inline-block; - text-align: left; - margin-left: auto; - margin-right: auto; - padding: 16px 16px; - border-radius: 8px; - width: min-content; - .formFieldLabel { - margin: 2px 2px; - } - input[type="text"] { - border: none; - border-radius: 4px; - background: #6a6a6a; - color: #fefefe; - box-shadow: none; - } -} - -html { - background: #ffffff; - color: #2a2a2a; -} - -.hint { - scale: 0.7; -} -h1.nav { - text-align: center; -} - -.pure-form > fieldset > label { - display: block; -} -.pure-form > fieldset > input[disabled] { - color: black !important; -} -.pure-form > fieldset > div > input[disabled] { - color: black !important; -} - -.pure-form > fieldset > div.channel > div { - display: inline-block; - margin: 1em; - border: 1px black solid; - width: fit-content; - padding: 0.4em; - cursor: pointer; -} - -.button-success { - background: rgb(28, 184, 65); - /* this is a green */ -} - -.button-error { - background: rgb(202, 60, 60); - /* this is a maroon */ -} - -.button-warning { - background: rgb(223, 117, 20); - /* this is an orange */ -} - -.button-secondary { - background: rgb(66, 184, 221); - /* this is a light blue */ -} - -[name=wire-transfer-form] > input { - margin-bottom: 1em; - -} - -.lds-ring { - display: inline-block; - position: relative; - width: 80px; - height: 80px; -} -.lds-ring div { - box-sizing: border-box; - display: block; - position: absolute; - width: 64px; - height: 64px; - margin: 8px; - border: 8px solid black; - border-radius: 50%; - animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; - border-color: black transparent transparent transparent; -} -.lds-ring div:nth-child(1) { - animation-delay: -0.45s; -} -.lds-ring div:nth-child(2) { - animation-delay: -0.3s; -} -.lds-ring div:nth-child(3) { - animation-delay: -0.15s; -} -@keyframes lds-ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/packages/demobank-ui/src/scss/colors-bank.scss b/packages/demobank-ui/src/scss/colors-bank.scss deleted file mode 100644 index e11bbe203..000000000 --- a/packages/demobank-ui/src/scss/colors-bank.scss +++ /dev/null @@ -1,31 +0,0 @@ -nav, -nav a, -nav span, -.navcontainer, -nav button, -.demobar, -.navbtn { - color: white; - background: #a00000; -} - -nav a.active, -nav button, -nav span.active, -.navbtn.active { - background-color: #7a0606; -} - -nav a.active:hover, -nav span.active:hover, -.navbtn.active:hover, -nav button:hover, -nav a:hover, -nav span:hover, -.navbtn:hover { - background: #df3d3d; -} - -nav a.navbtn.langbtn:focus { - background-color: #df3d3d; -} diff --git a/packages/demobank-ui/src/scss/demo.scss b/packages/demobank-ui/src/scss/demo.scss deleted file mode 100644 index c2d9fa903..000000000 --- a/packages/demobank-ui/src/scss/demo.scss +++ /dev/null @@ -1,167 +0,0 @@ -@charset "UTF-8"; -/* -Style common to all demo pages. - -Colors: -- #1e2739 (dark blue) -- #0042b2 (default blue) -- #3daee9 (highlight blue) -*/ - -.demobar h1 { - text-align: center; -} - -.demobar > p { - padding: 0.5em; -} - -.demobar a, -.demobar a:visited { - color: inherit; - background-color: inherit; -} - -.tt { - font-family: "Lucida Console", Monaco, monospace; -} - -.informational-ok { - background: lightgreen; - border-radius: 1em; - padding: 0.5em; -} - -.informational-fail { - background: lightpink; - border-radius: 1em; - padding: 0.5em; -} - -.content { - margin-left: 1em; - margin-right: 1em; - overflow-x: auto; -} - -.demobar { - overflow-x: auto; - background-color: #0042b2; - color: white; -} - -body { - overflow-x: hidden; - overflow-y: auto; -} - -.navcontainer { - background: #0042b2; - margin-bottom: 50px; - width: 100%; - color: white; - // position: -webkit-sticky; - // position: sticky; - top: 0px; - width: 100vw; - backdrop-filter: blur(10px); - opacity: 1; - z-index: 100; -} - -nav { - // left: 1vw; - position: relative; - background: #0042b2; - z-index: 100; -} - -nav a, -nav button, -nav span, -.navbtn { - border: none; - color: white; - text-align: center; - // text-decoration: none; - display: inline-block; - font-size: 16px; - background: #0042b2; - height: inherit; -} - -nav a, -nav button, -nav span, -.navbtn { - padding: 8px; -} - - -nav a:hover, -nav span:hover, -.navbtn:hover { - background: #3daee9; -} - -nav a.active, -nav span.active, -.navbtn.active { - background-color: #1e2739; -} - -nav a.active:hover, -nav button.active:hover, -nav span.active:hover, -.navbtn.active:hover { - background: #3daee9; -} - -nav a, -nav span, -.navbtn { - cursor: pointer; -} - -nav .right { - float: right; - margin-right: 5vw; -} -nav .hide div.nav { - display: none; -} -// nav .right div.nav:hover { -// display: block; -// } - -// nav .right:hover div.nav { -// display: block; -// } - -.langbtn { - width: 100px; - text-align: left; -} - -.skip { - position: absolute; - left: -10000px; - top: auto; - width: 1px; - height: 1px; - overflow: hidden; -} - -.skip:focus { - position: static; - width: auto; - height: auto; -} - -.demolist > a { - margin: 8px; -} - -.buttons-account input.pure-button { - margin: 8px; -} \ No newline at end of file diff --git a/packages/demobank-ui/src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf b/packages/demobank-ui/src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf deleted file mode 100644 index 7665ee336..000000000 Binary files a/packages/demobank-ui/src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf and /dev/null differ diff --git a/packages/demobank-ui/src/scss/fonts/nunito.css b/packages/demobank-ui/src/scss/fonts/nunito.css deleted file mode 100644 index 8d45df9a1..000000000 --- a/packages/demobank-ui/src/scss/fonts/nunito.css +++ /dev/null @@ -1,22 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -@font-face { - font-family: "Nunito"; - font-style: normal; - font-weight: 400; - src: url(./XRXV3I6Li01BKofINeaE.ttf) format("truetype"); -} diff --git a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.eot b/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.eot deleted file mode 100644 index ab6b25ded..000000000 Binary files a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.eot and /dev/null differ diff --git a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.ttf b/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.ttf deleted file mode 100644 index 824be10fa..000000000 Binary files a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.ttf and /dev/null differ diff --git a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff b/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff deleted file mode 100644 index 7e087c1de..000000000 Binary files a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff and /dev/null differ diff --git a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff2 b/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff2 deleted file mode 100644 index b5caa4ddc..000000000 Binary files a/packages/demobank-ui/src/scss/icons/fonts/materialdesignicons-webfont-4.9.95.woff2 and /dev/null differ diff --git a/packages/demobank-ui/src/scss/icons/materialdesignicons-4.9.95.min.css b/packages/demobank-ui/src/scss/icons/materialdesignicons-4.9.95.min.css deleted file mode 100644 index 2b8a2b244..000000000 --- a/packages/demobank-ui/src/scss/icons/materialdesignicons-4.9.95.min.css +++ /dev/null @@ -1,15109 +0,0 @@ -@font-face { - font-family: "Material Design Icons"; - src: url("./fonts/materialdesignicons-webfont-4.9.95.eot"); - src: url("./fonts/materialdesignicons-webfont-4.9.95.woff2") format("woff2"), - url("./fonts/materialdesignicons-webfont-4.9.95.woff") format("woff"), - url("./fonts/materialdesignicons-webfont-4.9.95.ttf") format("truetype"); - font-weight: normal; - font-style: normal; -} -.mdi:before, -.mdi-set { - display: inline-block; - font: normal normal normal 24px/1 "Material Design Icons"; - font-size: inherit; - text-rendering: auto; - line-height: inherit; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -.mdi-ab-testing::before { - content: "\F001C"; -} -.mdi-abjad-arabic::before { - content: "\F0353"; -} -.mdi-abjad-hebrew::before { - content: "\F0354"; -} -.mdi-abugida-devanagari::before { - content: "\F0355"; -} -.mdi-abugida-thai::before { - content: "\F0356"; -} -.mdi-access-point::before { - content: "\F002"; -} -.mdi-access-point-network::before { - content: "\F003"; -} -.mdi-access-point-network-off::before { - content: "\FBBD"; -} -.mdi-account::before { - content: "\F004"; -} -.mdi-account-alert::before { - content: "\F005"; -} -.mdi-account-alert-outline::before { - content: "\FB2C"; -} -.mdi-account-arrow-left::before { - content: "\FB2D"; -} -.mdi-account-arrow-left-outline::before { - content: "\FB2E"; -} -.mdi-account-arrow-right::before { - content: "\FB2F"; -} -.mdi-account-arrow-right-outline::before { - content: "\FB30"; -} -.mdi-account-badge::before { - content: "\FD83"; -} -.mdi-account-badge-alert::before { - content: "\FD84"; -} -.mdi-account-badge-alert-outline::before { - content: "\FD85"; -} -.mdi-account-badge-horizontal::before { - content: "\FDF0"; -} -.mdi-account-badge-horizontal-outline::before { - content: "\FDF1"; -} -.mdi-account-badge-outline::before { - content: "\FD86"; -} -.mdi-account-box::before { - content: "\F006"; -} -.mdi-account-box-multiple::before { - content: "\F933"; -} -.mdi-account-box-multiple-outline::before { - content: "\F002C"; -} -.mdi-account-box-outline::before { - content: "\F007"; -} -.mdi-account-cancel::before { - content: "\F030A"; -} -.mdi-account-cancel-outline::before { - content: "\F030B"; -} -.mdi-account-card-details::before { - content: "\F5D2"; -} -.mdi-account-card-details-outline::before { - content: "\FD87"; -} -.mdi-account-cash::before { - content: "\F00C2"; -} -.mdi-account-cash-outline::before { - content: "\F00C3"; -} -.mdi-account-check::before { - content: "\F008"; -} -.mdi-account-check-outline::before { - content: "\FBBE"; -} -.mdi-account-child::before { - content: "\FA88"; -} -.mdi-account-child-circle::before { - content: "\FA89"; -} -.mdi-account-child-outline::before { - content: "\F00F3"; -} -.mdi-account-circle::before { - content: "\F009"; -} -.mdi-account-circle-outline::before { - content: "\FB31"; -} -.mdi-account-clock::before { - content: "\FB32"; -} -.mdi-account-clock-outline::before { - content: "\FB33"; -} -.mdi-account-cog::before { - content: "\F039B"; -} -.mdi-account-cog-outline::before { - content: "\F039C"; -} -.mdi-account-convert::before { - content: "\F00A"; -} -.mdi-account-convert-outline::before { - content: "\F032C"; -} -.mdi-account-details::before { - content: "\F631"; -} -.mdi-account-details-outline::before { - content: "\F039D"; -} -.mdi-account-edit::before { - content: "\F6BB"; -} -.mdi-account-edit-outline::before { - content: "\F001D"; -} -.mdi-account-group::before { - content: "\F848"; -} -.mdi-account-group-outline::before { - content: "\FB34"; -} -.mdi-account-heart::before { - content: "\F898"; -} -.mdi-account-heart-outline::before { - content: "\FBBF"; -} -.mdi-account-key::before { - content: "\F00B"; -} -.mdi-account-key-outline::before { - content: "\FBC0"; -} -.mdi-account-lock::before { - content: "\F0189"; -} -.mdi-account-lock-outline::before { - content: "\F018A"; -} -.mdi-account-minus::before { - content: "\F00D"; -} -.mdi-account-minus-outline::before { - content: "\FAEB"; -} -.mdi-account-multiple::before { - content: "\F00E"; -} -.mdi-account-multiple-check::before { - content: "\F8C4"; -} -.mdi-account-multiple-check-outline::before { - content: "\F0229"; -} -.mdi-account-multiple-minus::before { - content: "\F5D3"; -} -.mdi-account-multiple-minus-outline::before { - content: "\FBC1"; -} -.mdi-account-multiple-outline::before { - content: "\F00F"; -} -.mdi-account-multiple-plus::before { - content: "\F010"; -} -.mdi-account-multiple-plus-outline::before { - content: "\F7FF"; -} -.mdi-account-multiple-remove::before { - content: "\F0235"; -} -.mdi-account-multiple-remove-outline::before { - content: "\F0236"; -} -.mdi-account-network::before { - content: "\F011"; -} -.mdi-account-network-outline::before { - content: "\FBC2"; -} -.mdi-account-off::before { - content: "\F012"; -} -.mdi-account-off-outline::before { - content: "\FBC3"; -} -.mdi-account-outline::before { - content: "\F013"; -} -.mdi-account-plus::before { - content: "\F014"; -} -.mdi-account-plus-outline::before { - content: "\F800"; -} -.mdi-account-question::before { - content: "\FB35"; -} -.mdi-account-question-outline::before { - content: "\FB36"; -} -.mdi-account-remove::before { - content: "\F015"; -} -.mdi-account-remove-outline::before { - content: "\FAEC"; -} -.mdi-account-search::before { - content: "\F016"; -} -.mdi-account-search-outline::before { - content: "\F934"; -} -.mdi-account-settings::before { - content: "\F630"; -} -.mdi-account-settings-outline::before { - content: "\F00F4"; -} -.mdi-account-star::before { - content: "\F017"; -} -.mdi-account-star-outline::before { - content: "\FBC4"; -} -.mdi-account-supervisor::before { - content: "\FA8A"; -} -.mdi-account-supervisor-circle::before { - content: "\FA8B"; -} -.mdi-account-supervisor-outline::before { - content: "\F0158"; -} -.mdi-account-switch::before { - content: "\F019"; -} -.mdi-account-tie::before { - content: "\FCBF"; -} -.mdi-account-tie-outline::before { - content: "\F00F5"; -} -.mdi-account-tie-voice::before { - content: "\F0333"; -} -.mdi-account-tie-voice-off::before { - content: "\F0335"; -} -.mdi-account-tie-voice-off-outline::before { - content: "\F0336"; -} -.mdi-account-tie-voice-outline::before { - content: "\F0334"; -} -.mdi-accusoft::before { - content: "\F849"; -} -.mdi-adjust::before { - content: "\F01A"; -} -.mdi-adobe::before { - content: "\F935"; -} -.mdi-adobe-acrobat::before { - content: "\FFBD"; -} -.mdi-air-conditioner::before { - content: "\F01B"; -} -.mdi-air-filter::before { - content: "\FD1F"; -} -.mdi-air-horn::before { - content: "\FD88"; -} -.mdi-air-humidifier::before { - content: "\F00C4"; -} -.mdi-air-purifier::before { - content: "\FD20"; -} -.mdi-airbag::before { - content: "\FBC5"; -} -.mdi-airballoon::before { - content: "\F01C"; -} -.mdi-airballoon-outline::before { - content: "\F002D"; -} -.mdi-airplane::before { - content: "\F01D"; -} -.mdi-airplane-landing::before { - content: "\F5D4"; -} -.mdi-airplane-off::before { - content: "\F01E"; -} -.mdi-airplane-takeoff::before { - content: "\F5D5"; -} -.mdi-airplay::before { - content: "\F01F"; -} -.mdi-airport::before { - content: "\F84A"; -} -.mdi-alarm::before { - content: "\F020"; -} -.mdi-alarm-bell::before { - content: "\F78D"; -} -.mdi-alarm-check::before { - content: "\F021"; -} -.mdi-alarm-light::before { - content: "\F78E"; -} -.mdi-alarm-light-outline::before { - content: "\FBC6"; -} -.mdi-alarm-multiple::before { - content: "\F022"; -} -.mdi-alarm-note::before { - content: "\FE8E"; -} -.mdi-alarm-note-off::before { - content: "\FE8F"; -} -.mdi-alarm-off::before { - content: "\F023"; -} -.mdi-alarm-plus::before { - content: "\F024"; -} -.mdi-alarm-snooze::before { - content: "\F68D"; -} -.mdi-album::before { - content: "\F025"; -} -.mdi-alert::before { - content: "\F026"; -} -.mdi-alert-box::before { - content: "\F027"; -} -.mdi-alert-box-outline::before { - content: "\FCC0"; -} -.mdi-alert-circle::before { - content: "\F028"; -} -.mdi-alert-circle-check::before { - content: "\F0218"; -} -.mdi-alert-circle-check-outline::before { - content: "\F0219"; -} -.mdi-alert-circle-outline::before { - content: "\F5D6"; -} -.mdi-alert-decagram::before { - content: "\F6BC"; -} -.mdi-alert-decagram-outline::before { - content: "\FCC1"; -} -.mdi-alert-octagon::before { - content: "\F029"; -} -.mdi-alert-octagon-outline::before { - content: "\FCC2"; -} -.mdi-alert-octagram::before { - content: "\F766"; -} -.mdi-alert-octagram-outline::before { - content: "\FCC3"; -} -.mdi-alert-outline::before { - content: "\F02A"; -} -.mdi-alert-rhombus::before { - content: "\F01F9"; -} -.mdi-alert-rhombus-outline::before { - content: "\F01FA"; -} -.mdi-alien::before { - content: "\F899"; -} -.mdi-alien-outline::before { - content: "\F00F6"; -} -.mdi-align-horizontal-center::before { - content: "\F01EE"; -} -.mdi-align-horizontal-left::before { - content: "\F01ED"; -} -.mdi-align-horizontal-right::before { - content: "\F01EF"; -} -.mdi-align-vertical-bottom::before { - content: "\F01F0"; -} -.mdi-align-vertical-center::before { - content: "\F01F1"; -} -.mdi-align-vertical-top::before { - content: "\F01F2"; -} -.mdi-all-inclusive::before { - content: "\F6BD"; -} -.mdi-allergy::before { - content: "\F0283"; -} -.mdi-alpha::before { - content: "\F02B"; -} -.mdi-alpha-a::before { - content: "\41"; -} -.mdi-alpha-a-box::before { - content: "\FAED"; -} -.mdi-alpha-a-box-outline::before { - content: "\FBC7"; -} -.mdi-alpha-a-circle::before { - content: "\FBC8"; -} -.mdi-alpha-a-circle-outline::before { - content: "\FBC9"; -} -.mdi-alpha-b::before { - content: "\42"; -} -.mdi-alpha-b-box::before { - content: "\FAEE"; -} -.mdi-alpha-b-box-outline::before { - content: "\FBCA"; -} -.mdi-alpha-b-circle::before { - content: "\FBCB"; -} -.mdi-alpha-b-circle-outline::before { - content: "\FBCC"; -} -.mdi-alpha-c::before { - content: "\43"; -} -.mdi-alpha-c-box::before { - content: "\FAEF"; -} -.mdi-alpha-c-box-outline::before { - content: "\FBCD"; -} -.mdi-alpha-c-circle::before { - content: "\FBCE"; -} -.mdi-alpha-c-circle-outline::before { - content: "\FBCF"; -} -.mdi-alpha-d::before { - content: "\44"; -} -.mdi-alpha-d-box::before { - content: "\FAF0"; -} -.mdi-alpha-d-box-outline::before { - content: "\FBD0"; -} -.mdi-alpha-d-circle::before { - content: "\FBD1"; -} -.mdi-alpha-d-circle-outline::before { - content: "\FBD2"; -} -.mdi-alpha-e::before { - content: "\45"; -} -.mdi-alpha-e-box::before { - content: "\FAF1"; -} -.mdi-alpha-e-box-outline::before { - content: "\FBD3"; -} -.mdi-alpha-e-circle::before { - content: "\FBD4"; -} -.mdi-alpha-e-circle-outline::before { - content: "\FBD5"; -} -.mdi-alpha-f::before { - content: "\46"; -} -.mdi-alpha-f-box::before { - content: "\FAF2"; -} -.mdi-alpha-f-box-outline::before { - content: "\FBD6"; -} -.mdi-alpha-f-circle::before { - content: "\FBD7"; -} -.mdi-alpha-f-circle-outline::before { - content: "\FBD8"; -} -.mdi-alpha-g::before { - content: "\47"; -} -.mdi-alpha-g-box::before { - content: "\FAF3"; -} -.mdi-alpha-g-box-outline::before { - content: "\FBD9"; -} -.mdi-alpha-g-circle::before { - content: "\FBDA"; -} -.mdi-alpha-g-circle-outline::before { - content: "\FBDB"; -} -.mdi-alpha-h::before { - content: "\48"; -} -.mdi-alpha-h-box::before { - content: "\FAF4"; -} -.mdi-alpha-h-box-outline::before { - content: "\FBDC"; -} -.mdi-alpha-h-circle::before { - content: "\FBDD"; -} -.mdi-alpha-h-circle-outline::before { - content: "\FBDE"; -} -.mdi-alpha-i::before { - content: "\49"; -} -.mdi-alpha-i-box::before { - content: "\FAF5"; -} -.mdi-alpha-i-box-outline::before { - content: "\FBDF"; -} -.mdi-alpha-i-circle::before { - content: "\FBE0"; -} -.mdi-alpha-i-circle-outline::before { - content: "\FBE1"; -} -.mdi-alpha-j::before { - content: "\4A"; -} -.mdi-alpha-j-box::before { - content: "\FAF6"; -} -.mdi-alpha-j-box-outline::before { - content: "\FBE2"; -} -.mdi-alpha-j-circle::before { - content: "\FBE3"; -} -.mdi-alpha-j-circle-outline::before { - content: "\FBE4"; -} -.mdi-alpha-k::before { - content: "\4B"; -} -.mdi-alpha-k-box::before { - content: "\FAF7"; -} -.mdi-alpha-k-box-outline::before { - content: "\FBE5"; -} -.mdi-alpha-k-circle::before { - content: "\FBE6"; -} -.mdi-alpha-k-circle-outline::before { - content: "\FBE7"; -} -.mdi-alpha-l::before { - content: "\4C"; -} -.mdi-alpha-l-box::before { - content: "\FAF8"; -} -.mdi-alpha-l-box-outline::before { - content: "\FBE8"; -} -.mdi-alpha-l-circle::before { - content: "\FBE9"; -} -.mdi-alpha-l-circle-outline::before { - content: "\FBEA"; -} -.mdi-alpha-m::before { - content: "\4D"; -} -.mdi-alpha-m-box::before { - content: "\FAF9"; -} -.mdi-alpha-m-box-outline::before { - content: "\FBEB"; -} -.mdi-alpha-m-circle::before { - content: "\FBEC"; -} -.mdi-alpha-m-circle-outline::before { - content: "\FBED"; -} -.mdi-alpha-n::before { - content: "\4E"; -} -.mdi-alpha-n-box::before { - content: "\FAFA"; -} -.mdi-alpha-n-box-outline::before { - content: "\FBEE"; -} -.mdi-alpha-n-circle::before { - content: "\FBEF"; -} -.mdi-alpha-n-circle-outline::before { - content: "\FBF0"; -} -.mdi-alpha-o::before { - content: "\4F"; -} -.mdi-alpha-o-box::before { - content: "\FAFB"; -} -.mdi-alpha-o-box-outline::before { - content: "\FBF1"; -} -.mdi-alpha-o-circle::before { - content: "\FBF2"; -} -.mdi-alpha-o-circle-outline::before { - content: "\FBF3"; -} -.mdi-alpha-p::before { - content: "\50"; -} -.mdi-alpha-p-box::before { - content: "\FAFC"; -} -.mdi-alpha-p-box-outline::before { - content: "\FBF4"; -} -.mdi-alpha-p-circle::before { - content: "\FBF5"; -} -.mdi-alpha-p-circle-outline::before { - content: "\FBF6"; -} -.mdi-alpha-q::before { - content: "\51"; -} -.mdi-alpha-q-box::before { - content: "\FAFD"; -} -.mdi-alpha-q-box-outline::before { - content: "\FBF7"; -} -.mdi-alpha-q-circle::before { - content: "\FBF8"; -} -.mdi-alpha-q-circle-outline::before { - content: "\FBF9"; -} -.mdi-alpha-r::before { - content: "\52"; -} -.mdi-alpha-r-box::before { - content: "\FAFE"; -} -.mdi-alpha-r-box-outline::before { - content: "\FBFA"; -} -.mdi-alpha-r-circle::before { - content: "\FBFB"; -} -.mdi-alpha-r-circle-outline::before { - content: "\FBFC"; -} -.mdi-alpha-s::before { - content: "\53"; -} -.mdi-alpha-s-box::before { - content: "\FAFF"; -} -.mdi-alpha-s-box-outline::before { - content: "\FBFD"; -} -.mdi-alpha-s-circle::before { - content: "\FBFE"; -} -.mdi-alpha-s-circle-outline::before { - content: "\FBFF"; -} -.mdi-alpha-t::before { - content: "\54"; -} -.mdi-alpha-t-box::before { - content: "\FB00"; -} -.mdi-alpha-t-box-outline::before { - content: "\FC00"; -} -.mdi-alpha-t-circle::before { - content: "\FC01"; -} -.mdi-alpha-t-circle-outline::before { - content: "\FC02"; -} -.mdi-alpha-u::before { - content: "\55"; -} -.mdi-alpha-u-box::before { - content: "\FB01"; -} -.mdi-alpha-u-box-outline::before { - content: "\FC03"; -} -.mdi-alpha-u-circle::before { - content: "\FC04"; -} -.mdi-alpha-u-circle-outline::before { - content: "\FC05"; -} -.mdi-alpha-v::before { - content: "\56"; -} -.mdi-alpha-v-box::before { - content: "\FB02"; -} -.mdi-alpha-v-box-outline::before { - content: "\FC06"; -} -.mdi-alpha-v-circle::before { - content: "\FC07"; -} -.mdi-alpha-v-circle-outline::before { - content: "\FC08"; -} -.mdi-alpha-w::before { - content: "\57"; -} -.mdi-alpha-w-box::before { - content: "\FB03"; -} -.mdi-alpha-w-box-outline::before { - content: "\FC09"; -} -.mdi-alpha-w-circle::before { - content: "\FC0A"; -} -.mdi-alpha-w-circle-outline::before { - content: "\FC0B"; -} -.mdi-alpha-x::before { - content: "\58"; -} -.mdi-alpha-x-box::before { - content: "\FB04"; -} -.mdi-alpha-x-box-outline::before { - content: "\FC0C"; -} -.mdi-alpha-x-circle::before { - content: "\FC0D"; -} -.mdi-alpha-x-circle-outline::before { - content: "\FC0E"; -} -.mdi-alpha-y::before { - content: "\59"; -} -.mdi-alpha-y-box::before { - content: "\FB05"; -} -.mdi-alpha-y-box-outline::before { - content: "\FC0F"; -} -.mdi-alpha-y-circle::before { - content: "\FC10"; -} -.mdi-alpha-y-circle-outline::before { - content: "\FC11"; -} -.mdi-alpha-z::before { - content: "\5A"; -} -.mdi-alpha-z-box::before { - content: "\FB06"; -} -.mdi-alpha-z-box-outline::before { - content: "\FC12"; -} -.mdi-alpha-z-circle::before { - content: "\FC13"; -} -.mdi-alpha-z-circle-outline::before { - content: "\FC14"; -} -.mdi-alphabet-aurebesh::before { - content: "\F0357"; -} -.mdi-alphabet-cyrillic::before { - content: "\F0358"; -} -.mdi-alphabet-greek::before { - content: "\F0359"; -} -.mdi-alphabet-latin::before { - content: "\F035A"; -} -.mdi-alphabet-piqad::before { - content: "\F035B"; -} -.mdi-alphabet-tengwar::before { - content: "\F0362"; -} -.mdi-alphabetical::before { - content: "\F02C"; -} -.mdi-alphabetical-off::before { - content: "\F002E"; -} -.mdi-alphabetical-variant::before { - content: "\F002F"; -} -.mdi-alphabetical-variant-off::before { - content: "\F0030"; -} -.mdi-altimeter::before { - content: "\F5D7"; -} -.mdi-amazon::before { - content: "\F02D"; -} -.mdi-amazon-alexa::before { - content: "\F8C5"; -} -.mdi-amazon-drive::before { - content: "\F02E"; -} -.mdi-ambulance::before { - content: "\F02F"; -} -.mdi-ammunition::before { - content: "\FCC4"; -} -.mdi-ampersand::before { - content: "\FA8C"; -} -.mdi-amplifier::before { - content: "\F030"; -} -.mdi-amplifier-off::before { - content: "\F01E0"; -} -.mdi-anchor::before { - content: "\F031"; -} -.mdi-android::before { - content: "\F032"; -} -.mdi-android-auto::before { - content: "\FA8D"; -} -.mdi-android-debug-bridge::before { - content: "\F033"; -} -.mdi-android-head::before { - content: "\F78F"; -} -.mdi-android-messages::before { - content: "\FD21"; -} -.mdi-android-studio::before { - content: "\F034"; -} -.mdi-angle-acute::before { - content: "\F936"; -} -.mdi-angle-obtuse::before { - content: "\F937"; -} -.mdi-angle-right::before { - content: "\F938"; -} -.mdi-angular::before { - content: "\F6B1"; -} -.mdi-angularjs::before { - content: "\F6BE"; -} -.mdi-animation::before { - content: "\F5D8"; -} -.mdi-animation-outline::before { - content: "\FA8E"; -} -.mdi-animation-play::before { - content: "\F939"; -} -.mdi-animation-play-outline::before { - content: "\FA8F"; -} -.mdi-ansible::before { - content: "\F00C5"; -} -.mdi-antenna::before { - content: "\F0144"; -} -.mdi-anvil::before { - content: "\F89A"; -} -.mdi-apache-kafka::before { - content: "\F0031"; -} -.mdi-api::before { - content: "\F00C6"; -} -.mdi-api-off::before { - content: "\F0282"; -} -.mdi-apple::before { - content: "\F035"; -} -.mdi-apple-finder::before { - content: "\F036"; -} -.mdi-apple-icloud::before { - content: "\F038"; -} -.mdi-apple-ios::before { - content: "\F037"; -} -.mdi-apple-keyboard-caps::before { - content: "\F632"; -} -.mdi-apple-keyboard-command::before { - content: "\F633"; -} -.mdi-apple-keyboard-control::before { - content: "\F634"; -} -.mdi-apple-keyboard-option::before { - content: "\F635"; -} -.mdi-apple-keyboard-shift::before { - content: "\F636"; -} -.mdi-apple-safari::before { - content: "\F039"; -} -.mdi-application::before { - content: "\F614"; -} -.mdi-application-export::before { - content: "\FD89"; -} -.mdi-application-import::before { - content: "\FD8A"; -} -.mdi-approximately-equal::before { - content: "\FFBE"; -} -.mdi-approximately-equal-box::before { - content: "\FFBF"; -} -.mdi-apps::before { - content: "\F03B"; -} -.mdi-apps-box::before { - content: "\FD22"; -} -.mdi-arch::before { - content: "\F8C6"; -} -.mdi-archive::before { - content: "\F03C"; -} -.mdi-archive-arrow-down::before { - content: "\F0284"; -} -.mdi-archive-arrow-down-outline::before { - content: "\F0285"; -} -.mdi-archive-arrow-up::before { - content: "\F0286"; -} -.mdi-archive-arrow-up-outline::before { - content: "\F0287"; -} -.mdi-archive-outline::before { - content: "\F0239"; -} -.mdi-arm-flex::before { - content: "\F008F"; -} -.mdi-arm-flex-outline::before { - content: "\F0090"; -} -.mdi-arrange-bring-forward::before { - content: "\F03D"; -} -.mdi-arrange-bring-to-front::before { - content: "\F03E"; -} -.mdi-arrange-send-backward::before { - content: "\F03F"; -} -.mdi-arrange-send-to-back::before { - content: "\F040"; -} -.mdi-arrow-all::before { - content: "\F041"; -} -.mdi-arrow-bottom-left::before { - content: "\F042"; -} -.mdi-arrow-bottom-left-bold-outline::before { - content: "\F9B6"; -} -.mdi-arrow-bottom-left-thick::before { - content: "\F9B7"; -} -.mdi-arrow-bottom-right::before { - content: "\F043"; -} -.mdi-arrow-bottom-right-bold-outline::before { - content: "\F9B8"; -} -.mdi-arrow-bottom-right-thick::before { - content: "\F9B9"; -} -.mdi-arrow-collapse::before { - content: "\F615"; -} -.mdi-arrow-collapse-all::before { - content: "\F044"; -} -.mdi-arrow-collapse-down::before { - content: "\F791"; -} -.mdi-arrow-collapse-horizontal::before { - content: "\F84B"; -} -.mdi-arrow-collapse-left::before { - content: "\F792"; -} -.mdi-arrow-collapse-right::before { - content: "\F793"; -} -.mdi-arrow-collapse-up::before { - content: "\F794"; -} -.mdi-arrow-collapse-vertical::before { - content: "\F84C"; -} -.mdi-arrow-decision::before { - content: "\F9BA"; -} -.mdi-arrow-decision-auto::before { - content: "\F9BB"; -} -.mdi-arrow-decision-auto-outline::before { - content: "\F9BC"; -} -.mdi-arrow-decision-outline::before { - content: "\F9BD"; -} -.mdi-arrow-down::before { - content: "\F045"; -} -.mdi-arrow-down-bold::before { - content: "\F72D"; -} -.mdi-arrow-down-bold-box::before { - content: "\F72E"; -} -.mdi-arrow-down-bold-box-outline::before { - content: "\F72F"; -} -.mdi-arrow-down-bold-circle::before { - content: "\F047"; -} -.mdi-arrow-down-bold-circle-outline::before { - content: "\F048"; -} -.mdi-arrow-down-bold-hexagon-outline::before { - content: "\F049"; -} -.mdi-arrow-down-bold-outline::before { - content: "\F9BE"; -} -.mdi-arrow-down-box::before { - content: "\F6BF"; -} -.mdi-arrow-down-circle::before { - content: "\FCB7"; -} -.mdi-arrow-down-circle-outline::before { - content: "\FCB8"; -} -.mdi-arrow-down-drop-circle::before { - content: "\F04A"; -} -.mdi-arrow-down-drop-circle-outline::before { - content: "\F04B"; -} -.mdi-arrow-down-thick::before { - content: "\F046"; -} -.mdi-arrow-expand::before { - content: "\F616"; -} -.mdi-arrow-expand-all::before { - content: "\F04C"; -} -.mdi-arrow-expand-down::before { - content: "\F795"; -} -.mdi-arrow-expand-horizontal::before { - content: "\F84D"; -} -.mdi-arrow-expand-left::before { - content: "\F796"; -} -.mdi-arrow-expand-right::before { - content: "\F797"; -} -.mdi-arrow-expand-up::before { - content: "\F798"; -} -.mdi-arrow-expand-vertical::before { - content: "\F84E"; -} -.mdi-arrow-horizontal-lock::before { - content: "\F0186"; -} -.mdi-arrow-left::before { - content: "\F04D"; -} -.mdi-arrow-left-bold::before { - content: "\F730"; -} -.mdi-arrow-left-bold-box::before { - content: "\F731"; -} -.mdi-arrow-left-bold-box-outline::before { - content: "\F732"; -} -.mdi-arrow-left-bold-circle::before { - content: "\F04F"; -} -.mdi-arrow-left-bold-circle-outline::before { - content: "\F050"; -} -.mdi-arrow-left-bold-hexagon-outline::before { - content: "\F051"; -} -.mdi-arrow-left-bold-outline::before { - content: "\F9BF"; -} -.mdi-arrow-left-box::before { - content: "\F6C0"; -} -.mdi-arrow-left-circle::before { - content: "\FCB9"; -} -.mdi-arrow-left-circle-outline::before { - content: "\FCBA"; -} -.mdi-arrow-left-drop-circle::before { - content: "\F052"; -} -.mdi-arrow-left-drop-circle-outline::before { - content: "\F053"; -} -.mdi-arrow-left-right::before { - content: "\FE90"; -} -.mdi-arrow-left-right-bold::before { - content: "\FE91"; -} -.mdi-arrow-left-right-bold-outline::before { - content: "\F9C0"; -} -.mdi-arrow-left-thick::before { - content: "\F04E"; -} -.mdi-arrow-right::before { - content: "\F054"; -} -.mdi-arrow-right-bold::before { - content: "\F733"; -} -.mdi-arrow-right-bold-box::before { - content: "\F734"; -} -.mdi-arrow-right-bold-box-outline::before { - content: "\F735"; -} -.mdi-arrow-right-bold-circle::before { - content: "\F056"; -} -.mdi-arrow-right-bold-circle-outline::before { - content: "\F057"; -} -.mdi-arrow-right-bold-hexagon-outline::before { - content: "\F058"; -} -.mdi-arrow-right-bold-outline::before { - content: "\F9C1"; -} -.mdi-arrow-right-box::before { - content: "\F6C1"; -} -.mdi-arrow-right-circle::before { - content: "\FCBB"; -} -.mdi-arrow-right-circle-outline::before { - content: "\FCBC"; -} -.mdi-arrow-right-drop-circle::before { - content: "\F059"; -} -.mdi-arrow-right-drop-circle-outline::before { - content: "\F05A"; -} -.mdi-arrow-right-thick::before { - content: "\F055"; -} -.mdi-arrow-split-horizontal::before { - content: "\F93A"; -} -.mdi-arrow-split-vertical::before { - content: "\F93B"; -} -.mdi-arrow-top-left::before { - content: "\F05B"; -} -.mdi-arrow-top-left-bold-outline::before { - content: "\F9C2"; -} -.mdi-arrow-top-left-bottom-right::before { - content: "\FE92"; -} -.mdi-arrow-top-left-bottom-right-bold::before { - content: "\FE93"; -} -.mdi-arrow-top-left-thick::before { - content: "\F9C3"; -} -.mdi-arrow-top-right::before { - content: "\F05C"; -} -.mdi-arrow-top-right-bold-outline::before { - content: "\F9C4"; -} -.mdi-arrow-top-right-bottom-left::before { - content: "\FE94"; -} -.mdi-arrow-top-right-bottom-left-bold::before { - content: "\FE95"; -} -.mdi-arrow-top-right-thick::before { - content: "\F9C5"; -} -.mdi-arrow-up::before { - content: "\F05D"; -} -.mdi-arrow-up-bold::before { - content: "\F736"; -} -.mdi-arrow-up-bold-box::before { - content: "\F737"; -} -.mdi-arrow-up-bold-box-outline::before { - content: "\F738"; -} -.mdi-arrow-up-bold-circle::before { - content: "\F05F"; -} -.mdi-arrow-up-bold-circle-outline::before { - content: "\F060"; -} -.mdi-arrow-up-bold-hexagon-outline::before { - content: "\F061"; -} -.mdi-arrow-up-bold-outline::before { - content: "\F9C6"; -} -.mdi-arrow-up-box::before { - content: "\F6C2"; -} -.mdi-arrow-up-circle::before { - content: "\FCBD"; -} -.mdi-arrow-up-circle-outline::before { - content: "\FCBE"; -} -.mdi-arrow-up-down::before { - content: "\FE96"; -} -.mdi-arrow-up-down-bold::before { - content: "\FE97"; -} -.mdi-arrow-up-down-bold-outline::before { - content: "\F9C7"; -} -.mdi-arrow-up-drop-circle::before { - content: "\F062"; -} -.mdi-arrow-up-drop-circle-outline::before { - content: "\F063"; -} -.mdi-arrow-up-thick::before { - content: "\F05E"; -} -.mdi-arrow-vertical-lock::before { - content: "\F0187"; -} -.mdi-artist::before { - content: "\F802"; -} -.mdi-artist-outline::before { - content: "\FCC5"; -} -.mdi-artstation::before { - content: "\FB37"; -} -.mdi-aspect-ratio::before { - content: "\FA23"; -} -.mdi-assistant::before { - content: "\F064"; -} -.mdi-asterisk::before { - content: "\F6C3"; -} -.mdi-at::before { - content: "\F065"; -} -.mdi-atlassian::before { - content: "\F803"; -} -.mdi-atm::before { - content: "\FD23"; -} -.mdi-atom::before { - content: "\F767"; -} -.mdi-atom-variant::before { - content: "\FE98"; -} -.mdi-attachment::before { - content: "\F066"; -} -.mdi-audio-video::before { - content: "\F93C"; -} -.mdi-audio-video-off::before { - content: "\F01E1"; -} -.mdi-audiobook::before { - content: "\F067"; -} -.mdi-augmented-reality::before { - content: "\F84F"; -} -.mdi-auto-download::before { - content: "\F03A9"; -} -.mdi-auto-fix::before { - content: "\F068"; -} -.mdi-auto-upload::before { - content: "\F069"; -} -.mdi-autorenew::before { - content: "\F06A"; -} -.mdi-av-timer::before { - content: "\F06B"; -} -.mdi-aws::before { - content: "\FDF2"; -} -.mdi-axe::before { - content: "\F8C7"; -} -.mdi-axis::before { - content: "\FD24"; -} -.mdi-axis-arrow::before { - content: "\FD25"; -} -.mdi-axis-arrow-lock::before { - content: "\FD26"; -} -.mdi-axis-lock::before { - content: "\FD27"; -} -.mdi-axis-x-arrow::before { - content: "\FD28"; -} -.mdi-axis-x-arrow-lock::before { - content: "\FD29"; -} -.mdi-axis-x-rotate-clockwise::before { - content: "\FD2A"; -} -.mdi-axis-x-rotate-counterclockwise::before { - content: "\FD2B"; -} -.mdi-axis-x-y-arrow-lock::before { - content: "\FD2C"; -} -.mdi-axis-y-arrow::before { - content: "\FD2D"; -} -.mdi-axis-y-arrow-lock::before { - content: "\FD2E"; -} -.mdi-axis-y-rotate-clockwise::before { - content: "\FD2F"; -} -.mdi-axis-y-rotate-counterclockwise::before { - content: "\FD30"; -} -.mdi-axis-z-arrow::before { - content: "\FD31"; -} -.mdi-axis-z-arrow-lock::before { - content: "\FD32"; -} -.mdi-axis-z-rotate-clockwise::before { - content: "\FD33"; -} -.mdi-axis-z-rotate-counterclockwise::before { - content: "\FD34"; -} -.mdi-azure::before { - content: "\F804"; -} -.mdi-azure-devops::before { - content: "\F0091"; -} -.mdi-babel::before { - content: "\FA24"; -} -.mdi-baby::before { - content: "\F06C"; -} -.mdi-baby-bottle::before { - content: "\FF56"; -} -.mdi-baby-bottle-outline::before { - content: "\FF57"; -} -.mdi-baby-carriage::before { - content: "\F68E"; -} -.mdi-baby-carriage-off::before { - content: "\FFC0"; -} -.mdi-baby-face::before { - content: "\FE99"; -} -.mdi-baby-face-outline::before { - content: "\FE9A"; -} -.mdi-backburger::before { - content: "\F06D"; -} -.mdi-backspace::before { - content: "\F06E"; -} -.mdi-backspace-outline::before { - content: "\FB38"; -} -.mdi-backspace-reverse::before { - content: "\FE9B"; -} -.mdi-backspace-reverse-outline::before { - content: "\FE9C"; -} -.mdi-backup-restore::before { - content: "\F06F"; -} -.mdi-bacteria::before { - content: "\FEF2"; -} -.mdi-bacteria-outline::before { - content: "\FEF3"; -} -.mdi-badminton::before { - content: "\F850"; -} -.mdi-bag-carry-on::before { - content: "\FF58"; -} -.mdi-bag-carry-on-check::before { - content: "\FD41"; -} -.mdi-bag-carry-on-off::before { - content: "\FF59"; -} -.mdi-bag-checked::before { - content: "\FF5A"; -} -.mdi-bag-personal::before { - content: "\FDF3"; -} -.mdi-bag-personal-off::before { - content: "\FDF4"; -} -.mdi-bag-personal-off-outline::before { - content: "\FDF5"; -} -.mdi-bag-personal-outline::before { - content: "\FDF6"; -} -.mdi-baguette::before { - content: "\FF5B"; -} -.mdi-balloon::before { - content: "\FA25"; -} -.mdi-ballot::before { - content: "\F9C8"; -} -.mdi-ballot-outline::before { - content: "\F9C9"; -} -.mdi-ballot-recount::before { - content: "\FC15"; -} -.mdi-ballot-recount-outline::before { - content: "\FC16"; -} -.mdi-bandage::before { - content: "\FD8B"; -} -.mdi-bandcamp::before { - content: "\F674"; -} -.mdi-bank::before { - content: "\F070"; -} -.mdi-bank-minus::before { - content: "\FD8C"; -} -.mdi-bank-outline::before { - content: "\FE9D"; -} -.mdi-bank-plus::before { - content: "\FD8D"; -} -.mdi-bank-remove::before { - content: "\FD8E"; -} -.mdi-bank-transfer::before { - content: "\FA26"; -} -.mdi-bank-transfer-in::before { - content: "\FA27"; -} -.mdi-bank-transfer-out::before { - content: "\FA28"; -} -.mdi-barcode::before { - content: "\F071"; -} -.mdi-barcode-off::before { - content: "\F0261"; -} -.mdi-barcode-scan::before { - content: "\F072"; -} -.mdi-barley::before { - content: "\F073"; -} -.mdi-barley-off::before { - content: "\FB39"; -} -.mdi-barn::before { - content: "\FB3A"; -} -.mdi-barrel::before { - content: "\F074"; -} -.mdi-baseball::before { - content: "\F851"; -} -.mdi-baseball-bat::before { - content: "\F852"; -} -.mdi-basecamp::before { - content: "\F075"; -} -.mdi-bash::before { - content: "\F01AE"; -} -.mdi-basket::before { - content: "\F076"; -} -.mdi-basket-fill::before { - content: "\F077"; -} -.mdi-basket-outline::before { - content: "\F01AC"; -} -.mdi-basket-unfill::before { - content: "\F078"; -} -.mdi-basketball::before { - content: "\F805"; -} -.mdi-basketball-hoop::before { - content: "\FC17"; -} -.mdi-basketball-hoop-outline::before { - content: "\FC18"; -} -.mdi-bat::before { - content: "\FB3B"; -} -.mdi-battery::before { - content: "\F079"; -} -.mdi-battery-10::before { - content: "\F07A"; -} -.mdi-battery-10-bluetooth::before { - content: "\F93D"; -} -.mdi-battery-20::before { - content: "\F07B"; -} -.mdi-battery-20-bluetooth::before { - content: "\F93E"; -} -.mdi-battery-30::before { - content: "\F07C"; -} -.mdi-battery-30-bluetooth::before { - content: "\F93F"; -} -.mdi-battery-40::before { - content: "\F07D"; -} -.mdi-battery-40-bluetooth::before { - content: "\F940"; -} -.mdi-battery-50::before { - content: "\F07E"; -} -.mdi-battery-50-bluetooth::before { - content: "\F941"; -} -.mdi-battery-60::before { - content: "\F07F"; -} -.mdi-battery-60-bluetooth::before { - content: "\F942"; -} -.mdi-battery-70::before { - content: "\F080"; -} -.mdi-battery-70-bluetooth::before { - content: "\F943"; -} -.mdi-battery-80::before { - content: "\F081"; -} -.mdi-battery-80-bluetooth::before { - content: "\F944"; -} -.mdi-battery-90::before { - content: "\F082"; -} -.mdi-battery-90-bluetooth::before { - content: "\F945"; -} -.mdi-battery-alert::before { - content: "\F083"; -} -.mdi-battery-alert-bluetooth::before { - content: "\F946"; -} -.mdi-battery-alert-variant::before { - content: "\F00F7"; -} -.mdi-battery-alert-variant-outline::before { - content: "\F00F8"; -} -.mdi-battery-bluetooth::before { - content: "\F947"; -} -.mdi-battery-bluetooth-variant::before { - content: "\F948"; -} -.mdi-battery-charging::before { - content: "\F084"; -} -.mdi-battery-charging-10::before { - content: "\F89B"; -} -.mdi-battery-charging-100::before { - content: "\F085"; -} -.mdi-battery-charging-20::before { - content: "\F086"; -} -.mdi-battery-charging-30::before { - content: "\F087"; -} -.mdi-battery-charging-40::before { - content: "\F088"; -} -.mdi-battery-charging-50::before { - content: "\F89C"; -} -.mdi-battery-charging-60::before { - content: "\F089"; -} -.mdi-battery-charging-70::before { - content: "\F89D"; -} -.mdi-battery-charging-80::before { - content: "\F08A"; -} -.mdi-battery-charging-90::before { - content: "\F08B"; -} -.mdi-battery-charging-high::before { - content: "\F02D1"; -} -.mdi-battery-charging-low::before { - content: "\F02CF"; -} -.mdi-battery-charging-medium::before { - content: "\F02D0"; -} -.mdi-battery-charging-outline::before { - content: "\F89E"; -} -.mdi-battery-charging-wireless::before { - content: "\F806"; -} -.mdi-battery-charging-wireless-10::before { - content: "\F807"; -} -.mdi-battery-charging-wireless-20::before { - content: "\F808"; -} -.mdi-battery-charging-wireless-30::before { - content: "\F809"; -} -.mdi-battery-charging-wireless-40::before { - content: "\F80A"; -} -.mdi-battery-charging-wireless-50::before { - content: "\F80B"; -} -.mdi-battery-charging-wireless-60::before { - content: "\F80C"; -} -.mdi-battery-charging-wireless-70::before { - content: "\F80D"; -} -.mdi-battery-charging-wireless-80::before { - content: "\F80E"; -} -.mdi-battery-charging-wireless-90::before { - content: "\F80F"; -} -.mdi-battery-charging-wireless-alert::before { - content: "\F810"; -} -.mdi-battery-charging-wireless-outline::before { - content: "\F811"; -} -.mdi-battery-heart::before { - content: "\F023A"; -} -.mdi-battery-heart-outline::before { - content: "\F023B"; -} -.mdi-battery-heart-variant::before { - content: "\F023C"; -} -.mdi-battery-high::before { - content: "\F02CE"; -} -.mdi-battery-low::before { - content: "\F02CC"; -} -.mdi-battery-medium::before { - content: "\F02CD"; -} -.mdi-battery-minus::before { - content: "\F08C"; -} -.mdi-battery-negative::before { - content: "\F08D"; -} -.mdi-battery-off::before { - content: "\F0288"; -} -.mdi-battery-off-outline::before { - content: "\F0289"; -} -.mdi-battery-outline::before { - content: "\F08E"; -} -.mdi-battery-plus::before { - content: "\F08F"; -} -.mdi-battery-positive::before { - content: "\F090"; -} -.mdi-battery-unknown::before { - content: "\F091"; -} -.mdi-battery-unknown-bluetooth::before { - content: "\F949"; -} -.mdi-battlenet::before { - content: "\FB3C"; -} -.mdi-beach::before { - content: "\F092"; -} -.mdi-beaker::before { - content: "\FCC6"; -} -.mdi-beaker-alert::before { - content: "\F0254"; -} -.mdi-beaker-alert-outline::before { - content: "\F0255"; -} -.mdi-beaker-check::before { - content: "\F0256"; -} -.mdi-beaker-check-outline::before { - content: "\F0257"; -} -.mdi-beaker-minus::before { - content: "\F0258"; -} -.mdi-beaker-minus-outline::before { - content: "\F0259"; -} -.mdi-beaker-outline::before { - content: "\F68F"; -} -.mdi-beaker-plus::before { - content: "\F025A"; -} -.mdi-beaker-plus-outline::before { - content: "\F025B"; -} -.mdi-beaker-question::before { - content: "\F025C"; -} -.mdi-beaker-question-outline::before { - content: "\F025D"; -} -.mdi-beaker-remove::before { - content: "\F025E"; -} -.mdi-beaker-remove-outline::before { - content: "\F025F"; -} -.mdi-beats::before { - content: "\F097"; -} -.mdi-bed-double::before { - content: "\F0092"; -} -.mdi-bed-double-outline::before { - content: "\F0093"; -} -.mdi-bed-empty::before { - content: "\F89F"; -} -.mdi-bed-king::before { - content: "\F0094"; -} -.mdi-bed-king-outline::before { - content: "\F0095"; -} -.mdi-bed-queen::before { - content: "\F0096"; -} -.mdi-bed-queen-outline::before { - content: "\F0097"; -} -.mdi-bed-single::before { - content: "\F0098"; -} -.mdi-bed-single-outline::before { - content: "\F0099"; -} -.mdi-bee::before { - content: "\FFC1"; -} -.mdi-bee-flower::before { - content: "\FFC2"; -} -.mdi-beehive-outline::before { - content: "\F00F9"; -} -.mdi-beer::before { - content: "\F098"; -} -.mdi-beer-outline::before { - content: "\F0337"; -} -.mdi-behance::before { - content: "\F099"; -} -.mdi-bell::before { - content: "\F09A"; -} -.mdi-bell-alert::before { - content: "\FD35"; -} -.mdi-bell-alert-outline::before { - content: "\FE9E"; -} -.mdi-bell-check::before { - content: "\F0210"; -} -.mdi-bell-check-outline::before { - content: "\F0211"; -} -.mdi-bell-circle::before { - content: "\FD36"; -} -.mdi-bell-circle-outline::before { - content: "\FD37"; -} -.mdi-bell-off::before { - content: "\F09B"; -} -.mdi-bell-off-outline::before { - content: "\FA90"; -} -.mdi-bell-outline::before { - content: "\F09C"; -} -.mdi-bell-plus::before { - content: "\F09D"; -} -.mdi-bell-plus-outline::before { - content: "\FA91"; -} -.mdi-bell-ring::before { - content: "\F09E"; -} -.mdi-bell-ring-outline::before { - content: "\F09F"; -} -.mdi-bell-sleep::before { - content: "\F0A0"; -} -.mdi-bell-sleep-outline::before { - content: "\FA92"; -} -.mdi-beta::before { - content: "\F0A1"; -} -.mdi-betamax::before { - content: "\F9CA"; -} -.mdi-biathlon::before { - content: "\FDF7"; -} -.mdi-bible::before { - content: "\F0A2"; -} -.mdi-bicycle::before { - content: "\F00C7"; -} -.mdi-bicycle-basket::before { - content: "\F0260"; -} -.mdi-bike::before { - content: "\F0A3"; -} -.mdi-bike-fast::before { - content: "\F014A"; -} -.mdi-billboard::before { - content: "\F0032"; -} -.mdi-billiards::before { - content: "\FB3D"; -} -.mdi-billiards-rack::before { - content: "\FB3E"; -} -.mdi-bing::before { - content: "\F0A4"; -} -.mdi-binoculars::before { - content: "\F0A5"; -} -.mdi-bio::before { - content: "\F0A6"; -} -.mdi-biohazard::before { - content: "\F0A7"; -} -.mdi-bitbucket::before { - content: "\F0A8"; -} -.mdi-bitcoin::before { - content: "\F812"; -} -.mdi-black-mesa::before { - content: "\F0A9"; -} -.mdi-blackberry::before { - content: "\F0AA"; -} -.mdi-blender::before { - content: "\FCC7"; -} -.mdi-blender-software::before { - content: "\F0AB"; -} -.mdi-blinds::before { - content: "\F0AC"; -} -.mdi-blinds-open::before { - content: "\F0033"; -} -.mdi-block-helper::before { - content: "\F0AD"; -} -.mdi-blogger::before { - content: "\F0AE"; -} -.mdi-blood-bag::before { - content: "\FCC8"; -} -.mdi-bluetooth::before { - content: "\F0AF"; -} -.mdi-bluetooth-audio::before { - content: "\F0B0"; -} -.mdi-bluetooth-connect::before { - content: "\F0B1"; -} -.mdi-bluetooth-off::before { - content: "\F0B2"; -} -.mdi-bluetooth-settings::before { - content: "\F0B3"; -} -.mdi-bluetooth-transfer::before { - content: "\F0B4"; -} -.mdi-blur::before { - content: "\F0B5"; -} -.mdi-blur-linear::before { - content: "\F0B6"; -} -.mdi-blur-off::before { - content: "\F0B7"; -} -.mdi-blur-radial::before { - content: "\F0B8"; -} -.mdi-bolnisi-cross::before { - content: "\FCC9"; -} -.mdi-bolt::before { - content: "\FD8F"; -} -.mdi-bomb::before { - content: "\F690"; -} -.mdi-bomb-off::before { - content: "\F6C4"; -} -.mdi-bone::before { - content: "\F0B9"; -} -.mdi-book::before { - content: "\F0BA"; -} -.mdi-book-information-variant::before { - content: "\F009A"; -} -.mdi-book-lock::before { - content: "\F799"; -} -.mdi-book-lock-open::before { - content: "\F79A"; -} -.mdi-book-minus::before { - content: "\F5D9"; -} -.mdi-book-minus-multiple::before { - content: "\FA93"; -} -.mdi-book-multiple::before { - content: "\F0BB"; -} -.mdi-book-open::before { - content: "\F0BD"; -} -.mdi-book-open-outline::before { - content: "\FB3F"; -} -.mdi-book-open-page-variant::before { - content: "\F5DA"; -} -.mdi-book-open-variant::before { - content: "\F0BE"; -} -.mdi-book-outline::before { - content: "\FB40"; -} -.mdi-book-play::before { - content: "\FE9F"; -} -.mdi-book-play-outline::before { - content: "\FEA0"; -} -.mdi-book-plus::before { - content: "\F5DB"; -} -.mdi-book-plus-multiple::before { - content: "\FA94"; -} -.mdi-book-remove::before { - content: "\FA96"; -} -.mdi-book-remove-multiple::before { - content: "\FA95"; -} -.mdi-book-search::before { - content: "\FEA1"; -} -.mdi-book-search-outline::before { - content: "\FEA2"; -} -.mdi-book-variant::before { - content: "\F0BF"; -} -.mdi-book-variant-multiple::before { - content: "\F0BC"; -} -.mdi-bookmark::before { - content: "\F0C0"; -} -.mdi-bookmark-check::before { - content: "\F0C1"; -} -.mdi-bookmark-check-outline::before { - content: "\F03A6"; -} -.mdi-bookmark-minus::before { - content: "\F9CB"; -} -.mdi-bookmark-minus-outline::before { - content: "\F9CC"; -} -.mdi-bookmark-multiple::before { - content: "\FDF8"; -} -.mdi-bookmark-multiple-outline::before { - content: "\FDF9"; -} -.mdi-bookmark-music::before { - content: "\F0C2"; -} -.mdi-bookmark-music-outline::before { - content: "\F03A4"; -} -.mdi-bookmark-off::before { - content: "\F9CD"; -} -.mdi-bookmark-off-outline::before { - content: "\F9CE"; -} -.mdi-bookmark-outline::before { - content: "\F0C3"; -} -.mdi-bookmark-plus::before { - content: "\F0C5"; -} -.mdi-bookmark-plus-outline::before { - content: "\F0C4"; -} -.mdi-bookmark-remove::before { - content: "\F0C6"; -} -.mdi-bookmark-remove-outline::before { - content: "\F03A5"; -} -.mdi-bookshelf::before { - content: "\F028A"; -} -.mdi-boom-gate::before { - content: "\FEA3"; -} -.mdi-boom-gate-alert::before { - content: "\FEA4"; -} -.mdi-boom-gate-alert-outline::before { - content: "\FEA5"; -} -.mdi-boom-gate-down::before { - content: "\FEA6"; -} -.mdi-boom-gate-down-outline::before { - content: "\FEA7"; -} -.mdi-boom-gate-outline::before { - content: "\FEA8"; -} -.mdi-boom-gate-up::before { - content: "\FEA9"; -} -.mdi-boom-gate-up-outline::before { - content: "\FEAA"; -} -.mdi-boombox::before { - content: "\F5DC"; -} -.mdi-boomerang::before { - content: "\F00FA"; -} -.mdi-bootstrap::before { - content: "\F6C5"; -} -.mdi-border-all::before { - content: "\F0C7"; -} -.mdi-border-all-variant::before { - content: "\F8A0"; -} -.mdi-border-bottom::before { - content: "\F0C8"; -} -.mdi-border-bottom-variant::before { - content: "\F8A1"; -} -.mdi-border-color::before { - content: "\F0C9"; -} -.mdi-border-horizontal::before { - content: "\F0CA"; -} -.mdi-border-inside::before { - content: "\F0CB"; -} -.mdi-border-left::before { - content: "\F0CC"; -} -.mdi-border-left-variant::before { - content: "\F8A2"; -} -.mdi-border-none::before { - content: "\F0CD"; -} -.mdi-border-none-variant::before { - content: "\F8A3"; -} -.mdi-border-outside::before { - content: "\F0CE"; -} -.mdi-border-right::before { - content: "\F0CF"; -} -.mdi-border-right-variant::before { - content: "\F8A4"; -} -.mdi-border-style::before { - content: "\F0D0"; -} -.mdi-border-top::before { - content: "\F0D1"; -} -.mdi-border-top-variant::before { - content: "\F8A5"; -} -.mdi-border-vertical::before { - content: "\F0D2"; -} -.mdi-bottle-soda::before { - content: "\F009B"; -} -.mdi-bottle-soda-classic::before { - content: "\F009C"; -} -.mdi-bottle-soda-classic-outline::before { - content: "\F038E"; -} -.mdi-bottle-soda-outline::before { - content: "\F009D"; -} -.mdi-bottle-tonic::before { - content: "\F0159"; -} -.mdi-bottle-tonic-outline::before { - content: "\F015A"; -} -.mdi-bottle-tonic-plus::before { - content: "\F015B"; -} -.mdi-bottle-tonic-plus-outline::before { - content: "\F015C"; -} -.mdi-bottle-tonic-skull::before { - content: "\F015D"; -} -.mdi-bottle-tonic-skull-outline::before { - content: "\F015E"; -} -.mdi-bottle-wine::before { - content: "\F853"; -} -.mdi-bottle-wine-outline::before { - content: "\F033B"; -} -.mdi-bow-tie::before { - content: "\F677"; -} -.mdi-bowl::before { - content: "\F617"; -} -.mdi-bowling::before { - content: "\F0D3"; -} -.mdi-box::before { - content: "\F0D4"; -} -.mdi-box-cutter::before { - content: "\F0D5"; -} -.mdi-box-shadow::before { - content: "\F637"; -} -.mdi-boxing-glove::before { - content: "\FB41"; -} -.mdi-braille::before { - content: "\F9CF"; -} -.mdi-brain::before { - content: "\F9D0"; -} -.mdi-bread-slice::before { - content: "\FCCA"; -} -.mdi-bread-slice-outline::before { - content: "\FCCB"; -} -.mdi-bridge::before { - content: "\F618"; -} -.mdi-briefcase::before { - content: "\F0D6"; -} -.mdi-briefcase-account::before { - content: "\FCCC"; -} -.mdi-briefcase-account-outline::before { - content: "\FCCD"; -} -.mdi-briefcase-check::before { - content: "\F0D7"; -} -.mdi-briefcase-check-outline::before { - content: "\F0349"; -} -.mdi-briefcase-clock::before { - content: "\F00FB"; -} -.mdi-briefcase-clock-outline::before { - content: "\F00FC"; -} -.mdi-briefcase-download::before { - content: "\F0D8"; -} -.mdi-briefcase-download-outline::before { - content: "\FC19"; -} -.mdi-briefcase-edit::before { - content: "\FA97"; -} -.mdi-briefcase-edit-outline::before { - content: "\FC1A"; -} -.mdi-briefcase-minus::before { - content: "\FA29"; -} -.mdi-briefcase-minus-outline::before { - content: "\FC1B"; -} -.mdi-briefcase-outline::before { - content: "\F813"; -} -.mdi-briefcase-plus::before { - content: "\FA2A"; -} -.mdi-briefcase-plus-outline::before { - content: "\FC1C"; -} -.mdi-briefcase-remove::before { - content: "\FA2B"; -} -.mdi-briefcase-remove-outline::before { - content: "\FC1D"; -} -.mdi-briefcase-search::before { - content: "\FA2C"; -} -.mdi-briefcase-search-outline::before { - content: "\FC1E"; -} -.mdi-briefcase-upload::before { - content: "\F0D9"; -} -.mdi-briefcase-upload-outline::before { - content: "\FC1F"; -} -.mdi-brightness-1::before { - content: "\F0DA"; -} -.mdi-brightness-2::before { - content: "\F0DB"; -} -.mdi-brightness-3::before { - content: "\F0DC"; -} -.mdi-brightness-4::before { - content: "\F0DD"; -} -.mdi-brightness-5::before { - content: "\F0DE"; -} -.mdi-brightness-6::before { - content: "\F0DF"; -} -.mdi-brightness-7::before { - content: "\F0E0"; -} -.mdi-brightness-auto::before { - content: "\F0E1"; -} -.mdi-brightness-percent::before { - content: "\FCCE"; -} -.mdi-broom::before { - content: "\F0E2"; -} -.mdi-brush::before { - content: "\F0E3"; -} -.mdi-buddhism::before { - content: "\F94A"; -} -.mdi-buffer::before { - content: "\F619"; -} -.mdi-bug::before { - content: "\F0E4"; -} -.mdi-bug-check::before { - content: "\FA2D"; -} -.mdi-bug-check-outline::before { - content: "\FA2E"; -} -.mdi-bug-outline::before { - content: "\FA2F"; -} -.mdi-bugle::before { - content: "\FD90"; -} -.mdi-bulldozer::before { - content: "\FB07"; -} -.mdi-bullet::before { - content: "\FCCF"; -} -.mdi-bulletin-board::before { - content: "\F0E5"; -} -.mdi-bullhorn::before { - content: "\F0E6"; -} -.mdi-bullhorn-outline::before { - content: "\FB08"; -} -.mdi-bullseye::before { - content: "\F5DD"; -} -.mdi-bullseye-arrow::before { - content: "\F8C8"; -} -.mdi-bulma::before { - content: "\F0312"; -} -.mdi-bunk-bed::before { - content: "\F032D"; -} -.mdi-bus::before { - content: "\F0E7"; -} -.mdi-bus-alert::before { - content: "\FA98"; -} -.mdi-bus-articulated-end::before { - content: "\F79B"; -} -.mdi-bus-articulated-front::before { - content: "\F79C"; -} -.mdi-bus-clock::before { - content: "\F8C9"; -} -.mdi-bus-double-decker::before { - content: "\F79D"; -} -.mdi-bus-marker::before { - content: "\F023D"; -} -.mdi-bus-multiple::before { - content: "\FF5C"; -} -.mdi-bus-school::before { - content: "\F79E"; -} -.mdi-bus-side::before { - content: "\F79F"; -} -.mdi-bus-stop::before { - content: "\F0034"; -} -.mdi-bus-stop-covered::before { - content: "\F0035"; -} -.mdi-bus-stop-uncovered::before { - content: "\F0036"; -} -.mdi-cached::before { - content: "\F0E8"; -} -.mdi-cactus::before { - content: "\FD91"; -} -.mdi-cake::before { - content: "\F0E9"; -} -.mdi-cake-layered::before { - content: "\F0EA"; -} -.mdi-cake-variant::before { - content: "\F0EB"; -} -.mdi-calculator::before { - content: "\F0EC"; -} -.mdi-calculator-variant::before { - content: "\FA99"; -} -.mdi-calendar::before { - content: "\F0ED"; -} -.mdi-calendar-account::before { - content: "\FEF4"; -} -.mdi-calendar-account-outline::before { - content: "\FEF5"; -} -.mdi-calendar-alert::before { - content: "\FA30"; -} -.mdi-calendar-arrow-left::before { - content: "\F015F"; -} -.mdi-calendar-arrow-right::before { - content: "\F0160"; -} -.mdi-calendar-blank::before { - content: "\F0EE"; -} -.mdi-calendar-blank-multiple::before { - content: "\F009E"; -} -.mdi-calendar-blank-outline::before { - content: "\FB42"; -} -.mdi-calendar-check::before { - content: "\F0EF"; -} -.mdi-calendar-check-outline::before { - content: "\FC20"; -} -.mdi-calendar-clock::before { - content: "\F0F0"; -} -.mdi-calendar-edit::before { - content: "\F8A6"; -} -.mdi-calendar-export::before { - content: "\FB09"; -} -.mdi-calendar-heart::before { - content: "\F9D1"; -} -.mdi-calendar-import::before { - content: "\FB0A"; -} -.mdi-calendar-minus::before { - content: "\FD38"; -} -.mdi-calendar-month::before { - content: "\FDFA"; -} -.mdi-calendar-month-outline::before { - content: "\FDFB"; -} -.mdi-calendar-multiple::before { - content: "\F0F1"; -} -.mdi-calendar-multiple-check::before { - content: "\F0F2"; -} -.mdi-calendar-multiselect::before { - content: "\FA31"; -} -.mdi-calendar-outline::before { - content: "\FB43"; -} -.mdi-calendar-plus::before { - content: "\F0F3"; -} -.mdi-calendar-question::before { - content: "\F691"; -} -.mdi-calendar-range::before { - content: "\F678"; -} -.mdi-calendar-range-outline::before { - content: "\FB44"; -} -.mdi-calendar-remove::before { - content: "\F0F4"; -} -.mdi-calendar-remove-outline::before { - content: "\FC21"; -} -.mdi-calendar-repeat::before { - content: "\FEAB"; -} -.mdi-calendar-repeat-outline::before { - content: "\FEAC"; -} -.mdi-calendar-search::before { - content: "\F94B"; -} -.mdi-calendar-star::before { - content: "\F9D2"; -} -.mdi-calendar-text::before { - content: "\F0F5"; -} -.mdi-calendar-text-outline::before { - content: "\FC22"; -} -.mdi-calendar-today::before { - content: "\F0F6"; -} -.mdi-calendar-week::before { - content: "\FA32"; -} -.mdi-calendar-week-begin::before { - content: "\FA33"; -} -.mdi-calendar-weekend::before { - content: "\FEF6"; -} -.mdi-calendar-weekend-outline::before { - content: "\FEF7"; -} -.mdi-call-made::before { - content: "\F0F7"; -} -.mdi-call-merge::before { - content: "\F0F8"; -} -.mdi-call-missed::before { - content: "\F0F9"; -} -.mdi-call-received::before { - content: "\F0FA"; -} -.mdi-call-split::before { - content: "\F0FB"; -} -.mdi-camcorder::before { - content: "\F0FC"; -} -.mdi-camcorder-box::before { - content: "\F0FD"; -} -.mdi-camcorder-box-off::before { - content: "\F0FE"; -} -.mdi-camcorder-off::before { - content: "\F0FF"; -} -.mdi-camera::before { - content: "\F100"; -} -.mdi-camera-account::before { - content: "\F8CA"; -} -.mdi-camera-burst::before { - content: "\F692"; -} -.mdi-camera-control::before { - content: "\FB45"; -} -.mdi-camera-enhance::before { - content: "\F101"; -} -.mdi-camera-enhance-outline::before { - content: "\FB46"; -} -.mdi-camera-front::before { - content: "\F102"; -} -.mdi-camera-front-variant::before { - content: "\F103"; -} -.mdi-camera-gopro::before { - content: "\F7A0"; -} -.mdi-camera-image::before { - content: "\F8CB"; -} -.mdi-camera-iris::before { - content: "\F104"; -} -.mdi-camera-metering-center::before { - content: "\F7A1"; -} -.mdi-camera-metering-matrix::before { - content: "\F7A2"; -} -.mdi-camera-metering-partial::before { - content: "\F7A3"; -} -.mdi-camera-metering-spot::before { - content: "\F7A4"; -} -.mdi-camera-off::before { - content: "\F5DF"; -} -.mdi-camera-outline::before { - content: "\FD39"; -} -.mdi-camera-party-mode::before { - content: "\F105"; -} -.mdi-camera-plus::before { - content: "\FEF8"; -} -.mdi-camera-plus-outline::before { - content: "\FEF9"; -} -.mdi-camera-rear::before { - content: "\F106"; -} -.mdi-camera-rear-variant::before { - content: "\F107"; -} -.mdi-camera-retake::before { - content: "\FDFC"; -} -.mdi-camera-retake-outline::before { - content: "\FDFD"; -} -.mdi-camera-switch::before { - content: "\F108"; -} -.mdi-camera-timer::before { - content: "\F109"; -} -.mdi-camera-wireless::before { - content: "\FD92"; -} -.mdi-camera-wireless-outline::before { - content: "\FD93"; -} -.mdi-campfire::before { - content: "\FEFA"; -} -.mdi-cancel::before { - content: "\F739"; -} -.mdi-candle::before { - content: "\F5E2"; -} -.mdi-candycane::before { - content: "\F10A"; -} -.mdi-cannabis::before { - content: "\F7A5"; -} -.mdi-caps-lock::before { - content: "\FA9A"; -} -.mdi-car::before { - content: "\F10B"; -} -.mdi-car-2-plus::before { - content: "\F0037"; -} -.mdi-car-3-plus::before { - content: "\F0038"; -} -.mdi-car-back::before { - content: "\FDFE"; -} -.mdi-car-battery::before { - content: "\F10C"; -} -.mdi-car-brake-abs::before { - content: "\FC23"; -} -.mdi-car-brake-alert::before { - content: "\FC24"; -} -.mdi-car-brake-hold::before { - content: "\FD3A"; -} -.mdi-car-brake-parking::before { - content: "\FD3B"; -} -.mdi-car-brake-retarder::before { - content: "\F0039"; -} -.mdi-car-child-seat::before { - content: "\FFC3"; -} -.mdi-car-clutch::before { - content: "\F003A"; -} -.mdi-car-connected::before { - content: "\F10D"; -} -.mdi-car-convertible::before { - content: "\F7A6"; -} -.mdi-car-coolant-level::before { - content: "\F003B"; -} -.mdi-car-cruise-control::before { - content: "\FD3C"; -} -.mdi-car-defrost-front::before { - content: "\FD3D"; -} -.mdi-car-defrost-rear::before { - content: "\FD3E"; -} -.mdi-car-door::before { - content: "\FB47"; -} -.mdi-car-door-lock::before { - content: "\F00C8"; -} -.mdi-car-electric::before { - content: "\FB48"; -} -.mdi-car-esp::before { - content: "\FC25"; -} -.mdi-car-estate::before { - content: "\F7A7"; -} -.mdi-car-hatchback::before { - content: "\F7A8"; -} -.mdi-car-info::before { - content: "\F01E9"; -} -.mdi-car-key::before { - content: "\FB49"; -} -.mdi-car-light-dimmed::before { - content: "\FC26"; -} -.mdi-car-light-fog::before { - content: "\FC27"; -} -.mdi-car-light-high::before { - content: "\FC28"; -} -.mdi-car-limousine::before { - content: "\F8CC"; -} -.mdi-car-multiple::before { - content: "\FB4A"; -} -.mdi-car-off::before { - content: "\FDFF"; -} -.mdi-car-parking-lights::before { - content: "\FD3F"; -} -.mdi-car-pickup::before { - content: "\F7A9"; -} -.mdi-car-seat::before { - content: "\FFC4"; -} -.mdi-car-seat-cooler::before { - content: "\FFC5"; -} -.mdi-car-seat-heater::before { - content: "\FFC6"; -} -.mdi-car-shift-pattern::before { - content: "\FF5D"; -} -.mdi-car-side::before { - content: "\F7AA"; -} -.mdi-car-sports::before { - content: "\F7AB"; -} -.mdi-car-tire-alert::before { - content: "\FC29"; -} -.mdi-car-traction-control::before { - content: "\FD40"; -} -.mdi-car-turbocharger::before { - content: "\F003C"; -} -.mdi-car-wash::before { - content: "\F10E"; -} -.mdi-car-windshield::before { - content: "\F003D"; -} -.mdi-car-windshield-outline::before { - content: "\F003E"; -} -.mdi-caravan::before { - content: "\F7AC"; -} -.mdi-card::before { - content: "\FB4B"; -} -.mdi-card-bulleted::before { - content: "\FB4C"; -} -.mdi-card-bulleted-off::before { - content: "\FB4D"; -} -.mdi-card-bulleted-off-outline::before { - content: "\FB4E"; -} -.mdi-card-bulleted-outline::before { - content: "\FB4F"; -} -.mdi-card-bulleted-settings::before { - content: "\FB50"; -} -.mdi-card-bulleted-settings-outline::before { - content: "\FB51"; -} -.mdi-card-outline::before { - content: "\FB52"; -} -.mdi-card-plus::before { - content: "\F022A"; -} -.mdi-card-plus-outline::before { - content: "\F022B"; -} -.mdi-card-search::before { - content: "\F009F"; -} -.mdi-card-search-outline::before { - content: "\F00A0"; -} -.mdi-card-text::before { - content: "\FB53"; -} -.mdi-card-text-outline::before { - content: "\FB54"; -} -.mdi-cards::before { - content: "\F638"; -} -.mdi-cards-club::before { - content: "\F8CD"; -} -.mdi-cards-diamond::before { - content: "\F8CE"; -} -.mdi-cards-diamond-outline::before { - content: "\F003F"; -} -.mdi-cards-heart::before { - content: "\F8CF"; -} -.mdi-cards-outline::before { - content: "\F639"; -} -.mdi-cards-playing-outline::before { - content: "\F63A"; -} -.mdi-cards-spade::before { - content: "\F8D0"; -} -.mdi-cards-variant::before { - content: "\F6C6"; -} -.mdi-carrot::before { - content: "\F10F"; -} -.mdi-cart::before { - content: "\F110"; -} -.mdi-cart-arrow-down::before { - content: "\FD42"; -} -.mdi-cart-arrow-right::before { - content: "\FC2A"; -} -.mdi-cart-arrow-up::before { - content: "\FD43"; -} -.mdi-cart-minus::before { - content: "\FD44"; -} -.mdi-cart-off::before { - content: "\F66B"; -} -.mdi-cart-outline::before { - content: "\F111"; -} -.mdi-cart-plus::before { - content: "\F112"; -} -.mdi-cart-remove::before { - content: "\FD45"; -} -.mdi-case-sensitive-alt::before { - content: "\F113"; -} -.mdi-cash::before { - content: "\F114"; -} -.mdi-cash-100::before { - content: "\F115"; -} -.mdi-cash-marker::before { - content: "\FD94"; -} -.mdi-cash-minus::before { - content: "\F028B"; -} -.mdi-cash-multiple::before { - content: "\F116"; -} -.mdi-cash-plus::before { - content: "\F028C"; -} -.mdi-cash-refund::before { - content: "\FA9B"; -} -.mdi-cash-register::before { - content: "\FCD0"; -} -.mdi-cash-remove::before { - content: "\F028D"; -} -.mdi-cash-usd::before { - content: "\F01A1"; -} -.mdi-cash-usd-outline::before { - content: "\F117"; -} -.mdi-cassette::before { - content: "\F9D3"; -} -.mdi-cast::before { - content: "\F118"; -} -.mdi-cast-audio::before { - content: "\F0040"; -} -.mdi-cast-connected::before { - content: "\F119"; -} -.mdi-cast-education::before { - content: "\FE6D"; -} -.mdi-cast-off::before { - content: "\F789"; -} -.mdi-castle::before { - content: "\F11A"; -} -.mdi-cat::before { - content: "\F11B"; -} -.mdi-cctv::before { - content: "\F7AD"; -} -.mdi-ceiling-light::before { - content: "\F768"; -} -.mdi-cellphone::before { - content: "\F11C"; -} -.mdi-cellphone-android::before { - content: "\F11D"; -} -.mdi-cellphone-arrow-down::before { - content: "\F9D4"; -} -.mdi-cellphone-basic::before { - content: "\F11E"; -} -.mdi-cellphone-dock::before { - content: "\F11F"; -} -.mdi-cellphone-erase::before { - content: "\F94C"; -} -.mdi-cellphone-information::before { - content: "\FF5E"; -} -.mdi-cellphone-iphone::before { - content: "\F120"; -} -.mdi-cellphone-key::before { - content: "\F94D"; -} -.mdi-cellphone-link::before { - content: "\F121"; -} -.mdi-cellphone-link-off::before { - content: "\F122"; -} -.mdi-cellphone-lock::before { - content: "\F94E"; -} -.mdi-cellphone-message::before { - content: "\F8D2"; -} -.mdi-cellphone-message-off::before { - content: "\F00FD"; -} -.mdi-cellphone-nfc::before { - content: "\FEAD"; -} -.mdi-cellphone-nfc-off::before { - content: "\F0303"; -} -.mdi-cellphone-off::before { - content: "\F94F"; -} -.mdi-cellphone-play::before { - content: "\F0041"; -} -.mdi-cellphone-screenshot::before { - content: "\FA34"; -} -.mdi-cellphone-settings::before { - content: "\F123"; -} -.mdi-cellphone-settings-variant::before { - content: "\F950"; -} -.mdi-cellphone-sound::before { - content: "\F951"; -} -.mdi-cellphone-text::before { - content: "\F8D1"; -} -.mdi-cellphone-wireless::before { - content: "\F814"; -} -.mdi-celtic-cross::before { - content: "\FCD1"; -} -.mdi-centos::before { - content: "\F0145"; -} -.mdi-certificate::before { - content: "\F124"; -} -.mdi-certificate-outline::before { - content: "\F01B3"; -} -.mdi-chair-rolling::before { - content: "\FFBA"; -} -.mdi-chair-school::before { - content: "\F125"; -} -.mdi-charity::before { - content: "\FC2B"; -} -.mdi-chart-arc::before { - content: "\F126"; -} -.mdi-chart-areaspline::before { - content: "\F127"; -} -.mdi-chart-areaspline-variant::before { - content: "\FEAE"; -} -.mdi-chart-bar::before { - content: "\F128"; -} -.mdi-chart-bar-stacked::before { - content: "\F769"; -} -.mdi-chart-bell-curve::before { - content: "\FC2C"; -} -.mdi-chart-bell-curve-cumulative::before { - content: "\FFC7"; -} -.mdi-chart-bubble::before { - content: "\F5E3"; -} -.mdi-chart-donut::before { - content: "\F7AE"; -} -.mdi-chart-donut-variant::before { - content: "\F7AF"; -} -.mdi-chart-gantt::before { - content: "\F66C"; -} -.mdi-chart-histogram::before { - content: "\F129"; -} -.mdi-chart-line::before { - content: "\F12A"; -} -.mdi-chart-line-stacked::before { - content: "\F76A"; -} -.mdi-chart-line-variant::before { - content: "\F7B0"; -} -.mdi-chart-multiline::before { - content: "\F8D3"; -} -.mdi-chart-multiple::before { - content: "\F023E"; -} -.mdi-chart-pie::before { - content: "\F12B"; -} -.mdi-chart-ppf::before { - content: "\F03AB"; -} -.mdi-chart-scatter-plot::before { - content: "\FEAF"; -} -.mdi-chart-scatter-plot-hexbin::before { - content: "\F66D"; -} -.mdi-chart-snakey::before { - content: "\F020A"; -} -.mdi-chart-snakey-variant::before { - content: "\F020B"; -} -.mdi-chart-timeline::before { - content: "\F66E"; -} -.mdi-chart-timeline-variant::before { - content: "\FEB0"; -} -.mdi-chart-tree::before { - content: "\FEB1"; -} -.mdi-chat::before { - content: "\FB55"; -} -.mdi-chat-alert::before { - content: "\FB56"; -} -.mdi-chat-alert-outline::before { - content: "\F02F4"; -} -.mdi-chat-outline::before { - content: "\FEFB"; -} -.mdi-chat-processing::before { - content: "\FB57"; -} -.mdi-chat-processing-outline::before { - content: "\F02F5"; -} -.mdi-chat-sleep::before { - content: "\F02FC"; -} -.mdi-chat-sleep-outline::before { - content: "\F02FD"; -} -.mdi-check::before { - content: "\F12C"; -} -.mdi-check-all::before { - content: "\F12D"; -} -.mdi-check-bold::before { - content: "\FE6E"; -} -.mdi-check-box-multiple-outline::before { - content: "\FC2D"; -} -.mdi-check-box-outline::before { - content: "\FC2E"; -} -.mdi-check-circle::before { - content: "\F5E0"; -} -.mdi-check-circle-outline::before { - content: "\F5E1"; -} -.mdi-check-decagram::before { - content: "\F790"; -} -.mdi-check-network::before { - content: "\FC2F"; -} -.mdi-check-network-outline::before { - content: "\FC30"; -} -.mdi-check-outline::before { - content: "\F854"; -} -.mdi-check-underline::before { - content: "\FE70"; -} -.mdi-check-underline-circle::before { - content: "\FE71"; -} -.mdi-check-underline-circle-outline::before { - content: "\FE72"; -} -.mdi-checkbook::before { - content: "\FA9C"; -} -.mdi-checkbox-blank::before { - content: "\F12E"; -} -.mdi-checkbox-blank-circle::before { - content: "\F12F"; -} -.mdi-checkbox-blank-circle-outline::before { - content: "\F130"; -} -.mdi-checkbox-blank-off::before { - content: "\F0317"; -} -.mdi-checkbox-blank-off-outline::before { - content: "\F0318"; -} -.mdi-checkbox-blank-outline::before { - content: "\F131"; -} -.mdi-checkbox-intermediate::before { - content: "\F855"; -} -.mdi-checkbox-marked::before { - content: "\F132"; -} -.mdi-checkbox-marked-circle::before { - content: "\F133"; -} -.mdi-checkbox-marked-circle-outline::before { - content: "\F134"; -} -.mdi-checkbox-marked-outline::before { - content: "\F135"; -} -.mdi-checkbox-multiple-blank::before { - content: "\F136"; -} -.mdi-checkbox-multiple-blank-circle::before { - content: "\F63B"; -} -.mdi-checkbox-multiple-blank-circle-outline::before { - content: "\F63C"; -} -.mdi-checkbox-multiple-blank-outline::before { - content: "\F137"; -} -.mdi-checkbox-multiple-marked::before { - content: "\F138"; -} -.mdi-checkbox-multiple-marked-circle::before { - content: "\F63D"; -} -.mdi-checkbox-multiple-marked-circle-outline::before { - content: "\F63E"; -} -.mdi-checkbox-multiple-marked-outline::before { - content: "\F139"; -} -.mdi-checkerboard::before { - content: "\F13A"; -} -.mdi-checkerboard-minus::before { - content: "\F022D"; -} -.mdi-checkerboard-plus::before { - content: "\F022C"; -} -.mdi-checkerboard-remove::before { - content: "\F022E"; -} -.mdi-cheese::before { - content: "\F02E4"; -} -.mdi-chef-hat::before { - content: "\FB58"; -} -.mdi-chemical-weapon::before { - content: "\F13B"; -} -.mdi-chess-bishop::before { - content: "\F85B"; -} -.mdi-chess-king::before { - content: "\F856"; -} -.mdi-chess-knight::before { - content: "\F857"; -} -.mdi-chess-pawn::before { - content: "\F858"; -} -.mdi-chess-queen::before { - content: "\F859"; -} -.mdi-chess-rook::before { - content: "\F85A"; -} -.mdi-chevron-double-down::before { - content: "\F13C"; -} -.mdi-chevron-double-left::before { - content: "\F13D"; -} -.mdi-chevron-double-right::before { - content: "\F13E"; -} -.mdi-chevron-double-up::before { - content: "\F13F"; -} -.mdi-chevron-down::before { - content: "\F140"; -} -.mdi-chevron-down-box::before { - content: "\F9D5"; -} -.mdi-chevron-down-box-outline::before { - content: "\F9D6"; -} -.mdi-chevron-down-circle::before { - content: "\FB0B"; -} -.mdi-chevron-down-circle-outline::before { - content: "\FB0C"; -} -.mdi-chevron-left::before { - content: "\F141"; -} -.mdi-chevron-left-box::before { - content: "\F9D7"; -} -.mdi-chevron-left-box-outline::before { - content: "\F9D8"; -} -.mdi-chevron-left-circle::before { - content: "\FB0D"; -} -.mdi-chevron-left-circle-outline::before { - content: "\FB0E"; -} -.mdi-chevron-right::before { - content: "\F142"; -} -.mdi-chevron-right-box::before { - content: "\F9D9"; -} -.mdi-chevron-right-box-outline::before { - content: "\F9DA"; -} -.mdi-chevron-right-circle::before { - content: "\FB0F"; -} -.mdi-chevron-right-circle-outline::before { - content: "\FB10"; -} -.mdi-chevron-triple-down::before { - content: "\FD95"; -} -.mdi-chevron-triple-left::before { - content: "\FD96"; -} -.mdi-chevron-triple-right::before { - content: "\FD97"; -} -.mdi-chevron-triple-up::before { - content: "\FD98"; -} -.mdi-chevron-up::before { - content: "\F143"; -} -.mdi-chevron-up-box::before { - content: "\F9DB"; -} -.mdi-chevron-up-box-outline::before { - content: "\F9DC"; -} -.mdi-chevron-up-circle::before { - content: "\FB11"; -} -.mdi-chevron-up-circle-outline::before { - content: "\FB12"; -} -.mdi-chili-hot::before { - content: "\F7B1"; -} -.mdi-chili-medium::before { - content: "\F7B2"; -} -.mdi-chili-mild::before { - content: "\F7B3"; -} -.mdi-chip::before { - content: "\F61A"; -} -.mdi-christianity::before { - content: "\F952"; -} -.mdi-christianity-outline::before { - content: "\FCD2"; -} -.mdi-church::before { - content: "\F144"; -} -.mdi-cigar::before { - content: "\F01B4"; -} -.mdi-circle::before { - content: "\F764"; -} -.mdi-circle-double::before { - content: "\FEB2"; -} -.mdi-circle-edit-outline::before { - content: "\F8D4"; -} -.mdi-circle-expand::before { - content: "\FEB3"; -} -.mdi-circle-medium::before { - content: "\F9DD"; -} -.mdi-circle-off-outline::before { - content: "\F00FE"; -} -.mdi-circle-outline::before { - content: "\F765"; -} -.mdi-circle-slice-1::before { - content: "\FA9D"; -} -.mdi-circle-slice-2::before { - content: "\FA9E"; -} -.mdi-circle-slice-3::before { - content: "\FA9F"; -} -.mdi-circle-slice-4::before { - content: "\FAA0"; -} -.mdi-circle-slice-5::before { - content: "\FAA1"; -} -.mdi-circle-slice-6::before { - content: "\FAA2"; -} -.mdi-circle-slice-7::before { - content: "\FAA3"; -} -.mdi-circle-slice-8::before { - content: "\FAA4"; -} -.mdi-circle-small::before { - content: "\F9DE"; -} -.mdi-circular-saw::before { - content: "\FE73"; -} -.mdi-cisco-webex::before { - content: "\F145"; -} -.mdi-city::before { - content: "\F146"; -} -.mdi-city-variant::before { - content: "\FA35"; -} -.mdi-city-variant-outline::before { - content: "\FA36"; -} -.mdi-clipboard::before { - content: "\F147"; -} -.mdi-clipboard-account::before { - content: "\F148"; -} -.mdi-clipboard-account-outline::before { - content: "\FC31"; -} -.mdi-clipboard-alert::before { - content: "\F149"; -} -.mdi-clipboard-alert-outline::before { - content: "\FCD3"; -} -.mdi-clipboard-arrow-down::before { - content: "\F14A"; -} -.mdi-clipboard-arrow-down-outline::before { - content: "\FC32"; -} -.mdi-clipboard-arrow-left::before { - content: "\F14B"; -} -.mdi-clipboard-arrow-left-outline::before { - content: "\FCD4"; -} -.mdi-clipboard-arrow-right::before { - content: "\FCD5"; -} -.mdi-clipboard-arrow-right-outline::before { - content: "\FCD6"; -} -.mdi-clipboard-arrow-up::before { - content: "\FC33"; -} -.mdi-clipboard-arrow-up-outline::before { - content: "\FC34"; -} -.mdi-clipboard-check::before { - content: "\F14C"; -} -.mdi-clipboard-check-multiple::before { - content: "\F028E"; -} -.mdi-clipboard-check-multiple-outline::before { - content: "\F028F"; -} -.mdi-clipboard-check-outline::before { - content: "\F8A7"; -} -.mdi-clipboard-file::before { - content: "\F0290"; -} -.mdi-clipboard-file-outline::before { - content: "\F0291"; -} -.mdi-clipboard-flow::before { - content: "\F6C7"; -} -.mdi-clipboard-flow-outline::before { - content: "\F0142"; -} -.mdi-clipboard-list::before { - content: "\F00FF"; -} -.mdi-clipboard-list-outline::before { - content: "\F0100"; -} -.mdi-clipboard-multiple::before { - content: "\F0292"; -} -.mdi-clipboard-multiple-outline::before { - content: "\F0293"; -} -.mdi-clipboard-outline::before { - content: "\F14D"; -} -.mdi-clipboard-play::before { - content: "\FC35"; -} -.mdi-clipboard-play-multiple::before { - content: "\F0294"; -} -.mdi-clipboard-play-multiple-outline::before { - content: "\F0295"; -} -.mdi-clipboard-play-outline::before { - content: "\FC36"; -} -.mdi-clipboard-plus::before { - content: "\F750"; -} -.mdi-clipboard-plus-outline::before { - content: "\F034A"; -} -.mdi-clipboard-pulse::before { - content: "\F85C"; -} -.mdi-clipboard-pulse-outline::before { - content: "\F85D"; -} -.mdi-clipboard-text::before { - content: "\F14E"; -} -.mdi-clipboard-text-multiple::before { - content: "\F0296"; -} -.mdi-clipboard-text-multiple-outline::before { - content: "\F0297"; -} -.mdi-clipboard-text-outline::before { - content: "\FA37"; -} -.mdi-clipboard-text-play::before { - content: "\FC37"; -} -.mdi-clipboard-text-play-outline::before { - content: "\FC38"; -} -.mdi-clippy::before { - content: "\F14F"; -} -.mdi-clock::before { - content: "\F953"; -} -.mdi-clock-alert::before { - content: "\F954"; -} -.mdi-clock-alert-outline::before { - content: "\F5CE"; -} -.mdi-clock-check::before { - content: "\FFC8"; -} -.mdi-clock-check-outline::before { - content: "\FFC9"; -} -.mdi-clock-digital::before { - content: "\FEB4"; -} -.mdi-clock-end::before { - content: "\F151"; -} -.mdi-clock-fast::before { - content: "\F152"; -} -.mdi-clock-in::before { - content: "\F153"; -} -.mdi-clock-out::before { - content: "\F154"; -} -.mdi-clock-outline::before { - content: "\F150"; -} -.mdi-clock-start::before { - content: "\F155"; -} -.mdi-close::before { - content: "\F156"; -} -.mdi-close-box::before { - content: "\F157"; -} -.mdi-close-box-multiple::before { - content: "\FC39"; -} -.mdi-close-box-multiple-outline::before { - content: "\FC3A"; -} -.mdi-close-box-outline::before { - content: "\F158"; -} -.mdi-close-circle::before { - content: "\F159"; -} -.mdi-close-circle-outline::before { - content: "\F15A"; -} -.mdi-close-network::before { - content: "\F15B"; -} -.mdi-close-network-outline::before { - content: "\FC3B"; -} -.mdi-close-octagon::before { - content: "\F15C"; -} -.mdi-close-octagon-outline::before { - content: "\F15D"; -} -.mdi-close-outline::before { - content: "\F6C8"; -} -.mdi-closed-caption::before { - content: "\F15E"; -} -.mdi-closed-caption-outline::before { - content: "\FD99"; -} -.mdi-cloud::before { - content: "\F15F"; -} -.mdi-cloud-alert::before { - content: "\F9DF"; -} -.mdi-cloud-braces::before { - content: "\F7B4"; -} -.mdi-cloud-check::before { - content: "\F160"; -} -.mdi-cloud-check-outline::before { - content: "\F02F7"; -} -.mdi-cloud-circle::before { - content: "\F161"; -} -.mdi-cloud-download::before { - content: "\F162"; -} -.mdi-cloud-download-outline::before { - content: "\FB59"; -} -.mdi-cloud-lock::before { - content: "\F021C"; -} -.mdi-cloud-lock-outline::before { - content: "\F021D"; -} -.mdi-cloud-off-outline::before { - content: "\F164"; -} -.mdi-cloud-outline::before { - content: "\F163"; -} -.mdi-cloud-print::before { - content: "\F165"; -} -.mdi-cloud-print-outline::before { - content: "\F166"; -} -.mdi-cloud-question::before { - content: "\FA38"; -} -.mdi-cloud-search::before { - content: "\F955"; -} -.mdi-cloud-search-outline::before { - content: "\F956"; -} -.mdi-cloud-sync::before { - content: "\F63F"; -} -.mdi-cloud-sync-outline::before { - content: "\F0301"; -} -.mdi-cloud-tags::before { - content: "\F7B5"; -} -.mdi-cloud-upload::before { - content: "\F167"; -} -.mdi-cloud-upload-outline::before { - content: "\FB5A"; -} -.mdi-clover::before { - content: "\F815"; -} -.mdi-coach-lamp::before { - content: "\F0042"; -} -.mdi-coat-rack::before { - content: "\F00C9"; -} -.mdi-code-array::before { - content: "\F168"; -} -.mdi-code-braces::before { - content: "\F169"; -} -.mdi-code-braces-box::before { - content: "\F0101"; -} -.mdi-code-brackets::before { - content: "\F16A"; -} -.mdi-code-equal::before { - content: "\F16B"; -} -.mdi-code-greater-than::before { - content: "\F16C"; -} -.mdi-code-greater-than-or-equal::before { - content: "\F16D"; -} -.mdi-code-less-than::before { - content: "\F16E"; -} -.mdi-code-less-than-or-equal::before { - content: "\F16F"; -} -.mdi-code-not-equal::before { - content: "\F170"; -} -.mdi-code-not-equal-variant::before { - content: "\F171"; -} -.mdi-code-parentheses::before { - content: "\F172"; -} -.mdi-code-parentheses-box::before { - content: "\F0102"; -} -.mdi-code-string::before { - content: "\F173"; -} -.mdi-code-tags::before { - content: "\F174"; -} -.mdi-code-tags-check::before { - content: "\F693"; -} -.mdi-codepen::before { - content: "\F175"; -} -.mdi-coffee::before { - content: "\F176"; -} -.mdi-coffee-maker::before { - content: "\F00CA"; -} -.mdi-coffee-off::before { - content: "\FFCA"; -} -.mdi-coffee-off-outline::before { - content: "\FFCB"; -} -.mdi-coffee-outline::before { - content: "\F6C9"; -} -.mdi-coffee-to-go::before { - content: "\F177"; -} -.mdi-coffee-to-go-outline::before { - content: "\F0339"; -} -.mdi-coffin::before { - content: "\FB5B"; -} -.mdi-cog-clockwise::before { - content: "\F0208"; -} -.mdi-cog-counterclockwise::before { - content: "\F0209"; -} -.mdi-cogs::before { - content: "\F8D5"; -} -.mdi-coin::before { - content: "\F0196"; -} -.mdi-coin-outline::before { - content: "\F178"; -} -.mdi-coins::before { - content: "\F694"; -} -.mdi-collage::before { - content: "\F640"; -} -.mdi-collapse-all::before { - content: "\FAA5"; -} -.mdi-collapse-all-outline::before { - content: "\FAA6"; -} -.mdi-color-helper::before { - content: "\F179"; -} -.mdi-comma::before { - content: "\FE74"; -} -.mdi-comma-box::before { - content: "\FE75"; -} -.mdi-comma-box-outline::before { - content: "\FE76"; -} -.mdi-comma-circle::before { - content: "\FE77"; -} -.mdi-comma-circle-outline::before { - content: "\FE78"; -} -.mdi-comment::before { - content: "\F17A"; -} -.mdi-comment-account::before { - content: "\F17B"; -} -.mdi-comment-account-outline::before { - content: "\F17C"; -} -.mdi-comment-alert::before { - content: "\F17D"; -} -.mdi-comment-alert-outline::before { - content: "\F17E"; -} -.mdi-comment-arrow-left::before { - content: "\F9E0"; -} -.mdi-comment-arrow-left-outline::before { - content: "\F9E1"; -} -.mdi-comment-arrow-right::before { - content: "\F9E2"; -} -.mdi-comment-arrow-right-outline::before { - content: "\F9E3"; -} -.mdi-comment-check::before { - content: "\F17F"; -} -.mdi-comment-check-outline::before { - content: "\F180"; -} -.mdi-comment-edit::before { - content: "\F01EA"; -} -.mdi-comment-edit-outline::before { - content: "\F02EF"; -} -.mdi-comment-eye::before { - content: "\FA39"; -} -.mdi-comment-eye-outline::before { - content: "\FA3A"; -} -.mdi-comment-multiple::before { - content: "\F85E"; -} -.mdi-comment-multiple-outline::before { - content: "\F181"; -} -.mdi-comment-outline::before { - content: "\F182"; -} -.mdi-comment-plus::before { - content: "\F9E4"; -} -.mdi-comment-plus-outline::before { - content: "\F183"; -} -.mdi-comment-processing::before { - content: "\F184"; -} -.mdi-comment-processing-outline::before { - content: "\F185"; -} -.mdi-comment-question::before { - content: "\F816"; -} -.mdi-comment-question-outline::before { - content: "\F186"; -} -.mdi-comment-quote::before { - content: "\F0043"; -} -.mdi-comment-quote-outline::before { - content: "\F0044"; -} -.mdi-comment-remove::before { - content: "\F5DE"; -} -.mdi-comment-remove-outline::before { - content: "\F187"; -} -.mdi-comment-search::before { - content: "\FA3B"; -} -.mdi-comment-search-outline::before { - content: "\FA3C"; -} -.mdi-comment-text::before { - content: "\F188"; -} -.mdi-comment-text-multiple::before { - content: "\F85F"; -} -.mdi-comment-text-multiple-outline::before { - content: "\F860"; -} -.mdi-comment-text-outline::before { - content: "\F189"; -} -.mdi-compare::before { - content: "\F18A"; -} -.mdi-compass::before { - content: "\F18B"; -} -.mdi-compass-off::before { - content: "\FB5C"; -} -.mdi-compass-off-outline::before { - content: "\FB5D"; -} -.mdi-compass-outline::before { - content: "\F18C"; -} -.mdi-compass-rose::before { - content: "\F03AD"; -} -.mdi-concourse-ci::before { - content: "\F00CB"; -} -.mdi-console::before { - content: "\F18D"; -} -.mdi-console-line::before { - content: "\F7B6"; -} -.mdi-console-network::before { - content: "\F8A8"; -} -.mdi-console-network-outline::before { - content: "\FC3C"; -} -.mdi-consolidate::before { - content: "\F0103"; -} -.mdi-contact-mail::before { - content: "\F18E"; -} -.mdi-contact-mail-outline::before { - content: "\FEB5"; -} -.mdi-contact-phone::before { - content: "\FEB6"; -} -.mdi-contact-phone-outline::before { - content: "\FEB7"; -} -.mdi-contactless-payment::before { - content: "\FD46"; -} -.mdi-contacts::before { - content: "\F6CA"; -} -.mdi-contain::before { - content: "\FA3D"; -} -.mdi-contain-end::before { - content: "\FA3E"; -} -.mdi-contain-start::before { - content: "\FA3F"; -} -.mdi-content-copy::before { - content: "\F18F"; -} -.mdi-content-cut::before { - content: "\F190"; -} -.mdi-content-duplicate::before { - content: "\F191"; -} -.mdi-content-paste::before { - content: "\F192"; -} -.mdi-content-save::before { - content: "\F193"; -} -.mdi-content-save-alert::before { - content: "\FF5F"; -} -.mdi-content-save-alert-outline::before { - content: "\FF60"; -} -.mdi-content-save-all::before { - content: "\F194"; -} -.mdi-content-save-all-outline::before { - content: "\FF61"; -} -.mdi-content-save-edit::before { - content: "\FCD7"; -} -.mdi-content-save-edit-outline::before { - content: "\FCD8"; -} -.mdi-content-save-move::before { - content: "\FE79"; -} -.mdi-content-save-move-outline::before { - content: "\FE7A"; -} -.mdi-content-save-outline::before { - content: "\F817"; -} -.mdi-content-save-settings::before { - content: "\F61B"; -} -.mdi-content-save-settings-outline::before { - content: "\FB13"; -} -.mdi-contrast::before { - content: "\F195"; -} -.mdi-contrast-box::before { - content: "\F196"; -} -.mdi-contrast-circle::before { - content: "\F197"; -} -.mdi-controller-classic::before { - content: "\FB5E"; -} -.mdi-controller-classic-outline::before { - content: "\FB5F"; -} -.mdi-cookie::before { - content: "\F198"; -} -.mdi-coolant-temperature::before { - content: "\F3C8"; -} -.mdi-copyright::before { - content: "\F5E6"; -} -.mdi-cordova::before { - content: "\F957"; -} -.mdi-corn::before { - content: "\F7B7"; -} -.mdi-counter::before { - content: "\F199"; -} -.mdi-cow::before { - content: "\F19A"; -} -.mdi-cowboy::before { - content: "\FEB8"; -} -.mdi-cpu-32-bit::before { - content: "\FEFC"; -} -.mdi-cpu-64-bit::before { - content: "\FEFD"; -} -.mdi-crane::before { - content: "\F861"; -} -.mdi-creation::before { - content: "\F1C9"; -} -.mdi-creative-commons::before { - content: "\FD47"; -} -.mdi-credit-card::before { - content: "\F0010"; -} -.mdi-credit-card-clock::before { - content: "\FEFE"; -} -.mdi-credit-card-clock-outline::before { - content: "\FFBC"; -} -.mdi-credit-card-marker::before { - content: "\F6A7"; -} -.mdi-credit-card-marker-outline::before { - content: "\FD9A"; -} -.mdi-credit-card-minus::before { - content: "\FFCC"; -} -.mdi-credit-card-minus-outline::before { - content: "\FFCD"; -} -.mdi-credit-card-multiple::before { - content: "\F0011"; -} -.mdi-credit-card-multiple-outline::before { - content: "\F19C"; -} -.mdi-credit-card-off::before { - content: "\F0012"; -} -.mdi-credit-card-off-outline::before { - content: "\F5E4"; -} -.mdi-credit-card-outline::before { - content: "\F19B"; -} -.mdi-credit-card-plus::before { - content: "\F0013"; -} -.mdi-credit-card-plus-outline::before { - content: "\F675"; -} -.mdi-credit-card-refund::before { - content: "\F0014"; -} -.mdi-credit-card-refund-outline::before { - content: "\FAA7"; -} -.mdi-credit-card-remove::before { - content: "\FFCE"; -} -.mdi-credit-card-remove-outline::before { - content: "\FFCF"; -} -.mdi-credit-card-scan::before { - content: "\F0015"; -} -.mdi-credit-card-scan-outline::before { - content: "\F19D"; -} -.mdi-credit-card-settings::before { - content: "\F0016"; -} -.mdi-credit-card-settings-outline::before { - content: "\F8D6"; -} -.mdi-credit-card-wireless::before { - content: "\F801"; -} -.mdi-credit-card-wireless-outline::before { - content: "\FD48"; -} -.mdi-cricket::before { - content: "\FD49"; -} -.mdi-crop::before { - content: "\F19E"; -} -.mdi-crop-free::before { - content: "\F19F"; -} -.mdi-crop-landscape::before { - content: "\F1A0"; -} -.mdi-crop-portrait::before { - content: "\F1A1"; -} -.mdi-crop-rotate::before { - content: "\F695"; -} -.mdi-crop-square::before { - content: "\F1A2"; -} -.mdi-crosshairs::before { - content: "\F1A3"; -} -.mdi-crosshairs-gps::before { - content: "\F1A4"; -} -.mdi-crosshairs-off::before { - content: "\FF62"; -} -.mdi-crosshairs-question::before { - content: "\F0161"; -} -.mdi-crown::before { - content: "\F1A5"; -} -.mdi-crown-outline::before { - content: "\F01FB"; -} -.mdi-cryengine::before { - content: "\F958"; -} -.mdi-crystal-ball::before { - content: "\FB14"; -} -.mdi-cube::before { - content: "\F1A6"; -} -.mdi-cube-outline::before { - content: "\F1A7"; -} -.mdi-cube-scan::before { - content: "\FB60"; -} -.mdi-cube-send::before { - content: "\F1A8"; -} -.mdi-cube-unfolded::before { - content: "\F1A9"; -} -.mdi-cup::before { - content: "\F1AA"; -} -.mdi-cup-off::before { - content: "\F5E5"; -} -.mdi-cup-off-outline::before { - content: "\F03A8"; -} -.mdi-cup-outline::before { - content: "\F033A"; -} -.mdi-cup-water::before { - content: "\F1AB"; -} -.mdi-cupboard::before { - content: "\FF63"; -} -.mdi-cupboard-outline::before { - content: "\FF64"; -} -.mdi-cupcake::before { - content: "\F959"; -} -.mdi-curling::before { - content: "\F862"; -} -.mdi-currency-bdt::before { - content: "\F863"; -} -.mdi-currency-brl::before { - content: "\FB61"; -} -.mdi-currency-btc::before { - content: "\F1AC"; -} -.mdi-currency-cny::before { - content: "\F7B9"; -} -.mdi-currency-eth::before { - content: "\F7BA"; -} -.mdi-currency-eur::before { - content: "\F1AD"; -} -.mdi-currency-eur-off::before { - content: "\F0340"; -} -.mdi-currency-gbp::before { - content: "\F1AE"; -} -.mdi-currency-ils::before { - content: "\FC3D"; -} -.mdi-currency-inr::before { - content: "\F1AF"; -} -.mdi-currency-jpy::before { - content: "\F7BB"; -} -.mdi-currency-krw::before { - content: "\F7BC"; -} -.mdi-currency-kzt::before { - content: "\F864"; -} -.mdi-currency-ngn::before { - content: "\F1B0"; -} -.mdi-currency-php::before { - content: "\F9E5"; -} -.mdi-currency-rial::before { - content: "\FEB9"; -} -.mdi-currency-rub::before { - content: "\F1B1"; -} -.mdi-currency-sign::before { - content: "\F7BD"; -} -.mdi-currency-try::before { - content: "\F1B2"; -} -.mdi-currency-twd::before { - content: "\F7BE"; -} -.mdi-currency-usd::before { - content: "\F1B3"; -} -.mdi-currency-usd-off::before { - content: "\F679"; -} -.mdi-current-ac::before { - content: "\F95A"; -} -.mdi-current-dc::before { - content: "\F95B"; -} -.mdi-cursor-default::before { - content: "\F1B4"; -} -.mdi-cursor-default-click::before { - content: "\FCD9"; -} -.mdi-cursor-default-click-outline::before { - content: "\FCDA"; -} -.mdi-cursor-default-gesture::before { - content: "\F0152"; -} -.mdi-cursor-default-gesture-outline::before { - content: "\F0153"; -} -.mdi-cursor-default-outline::before { - content: "\F1B5"; -} -.mdi-cursor-move::before { - content: "\F1B6"; -} -.mdi-cursor-pointer::before { - content: "\F1B7"; -} -.mdi-cursor-text::before { - content: "\F5E7"; -} -.mdi-database::before { - content: "\F1B8"; -} -.mdi-database-check::before { - content: "\FAA8"; -} -.mdi-database-edit::before { - content: "\FB62"; -} -.mdi-database-export::before { - content: "\F95D"; -} -.mdi-database-import::before { - content: "\F95C"; -} -.mdi-database-lock::before { - content: "\FAA9"; -} -.mdi-database-marker::before { - content: "\F0321"; -} -.mdi-database-minus::before { - content: "\F1B9"; -} -.mdi-database-plus::before { - content: "\F1BA"; -} -.mdi-database-refresh::before { - content: "\FCDB"; -} -.mdi-database-remove::before { - content: "\FCDC"; -} -.mdi-database-search::before { - content: "\F865"; -} -.mdi-database-settings::before { - content: "\FCDD"; -} -.mdi-death-star::before { - content: "\F8D7"; -} -.mdi-death-star-variant::before { - content: "\F8D8"; -} -.mdi-deathly-hallows::before { - content: "\FB63"; -} -.mdi-debian::before { - content: "\F8D9"; -} -.mdi-debug-step-into::before { - content: "\F1BB"; -} -.mdi-debug-step-out::before { - content: "\F1BC"; -} -.mdi-debug-step-over::before { - content: "\F1BD"; -} -.mdi-decagram::before { - content: "\F76B"; -} -.mdi-decagram-outline::before { - content: "\F76C"; -} -.mdi-decimal::before { - content: "\F00CC"; -} -.mdi-decimal-comma::before { - content: "\F00CD"; -} -.mdi-decimal-comma-decrease::before { - content: "\F00CE"; -} -.mdi-decimal-comma-increase::before { - content: "\F00CF"; -} -.mdi-decimal-decrease::before { - content: "\F1BE"; -} -.mdi-decimal-increase::before { - content: "\F1BF"; -} -.mdi-delete::before { - content: "\F1C0"; -} -.mdi-delete-alert::before { - content: "\F00D0"; -} -.mdi-delete-alert-outline::before { - content: "\F00D1"; -} -.mdi-delete-circle::before { - content: "\F682"; -} -.mdi-delete-circle-outline::before { - content: "\FB64"; -} -.mdi-delete-empty::before { - content: "\F6CB"; -} -.mdi-delete-empty-outline::before { - content: "\FEBA"; -} -.mdi-delete-forever::before { - content: "\F5E8"; -} -.mdi-delete-forever-outline::before { - content: "\FB65"; -} -.mdi-delete-off::before { - content: "\F00D2"; -} -.mdi-delete-off-outline::before { - content: "\F00D3"; -} -.mdi-delete-outline::before { - content: "\F9E6"; -} -.mdi-delete-restore::before { - content: "\F818"; -} -.mdi-delete-sweep::before { - content: "\F5E9"; -} -.mdi-delete-sweep-outline::before { - content: "\FC3E"; -} -.mdi-delete-variant::before { - content: "\F1C1"; -} -.mdi-delta::before { - content: "\F1C2"; -} -.mdi-desk::before { - content: "\F0264"; -} -.mdi-desk-lamp::before { - content: "\F95E"; -} -.mdi-deskphone::before { - content: "\F1C3"; -} -.mdi-desktop-classic::before { - content: "\F7BF"; -} -.mdi-desktop-mac::before { - content: "\F1C4"; -} -.mdi-desktop-mac-dashboard::before { - content: "\F9E7"; -} -.mdi-desktop-tower::before { - content: "\F1C5"; -} -.mdi-desktop-tower-monitor::before { - content: "\FAAA"; -} -.mdi-details::before { - content: "\F1C6"; -} -.mdi-dev-to::before { - content: "\FD4A"; -} -.mdi-developer-board::before { - content: "\F696"; -} -.mdi-deviantart::before { - content: "\F1C7"; -} -.mdi-devices::before { - content: "\FFD0"; -} -.mdi-diabetes::before { - content: "\F0151"; -} -.mdi-dialpad::before { - content: "\F61C"; -} -.mdi-diameter::before { - content: "\FC3F"; -} -.mdi-diameter-outline::before { - content: "\FC40"; -} -.mdi-diameter-variant::before { - content: "\FC41"; -} -.mdi-diamond::before { - content: "\FB66"; -} -.mdi-diamond-outline::before { - content: "\FB67"; -} -.mdi-diamond-stone::before { - content: "\F1C8"; -} -.mdi-dice-1::before { - content: "\F1CA"; -} -.mdi-dice-1-outline::before { - content: "\F0175"; -} -.mdi-dice-2::before { - content: "\F1CB"; -} -.mdi-dice-2-outline::before { - content: "\F0176"; -} -.mdi-dice-3::before { - content: "\F1CC"; -} -.mdi-dice-3-outline::before { - content: "\F0177"; -} -.mdi-dice-4::before { - content: "\F1CD"; -} -.mdi-dice-4-outline::before { - content: "\F0178"; -} -.mdi-dice-5::before { - content: "\F1CE"; -} -.mdi-dice-5-outline::before { - content: "\F0179"; -} -.mdi-dice-6::before { - content: "\F1CF"; -} -.mdi-dice-6-outline::before { - content: "\F017A"; -} -.mdi-dice-d10::before { - content: "\F017E"; -} -.mdi-dice-d10-outline::before { - content: "\F76E"; -} -.mdi-dice-d12::before { - content: "\F017F"; -} -.mdi-dice-d12-outline::before { - content: "\F866"; -} -.mdi-dice-d20::before { - content: "\F0180"; -} -.mdi-dice-d20-outline::before { - content: "\F5EA"; -} -.mdi-dice-d4::before { - content: "\F017B"; -} -.mdi-dice-d4-outline::before { - content: "\F5EB"; -} -.mdi-dice-d6::before { - content: "\F017C"; -} -.mdi-dice-d6-outline::before { - content: "\F5EC"; -} -.mdi-dice-d8::before { - content: "\F017D"; -} -.mdi-dice-d8-outline::before { - content: "\F5ED"; -} -.mdi-dice-multiple::before { - content: "\F76D"; -} -.mdi-dice-multiple-outline::before { - content: "\F0181"; -} -.mdi-dictionary::before { - content: "\F61D"; -} -.mdi-digital-ocean::before { - content: "\F0262"; -} -.mdi-dip-switch::before { - content: "\F7C0"; -} -.mdi-directions::before { - content: "\F1D0"; -} -.mdi-directions-fork::before { - content: "\F641"; -} -.mdi-disc::before { - content: "\F5EE"; -} -.mdi-disc-alert::before { - content: "\F1D1"; -} -.mdi-disc-player::before { - content: "\F95F"; -} -.mdi-discord::before { - content: "\F66F"; -} -.mdi-dishwasher::before { - content: "\FAAB"; -} -.mdi-dishwasher-alert::before { - content: "\F01E3"; -} -.mdi-dishwasher-off::before { - content: "\F01E4"; -} -.mdi-disqus::before { - content: "\F1D2"; -} -.mdi-disqus-outline::before { - content: "\F1D3"; -} -.mdi-distribute-horizontal-center::before { - content: "\F01F4"; -} -.mdi-distribute-horizontal-left::before { - content: "\F01F3"; -} -.mdi-distribute-horizontal-right::before { - content: "\F01F5"; -} -.mdi-distribute-vertical-bottom::before { - content: "\F01F6"; -} -.mdi-distribute-vertical-center::before { - content: "\F01F7"; -} -.mdi-distribute-vertical-top::before { - content: "\F01F8"; -} -.mdi-diving-flippers::before { - content: "\FD9B"; -} -.mdi-diving-helmet::before { - content: "\FD9C"; -} -.mdi-diving-scuba::before { - content: "\FD9D"; -} -.mdi-diving-scuba-flag::before { - content: "\FD9E"; -} -.mdi-diving-scuba-tank::before { - content: "\FD9F"; -} -.mdi-diving-scuba-tank-multiple::before { - content: "\FDA0"; -} -.mdi-diving-snorkel::before { - content: "\FDA1"; -} -.mdi-division::before { - content: "\F1D4"; -} -.mdi-division-box::before { - content: "\F1D5"; -} -.mdi-dlna::before { - content: "\FA40"; -} -.mdi-dna::before { - content: "\F683"; -} -.mdi-dns::before { - content: "\F1D6"; -} -.mdi-dns-outline::before { - content: "\FB68"; -} -.mdi-do-not-disturb::before { - content: "\F697"; -} -.mdi-do-not-disturb-off::before { - content: "\F698"; -} -.mdi-dock-bottom::before { - content: "\F00D4"; -} -.mdi-dock-left::before { - content: "\F00D5"; -} -.mdi-dock-right::before { - content: "\F00D6"; -} -.mdi-dock-window::before { - content: "\F00D7"; -} -.mdi-docker::before { - content: "\F867"; -} -.mdi-doctor::before { - content: "\FA41"; -} -.mdi-dog::before { - content: "\FA42"; -} -.mdi-dog-service::before { - content: "\FAAC"; -} -.mdi-dog-side::before { - content: "\FA43"; -} -.mdi-dolby::before { - content: "\F6B2"; -} -.mdi-dolly::before { - content: "\FEBB"; -} -.mdi-domain::before { - content: "\F1D7"; -} -.mdi-domain-off::before { - content: "\FD4B"; -} -.mdi-domain-plus::before { - content: "\F00D8"; -} -.mdi-domain-remove::before { - content: "\F00D9"; -} -.mdi-domino-mask::before { - content: "\F0045"; -} -.mdi-donkey::before { - content: "\F7C1"; -} -.mdi-door::before { - content: "\F819"; -} -.mdi-door-closed::before { - content: "\F81A"; -} -.mdi-door-closed-lock::before { - content: "\F00DA"; -} -.mdi-door-open::before { - content: "\F81B"; -} -.mdi-doorbell::before { - content: "\F0311"; -} -.mdi-doorbell-video::before { - content: "\F868"; -} -.mdi-dot-net::before { - content: "\FAAD"; -} -.mdi-dots-horizontal::before { - content: "\F1D8"; -} -.mdi-dots-horizontal-circle::before { - content: "\F7C2"; -} -.mdi-dots-horizontal-circle-outline::before { - content: "\FB69"; -} -.mdi-dots-vertical::before { - content: "\F1D9"; -} -.mdi-dots-vertical-circle::before { - content: "\F7C3"; -} -.mdi-dots-vertical-circle-outline::before { - content: "\FB6A"; -} -.mdi-douban::before { - content: "\F699"; -} -.mdi-download::before { - content: "\F1DA"; -} -.mdi-download-lock::before { - content: "\F034B"; -} -.mdi-download-lock-outline::before { - content: "\F034C"; -} -.mdi-download-multiple::before { - content: "\F9E8"; -} -.mdi-download-network::before { - content: "\F6F3"; -} -.mdi-download-network-outline::before { - content: "\FC42"; -} -.mdi-download-off::before { - content: "\F00DB"; -} -.mdi-download-off-outline::before { - content: "\F00DC"; -} -.mdi-download-outline::before { - content: "\FB6B"; -} -.mdi-drag::before { - content: "\F1DB"; -} -.mdi-drag-horizontal::before { - content: "\F1DC"; -} -.mdi-drag-horizontal-variant::before { - content: "\F031B"; -} -.mdi-drag-variant::before { - content: "\FB6C"; -} -.mdi-drag-vertical::before { - content: "\F1DD"; -} -.mdi-drag-vertical-variant::before { - content: "\F031C"; -} -.mdi-drama-masks::before { - content: "\FCDE"; -} -.mdi-draw::before { - content: "\FF66"; -} -.mdi-drawing::before { - content: "\F1DE"; -} -.mdi-drawing-box::before { - content: "\F1DF"; -} -.mdi-dresser::before { - content: "\FF67"; -} -.mdi-dresser-outline::before { - content: "\FF68"; -} -.mdi-dribbble::before { - content: "\F1E0"; -} -.mdi-dribbble-box::before { - content: "\F1E1"; -} -.mdi-drone::before { - content: "\F1E2"; -} -.mdi-dropbox::before { - content: "\F1E3"; -} -.mdi-drupal::before { - content: "\F1E4"; -} -.mdi-duck::before { - content: "\F1E5"; -} -.mdi-dumbbell::before { - content: "\F1E6"; -} -.mdi-dump-truck::before { - content: "\FC43"; -} -.mdi-ear-hearing::before { - content: "\F7C4"; -} -.mdi-ear-hearing-off::before { - content: "\FA44"; -} -.mdi-earth::before { - content: "\F1E7"; -} -.mdi-earth-arrow-right::before { - content: "\F033C"; -} -.mdi-earth-box::before { - content: "\F6CC"; -} -.mdi-earth-box-off::before { - content: "\F6CD"; -} -.mdi-earth-off::before { - content: "\F1E8"; -} -.mdi-edge::before { - content: "\F1E9"; -} -.mdi-edge-legacy::before { - content: "\F027B"; -} -.mdi-egg::before { - content: "\FAAE"; -} -.mdi-egg-easter::before { - content: "\FAAF"; -} -.mdi-eight-track::before { - content: "\F9E9"; -} -.mdi-eject::before { - content: "\F1EA"; -} -.mdi-eject-outline::before { - content: "\FB6D"; -} -.mdi-electric-switch::before { - content: "\FEBC"; -} -.mdi-electric-switch-closed::before { - content: "\F0104"; -} -.mdi-electron-framework::before { - content: "\F0046"; -} -.mdi-elephant::before { - content: "\F7C5"; -} -.mdi-elevation-decline::before { - content: "\F1EB"; -} -.mdi-elevation-rise::before { - content: "\F1EC"; -} -.mdi-elevator::before { - content: "\F1ED"; -} -.mdi-elevator-down::before { - content: "\F02ED"; -} -.mdi-elevator-passenger::before { - content: "\F03AC"; -} -.mdi-elevator-up::before { - content: "\F02EC"; -} -.mdi-ellipse::before { - content: "\FEBD"; -} -.mdi-ellipse-outline::before { - content: "\FEBE"; -} -.mdi-email::before { - content: "\F1EE"; -} -.mdi-email-alert::before { - content: "\F6CE"; -} -.mdi-email-alert-outline::before { - content: "\FD1E"; -} -.mdi-email-box::before { - content: "\FCDF"; -} -.mdi-email-check::before { - content: "\FAB0"; -} -.mdi-email-check-outline::before { - content: "\FAB1"; -} -.mdi-email-edit::before { - content: "\FF00"; -} -.mdi-email-edit-outline::before { - content: "\FF01"; -} -.mdi-email-lock::before { - content: "\F1F1"; -} -.mdi-email-mark-as-unread::before { - content: "\FB6E"; -} -.mdi-email-minus::before { - content: "\FF02"; -} -.mdi-email-minus-outline::before { - content: "\FF03"; -} -.mdi-email-multiple::before { - content: "\FF04"; -} -.mdi-email-multiple-outline::before { - content: "\FF05"; -} -.mdi-email-newsletter::before { - content: "\FFD1"; -} -.mdi-email-open::before { - content: "\F1EF"; -} -.mdi-email-open-multiple::before { - content: "\FF06"; -} -.mdi-email-open-multiple-outline::before { - content: "\FF07"; -} -.mdi-email-open-outline::before { - content: "\F5EF"; -} -.mdi-email-outline::before { - content: "\F1F0"; -} -.mdi-email-plus::before { - content: "\F9EA"; -} -.mdi-email-plus-outline::before { - content: "\F9EB"; -} -.mdi-email-receive::before { - content: "\F0105"; -} -.mdi-email-receive-outline::before { - content: "\F0106"; -} -.mdi-email-search::before { - content: "\F960"; -} -.mdi-email-search-outline::before { - content: "\F961"; -} -.mdi-email-send::before { - content: "\F0107"; -} -.mdi-email-send-outline::before { - content: "\F0108"; -} -.mdi-email-sync::before { - content: "\F02F2"; -} -.mdi-email-sync-outline::before { - content: "\F02F3"; -} -.mdi-email-variant::before { - content: "\F5F0"; -} -.mdi-ember::before { - content: "\FB15"; -} -.mdi-emby::before { - content: "\F6B3"; -} -.mdi-emoticon::before { - content: "\FC44"; -} -.mdi-emoticon-angry::before { - content: "\FC45"; -} -.mdi-emoticon-angry-outline::before { - content: "\FC46"; -} -.mdi-emoticon-confused::before { - content: "\F0109"; -} -.mdi-emoticon-confused-outline::before { - content: "\F010A"; -} -.mdi-emoticon-cool::before { - content: "\FC47"; -} -.mdi-emoticon-cool-outline::before { - content: "\F1F3"; -} -.mdi-emoticon-cry::before { - content: "\FC48"; -} -.mdi-emoticon-cry-outline::before { - content: "\FC49"; -} -.mdi-emoticon-dead::before { - content: "\FC4A"; -} -.mdi-emoticon-dead-outline::before { - content: "\F69A"; -} -.mdi-emoticon-devil::before { - content: "\FC4B"; -} -.mdi-emoticon-devil-outline::before { - content: "\F1F4"; -} -.mdi-emoticon-excited::before { - content: "\FC4C"; -} -.mdi-emoticon-excited-outline::before { - content: "\F69B"; -} -.mdi-emoticon-frown::before { - content: "\FF69"; -} -.mdi-emoticon-frown-outline::before { - content: "\FF6A"; -} -.mdi-emoticon-happy::before { - content: "\FC4D"; -} -.mdi-emoticon-happy-outline::before { - content: "\F1F5"; -} -.mdi-emoticon-kiss::before { - content: "\FC4E"; -} -.mdi-emoticon-kiss-outline::before { - content: "\FC4F"; -} -.mdi-emoticon-lol::before { - content: "\F023F"; -} -.mdi-emoticon-lol-outline::before { - content: "\F0240"; -} -.mdi-emoticon-neutral::before { - content: "\FC50"; -} -.mdi-emoticon-neutral-outline::before { - content: "\F1F6"; -} -.mdi-emoticon-outline::before { - content: "\F1F2"; -} -.mdi-emoticon-poop::before { - content: "\F1F7"; -} -.mdi-emoticon-poop-outline::before { - content: "\FC51"; -} -.mdi-emoticon-sad::before { - content: "\FC52"; -} -.mdi-emoticon-sad-outline::before { - content: "\F1F8"; -} -.mdi-emoticon-tongue::before { - content: "\F1F9"; -} -.mdi-emoticon-tongue-outline::before { - content: "\FC53"; -} -.mdi-emoticon-wink::before { - content: "\FC54"; -} -.mdi-emoticon-wink-outline::before { - content: "\FC55"; -} -.mdi-engine::before { - content: "\F1FA"; -} -.mdi-engine-off::before { - content: "\FA45"; -} -.mdi-engine-off-outline::before { - content: "\FA46"; -} -.mdi-engine-outline::before { - content: "\F1FB"; -} -.mdi-epsilon::before { - content: "\F010B"; -} -.mdi-equal::before { - content: "\F1FC"; -} -.mdi-equal-box::before { - content: "\F1FD"; -} -.mdi-equalizer::before { - content: "\FEBF"; -} -.mdi-equalizer-outline::before { - content: "\FEC0"; -} -.mdi-eraser::before { - content: "\F1FE"; -} -.mdi-eraser-variant::before { - content: "\F642"; -} -.mdi-escalator::before { - content: "\F1FF"; -} -.mdi-escalator-down::before { - content: "\F02EB"; -} -.mdi-escalator-up::before { - content: "\F02EA"; -} -.mdi-eslint::before { - content: "\FC56"; -} -.mdi-et::before { - content: "\FAB2"; -} -.mdi-ethereum::before { - content: "\F869"; -} -.mdi-ethernet::before { - content: "\F200"; -} -.mdi-ethernet-cable::before { - content: "\F201"; -} -.mdi-ethernet-cable-off::before { - content: "\F202"; -} -.mdi-etsy::before { - content: "\F203"; -} -.mdi-ev-station::before { - content: "\F5F1"; -} -.mdi-eventbrite::before { - content: "\F7C6"; -} -.mdi-evernote::before { - content: "\F204"; -} -.mdi-excavator::before { - content: "\F0047"; -} -.mdi-exclamation::before { - content: "\F205"; -} -.mdi-exclamation-thick::before { - content: "\F0263"; -} -.mdi-exit-run::before { - content: "\FA47"; -} -.mdi-exit-to-app::before { - content: "\F206"; -} -.mdi-expand-all::before { - content: "\FAB3"; -} -.mdi-expand-all-outline::before { - content: "\FAB4"; -} -.mdi-expansion-card::before { - content: "\F8AD"; -} -.mdi-expansion-card-variant::before { - content: "\FFD2"; -} -.mdi-exponent::before { - content: "\F962"; -} -.mdi-exponent-box::before { - content: "\F963"; -} -.mdi-export::before { - content: "\F207"; -} -.mdi-export-variant::before { - content: "\FB6F"; -} -.mdi-eye::before { - content: "\F208"; -} -.mdi-eye-check::before { - content: "\FCE0"; -} -.mdi-eye-check-outline::before { - content: "\FCE1"; -} -.mdi-eye-circle::before { - content: "\FB70"; -} -.mdi-eye-circle-outline::before { - content: "\FB71"; -} -.mdi-eye-minus::before { - content: "\F0048"; -} -.mdi-eye-minus-outline::before { - content: "\F0049"; -} -.mdi-eye-off::before { - content: "\F209"; -} -.mdi-eye-off-outline::before { - content: "\F6D0"; -} -.mdi-eye-outline::before { - content: "\F6CF"; -} -.mdi-eye-plus::before { - content: "\F86A"; -} -.mdi-eye-plus-outline::before { - content: "\F86B"; -} -.mdi-eye-settings::before { - content: "\F86C"; -} -.mdi-eye-settings-outline::before { - content: "\F86D"; -} -.mdi-eyedropper::before { - content: "\F20A"; -} -.mdi-eyedropper-variant::before { - content: "\F20B"; -} -.mdi-face::before { - content: "\F643"; -} -.mdi-face-agent::before { - content: "\FD4C"; -} -.mdi-face-outline::before { - content: "\FB72"; -} -.mdi-face-profile::before { - content: "\F644"; -} -.mdi-face-profile-woman::before { - content: "\F00A1"; -} -.mdi-face-recognition::before { - content: "\FC57"; -} -.mdi-face-woman::before { - content: "\F00A2"; -} -.mdi-face-woman-outline::before { - content: "\F00A3"; -} -.mdi-facebook::before { - content: "\F20C"; -} -.mdi-facebook-box::before { - content: "\F20D"; -} -.mdi-facebook-messenger::before { - content: "\F20E"; -} -.mdi-facebook-workplace::before { - content: "\FB16"; -} -.mdi-factory::before { - content: "\F20F"; -} -.mdi-fan::before { - content: "\F210"; -} -.mdi-fan-off::before { - content: "\F81C"; -} -.mdi-fast-forward::before { - content: "\F211"; -} -.mdi-fast-forward-10::before { - content: "\FD4D"; -} -.mdi-fast-forward-30::before { - content: "\FCE2"; -} -.mdi-fast-forward-5::before { - content: "\F0223"; -} -.mdi-fast-forward-outline::before { - content: "\F6D1"; -} -.mdi-fax::before { - content: "\F212"; -} -.mdi-feather::before { - content: "\F6D2"; -} -.mdi-feature-search::before { - content: "\FA48"; -} -.mdi-feature-search-outline::before { - content: "\FA49"; -} -.mdi-fedora::before { - content: "\F8DA"; -} -.mdi-ferris-wheel::before { - content: "\FEC1"; -} -.mdi-ferry::before { - content: "\F213"; -} -.mdi-file::before { - content: "\F214"; -} -.mdi-file-account::before { - content: "\F73A"; -} -.mdi-file-account-outline::before { - content: "\F004A"; -} -.mdi-file-alert::before { - content: "\FA4A"; -} -.mdi-file-alert-outline::before { - content: "\FA4B"; -} -.mdi-file-cabinet::before { - content: "\FAB5"; -} -.mdi-file-cad::before { - content: "\FF08"; -} -.mdi-file-cad-box::before { - content: "\FF09"; -} -.mdi-file-cancel::before { - content: "\FDA2"; -} -.mdi-file-cancel-outline::before { - content: "\FDA3"; -} -.mdi-file-certificate::before { - content: "\F01B1"; -} -.mdi-file-certificate-outline::before { - content: "\F01B2"; -} -.mdi-file-chart::before { - content: "\F215"; -} -.mdi-file-chart-outline::before { - content: "\F004B"; -} -.mdi-file-check::before { - content: "\F216"; -} -.mdi-file-check-outline::before { - content: "\FE7B"; -} -.mdi-file-clock::before { - content: "\F030C"; -} -.mdi-file-clock-outline::before { - content: "\F030D"; -} -.mdi-file-cloud::before { - content: "\F217"; -} -.mdi-file-cloud-outline::before { - content: "\F004C"; -} -.mdi-file-code::before { - content: "\F22E"; -} -.mdi-file-code-outline::before { - content: "\F004D"; -} -.mdi-file-compare::before { - content: "\F8A9"; -} -.mdi-file-delimited::before { - content: "\F218"; -} -.mdi-file-delimited-outline::before { - content: "\FEC2"; -} -.mdi-file-document::before { - content: "\F219"; -} -.mdi-file-document-box::before { - content: "\F21A"; -} -.mdi-file-document-box-check::before { - content: "\FEC3"; -} -.mdi-file-document-box-check-outline::before { - content: "\FEC4"; -} -.mdi-file-document-box-minus::before { - content: "\FEC5"; -} -.mdi-file-document-box-minus-outline::before { - content: "\FEC6"; -} -.mdi-file-document-box-multiple::before { - content: "\FAB6"; -} -.mdi-file-document-box-multiple-outline::before { - content: "\FAB7"; -} -.mdi-file-document-box-outline::before { - content: "\F9EC"; -} -.mdi-file-document-box-plus::before { - content: "\FEC7"; -} -.mdi-file-document-box-plus-outline::before { - content: "\FEC8"; -} -.mdi-file-document-box-remove::before { - content: "\FEC9"; -} -.mdi-file-document-box-remove-outline::before { - content: "\FECA"; -} -.mdi-file-document-box-search::before { - content: "\FECB"; -} -.mdi-file-document-box-search-outline::before { - content: "\FECC"; -} -.mdi-file-document-edit::before { - content: "\FDA4"; -} -.mdi-file-document-edit-outline::before { - content: "\FDA5"; -} -.mdi-file-document-outline::before { - content: "\F9ED"; -} -.mdi-file-download::before { - content: "\F964"; -} -.mdi-file-download-outline::before { - content: "\F965"; -} -.mdi-file-edit::before { - content: "\F0212"; -} -.mdi-file-edit-outline::before { - content: "\F0213"; -} -.mdi-file-excel::before { - content: "\F21B"; -} -.mdi-file-excel-box::before { - content: "\F21C"; -} -.mdi-file-excel-box-outline::before { - content: "\F004E"; -} -.mdi-file-excel-outline::before { - content: "\F004F"; -} -.mdi-file-export::before { - content: "\F21D"; -} -.mdi-file-export-outline::before { - content: "\F0050"; -} -.mdi-file-eye::before { - content: "\FDA6"; -} -.mdi-file-eye-outline::before { - content: "\FDA7"; -} -.mdi-file-find::before { - content: "\F21E"; -} -.mdi-file-find-outline::before { - content: "\FB73"; -} -.mdi-file-hidden::before { - content: "\F613"; -} -.mdi-file-image::before { - content: "\F21F"; -} -.mdi-file-image-outline::before { - content: "\FECD"; -} -.mdi-file-import::before { - content: "\F220"; -} -.mdi-file-import-outline::before { - content: "\F0051"; -} -.mdi-file-key::before { - content: "\F01AF"; -} -.mdi-file-key-outline::before { - content: "\F01B0"; -} -.mdi-file-link::before { - content: "\F01A2"; -} -.mdi-file-link-outline::before { - content: "\F01A3"; -} -.mdi-file-lock::before { - content: "\F221"; -} -.mdi-file-lock-outline::before { - content: "\F0052"; -} -.mdi-file-move::before { - content: "\FAB8"; -} -.mdi-file-move-outline::before { - content: "\F0053"; -} -.mdi-file-multiple::before { - content: "\F222"; -} -.mdi-file-multiple-outline::before { - content: "\F0054"; -} -.mdi-file-music::before { - content: "\F223"; -} -.mdi-file-music-outline::before { - content: "\FE7C"; -} -.mdi-file-outline::before { - content: "\F224"; -} -.mdi-file-pdf::before { - content: "\F225"; -} -.mdi-file-pdf-box::before { - content: "\F226"; -} -.mdi-file-pdf-box-outline::before { - content: "\FFD3"; -} -.mdi-file-pdf-outline::before { - content: "\FE7D"; -} -.mdi-file-percent::before { - content: "\F81D"; -} -.mdi-file-percent-outline::before { - content: "\F0055"; -} -.mdi-file-phone::before { - content: "\F01A4"; -} -.mdi-file-phone-outline::before { - content: "\F01A5"; -} -.mdi-file-plus::before { - content: "\F751"; -} -.mdi-file-plus-outline::before { - content: "\FF0A"; -} -.mdi-file-powerpoint::before { - content: "\F227"; -} -.mdi-file-powerpoint-box::before { - content: "\F228"; -} -.mdi-file-powerpoint-box-outline::before { - content: "\F0056"; -} -.mdi-file-powerpoint-outline::before { - content: "\F0057"; -} -.mdi-file-presentation-box::before { - content: "\F229"; -} -.mdi-file-question::before { - content: "\F86E"; -} -.mdi-file-question-outline::before { - content: "\F0058"; -} -.mdi-file-remove::before { - content: "\FB74"; -} -.mdi-file-remove-outline::before { - content: "\F0059"; -} -.mdi-file-replace::before { - content: "\FB17"; -} -.mdi-file-replace-outline::before { - content: "\FB18"; -} -.mdi-file-restore::before { - content: "\F670"; -} -.mdi-file-restore-outline::before { - content: "\F005A"; -} -.mdi-file-search::before { - content: "\FC58"; -} -.mdi-file-search-outline::before { - content: "\FC59"; -} -.mdi-file-send::before { - content: "\F22A"; -} -.mdi-file-send-outline::before { - content: "\F005B"; -} -.mdi-file-settings::before { - content: "\F00A4"; -} -.mdi-file-settings-outline::before { - content: "\F00A5"; -} -.mdi-file-settings-variant::before { - content: "\F00A6"; -} -.mdi-file-settings-variant-outline::before { - content: "\F00A7"; -} -.mdi-file-star::before { - content: "\F005C"; -} -.mdi-file-star-outline::before { - content: "\F005D"; -} -.mdi-file-swap::before { - content: "\FFD4"; -} -.mdi-file-swap-outline::before { - content: "\FFD5"; -} -.mdi-file-sync::before { - content: "\F0241"; -} -.mdi-file-sync-outline::before { - content: "\F0242"; -} -.mdi-file-table::before { - content: "\FC5A"; -} -.mdi-file-table-box::before { - content: "\F010C"; -} -.mdi-file-table-box-multiple::before { - content: "\F010D"; -} -.mdi-file-table-box-multiple-outline::before { - content: "\F010E"; -} -.mdi-file-table-box-outline::before { - content: "\F010F"; -} -.mdi-file-table-outline::before { - content: "\FC5B"; -} -.mdi-file-tree::before { - content: "\F645"; -} -.mdi-file-undo::before { - content: "\F8DB"; -} -.mdi-file-undo-outline::before { - content: "\F005E"; -} -.mdi-file-upload::before { - content: "\FA4C"; -} -.mdi-file-upload-outline::before { - content: "\FA4D"; -} -.mdi-file-video::before { - content: "\F22B"; -} -.mdi-file-video-outline::before { - content: "\FE10"; -} -.mdi-file-word::before { - content: "\F22C"; -} -.mdi-file-word-box::before { - content: "\F22D"; -} -.mdi-file-word-box-outline::before { - content: "\F005F"; -} -.mdi-file-word-outline::before { - content: "\F0060"; -} -.mdi-film::before { - content: "\F22F"; -} -.mdi-filmstrip::before { - content: "\F230"; -} -.mdi-filmstrip-off::before { - content: "\F231"; -} -.mdi-filter::before { - content: "\F232"; -} -.mdi-filter-menu::before { - content: "\F0110"; -} -.mdi-filter-menu-outline::before { - content: "\F0111"; -} -.mdi-filter-minus::before { - content: "\FF0B"; -} -.mdi-filter-minus-outline::before { - content: "\FF0C"; -} -.mdi-filter-outline::before { - content: "\F233"; -} -.mdi-filter-plus::before { - content: "\FF0D"; -} -.mdi-filter-plus-outline::before { - content: "\FF0E"; -} -.mdi-filter-remove::before { - content: "\F234"; -} -.mdi-filter-remove-outline::before { - content: "\F235"; -} -.mdi-filter-variant::before { - content: "\F236"; -} -.mdi-filter-variant-minus::before { - content: "\F013D"; -} -.mdi-filter-variant-plus::before { - content: "\F013E"; -} -.mdi-filter-variant-remove::before { - content: "\F0061"; -} -.mdi-finance::before { - content: "\F81E"; -} -.mdi-find-replace::before { - content: "\F6D3"; -} -.mdi-fingerprint::before { - content: "\F237"; -} -.mdi-fingerprint-off::before { - content: "\FECE"; -} -.mdi-fire::before { - content: "\F238"; -} -.mdi-fire-extinguisher::before { - content: "\FF0F"; -} -.mdi-fire-hydrant::before { - content: "\F0162"; -} -.mdi-fire-hydrant-alert::before { - content: "\F0163"; -} -.mdi-fire-hydrant-off::before { - content: "\F0164"; -} -.mdi-fire-truck::before { - content: "\F8AA"; -} -.mdi-firebase::before { - content: "\F966"; -} -.mdi-firefox::before { - content: "\F239"; -} -.mdi-fireplace::before { - content: "\FE11"; -} -.mdi-fireplace-off::before { - content: "\FE12"; -} -.mdi-firework::before { - content: "\FE13"; -} -.mdi-fish::before { - content: "\F23A"; -} -.mdi-fishbowl::before { - content: "\FF10"; -} -.mdi-fishbowl-outline::before { - content: "\FF11"; -} -.mdi-fit-to-page::before { - content: "\FF12"; -} -.mdi-fit-to-page-outline::before { - content: "\FF13"; -} -.mdi-flag::before { - content: "\F23B"; -} -.mdi-flag-checkered::before { - content: "\F23C"; -} -.mdi-flag-minus::before { - content: "\FB75"; -} -.mdi-flag-minus-outline::before { - content: "\F00DD"; -} -.mdi-flag-outline::before { - content: "\F23D"; -} -.mdi-flag-plus::before { - content: "\FB76"; -} -.mdi-flag-plus-outline::before { - content: "\F00DE"; -} -.mdi-flag-remove::before { - content: "\FB77"; -} -.mdi-flag-remove-outline::before { - content: "\F00DF"; -} -.mdi-flag-triangle::before { - content: "\F23F"; -} -.mdi-flag-variant::before { - content: "\F240"; -} -.mdi-flag-variant-outline::before { - content: "\F23E"; -} -.mdi-flare::before { - content: "\FD4E"; -} -.mdi-flash::before { - content: "\F241"; -} -.mdi-flash-alert::before { - content: "\FF14"; -} -.mdi-flash-alert-outline::before { - content: "\FF15"; -} -.mdi-flash-auto::before { - content: "\F242"; -} -.mdi-flash-circle::before { - content: "\F81F"; -} -.mdi-flash-off::before { - content: "\F243"; -} -.mdi-flash-outline::before { - content: "\F6D4"; -} -.mdi-flash-red-eye::before { - content: "\F67A"; -} -.mdi-flashlight::before { - content: "\F244"; -} -.mdi-flashlight-off::before { - content: "\F245"; -} -.mdi-flask::before { - content: "\F093"; -} -.mdi-flask-empty::before { - content: "\F094"; -} -.mdi-flask-empty-minus::before { - content: "\F0265"; -} -.mdi-flask-empty-minus-outline::before { - content: "\F0266"; -} -.mdi-flask-empty-outline::before { - content: "\F095"; -} -.mdi-flask-empty-plus::before { - content: "\F0267"; -} -.mdi-flask-empty-plus-outline::before { - content: "\F0268"; -} -.mdi-flask-empty-remove::before { - content: "\F0269"; -} -.mdi-flask-empty-remove-outline::before { - content: "\F026A"; -} -.mdi-flask-minus::before { - content: "\F026B"; -} -.mdi-flask-minus-outline::before { - content: "\F026C"; -} -.mdi-flask-outline::before { - content: "\F096"; -} -.mdi-flask-plus::before { - content: "\F026D"; -} -.mdi-flask-plus-outline::before { - content: "\F026E"; -} -.mdi-flask-remove::before { - content: "\F026F"; -} -.mdi-flask-remove-outline::before { - content: "\F0270"; -} -.mdi-flask-round-bottom::before { - content: "\F0276"; -} -.mdi-flask-round-bottom-empty::before { - content: "\F0277"; -} -.mdi-flask-round-bottom-empty-outline::before { - content: "\F0278"; -} -.mdi-flask-round-bottom-outline::before { - content: "\F0279"; -} -.mdi-flattr::before { - content: "\F246"; -} -.mdi-fleur-de-lis::before { - content: "\F032E"; -} -.mdi-flickr::before { - content: "\FCE3"; -} -.mdi-flip-horizontal::before { - content: "\F0112"; -} -.mdi-flip-to-back::before { - content: "\F247"; -} -.mdi-flip-to-front::before { - content: "\F248"; -} -.mdi-flip-vertical::before { - content: "\F0113"; -} -.mdi-floor-lamp::before { - content: "\F8DC"; -} -.mdi-floor-lamp-dual::before { - content: "\F0062"; -} -.mdi-floor-lamp-variant::before { - content: "\F0063"; -} -.mdi-floor-plan::before { - content: "\F820"; -} -.mdi-floppy::before { - content: "\F249"; -} -.mdi-floppy-variant::before { - content: "\F9EE"; -} -.mdi-flower::before { - content: "\F24A"; -} -.mdi-flower-outline::before { - content: "\F9EF"; -} -.mdi-flower-poppy::before { - content: "\FCE4"; -} -.mdi-flower-tulip::before { - content: "\F9F0"; -} -.mdi-flower-tulip-outline::before { - content: "\F9F1"; -} -.mdi-focus-auto::before { - content: "\FF6B"; -} -.mdi-focus-field::before { - content: "\FF6C"; -} -.mdi-focus-field-horizontal::before { - content: "\FF6D"; -} -.mdi-focus-field-vertical::before { - content: "\FF6E"; -} -.mdi-folder::before { - content: "\F24B"; -} -.mdi-folder-account::before { - content: "\F24C"; -} -.mdi-folder-account-outline::before { - content: "\FB78"; -} -.mdi-folder-alert::before { - content: "\FDA8"; -} -.mdi-folder-alert-outline::before { - content: "\FDA9"; -} -.mdi-folder-clock::before { - content: "\FAB9"; -} -.mdi-folder-clock-outline::before { - content: "\FABA"; -} -.mdi-folder-download::before { - content: "\F24D"; -} -.mdi-folder-download-outline::before { - content: "\F0114"; -} -.mdi-folder-edit::before { - content: "\F8DD"; -} -.mdi-folder-edit-outline::before { - content: "\FDAA"; -} -.mdi-folder-google-drive::before { - content: "\F24E"; -} -.mdi-folder-heart::before { - content: "\F0115"; -} -.mdi-folder-heart-outline::before { - content: "\F0116"; -} -.mdi-folder-home::before { - content: "\F00E0"; -} -.mdi-folder-home-outline::before { - content: "\F00E1"; -} -.mdi-folder-image::before { - content: "\F24F"; -} -.mdi-folder-information::before { - content: "\F00E2"; -} -.mdi-folder-information-outline::before { - content: "\F00E3"; -} -.mdi-folder-key::before { - content: "\F8AB"; -} -.mdi-folder-key-network::before { - content: "\F8AC"; -} -.mdi-folder-key-network-outline::before { - content: "\FC5C"; -} -.mdi-folder-key-outline::before { - content: "\F0117"; -} -.mdi-folder-lock::before { - content: "\F250"; -} -.mdi-folder-lock-open::before { - content: "\F251"; -} -.mdi-folder-marker::before { - content: "\F0298"; -} -.mdi-folder-marker-outline::before { - content: "\F0299"; -} -.mdi-folder-move::before { - content: "\F252"; -} -.mdi-folder-move-outline::before { - content: "\F0271"; -} -.mdi-folder-multiple::before { - content: "\F253"; -} -.mdi-folder-multiple-image::before { - content: "\F254"; -} -.mdi-folder-multiple-outline::before { - content: "\F255"; -} -.mdi-folder-music::before { - content: "\F0384"; -} -.mdi-folder-music-outline::before { - content: "\F0385"; -} -.mdi-folder-network::before { - content: "\F86F"; -} -.mdi-folder-network-outline::before { - content: "\FC5D"; -} -.mdi-folder-open::before { - content: "\F76F"; -} -.mdi-folder-open-outline::before { - content: "\FDAB"; -} -.mdi-folder-outline::before { - content: "\F256"; -} -.mdi-folder-plus::before { - content: "\F257"; -} -.mdi-folder-plus-outline::before { - content: "\FB79"; -} -.mdi-folder-pound::before { - content: "\FCE5"; -} -.mdi-folder-pound-outline::before { - content: "\FCE6"; -} -.mdi-folder-remove::before { - content: "\F258"; -} -.mdi-folder-remove-outline::before { - content: "\FB7A"; -} -.mdi-folder-search::before { - content: "\F967"; -} -.mdi-folder-search-outline::before { - content: "\F968"; -} -.mdi-folder-settings::before { - content: "\F00A8"; -} -.mdi-folder-settings-outline::before { - content: "\F00A9"; -} -.mdi-folder-settings-variant::before { - content: "\F00AA"; -} -.mdi-folder-settings-variant-outline::before { - content: "\F00AB"; -} -.mdi-folder-star::before { - content: "\F69C"; -} -.mdi-folder-star-outline::before { - content: "\FB7B"; -} -.mdi-folder-swap::before { - content: "\FFD6"; -} -.mdi-folder-swap-outline::before { - content: "\FFD7"; -} -.mdi-folder-sync::before { - content: "\FCE7"; -} -.mdi-folder-sync-outline::before { - content: "\FCE8"; -} -.mdi-folder-table::before { - content: "\F030E"; -} -.mdi-folder-table-outline::before { - content: "\F030F"; -} -.mdi-folder-text::before { - content: "\FC5E"; -} -.mdi-folder-text-outline::before { - content: "\FC5F"; -} -.mdi-folder-upload::before { - content: "\F259"; -} -.mdi-folder-upload-outline::before { - content: "\F0118"; -} -.mdi-folder-zip::before { - content: "\F6EA"; -} -.mdi-folder-zip-outline::before { - content: "\F7B8"; -} -.mdi-font-awesome::before { - content: "\F03A"; -} -.mdi-food::before { - content: "\F25A"; -} -.mdi-food-apple::before { - content: "\F25B"; -} -.mdi-food-apple-outline::before { - content: "\FC60"; -} -.mdi-food-croissant::before { - content: "\F7C7"; -} -.mdi-food-fork-drink::before { - content: "\F5F2"; -} -.mdi-food-off::before { - content: "\F5F3"; -} -.mdi-food-variant::before { - content: "\F25C"; -} -.mdi-foot-print::before { - content: "\FF6F"; -} -.mdi-football::before { - content: "\F25D"; -} -.mdi-football-australian::before { - content: "\F25E"; -} -.mdi-football-helmet::before { - content: "\F25F"; -} -.mdi-forklift::before { - content: "\F7C8"; -} -.mdi-format-align-bottom::before { - content: "\F752"; -} -.mdi-format-align-center::before { - content: "\F260"; -} -.mdi-format-align-justify::before { - content: "\F261"; -} -.mdi-format-align-left::before { - content: "\F262"; -} -.mdi-format-align-middle::before { - content: "\F753"; -} -.mdi-format-align-right::before { - content: "\F263"; -} -.mdi-format-align-top::before { - content: "\F754"; -} -.mdi-format-annotation-minus::before { - content: "\FABB"; -} -.mdi-format-annotation-plus::before { - content: "\F646"; -} -.mdi-format-bold::before { - content: "\F264"; -} -.mdi-format-clear::before { - content: "\F265"; -} -.mdi-format-color-fill::before { - content: "\F266"; -} -.mdi-format-color-highlight::before { - content: "\FE14"; -} -.mdi-format-color-marker-cancel::before { - content: "\F033E"; -} -.mdi-format-color-text::before { - content: "\F69D"; -} -.mdi-format-columns::before { - content: "\F8DE"; -} -.mdi-format-float-center::before { - content: "\F267"; -} -.mdi-format-float-left::before { - content: "\F268"; -} -.mdi-format-float-none::before { - content: "\F269"; -} -.mdi-format-float-right::before { - content: "\F26A"; -} -.mdi-format-font::before { - content: "\F6D5"; -} -.mdi-format-font-size-decrease::before { - content: "\F9F2"; -} -.mdi-format-font-size-increase::before { - content: "\F9F3"; -} -.mdi-format-header-1::before { - content: "\F26B"; -} -.mdi-format-header-2::before { - content: "\F26C"; -} -.mdi-format-header-3::before { - content: "\F26D"; -} -.mdi-format-header-4::before { - content: "\F26E"; -} -.mdi-format-header-5::before { - content: "\F26F"; -} -.mdi-format-header-6::before { - content: "\F270"; -} -.mdi-format-header-decrease::before { - content: "\F271"; -} -.mdi-format-header-equal::before { - content: "\F272"; -} -.mdi-format-header-increase::before { - content: "\F273"; -} -.mdi-format-header-pound::before { - content: "\F274"; -} -.mdi-format-horizontal-align-center::before { - content: "\F61E"; -} -.mdi-format-horizontal-align-left::before { - content: "\F61F"; -} -.mdi-format-horizontal-align-right::before { - content: "\F620"; -} -.mdi-format-indent-decrease::before { - content: "\F275"; -} -.mdi-format-indent-increase::before { - content: "\F276"; -} -.mdi-format-italic::before { - content: "\F277"; -} -.mdi-format-letter-case::before { - content: "\FB19"; -} -.mdi-format-letter-case-lower::before { - content: "\FB1A"; -} -.mdi-format-letter-case-upper::before { - content: "\FB1B"; -} -.mdi-format-letter-ends-with::before { - content: "\FFD8"; -} -.mdi-format-letter-matches::before { - content: "\FFD9"; -} -.mdi-format-letter-starts-with::before { - content: "\FFDA"; -} -.mdi-format-line-spacing::before { - content: "\F278"; -} -.mdi-format-line-style::before { - content: "\F5C8"; -} -.mdi-format-line-weight::before { - content: "\F5C9"; -} -.mdi-format-list-bulleted::before { - content: "\F279"; -} -.mdi-format-list-bulleted-square::before { - content: "\FDAC"; -} -.mdi-format-list-bulleted-triangle::before { - content: "\FECF"; -} -.mdi-format-list-bulleted-type::before { - content: "\F27A"; -} -.mdi-format-list-checkbox::before { - content: "\F969"; -} -.mdi-format-list-checks::before { - content: "\F755"; -} -.mdi-format-list-numbered::before { - content: "\F27B"; -} -.mdi-format-list-numbered-rtl::before { - content: "\FCE9"; -} -.mdi-format-list-text::before { - content: "\F029A"; -} -.mdi-format-overline::before { - content: "\FED0"; -} -.mdi-format-page-break::before { - content: "\F6D6"; -} -.mdi-format-paint::before { - content: "\F27C"; -} -.mdi-format-paragraph::before { - content: "\F27D"; -} -.mdi-format-pilcrow::before { - content: "\F6D7"; -} -.mdi-format-quote-close::before { - content: "\F27E"; -} -.mdi-format-quote-close-outline::before { - content: "\F01D3"; -} -.mdi-format-quote-open::before { - content: "\F756"; -} -.mdi-format-quote-open-outline::before { - content: "\F01D2"; -} -.mdi-format-rotate-90::before { - content: "\F6A9"; -} -.mdi-format-section::before { - content: "\F69E"; -} -.mdi-format-size::before { - content: "\F27F"; -} -.mdi-format-strikethrough::before { - content: "\F280"; -} -.mdi-format-strikethrough-variant::before { - content: "\F281"; -} -.mdi-format-subscript::before { - content: "\F282"; -} -.mdi-format-superscript::before { - content: "\F283"; -} -.mdi-format-text::before { - content: "\F284"; -} -.mdi-format-text-rotation-angle-down::before { - content: "\FFDB"; -} -.mdi-format-text-rotation-angle-up::before { - content: "\FFDC"; -} -.mdi-format-text-rotation-down::before { - content: "\FD4F"; -} -.mdi-format-text-rotation-down-vertical::before { - content: "\FFDD"; -} -.mdi-format-text-rotation-none::before { - content: "\FD50"; -} -.mdi-format-text-rotation-up::before { - content: "\FFDE"; -} -.mdi-format-text-rotation-vertical::before { - content: "\FFDF"; -} -.mdi-format-text-variant::before { - content: "\FE15"; -} -.mdi-format-text-wrapping-clip::before { - content: "\FCEA"; -} -.mdi-format-text-wrapping-overflow::before { - content: "\FCEB"; -} -.mdi-format-text-wrapping-wrap::before { - content: "\FCEC"; -} -.mdi-format-textbox::before { - content: "\FCED"; -} -.mdi-format-textdirection-l-to-r::before { - content: "\F285"; -} -.mdi-format-textdirection-r-to-l::before { - content: "\F286"; -} -.mdi-format-title::before { - content: "\F5F4"; -} -.mdi-format-underline::before { - content: "\F287"; -} -.mdi-format-vertical-align-bottom::before { - content: "\F621"; -} -.mdi-format-vertical-align-center::before { - content: "\F622"; -} -.mdi-format-vertical-align-top::before { - content: "\F623"; -} -.mdi-format-wrap-inline::before { - content: "\F288"; -} -.mdi-format-wrap-square::before { - content: "\F289"; -} -.mdi-format-wrap-tight::before { - content: "\F28A"; -} -.mdi-format-wrap-top-bottom::before { - content: "\F28B"; -} -.mdi-forum::before { - content: "\F28C"; -} -.mdi-forum-outline::before { - content: "\F821"; -} -.mdi-forward::before { - content: "\F28D"; -} -.mdi-forwardburger::before { - content: "\FD51"; -} -.mdi-fountain::before { - content: "\F96A"; -} -.mdi-fountain-pen::before { - content: "\FCEE"; -} -.mdi-fountain-pen-tip::before { - content: "\FCEF"; -} -.mdi-foursquare::before { - content: "\F28E"; -} -.mdi-freebsd::before { - content: "\F8DF"; -} -.mdi-frequently-asked-questions::before { - content: "\FED1"; -} -.mdi-fridge::before { - content: "\F290"; -} -.mdi-fridge-alert::before { - content: "\F01DC"; -} -.mdi-fridge-alert-outline::before { - content: "\F01DD"; -} -.mdi-fridge-bottom::before { - content: "\F292"; -} -.mdi-fridge-off::before { - content: "\F01DA"; -} -.mdi-fridge-off-outline::before { - content: "\F01DB"; -} -.mdi-fridge-outline::before { - content: "\F28F"; -} -.mdi-fridge-top::before { - content: "\F291"; -} -.mdi-fruit-cherries::before { - content: "\F0064"; -} -.mdi-fruit-citrus::before { - content: "\F0065"; -} -.mdi-fruit-grapes::before { - content: "\F0066"; -} -.mdi-fruit-grapes-outline::before { - content: "\F0067"; -} -.mdi-fruit-pineapple::before { - content: "\F0068"; -} -.mdi-fruit-watermelon::before { - content: "\F0069"; -} -.mdi-fuel::before { - content: "\F7C9"; -} -.mdi-fullscreen::before { - content: "\F293"; -} -.mdi-fullscreen-exit::before { - content: "\F294"; -} -.mdi-function::before { - content: "\F295"; -} -.mdi-function-variant::before { - content: "\F870"; -} -.mdi-furigana-horizontal::before { - content: "\F00AC"; -} -.mdi-furigana-vertical::before { - content: "\F00AD"; -} -.mdi-fuse::before { - content: "\FC61"; -} -.mdi-fuse-blade::before { - content: "\FC62"; -} -.mdi-gamepad::before { - content: "\F296"; -} -.mdi-gamepad-circle::before { - content: "\FE16"; -} -.mdi-gamepad-circle-down::before { - content: "\FE17"; -} -.mdi-gamepad-circle-left::before { - content: "\FE18"; -} -.mdi-gamepad-circle-outline::before { - content: "\FE19"; -} -.mdi-gamepad-circle-right::before { - content: "\FE1A"; -} -.mdi-gamepad-circle-up::before { - content: "\FE1B"; -} -.mdi-gamepad-down::before { - content: "\FE1C"; -} -.mdi-gamepad-left::before { - content: "\FE1D"; -} -.mdi-gamepad-right::before { - content: "\FE1E"; -} -.mdi-gamepad-round::before { - content: "\FE1F"; -} -.mdi-gamepad-round-down::before { - content: "\FE7E"; -} -.mdi-gamepad-round-left::before { - content: "\FE7F"; -} -.mdi-gamepad-round-outline::before { - content: "\FE80"; -} -.mdi-gamepad-round-right::before { - content: "\FE81"; -} -.mdi-gamepad-round-up::before { - content: "\FE82"; -} -.mdi-gamepad-square::before { - content: "\FED2"; -} -.mdi-gamepad-square-outline::before { - content: "\FED3"; -} -.mdi-gamepad-up::before { - content: "\FE83"; -} -.mdi-gamepad-variant::before { - content: "\F297"; -} -.mdi-gamepad-variant-outline::before { - content: "\FED4"; -} -.mdi-gamma::before { - content: "\F0119"; -} -.mdi-gantry-crane::before { - content: "\FDAD"; -} -.mdi-garage::before { - content: "\F6D8"; -} -.mdi-garage-alert::before { - content: "\F871"; -} -.mdi-garage-alert-variant::before { - content: "\F0300"; -} -.mdi-garage-open::before { - content: "\F6D9"; -} -.mdi-garage-open-variant::before { - content: "\F02FF"; -} -.mdi-garage-variant::before { - content: "\F02FE"; -} -.mdi-gas-cylinder::before { - content: "\F647"; -} -.mdi-gas-station::before { - content: "\F298"; -} -.mdi-gas-station-outline::before { - content: "\FED5"; -} -.mdi-gate::before { - content: "\F299"; -} -.mdi-gate-and::before { - content: "\F8E0"; -} -.mdi-gate-arrow-right::before { - content: "\F0194"; -} -.mdi-gate-nand::before { - content: "\F8E1"; -} -.mdi-gate-nor::before { - content: "\F8E2"; -} -.mdi-gate-not::before { - content: "\F8E3"; -} -.mdi-gate-open::before { - content: "\F0195"; -} -.mdi-gate-or::before { - content: "\F8E4"; -} -.mdi-gate-xnor::before { - content: "\F8E5"; -} -.mdi-gate-xor::before { - content: "\F8E6"; -} -.mdi-gatsby::before { - content: "\FE84"; -} -.mdi-gauge::before { - content: "\F29A"; -} -.mdi-gauge-empty::before { - content: "\F872"; -} -.mdi-gauge-full::before { - content: "\F873"; -} -.mdi-gauge-low::before { - content: "\F874"; -} -.mdi-gavel::before { - content: "\F29B"; -} -.mdi-gender-female::before { - content: "\F29C"; -} -.mdi-gender-male::before { - content: "\F29D"; -} -.mdi-gender-male-female::before { - content: "\F29E"; -} -.mdi-gender-male-female-variant::before { - content: "\F016A"; -} -.mdi-gender-non-binary::before { - content: "\F016B"; -} -.mdi-gender-transgender::before { - content: "\F29F"; -} -.mdi-gentoo::before { - content: "\F8E7"; -} -.mdi-gesture::before { - content: "\F7CA"; -} -.mdi-gesture-double-tap::before { - content: "\F73B"; -} -.mdi-gesture-pinch::before { - content: "\FABC"; -} -.mdi-gesture-spread::before { - content: "\FABD"; -} -.mdi-gesture-swipe::before { - content: "\FD52"; -} -.mdi-gesture-swipe-down::before { - content: "\F73C"; -} -.mdi-gesture-swipe-horizontal::before { - content: "\FABE"; -} -.mdi-gesture-swipe-left::before { - content: "\F73D"; -} -.mdi-gesture-swipe-right::before { - content: "\F73E"; -} -.mdi-gesture-swipe-up::before { - content: "\F73F"; -} -.mdi-gesture-swipe-vertical::before { - content: "\FABF"; -} -.mdi-gesture-tap::before { - content: "\F740"; -} -.mdi-gesture-tap-box::before { - content: "\F02D4"; -} -.mdi-gesture-tap-button::before { - content: "\F02D3"; -} -.mdi-gesture-tap-hold::before { - content: "\FD53"; -} -.mdi-gesture-two-double-tap::before { - content: "\F741"; -} -.mdi-gesture-two-tap::before { - content: "\F742"; -} -.mdi-ghost::before { - content: "\F2A0"; -} -.mdi-ghost-off::before { - content: "\F9F4"; -} -.mdi-gif::before { - content: "\FD54"; -} -.mdi-gift::before { - content: "\FE85"; -} -.mdi-gift-outline::before { - content: "\F2A1"; -} -.mdi-git::before { - content: "\F2A2"; -} -.mdi-github-box::before { - content: "\F2A3"; -} -.mdi-github-circle::before { - content: "\F2A4"; -} -.mdi-github-face::before { - content: "\F6DA"; -} -.mdi-gitlab::before { - content: "\FB7C"; -} -.mdi-glass-cocktail::before { - content: "\F356"; -} -.mdi-glass-flute::before { - content: "\F2A5"; -} -.mdi-glass-mug::before { - content: "\F2A6"; -} -.mdi-glass-mug-variant::before { - content: "\F0141"; -} -.mdi-glass-pint-outline::before { - content: "\F0338"; -} -.mdi-glass-stange::before { - content: "\F2A7"; -} -.mdi-glass-tulip::before { - content: "\F2A8"; -} -.mdi-glass-wine::before { - content: "\F875"; -} -.mdi-glassdoor::before { - content: "\F2A9"; -} -.mdi-glasses::before { - content: "\F2AA"; -} -.mdi-globe-light::before { - content: "\F0302"; -} -.mdi-globe-model::before { - content: "\F8E8"; -} -.mdi-gmail::before { - content: "\F2AB"; -} -.mdi-gnome::before { - content: "\F2AC"; -} -.mdi-go-kart::before { - content: "\FD55"; -} -.mdi-go-kart-track::before { - content: "\FD56"; -} -.mdi-gog::before { - content: "\FB7D"; -} -.mdi-gold::before { - content: "\F027A"; -} -.mdi-golf::before { - content: "\F822"; -} -.mdi-golf-cart::before { - content: "\F01CF"; -} -.mdi-golf-tee::before { - content: "\F00AE"; -} -.mdi-gondola::before { - content: "\F685"; -} -.mdi-goodreads::before { - content: "\FD57"; -} -.mdi-google::before { - content: "\F2AD"; -} -.mdi-google-adwords::before { - content: "\FC63"; -} -.mdi-google-analytics::before { - content: "\F7CB"; -} -.mdi-google-assistant::before { - content: "\F7CC"; -} -.mdi-google-cardboard::before { - content: "\F2AE"; -} -.mdi-google-chrome::before { - content: "\F2AF"; -} -.mdi-google-circles::before { - content: "\F2B0"; -} -.mdi-google-circles-communities::before { - content: "\F2B1"; -} -.mdi-google-circles-extended::before { - content: "\F2B2"; -} -.mdi-google-circles-group::before { - content: "\F2B3"; -} -.mdi-google-classroom::before { - content: "\F2C0"; -} -.mdi-google-cloud::before { - content: "\F0221"; -} -.mdi-google-controller::before { - content: "\F2B4"; -} -.mdi-google-controller-off::before { - content: "\F2B5"; -} -.mdi-google-downasaur::before { - content: "\F038D"; -} -.mdi-google-drive::before { - content: "\F2B6"; -} -.mdi-google-earth::before { - content: "\F2B7"; -} -.mdi-google-fit::before { - content: "\F96B"; -} -.mdi-google-glass::before { - content: "\F2B8"; -} -.mdi-google-hangouts::before { - content: "\F2C9"; -} -.mdi-google-home::before { - content: "\F823"; -} -.mdi-google-keep::before { - content: "\F6DB"; -} -.mdi-google-lens::before { - content: "\F9F5"; -} -.mdi-google-maps::before { - content: "\F5F5"; -} -.mdi-google-my-business::before { - content: "\F006A"; -} -.mdi-google-nearby::before { - content: "\F2B9"; -} -.mdi-google-pages::before { - content: "\F2BA"; -} -.mdi-google-photos::before { - content: "\F6DC"; -} -.mdi-google-physical-web::before { - content: "\F2BB"; -} -.mdi-google-play::before { - content: "\F2BC"; -} -.mdi-google-plus::before { - content: "\F2BD"; -} -.mdi-google-plus-box::before { - content: "\F2BE"; -} -.mdi-google-podcast::before { - content: "\FED6"; -} -.mdi-google-spreadsheet::before { - content: "\F9F6"; -} -.mdi-google-street-view::before { - content: "\FC64"; -} -.mdi-google-translate::before { - content: "\F2BF"; -} -.mdi-gradient::before { - content: "\F69F"; -} -.mdi-grain::before { - content: "\FD58"; -} -.mdi-graph::before { - content: "\F006B"; -} -.mdi-graph-outline::before { - content: "\F006C"; -} -.mdi-graphql::before { - content: "\F876"; -} -.mdi-grave-stone::before { - content: "\FB7E"; -} -.mdi-grease-pencil::before { - content: "\F648"; -} -.mdi-greater-than::before { - content: "\F96C"; -} -.mdi-greater-than-or-equal::before { - content: "\F96D"; -} -.mdi-grid::before { - content: "\F2C1"; -} -.mdi-grid-large::before { - content: "\F757"; -} -.mdi-grid-off::before { - content: "\F2C2"; -} -.mdi-grill::before { - content: "\FE86"; -} -.mdi-grill-outline::before { - content: "\F01B5"; -} -.mdi-group::before { - content: "\F2C3"; -} -.mdi-guitar-acoustic::before { - content: "\F770"; -} -.mdi-guitar-electric::before { - content: "\F2C4"; -} -.mdi-guitar-pick::before { - content: "\F2C5"; -} -.mdi-guitar-pick-outline::before { - content: "\F2C6"; -} -.mdi-guy-fawkes-mask::before { - content: "\F824"; -} -.mdi-hackernews::before { - content: "\F624"; -} -.mdi-hail::before { - content: "\FAC0"; -} -.mdi-hair-dryer::before { - content: "\F011A"; -} -.mdi-hair-dryer-outline::before { - content: "\F011B"; -} -.mdi-halloween::before { - content: "\FB7F"; -} -.mdi-hamburger::before { - content: "\F684"; -} -.mdi-hammer::before { - content: "\F8E9"; -} -.mdi-hammer-screwdriver::before { - content: "\F034D"; -} -.mdi-hammer-wrench::before { - content: "\F034E"; -} -.mdi-hand::before { - content: "\FA4E"; -} -.mdi-hand-heart::before { - content: "\F011C"; -} -.mdi-hand-left::before { - content: "\FE87"; -} -.mdi-hand-okay::before { - content: "\FA4F"; -} -.mdi-hand-peace::before { - content: "\FA50"; -} -.mdi-hand-peace-variant::before { - content: "\FA51"; -} -.mdi-hand-pointing-down::before { - content: "\FA52"; -} -.mdi-hand-pointing-left::before { - content: "\FA53"; -} -.mdi-hand-pointing-right::before { - content: "\F2C7"; -} -.mdi-hand-pointing-up::before { - content: "\FA54"; -} -.mdi-hand-right::before { - content: "\FE88"; -} -.mdi-hand-saw::before { - content: "\FE89"; -} -.mdi-handball::before { - content: "\FF70"; -} -.mdi-handcuffs::before { - content: "\F0169"; -} -.mdi-handshake::before { - content: "\F0243"; -} -.mdi-hanger::before { - content: "\F2C8"; -} -.mdi-hard-hat::before { - content: "\F96E"; -} -.mdi-harddisk::before { - content: "\F2CA"; -} -.mdi-harddisk-plus::before { - content: "\F006D"; -} -.mdi-harddisk-remove::before { - content: "\F006E"; -} -.mdi-hat-fedora::before { - content: "\FB80"; -} -.mdi-hazard-lights::before { - content: "\FC65"; -} -.mdi-hdr::before { - content: "\FD59"; -} -.mdi-hdr-off::before { - content: "\FD5A"; -} -.mdi-head::before { - content: "\F0389"; -} -.mdi-head-alert::before { - content: "\F0363"; -} -.mdi-head-alert-outline::before { - content: "\F0364"; -} -.mdi-head-check::before { - content: "\F0365"; -} -.mdi-head-check-outline::before { - content: "\F0366"; -} -.mdi-head-cog::before { - content: "\F0367"; -} -.mdi-head-cog-outline::before { - content: "\F0368"; -} -.mdi-head-dots-horizontal::before { - content: "\F0369"; -} -.mdi-head-dots-horizontal-outline::before { - content: "\F036A"; -} -.mdi-head-flash::before { - content: "\F036B"; -} -.mdi-head-flash-outline::before { - content: "\F036C"; -} -.mdi-head-heart::before { - content: "\F036D"; -} -.mdi-head-heart-outline::before { - content: "\F036E"; -} -.mdi-head-lightbulb::before { - content: "\F036F"; -} -.mdi-head-lightbulb-outline::before { - content: "\F0370"; -} -.mdi-head-minus::before { - content: "\F0371"; -} -.mdi-head-minus-outline::before { - content: "\F0372"; -} -.mdi-head-outline::before { - content: "\F038A"; -} -.mdi-head-plus::before { - content: "\F0373"; -} -.mdi-head-plus-outline::before { - content: "\F0374"; -} -.mdi-head-question::before { - content: "\F0375"; -} -.mdi-head-question-outline::before { - content: "\F0376"; -} -.mdi-head-remove::before { - content: "\F0377"; -} -.mdi-head-remove-outline::before { - content: "\F0378"; -} -.mdi-head-snowflake::before { - content: "\F0379"; -} -.mdi-head-snowflake-outline::before { - content: "\F037A"; -} -.mdi-head-sync::before { - content: "\F037B"; -} -.mdi-head-sync-outline::before { - content: "\F037C"; -} -.mdi-headphones::before { - content: "\F2CB"; -} -.mdi-headphones-bluetooth::before { - content: "\F96F"; -} -.mdi-headphones-box::before { - content: "\F2CC"; -} -.mdi-headphones-off::before { - content: "\F7CD"; -} -.mdi-headphones-settings::before { - content: "\F2CD"; -} -.mdi-headset::before { - content: "\F2CE"; -} -.mdi-headset-dock::before { - content: "\F2CF"; -} -.mdi-headset-off::before { - content: "\F2D0"; -} -.mdi-heart::before { - content: "\F2D1"; -} -.mdi-heart-box::before { - content: "\F2D2"; -} -.mdi-heart-box-outline::before { - content: "\F2D3"; -} -.mdi-heart-broken::before { - content: "\F2D4"; -} -.mdi-heart-broken-outline::before { - content: "\FCF0"; -} -.mdi-heart-circle::before { - content: "\F970"; -} -.mdi-heart-circle-outline::before { - content: "\F971"; -} -.mdi-heart-flash::before { - content: "\FF16"; -} -.mdi-heart-half::before { - content: "\F6DE"; -} -.mdi-heart-half-full::before { - content: "\F6DD"; -} -.mdi-heart-half-outline::before { - content: "\F6DF"; -} -.mdi-heart-multiple::before { - content: "\FA55"; -} -.mdi-heart-multiple-outline::before { - content: "\FA56"; -} -.mdi-heart-off::before { - content: "\F758"; -} -.mdi-heart-outline::before { - content: "\F2D5"; -} -.mdi-heart-pulse::before { - content: "\F5F6"; -} -.mdi-helicopter::before { - content: "\FAC1"; -} -.mdi-help::before { - content: "\F2D6"; -} -.mdi-help-box::before { - content: "\F78A"; -} -.mdi-help-circle::before { - content: "\F2D7"; -} -.mdi-help-circle-outline::before { - content: "\F625"; -} -.mdi-help-network::before { - content: "\F6F4"; -} -.mdi-help-network-outline::before { - content: "\FC66"; -} -.mdi-help-rhombus::before { - content: "\FB81"; -} -.mdi-help-rhombus-outline::before { - content: "\FB82"; -} -.mdi-hexadecimal::before { - content: "\F02D2"; -} -.mdi-hexagon::before { - content: "\F2D8"; -} -.mdi-hexagon-multiple::before { - content: "\F6E0"; -} -.mdi-hexagon-multiple-outline::before { - content: "\F011D"; -} -.mdi-hexagon-outline::before { - content: "\F2D9"; -} -.mdi-hexagon-slice-1::before { - content: "\FAC2"; -} -.mdi-hexagon-slice-2::before { - content: "\FAC3"; -} -.mdi-hexagon-slice-3::before { - content: "\FAC4"; -} -.mdi-hexagon-slice-4::before { - content: "\FAC5"; -} -.mdi-hexagon-slice-5::before { - content: "\FAC6"; -} -.mdi-hexagon-slice-6::before { - content: "\FAC7"; -} -.mdi-hexagram::before { - content: "\FAC8"; -} -.mdi-hexagram-outline::before { - content: "\FAC9"; -} -.mdi-high-definition::before { - content: "\F7CE"; -} -.mdi-high-definition-box::before { - content: "\F877"; -} -.mdi-highway::before { - content: "\F5F7"; -} -.mdi-hiking::before { - content: "\FD5B"; -} -.mdi-hinduism::before { - content: "\F972"; -} -.mdi-history::before { - content: "\F2DA"; -} -.mdi-hockey-puck::before { - content: "\F878"; -} -.mdi-hockey-sticks::before { - content: "\F879"; -} -.mdi-hololens::before { - content: "\F2DB"; -} -.mdi-home::before { - content: "\F2DC"; -} -.mdi-home-account::before { - content: "\F825"; -} -.mdi-home-alert::before { - content: "\F87A"; -} -.mdi-home-analytics::before { - content: "\FED7"; -} -.mdi-home-assistant::before { - content: "\F7CF"; -} -.mdi-home-automation::before { - content: "\F7D0"; -} -.mdi-home-circle::before { - content: "\F7D1"; -} -.mdi-home-circle-outline::before { - content: "\F006F"; -} -.mdi-home-city::before { - content: "\FCF1"; -} -.mdi-home-city-outline::before { - content: "\FCF2"; -} -.mdi-home-currency-usd::before { - content: "\F8AE"; -} -.mdi-home-edit::before { - content: "\F0184"; -} -.mdi-home-edit-outline::before { - content: "\F0185"; -} -.mdi-home-export-outline::before { - content: "\FFB8"; -} -.mdi-home-flood::before { - content: "\FF17"; -} -.mdi-home-floor-0::before { - content: "\FDAE"; -} -.mdi-home-floor-1::before { - content: "\FD5C"; -} -.mdi-home-floor-2::before { - content: "\FD5D"; -} -.mdi-home-floor-3::before { - content: "\FD5E"; -} -.mdi-home-floor-a::before { - content: "\FD5F"; -} -.mdi-home-floor-b::before { - content: "\FD60"; -} -.mdi-home-floor-g::before { - content: "\FD61"; -} -.mdi-home-floor-l::before { - content: "\FD62"; -} -.mdi-home-floor-negative-1::before { - content: "\FDAF"; -} -.mdi-home-group::before { - content: "\FDB0"; -} -.mdi-home-heart::before { - content: "\F826"; -} -.mdi-home-import-outline::before { - content: "\FFB9"; -} -.mdi-home-lightbulb::before { - content: "\F027C"; -} -.mdi-home-lightbulb-outline::before { - content: "\F027D"; -} -.mdi-home-lock::before { - content: "\F8EA"; -} -.mdi-home-lock-open::before { - content: "\F8EB"; -} -.mdi-home-map-marker::before { - content: "\F5F8"; -} -.mdi-home-minus::before { - content: "\F973"; -} -.mdi-home-modern::before { - content: "\F2DD"; -} -.mdi-home-outline::before { - content: "\F6A0"; -} -.mdi-home-plus::before { - content: "\F974"; -} -.mdi-home-remove::before { - content: "\F0272"; -} -.mdi-home-roof::before { - content: "\F0156"; -} -.mdi-home-thermometer::before { - content: "\FF71"; -} -.mdi-home-thermometer-outline::before { - content: "\FF72"; -} -.mdi-home-variant::before { - content: "\F2DE"; -} -.mdi-home-variant-outline::before { - content: "\FB83"; -} -.mdi-hook::before { - content: "\F6E1"; -} -.mdi-hook-off::before { - content: "\F6E2"; -} -.mdi-hops::before { - content: "\F2DF"; -} -.mdi-horizontal-rotate-clockwise::before { - content: "\F011E"; -} -.mdi-horizontal-rotate-counterclockwise::before { - content: "\F011F"; -} -.mdi-horseshoe::before { - content: "\FA57"; -} -.mdi-hospital::before { - content: "\F0017"; -} -.mdi-hospital-box::before { - content: "\F2E0"; -} -.mdi-hospital-box-outline::before { - content: "\F0018"; -} -.mdi-hospital-building::before { - content: "\F2E1"; -} -.mdi-hospital-marker::before { - content: "\F2E2"; -} -.mdi-hot-tub::before { - content: "\F827"; -} -.mdi-hotel::before { - content: "\F2E3"; -} -.mdi-houzz::before { - content: "\F2E4"; -} -.mdi-houzz-box::before { - content: "\F2E5"; -} -.mdi-hubspot::before { - content: "\FCF3"; -} -.mdi-hulu::before { - content: "\F828"; -} -.mdi-human::before { - content: "\F2E6"; -} -.mdi-human-child::before { - content: "\F2E7"; -} -.mdi-human-female::before { - content: "\F649"; -} -.mdi-human-female-boy::before { - content: "\FA58"; -} -.mdi-human-female-female::before { - content: "\FA59"; -} -.mdi-human-female-girl::before { - content: "\FA5A"; -} -.mdi-human-greeting::before { - content: "\F64A"; -} -.mdi-human-handsdown::before { - content: "\F64B"; -} -.mdi-human-handsup::before { - content: "\F64C"; -} -.mdi-human-male::before { - content: "\F64D"; -} -.mdi-human-male-boy::before { - content: "\FA5B"; -} -.mdi-human-male-female::before { - content: "\F2E8"; -} -.mdi-human-male-girl::before { - content: "\FA5C"; -} -.mdi-human-male-height::before { - content: "\FF18"; -} -.mdi-human-male-height-variant::before { - content: "\FF19"; -} -.mdi-human-male-male::before { - content: "\FA5D"; -} -.mdi-human-pregnant::before { - content: "\F5CF"; -} -.mdi-humble-bundle::before { - content: "\F743"; -} -.mdi-hvac::before { - content: "\F037D"; -} -.mdi-hydraulic-oil-level::before { - content: "\F034F"; -} -.mdi-hydraulic-oil-temperature::before { - content: "\F0350"; -} -.mdi-hydro-power::before { - content: "\F0310"; -} -.mdi-ice-cream::before { - content: "\F829"; -} -.mdi-ice-pop::before { - content: "\FF1A"; -} -.mdi-id-card::before { - content: "\FFE0"; -} -.mdi-identifier::before { - content: "\FF1B"; -} -.mdi-ideogram-cjk::before { - content: "\F035C"; -} -.mdi-ideogram-cjk-variant::before { - content: "\F035D"; -} -.mdi-iframe::before { - content: "\FC67"; -} -.mdi-iframe-array::before { - content: "\F0120"; -} -.mdi-iframe-array-outline::before { - content: "\F0121"; -} -.mdi-iframe-braces::before { - content: "\F0122"; -} -.mdi-iframe-braces-outline::before { - content: "\F0123"; -} -.mdi-iframe-outline::before { - content: "\FC68"; -} -.mdi-iframe-parentheses::before { - content: "\F0124"; -} -.mdi-iframe-parentheses-outline::before { - content: "\F0125"; -} -.mdi-iframe-variable::before { - content: "\F0126"; -} -.mdi-iframe-variable-outline::before { - content: "\F0127"; -} -.mdi-image::before { - content: "\F2E9"; -} -.mdi-image-album::before { - content: "\F2EA"; -} -.mdi-image-area::before { - content: "\F2EB"; -} -.mdi-image-area-close::before { - content: "\F2EC"; -} -.mdi-image-auto-adjust::before { - content: "\FFE1"; -} -.mdi-image-broken::before { - content: "\F2ED"; -} -.mdi-image-broken-variant::before { - content: "\F2EE"; -} -.mdi-image-edit::before { - content: "\F020E"; -} -.mdi-image-edit-outline::before { - content: "\F020F"; -} -.mdi-image-filter::before { - content: "\F2EF"; -} -.mdi-image-filter-black-white::before { - content: "\F2F0"; -} -.mdi-image-filter-center-focus::before { - content: "\F2F1"; -} -.mdi-image-filter-center-focus-strong::before { - content: "\FF1C"; -} -.mdi-image-filter-center-focus-strong-outline::before { - content: "\FF1D"; -} -.mdi-image-filter-center-focus-weak::before { - content: "\F2F2"; -} -.mdi-image-filter-drama::before { - content: "\F2F3"; -} -.mdi-image-filter-frames::before { - content: "\F2F4"; -} -.mdi-image-filter-hdr::before { - content: "\F2F5"; -} -.mdi-image-filter-none::before { - content: "\F2F6"; -} -.mdi-image-filter-tilt-shift::before { - content: "\F2F7"; -} -.mdi-image-filter-vintage::before { - content: "\F2F8"; -} -.mdi-image-frame::before { - content: "\FE8A"; -} -.mdi-image-move::before { - content: "\F9F7"; -} -.mdi-image-multiple::before { - content: "\F2F9"; -} -.mdi-image-off::before { - content: "\F82A"; -} -.mdi-image-off-outline::before { - content: "\F01FC"; -} -.mdi-image-outline::before { - content: "\F975"; -} -.mdi-image-plus::before { - content: "\F87B"; -} -.mdi-image-search::before { - content: "\F976"; -} -.mdi-image-search-outline::before { - content: "\F977"; -} -.mdi-image-size-select-actual::before { - content: "\FC69"; -} -.mdi-image-size-select-large::before { - content: "\FC6A"; -} -.mdi-image-size-select-small::before { - content: "\FC6B"; -} -.mdi-import::before { - content: "\F2FA"; -} -.mdi-inbox::before { - content: "\F686"; -} -.mdi-inbox-arrow-down::before { - content: "\F2FB"; -} -.mdi-inbox-arrow-down-outline::before { - content: "\F029B"; -} -.mdi-inbox-arrow-up::before { - content: "\F3D1"; -} -.mdi-inbox-arrow-up-outline::before { - content: "\F029C"; -} -.mdi-inbox-full::before { - content: "\F029D"; -} -.mdi-inbox-full-outline::before { - content: "\F029E"; -} -.mdi-inbox-multiple::before { - content: "\F8AF"; -} -.mdi-inbox-multiple-outline::before { - content: "\FB84"; -} -.mdi-inbox-outline::before { - content: "\F029F"; -} -.mdi-incognito::before { - content: "\F5F9"; -} -.mdi-infinity::before { - content: "\F6E3"; -} -.mdi-information::before { - content: "\F2FC"; -} -.mdi-information-outline::before { - content: "\F2FD"; -} -.mdi-information-variant::before { - content: "\F64E"; -} -.mdi-instagram::before { - content: "\F2FE"; -} -.mdi-instapaper::before { - content: "\F2FF"; -} -.mdi-instrument-triangle::before { - content: "\F0070"; -} -.mdi-internet-explorer::before { - content: "\F300"; -} -.mdi-invert-colors::before { - content: "\F301"; -} -.mdi-invert-colors-off::before { - content: "\FE8B"; -} -.mdi-iobroker::before { - content: "\F0313"; -} -.mdi-ip::before { - content: "\FA5E"; -} -.mdi-ip-network::before { - content: "\FA5F"; -} -.mdi-ip-network-outline::before { - content: "\FC6C"; -} -.mdi-ipod::before { - content: "\FC6D"; -} -.mdi-islam::before { - content: "\F978"; -} -.mdi-island::before { - content: "\F0071"; -} -.mdi-itunes::before { - content: "\F676"; -} -.mdi-iv-bag::before { - content: "\F00E4"; -} -.mdi-jabber::before { - content: "\FDB1"; -} -.mdi-jeepney::before { - content: "\F302"; -} -.mdi-jellyfish::before { - content: "\FF1E"; -} -.mdi-jellyfish-outline::before { - content: "\FF1F"; -} -.mdi-jira::before { - content: "\F303"; -} -.mdi-jquery::before { - content: "\F87C"; -} -.mdi-jsfiddle::before { - content: "\F304"; -} -.mdi-json::before { - content: "\F626"; -} -.mdi-judaism::before { - content: "\F979"; -} -.mdi-jump-rope::before { - content: "\F032A"; -} -.mdi-kabaddi::before { - content: "\FD63"; -} -.mdi-karate::before { - content: "\F82B"; -} -.mdi-keg::before { - content: "\F305"; -} -.mdi-kettle::before { - content: "\F5FA"; -} -.mdi-kettle-alert::before { - content: "\F0342"; -} -.mdi-kettle-alert-outline::before { - content: "\F0343"; -} -.mdi-kettle-off::before { - content: "\F0346"; -} -.mdi-kettle-off-outline::before { - content: "\F0347"; -} -.mdi-kettle-outline::before { - content: "\FF73"; -} -.mdi-kettle-steam::before { - content: "\F0344"; -} -.mdi-kettle-steam-outline::before { - content: "\F0345"; -} -.mdi-kettlebell::before { - content: "\F032B"; -} -.mdi-key::before { - content: "\F306"; -} -.mdi-key-arrow-right::before { - content: "\F033D"; -} -.mdi-key-change::before { - content: "\F307"; -} -.mdi-key-link::before { - content: "\F01CA"; -} -.mdi-key-minus::before { - content: "\F308"; -} -.mdi-key-outline::before { - content: "\FDB2"; -} -.mdi-key-plus::before { - content: "\F309"; -} -.mdi-key-remove::before { - content: "\F30A"; -} -.mdi-key-star::before { - content: "\F01C9"; -} -.mdi-key-variant::before { - content: "\F30B"; -} -.mdi-key-wireless::before { - content: "\FFE2"; -} -.mdi-keyboard::before { - content: "\F30C"; -} -.mdi-keyboard-backspace::before { - content: "\F30D"; -} -.mdi-keyboard-caps::before { - content: "\F30E"; -} -.mdi-keyboard-close::before { - content: "\F30F"; -} -.mdi-keyboard-esc::before { - content: "\F02E2"; -} -.mdi-keyboard-f1::before { - content: "\F02D6"; -} -.mdi-keyboard-f10::before { - content: "\F02DF"; -} -.mdi-keyboard-f11::before { - content: "\F02E0"; -} -.mdi-keyboard-f12::before { - content: "\F02E1"; -} -.mdi-keyboard-f2::before { - content: "\F02D7"; -} -.mdi-keyboard-f3::before { - content: "\F02D8"; -} -.mdi-keyboard-f4::before { - content: "\F02D9"; -} -.mdi-keyboard-f5::before { - content: "\F02DA"; -} -.mdi-keyboard-f6::before { - content: "\F02DB"; -} -.mdi-keyboard-f7::before { - content: "\F02DC"; -} -.mdi-keyboard-f8::before { - content: "\F02DD"; -} -.mdi-keyboard-f9::before { - content: "\F02DE"; -} -.mdi-keyboard-off::before { - content: "\F310"; -} -.mdi-keyboard-off-outline::before { - content: "\FE8C"; -} -.mdi-keyboard-outline::before { - content: "\F97A"; -} -.mdi-keyboard-return::before { - content: "\F311"; -} -.mdi-keyboard-settings::before { - content: "\F9F8"; -} -.mdi-keyboard-settings-outline::before { - content: "\F9F9"; -} -.mdi-keyboard-space::before { - content: "\F0072"; -} -.mdi-keyboard-tab::before { - content: "\F312"; -} -.mdi-keyboard-variant::before { - content: "\F313"; -} -.mdi-khanda::before { - content: "\F0128"; -} -.mdi-kickstarter::before { - content: "\F744"; -} -.mdi-klingon::before { - content: "\F0386"; -} -.mdi-knife::before { - content: "\F9FA"; -} -.mdi-knife-military::before { - content: "\F9FB"; -} -.mdi-kodi::before { - content: "\F314"; -} -.mdi-kotlin::before { - content: "\F0244"; -} -.mdi-kubernetes::before { - content: "\F0129"; -} -.mdi-label::before { - content: "\F315"; -} -.mdi-label-multiple::before { - content: "\F03A0"; -} -.mdi-label-multiple-outline::before { - content: "\F03A1"; -} -.mdi-label-off::before { - content: "\FACA"; -} -.mdi-label-off-outline::before { - content: "\FACB"; -} -.mdi-label-outline::before { - content: "\F316"; -} -.mdi-label-percent::before { - content: "\F0315"; -} -.mdi-label-percent-outline::before { - content: "\F0316"; -} -.mdi-label-variant::before { - content: "\FACC"; -} -.mdi-label-variant-outline::before { - content: "\FACD"; -} -.mdi-ladybug::before { - content: "\F82C"; -} -.mdi-lambda::before { - content: "\F627"; -} -.mdi-lamp::before { - content: "\F6B4"; -} -.mdi-lan::before { - content: "\F317"; -} -.mdi-lan-check::before { - content: "\F02D5"; -} -.mdi-lan-connect::before { - content: "\F318"; -} -.mdi-lan-disconnect::before { - content: "\F319"; -} -.mdi-lan-pending::before { - content: "\F31A"; -} -.mdi-language-c::before { - content: "\F671"; -} -.mdi-language-cpp::before { - content: "\F672"; -} -.mdi-language-csharp::before { - content: "\F31B"; -} -.mdi-language-css3::before { - content: "\F31C"; -} -.mdi-language-fortran::before { - content: "\F0245"; -} -.mdi-language-go::before { - content: "\F7D2"; -} -.mdi-language-haskell::before { - content: "\FC6E"; -} -.mdi-language-html5::before { - content: "\F31D"; -} -.mdi-language-java::before { - content: "\FB1C"; -} -.mdi-language-javascript::before { - content: "\F31E"; -} -.mdi-language-lua::before { - content: "\F8B0"; -} -.mdi-language-php::before { - content: "\F31F"; -} -.mdi-language-python::before { - content: "\F320"; -} -.mdi-language-python-text::before { - content: "\F321"; -} -.mdi-language-r::before { - content: "\F7D3"; -} -.mdi-language-ruby-on-rails::before { - content: "\FACE"; -} -.mdi-language-swift::before { - content: "\F6E4"; -} -.mdi-language-typescript::before { - content: "\F6E5"; -} -.mdi-laptop::before { - content: "\F322"; -} -.mdi-laptop-chromebook::before { - content: "\F323"; -} -.mdi-laptop-mac::before { - content: "\F324"; -} -.mdi-laptop-off::before { - content: "\F6E6"; -} -.mdi-laptop-windows::before { - content: "\F325"; -} -.mdi-laravel::before { - content: "\FACF"; -} -.mdi-lasso::before { - content: "\FF20"; -} -.mdi-lastfm::before { - content: "\F326"; -} -.mdi-lastpass::before { - content: "\F446"; -} -.mdi-latitude::before { - content: "\FF74"; -} -.mdi-launch::before { - content: "\F327"; -} -.mdi-lava-lamp::before { - content: "\F7D4"; -} -.mdi-layers::before { - content: "\F328"; -} -.mdi-layers-minus::before { - content: "\FE8D"; -} -.mdi-layers-off::before { - content: "\F329"; -} -.mdi-layers-off-outline::before { - content: "\F9FC"; -} -.mdi-layers-outline::before { - content: "\F9FD"; -} -.mdi-layers-plus::before { - content: "\FE30"; -} -.mdi-layers-remove::before { - content: "\FE31"; -} -.mdi-layers-search::before { - content: "\F0231"; -} -.mdi-layers-search-outline::before { - content: "\F0232"; -} -.mdi-layers-triple::before { - content: "\FF75"; -} -.mdi-layers-triple-outline::before { - content: "\FF76"; -} -.mdi-lead-pencil::before { - content: "\F64F"; -} -.mdi-leaf::before { - content: "\F32A"; -} -.mdi-leaf-maple::before { - content: "\FC6F"; -} -.mdi-leaf-maple-off::before { - content: "\F0305"; -} -.mdi-leaf-off::before { - content: "\F0304"; -} -.mdi-leak::before { - content: "\FDB3"; -} -.mdi-leak-off::before { - content: "\FDB4"; -} -.mdi-led-off::before { - content: "\F32B"; -} -.mdi-led-on::before { - content: "\F32C"; -} -.mdi-led-outline::before { - content: "\F32D"; -} -.mdi-led-strip::before { - content: "\F7D5"; -} -.mdi-led-strip-variant::before { - content: "\F0073"; -} -.mdi-led-variant-off::before { - content: "\F32E"; -} -.mdi-led-variant-on::before { - content: "\F32F"; -} -.mdi-led-variant-outline::before { - content: "\F330"; -} -.mdi-leek::before { - content: "\F01A8"; -} -.mdi-less-than::before { - content: "\F97B"; -} -.mdi-less-than-or-equal::before { - content: "\F97C"; -} -.mdi-library::before { - content: "\F331"; -} -.mdi-library-books::before { - content: "\F332"; -} -.mdi-library-movie::before { - content: "\FCF4"; -} -.mdi-library-music::before { - content: "\F333"; -} -.mdi-library-music-outline::before { - content: "\FF21"; -} -.mdi-library-shelves::before { - content: "\FB85"; -} -.mdi-library-video::before { - content: "\FCF5"; -} -.mdi-license::before { - content: "\FFE3"; -} -.mdi-lifebuoy::before { - content: "\F87D"; -} -.mdi-light-switch::before { - content: "\F97D"; -} -.mdi-lightbulb::before { - content: "\F335"; -} -.mdi-lightbulb-cfl::before { - content: "\F0233"; -} -.mdi-lightbulb-cfl-off::before { - content: "\F0234"; -} -.mdi-lightbulb-cfl-spiral::before { - content: "\F02A0"; -} -.mdi-lightbulb-cfl-spiral-off::before { - content: "\F02EE"; -} -.mdi-lightbulb-group::before { - content: "\F027E"; -} -.mdi-lightbulb-group-off::before { - content: "\F02F8"; -} -.mdi-lightbulb-group-off-outline::before { - content: "\F02F9"; -} -.mdi-lightbulb-group-outline::before { - content: "\F027F"; -} -.mdi-lightbulb-multiple::before { - content: "\F0280"; -} -.mdi-lightbulb-multiple-off::before { - content: "\F02FA"; -} -.mdi-lightbulb-multiple-off-outline::before { - content: "\F02FB"; -} -.mdi-lightbulb-multiple-outline::before { - content: "\F0281"; -} -.mdi-lightbulb-off::before { - content: "\FE32"; -} -.mdi-lightbulb-off-outline::before { - content: "\FE33"; -} -.mdi-lightbulb-on::before { - content: "\F6E7"; -} -.mdi-lightbulb-on-outline::before { - content: "\F6E8"; -} -.mdi-lightbulb-outline::before { - content: "\F336"; -} -.mdi-lighthouse::before { - content: "\F9FE"; -} -.mdi-lighthouse-on::before { - content: "\F9FF"; -} -.mdi-link::before { - content: "\F337"; -} -.mdi-link-box::before { - content: "\FCF6"; -} -.mdi-link-box-outline::before { - content: "\FCF7"; -} -.mdi-link-box-variant::before { - content: "\FCF8"; -} -.mdi-link-box-variant-outline::before { - content: "\FCF9"; -} -.mdi-link-lock::before { - content: "\F00E5"; -} -.mdi-link-off::before { - content: "\F338"; -} -.mdi-link-plus::before { - content: "\FC70"; -} -.mdi-link-variant::before { - content: "\F339"; -} -.mdi-link-variant-minus::before { - content: "\F012A"; -} -.mdi-link-variant-off::before { - content: "\F33A"; -} -.mdi-link-variant-plus::before { - content: "\F012B"; -} -.mdi-link-variant-remove::before { - content: "\F012C"; -} -.mdi-linkedin::before { - content: "\F33B"; -} -.mdi-linkedin-box::before { - content: "\F33C"; -} -.mdi-linux::before { - content: "\F33D"; -} -.mdi-linux-mint::before { - content: "\F8EC"; -} -.mdi-litecoin::before { - content: "\FA60"; -} -.mdi-loading::before { - content: "\F771"; -} -.mdi-location-enter::before { - content: "\FFE4"; -} -.mdi-location-exit::before { - content: "\FFE5"; -} -.mdi-lock::before { - content: "\F33E"; -} -.mdi-lock-alert::before { - content: "\F8ED"; -} -.mdi-lock-clock::before { - content: "\F97E"; -} -.mdi-lock-open::before { - content: "\F33F"; -} -.mdi-lock-open-outline::before { - content: "\F340"; -} -.mdi-lock-open-variant::before { - content: "\FFE6"; -} -.mdi-lock-open-variant-outline::before { - content: "\FFE7"; -} -.mdi-lock-outline::before { - content: "\F341"; -} -.mdi-lock-pattern::before { - content: "\F6E9"; -} -.mdi-lock-plus::before { - content: "\F5FB"; -} -.mdi-lock-question::before { - content: "\F8EE"; -} -.mdi-lock-reset::before { - content: "\F772"; -} -.mdi-lock-smart::before { - content: "\F8B1"; -} -.mdi-locker::before { - content: "\F7D6"; -} -.mdi-locker-multiple::before { - content: "\F7D7"; -} -.mdi-login::before { - content: "\F342"; -} -.mdi-login-variant::before { - content: "\F5FC"; -} -.mdi-logout::before { - content: "\F343"; -} -.mdi-logout-variant::before { - content: "\F5FD"; -} -.mdi-longitude::before { - content: "\FF77"; -} -.mdi-looks::before { - content: "\F344"; -} -.mdi-loupe::before { - content: "\F345"; -} -.mdi-lumx::before { - content: "\F346"; -} -.mdi-lungs::before { - content: "\F00AF"; -} -.mdi-lyft::before { - content: "\FB1D"; -} -.mdi-magnet::before { - content: "\F347"; -} -.mdi-magnet-on::before { - content: "\F348"; -} -.mdi-magnify::before { - content: "\F349"; -} -.mdi-magnify-close::before { - content: "\F97F"; -} -.mdi-magnify-minus::before { - content: "\F34A"; -} -.mdi-magnify-minus-cursor::before { - content: "\FA61"; -} -.mdi-magnify-minus-outline::before { - content: "\F6EB"; -} -.mdi-magnify-plus::before { - content: "\F34B"; -} -.mdi-magnify-plus-cursor::before { - content: "\FA62"; -} -.mdi-magnify-plus-outline::before { - content: "\F6EC"; -} -.mdi-magnify-remove-cursor::before { - content: "\F0237"; -} -.mdi-magnify-remove-outline::before { - content: "\F0238"; -} -.mdi-magnify-scan::before { - content: "\F02A1"; -} -.mdi-mail::before { - content: "\FED8"; -} -.mdi-mail-ru::before { - content: "\F34C"; -} -.mdi-mailbox::before { - content: "\F6ED"; -} -.mdi-mailbox-open::before { - content: "\FD64"; -} -.mdi-mailbox-open-outline::before { - content: "\FD65"; -} -.mdi-mailbox-open-up::before { - content: "\FD66"; -} -.mdi-mailbox-open-up-outline::before { - content: "\FD67"; -} -.mdi-mailbox-outline::before { - content: "\FD68"; -} -.mdi-mailbox-up::before { - content: "\FD69"; -} -.mdi-mailbox-up-outline::before { - content: "\FD6A"; -} -.mdi-map::before { - content: "\F34D"; -} -.mdi-map-check::before { - content: "\FED9"; -} -.mdi-map-check-outline::before { - content: "\FEDA"; -} -.mdi-map-clock::before { - content: "\FCFA"; -} -.mdi-map-clock-outline::before { - content: "\FCFB"; -} -.mdi-map-legend::before { - content: "\FA00"; -} -.mdi-map-marker::before { - content: "\F34E"; -} -.mdi-map-marker-alert::before { - content: "\FF22"; -} -.mdi-map-marker-alert-outline::before { - content: "\FF23"; -} -.mdi-map-marker-check::before { - content: "\FC71"; -} -.mdi-map-marker-check-outline::before { - content: "\F0326"; -} -.mdi-map-marker-circle::before { - content: "\F34F"; -} -.mdi-map-marker-distance::before { - content: "\F8EF"; -} -.mdi-map-marker-down::before { - content: "\F012D"; -} -.mdi-map-marker-left::before { - content: "\F0306"; -} -.mdi-map-marker-left-outline::before { - content: "\F0308"; -} -.mdi-map-marker-minus::before { - content: "\F650"; -} -.mdi-map-marker-minus-outline::before { - content: "\F0324"; -} -.mdi-map-marker-multiple::before { - content: "\F350"; -} -.mdi-map-marker-multiple-outline::before { - content: "\F02A2"; -} -.mdi-map-marker-off::before { - content: "\F351"; -} -.mdi-map-marker-off-outline::before { - content: "\F0328"; -} -.mdi-map-marker-outline::before { - content: "\F7D8"; -} -.mdi-map-marker-path::before { - content: "\FCFC"; -} -.mdi-map-marker-plus::before { - content: "\F651"; -} -.mdi-map-marker-plus-outline::before { - content: "\F0323"; -} -.mdi-map-marker-question::before { - content: "\FF24"; -} -.mdi-map-marker-question-outline::before { - content: "\FF25"; -} -.mdi-map-marker-radius::before { - content: "\F352"; -} -.mdi-map-marker-radius-outline::before { - content: "\F0327"; -} -.mdi-map-marker-remove::before { - content: "\FF26"; -} -.mdi-map-marker-remove-outline::before { - content: "\F0325"; -} -.mdi-map-marker-remove-variant::before { - content: "\FF27"; -} -.mdi-map-marker-right::before { - content: "\F0307"; -} -.mdi-map-marker-right-outline::before { - content: "\F0309"; -} -.mdi-map-marker-up::before { - content: "\F012E"; -} -.mdi-map-minus::before { - content: "\F980"; -} -.mdi-map-outline::before { - content: "\F981"; -} -.mdi-map-plus::before { - content: "\F982"; -} -.mdi-map-search::before { - content: "\F983"; -} -.mdi-map-search-outline::before { - content: "\F984"; -} -.mdi-mapbox::before { - content: "\FB86"; -} -.mdi-margin::before { - content: "\F353"; -} -.mdi-markdown::before { - content: "\F354"; -} -.mdi-markdown-outline::before { - content: "\FF78"; -} -.mdi-marker::before { - content: "\F652"; -} -.mdi-marker-cancel::before { - content: "\FDB5"; -} -.mdi-marker-check::before { - content: "\F355"; -} -.mdi-mastodon::before { - content: "\FAD0"; -} -.mdi-mastodon-variant::before { - content: "\FAD1"; -} -.mdi-material-design::before { - content: "\F985"; -} -.mdi-material-ui::before { - content: "\F357"; -} -.mdi-math-compass::before { - content: "\F358"; -} -.mdi-math-cos::before { - content: "\FC72"; -} -.mdi-math-integral::before { - content: "\FFE8"; -} -.mdi-math-integral-box::before { - content: "\FFE9"; -} -.mdi-math-log::before { - content: "\F00B0"; -} -.mdi-math-norm::before { - content: "\FFEA"; -} -.mdi-math-norm-box::before { - content: "\FFEB"; -} -.mdi-math-sin::before { - content: "\FC73"; -} -.mdi-math-tan::before { - content: "\FC74"; -} -.mdi-matrix::before { - content: "\F628"; -} -.mdi-medal::before { - content: "\F986"; -} -.mdi-medal-outline::before { - content: "\F0351"; -} -.mdi-medical-bag::before { - content: "\F6EE"; -} -.mdi-meditation::before { - content: "\F01A6"; -} -.mdi-medium::before { - content: "\F35A"; -} -.mdi-meetup::before { - content: "\FAD2"; -} -.mdi-memory::before { - content: "\F35B"; -} -.mdi-menu::before { - content: "\F35C"; -} -.mdi-menu-down::before { - content: "\F35D"; -} -.mdi-menu-down-outline::before { - content: "\F6B5"; -} -.mdi-menu-left::before { - content: "\F35E"; -} -.mdi-menu-left-outline::before { - content: "\FA01"; -} -.mdi-menu-open::before { - content: "\FB87"; -} -.mdi-menu-right::before { - content: "\F35F"; -} -.mdi-menu-right-outline::before { - content: "\FA02"; -} -.mdi-menu-swap::before { - content: "\FA63"; -} -.mdi-menu-swap-outline::before { - content: "\FA64"; -} -.mdi-menu-up::before { - content: "\F360"; -} -.mdi-menu-up-outline::before { - content: "\F6B6"; -} -.mdi-merge::before { - content: "\FF79"; -} -.mdi-message::before { - content: "\F361"; -} -.mdi-message-alert::before { - content: "\F362"; -} -.mdi-message-alert-outline::before { - content: "\FA03"; -} -.mdi-message-arrow-left::before { - content: "\F031D"; -} -.mdi-message-arrow-left-outline::before { - content: "\F031E"; -} -.mdi-message-arrow-right::before { - content: "\F031F"; -} -.mdi-message-arrow-right-outline::before { - content: "\F0320"; -} -.mdi-message-bulleted::before { - content: "\F6A1"; -} -.mdi-message-bulleted-off::before { - content: "\F6A2"; -} -.mdi-message-draw::before { - content: "\F363"; -} -.mdi-message-image::before { - content: "\F364"; -} -.mdi-message-image-outline::before { - content: "\F0197"; -} -.mdi-message-lock::before { - content: "\FFEC"; -} -.mdi-message-lock-outline::before { - content: "\F0198"; -} -.mdi-message-minus::before { - content: "\F0199"; -} -.mdi-message-minus-outline::before { - content: "\F019A"; -} -.mdi-message-outline::before { - content: "\F365"; -} -.mdi-message-plus::before { - content: "\F653"; -} -.mdi-message-plus-outline::before { - content: "\F00E6"; -} -.mdi-message-processing::before { - content: "\F366"; -} -.mdi-message-processing-outline::before { - content: "\F019B"; -} -.mdi-message-reply::before { - content: "\F367"; -} -.mdi-message-reply-text::before { - content: "\F368"; -} -.mdi-message-settings::before { - content: "\F6EF"; -} -.mdi-message-settings-outline::before { - content: "\F019C"; -} -.mdi-message-settings-variant::before { - content: "\F6F0"; -} -.mdi-message-settings-variant-outline::before { - content: "\F019D"; -} -.mdi-message-text::before { - content: "\F369"; -} -.mdi-message-text-clock::before { - content: "\F019E"; -} -.mdi-message-text-clock-outline::before { - content: "\F019F"; -} -.mdi-message-text-lock::before { - content: "\FFED"; -} -.mdi-message-text-lock-outline::before { - content: "\F01A0"; -} -.mdi-message-text-outline::before { - content: "\F36A"; -} -.mdi-message-video::before { - content: "\F36B"; -} -.mdi-meteor::before { - content: "\F629"; -} -.mdi-metronome::before { - content: "\F7D9"; -} -.mdi-metronome-tick::before { - content: "\F7DA"; -} -.mdi-micro-sd::before { - content: "\F7DB"; -} -.mdi-microphone::before { - content: "\F36C"; -} -.mdi-microphone-minus::before { - content: "\F8B2"; -} -.mdi-microphone-off::before { - content: "\F36D"; -} -.mdi-microphone-outline::before { - content: "\F36E"; -} -.mdi-microphone-plus::before { - content: "\F8B3"; -} -.mdi-microphone-settings::before { - content: "\F36F"; -} -.mdi-microphone-variant::before { - content: "\F370"; -} -.mdi-microphone-variant-off::before { - content: "\F371"; -} -.mdi-microscope::before { - content: "\F654"; -} -.mdi-microsoft::before { - content: "\F372"; -} -.mdi-microsoft-dynamics::before { - content: "\F987"; -} -.mdi-microwave::before { - content: "\FC75"; -} -.mdi-middleware::before { - content: "\FF7A"; -} -.mdi-middleware-outline::before { - content: "\FF7B"; -} -.mdi-midi::before { - content: "\F8F0"; -} -.mdi-midi-port::before { - content: "\F8F1"; -} -.mdi-mine::before { - content: "\FDB6"; -} -.mdi-minecraft::before { - content: "\F373"; -} -.mdi-mini-sd::before { - content: "\FA04"; -} -.mdi-minidisc::before { - content: "\FA05"; -} -.mdi-minus::before { - content: "\F374"; -} -.mdi-minus-box::before { - content: "\F375"; -} -.mdi-minus-box-multiple::before { - content: "\F016C"; -} -.mdi-minus-box-multiple-outline::before { - content: "\F016D"; -} -.mdi-minus-box-outline::before { - content: "\F6F1"; -} -.mdi-minus-circle::before { - content: "\F376"; -} -.mdi-minus-circle-outline::before { - content: "\F377"; -} -.mdi-minus-network::before { - content: "\F378"; -} -.mdi-minus-network-outline::before { - content: "\FC76"; -} -.mdi-mirror::before { - content: "\F0228"; -} -.mdi-mixcloud::before { - content: "\F62A"; -} -.mdi-mixed-martial-arts::before { - content: "\FD6B"; -} -.mdi-mixed-reality::before { - content: "\F87E"; -} -.mdi-mixer::before { - content: "\F7DC"; -} -.mdi-molecule::before { - content: "\FB88"; -} -.mdi-monitor::before { - content: "\F379"; -} -.mdi-monitor-cellphone::before { - content: "\F988"; -} -.mdi-monitor-cellphone-star::before { - content: "\F989"; -} -.mdi-monitor-clean::before { - content: "\F012F"; -} -.mdi-monitor-dashboard::before { - content: "\FA06"; -} -.mdi-monitor-edit::before { - content: "\F02F1"; -} -.mdi-monitor-lock::before { - content: "\FDB7"; -} -.mdi-monitor-multiple::before { - content: "\F37A"; -} -.mdi-monitor-off::before { - content: "\FD6C"; -} -.mdi-monitor-screenshot::before { - content: "\FE34"; -} -.mdi-monitor-speaker::before { - content: "\FF7C"; -} -.mdi-monitor-speaker-off::before { - content: "\FF7D"; -} -.mdi-monitor-star::before { - content: "\FDB8"; -} -.mdi-moon-first-quarter::before { - content: "\FF7E"; -} -.mdi-moon-full::before { - content: "\FF7F"; -} -.mdi-moon-last-quarter::before { - content: "\FF80"; -} -.mdi-moon-new::before { - content: "\FF81"; -} -.mdi-moon-waning-crescent::before { - content: "\FF82"; -} -.mdi-moon-waning-gibbous::before { - content: "\FF83"; -} -.mdi-moon-waxing-crescent::before { - content: "\FF84"; -} -.mdi-moon-waxing-gibbous::before { - content: "\FF85"; -} -.mdi-moped::before { - content: "\F00B1"; -} -.mdi-more::before { - content: "\F37B"; -} -.mdi-mother-heart::before { - content: "\F033F"; -} -.mdi-mother-nurse::before { - content: "\FCFD"; -} -.mdi-motion-sensor::before { - content: "\FD6D"; -} -.mdi-motorbike::before { - content: "\F37C"; -} -.mdi-mouse::before { - content: "\F37D"; -} -.mdi-mouse-bluetooth::before { - content: "\F98A"; -} -.mdi-mouse-off::before { - content: "\F37E"; -} -.mdi-mouse-variant::before { - content: "\F37F"; -} -.mdi-mouse-variant-off::before { - content: "\F380"; -} -.mdi-move-resize::before { - content: "\F655"; -} -.mdi-move-resize-variant::before { - content: "\F656"; -} -.mdi-movie::before { - content: "\F381"; -} -.mdi-movie-edit::before { - content: "\F014D"; -} -.mdi-movie-edit-outline::before { - content: "\F014E"; -} -.mdi-movie-filter::before { - content: "\F014F"; -} -.mdi-movie-filter-outline::before { - content: "\F0150"; -} -.mdi-movie-open::before { - content: "\FFEE"; -} -.mdi-movie-open-outline::before { - content: "\FFEF"; -} -.mdi-movie-outline::before { - content: "\FDB9"; -} -.mdi-movie-roll::before { - content: "\F7DD"; -} -.mdi-movie-search::before { - content: "\F01FD"; -} -.mdi-movie-search-outline::before { - content: "\F01FE"; -} -.mdi-muffin::before { - content: "\F98B"; -} -.mdi-multiplication::before { - content: "\F382"; -} -.mdi-multiplication-box::before { - content: "\F383"; -} -.mdi-mushroom::before { - content: "\F7DE"; -} -.mdi-mushroom-outline::before { - content: "\F7DF"; -} -.mdi-music::before { - content: "\F759"; -} -.mdi-music-accidental-double-flat::before { - content: "\FF86"; -} -.mdi-music-accidental-double-sharp::before { - content: "\FF87"; -} -.mdi-music-accidental-flat::before { - content: "\FF88"; -} -.mdi-music-accidental-natural::before { - content: "\FF89"; -} -.mdi-music-accidental-sharp::before { - content: "\FF8A"; -} -.mdi-music-box::before { - content: "\F384"; -} -.mdi-music-box-outline::before { - content: "\F385"; -} -.mdi-music-circle::before { - content: "\F386"; -} -.mdi-music-circle-outline::before { - content: "\FAD3"; -} -.mdi-music-clef-alto::before { - content: "\FF8B"; -} -.mdi-music-clef-bass::before { - content: "\FF8C"; -} -.mdi-music-clef-treble::before { - content: "\FF8D"; -} -.mdi-music-note::before { - content: "\F387"; -} -.mdi-music-note-bluetooth::before { - content: "\F5FE"; -} -.mdi-music-note-bluetooth-off::before { - content: "\F5FF"; -} -.mdi-music-note-eighth::before { - content: "\F388"; -} -.mdi-music-note-eighth-dotted::before { - content: "\FF8E"; -} -.mdi-music-note-half::before { - content: "\F389"; -} -.mdi-music-note-half-dotted::before { - content: "\FF8F"; -} -.mdi-music-note-off::before { - content: "\F38A"; -} -.mdi-music-note-off-outline::before { - content: "\FF90"; -} -.mdi-music-note-outline::before { - content: "\FF91"; -} -.mdi-music-note-plus::before { - content: "\FDBA"; -} -.mdi-music-note-quarter::before { - content: "\F38B"; -} -.mdi-music-note-quarter-dotted::before { - content: "\FF92"; -} -.mdi-music-note-sixteenth::before { - content: "\F38C"; -} -.mdi-music-note-sixteenth-dotted::before { - content: "\FF93"; -} -.mdi-music-note-whole::before { - content: "\F38D"; -} -.mdi-music-note-whole-dotted::before { - content: "\FF94"; -} -.mdi-music-off::before { - content: "\F75A"; -} -.mdi-music-rest-eighth::before { - content: "\FF95"; -} -.mdi-music-rest-half::before { - content: "\FF96"; -} -.mdi-music-rest-quarter::before { - content: "\FF97"; -} -.mdi-music-rest-sixteenth::before { - content: "\FF98"; -} -.mdi-music-rest-whole::before { - content: "\FF99"; -} -.mdi-nail::before { - content: "\FDBB"; -} -.mdi-nas::before { - content: "\F8F2"; -} -.mdi-nativescript::before { - content: "\F87F"; -} -.mdi-nature::before { - content: "\F38E"; -} -.mdi-nature-people::before { - content: "\F38F"; -} -.mdi-navigation::before { - content: "\F390"; -} -.mdi-near-me::before { - content: "\F5CD"; -} -.mdi-necklace::before { - content: "\FF28"; -} -.mdi-needle::before { - content: "\F391"; -} -.mdi-netflix::before { - content: "\F745"; -} -.mdi-network::before { - content: "\F6F2"; -} -.mdi-network-off::before { - content: "\FC77"; -} -.mdi-network-off-outline::before { - content: "\FC78"; -} -.mdi-network-outline::before { - content: "\FC79"; -} -.mdi-network-router::before { - content: "\F00B2"; -} -.mdi-network-strength-1::before { - content: "\F8F3"; -} -.mdi-network-strength-1-alert::before { - content: "\F8F4"; -} -.mdi-network-strength-2::before { - content: "\F8F5"; -} -.mdi-network-strength-2-alert::before { - content: "\F8F6"; -} -.mdi-network-strength-3::before { - content: "\F8F7"; -} -.mdi-network-strength-3-alert::before { - content: "\F8F8"; -} -.mdi-network-strength-4::before { - content: "\F8F9"; -} -.mdi-network-strength-4-alert::before { - content: "\F8FA"; -} -.mdi-network-strength-off::before { - content: "\F8FB"; -} -.mdi-network-strength-off-outline::before { - content: "\F8FC"; -} -.mdi-network-strength-outline::before { - content: "\F8FD"; -} -.mdi-new-box::before { - content: "\F394"; -} -.mdi-newspaper::before { - content: "\F395"; -} -.mdi-newspaper-minus::before { - content: "\FF29"; -} -.mdi-newspaper-plus::before { - content: "\FF2A"; -} -.mdi-newspaper-variant::before { - content: "\F0023"; -} -.mdi-newspaper-variant-multiple::before { - content: "\F0024"; -} -.mdi-newspaper-variant-multiple-outline::before { - content: "\F0025"; -} -.mdi-newspaper-variant-outline::before { - content: "\F0026"; -} -.mdi-nfc::before { - content: "\F396"; -} -.mdi-nfc-off::before { - content: "\FE35"; -} -.mdi-nfc-search-variant::before { - content: "\FE36"; -} -.mdi-nfc-tap::before { - content: "\F397"; -} -.mdi-nfc-variant::before { - content: "\F398"; -} -.mdi-nfc-variant-off::before { - content: "\FE37"; -} -.mdi-ninja::before { - content: "\F773"; -} -.mdi-nintendo-switch::before { - content: "\F7E0"; -} -.mdi-nix::before { - content: "\F0130"; -} -.mdi-nodejs::before { - content: "\F399"; -} -.mdi-noodles::before { - content: "\F01A9"; -} -.mdi-not-equal::before { - content: "\F98C"; -} -.mdi-not-equal-variant::before { - content: "\F98D"; -} -.mdi-note::before { - content: "\F39A"; -} -.mdi-note-multiple::before { - content: "\F6B7"; -} -.mdi-note-multiple-outline::before { - content: "\F6B8"; -} -.mdi-note-outline::before { - content: "\F39B"; -} -.mdi-note-plus::before { - content: "\F39C"; -} -.mdi-note-plus-outline::before { - content: "\F39D"; -} -.mdi-note-text::before { - content: "\F39E"; -} -.mdi-note-text-outline::before { - content: "\F0202"; -} -.mdi-notebook::before { - content: "\F82D"; -} -.mdi-notebook-multiple::before { - content: "\FE38"; -} -.mdi-notebook-outline::before { - content: "\FEDC"; -} -.mdi-notification-clear-all::before { - content: "\F39F"; -} -.mdi-npm::before { - content: "\F6F6"; -} -.mdi-npm-variant::before { - content: "\F98E"; -} -.mdi-npm-variant-outline::before { - content: "\F98F"; -} -.mdi-nuke::before { - content: "\F6A3"; -} -.mdi-null::before { - content: "\F7E1"; -} -.mdi-numeric::before { - content: "\F3A0"; -} -.mdi-numeric-0::before { - content: "\30"; -} -.mdi-numeric-0-box::before { - content: "\F3A1"; -} -.mdi-numeric-0-box-multiple::before { - content: "\FF2B"; -} -.mdi-numeric-0-box-multiple-outline::before { - content: "\F3A2"; -} -.mdi-numeric-0-box-outline::before { - content: "\F3A3"; -} -.mdi-numeric-0-circle::before { - content: "\FC7A"; -} -.mdi-numeric-0-circle-outline::before { - content: "\FC7B"; -} -.mdi-numeric-1::before { - content: "\31"; -} -.mdi-numeric-1-box::before { - content: "\F3A4"; -} -.mdi-numeric-1-box-multiple::before { - content: "\FF2C"; -} -.mdi-numeric-1-box-multiple-outline::before { - content: "\F3A5"; -} -.mdi-numeric-1-box-outline::before { - content: "\F3A6"; -} -.mdi-numeric-1-circle::before { - content: "\FC7C"; -} -.mdi-numeric-1-circle-outline::before { - content: "\FC7D"; -} -.mdi-numeric-10::before { - content: "\F000A"; -} -.mdi-numeric-10-box::before { - content: "\FF9A"; -} -.mdi-numeric-10-box-multiple::before { - content: "\F000B"; -} -.mdi-numeric-10-box-multiple-outline::before { - content: "\F000C"; -} -.mdi-numeric-10-box-outline::before { - content: "\FF9B"; -} -.mdi-numeric-10-circle::before { - content: "\F000D"; -} -.mdi-numeric-10-circle-outline::before { - content: "\F000E"; -} -.mdi-numeric-2::before { - content: "\32"; -} -.mdi-numeric-2-box::before { - content: "\F3A7"; -} -.mdi-numeric-2-box-multiple::before { - content: "\FF2D"; -} -.mdi-numeric-2-box-multiple-outline::before { - content: "\F3A8"; -} -.mdi-numeric-2-box-outline::before { - content: "\F3A9"; -} -.mdi-numeric-2-circle::before { - content: "\FC7E"; -} -.mdi-numeric-2-circle-outline::before { - content: "\FC7F"; -} -.mdi-numeric-3::before { - content: "\33"; -} -.mdi-numeric-3-box::before { - content: "\F3AA"; -} -.mdi-numeric-3-box-multiple::before { - content: "\FF2E"; -} -.mdi-numeric-3-box-multiple-outline::before { - content: "\F3AB"; -} -.mdi-numeric-3-box-outline::before { - content: "\F3AC"; -} -.mdi-numeric-3-circle::before { - content: "\FC80"; -} -.mdi-numeric-3-circle-outline::before { - content: "\FC81"; -} -.mdi-numeric-4::before { - content: "\34"; -} -.mdi-numeric-4-box::before { - content: "\F3AD"; -} -.mdi-numeric-4-box-multiple::before { - content: "\FF2F"; -} -.mdi-numeric-4-box-multiple-outline::before { - content: "\F3AE"; -} -.mdi-numeric-4-box-outline::before { - content: "\F3AF"; -} -.mdi-numeric-4-circle::before { - content: "\FC82"; -} -.mdi-numeric-4-circle-outline::before { - content: "\FC83"; -} -.mdi-numeric-5::before { - content: "\35"; -} -.mdi-numeric-5-box::before { - content: "\F3B0"; -} -.mdi-numeric-5-box-multiple::before { - content: "\FF30"; -} -.mdi-numeric-5-box-multiple-outline::before { - content: "\F3B1"; -} -.mdi-numeric-5-box-outline::before { - content: "\F3B2"; -} -.mdi-numeric-5-circle::before { - content: "\FC84"; -} -.mdi-numeric-5-circle-outline::before { - content: "\FC85"; -} -.mdi-numeric-6::before { - content: "\36"; -} -.mdi-numeric-6-box::before { - content: "\F3B3"; -} -.mdi-numeric-6-box-multiple::before { - content: "\FF31"; -} -.mdi-numeric-6-box-multiple-outline::before { - content: "\F3B4"; -} -.mdi-numeric-6-box-outline::before { - content: "\F3B5"; -} -.mdi-numeric-6-circle::before { - content: "\FC86"; -} -.mdi-numeric-6-circle-outline::before { - content: "\FC87"; -} -.mdi-numeric-7::before { - content: "\37"; -} -.mdi-numeric-7-box::before { - content: "\F3B6"; -} -.mdi-numeric-7-box-multiple::before { - content: "\FF32"; -} -.mdi-numeric-7-box-multiple-outline::before { - content: "\F3B7"; -} -.mdi-numeric-7-box-outline::before { - content: "\F3B8"; -} -.mdi-numeric-7-circle::before { - content: "\FC88"; -} -.mdi-numeric-7-circle-outline::before { - content: "\FC89"; -} -.mdi-numeric-8::before { - content: "\38"; -} -.mdi-numeric-8-box::before { - content: "\F3B9"; -} -.mdi-numeric-8-box-multiple::before { - content: "\FF33"; -} -.mdi-numeric-8-box-multiple-outline::before { - content: "\F3BA"; -} -.mdi-numeric-8-box-outline::before { - content: "\F3BB"; -} -.mdi-numeric-8-circle::before { - content: "\FC8A"; -} -.mdi-numeric-8-circle-outline::before { - content: "\FC8B"; -} -.mdi-numeric-9::before { - content: "\39"; -} -.mdi-numeric-9-box::before { - content: "\F3BC"; -} -.mdi-numeric-9-box-multiple::before { - content: "\FF34"; -} -.mdi-numeric-9-box-multiple-outline::before { - content: "\F3BD"; -} -.mdi-numeric-9-box-outline::before { - content: "\F3BE"; -} -.mdi-numeric-9-circle::before { - content: "\FC8C"; -} -.mdi-numeric-9-circle-outline::before { - content: "\FC8D"; -} -.mdi-numeric-9-plus::before { - content: "\F000F"; -} -.mdi-numeric-9-plus-box::before { - content: "\F3BF"; -} -.mdi-numeric-9-plus-box-multiple::before { - content: "\FF35"; -} -.mdi-numeric-9-plus-box-multiple-outline::before { - content: "\F3C0"; -} -.mdi-numeric-9-plus-box-outline::before { - content: "\F3C1"; -} -.mdi-numeric-9-plus-circle::before { - content: "\FC8E"; -} -.mdi-numeric-9-plus-circle-outline::before { - content: "\FC8F"; -} -.mdi-numeric-negative-1::before { - content: "\F0074"; -} -.mdi-nut::before { - content: "\F6F7"; -} -.mdi-nutrition::before { - content: "\F3C2"; -} -.mdi-nuxt::before { - content: "\F0131"; -} -.mdi-oar::before { - content: "\F67B"; -} -.mdi-ocarina::before { - content: "\FDBC"; -} -.mdi-oci::before { - content: "\F0314"; -} -.mdi-ocr::before { - content: "\F0165"; -} -.mdi-octagon::before { - content: "\F3C3"; -} -.mdi-octagon-outline::before { - content: "\F3C4"; -} -.mdi-octagram::before { - content: "\F6F8"; -} -.mdi-octagram-outline::before { - content: "\F774"; -} -.mdi-odnoklassniki::before { - content: "\F3C5"; -} -.mdi-offer::before { - content: "\F0246"; -} -.mdi-office::before { - content: "\F3C6"; -} -.mdi-office-building::before { - content: "\F990"; -} -.mdi-oil::before { - content: "\F3C7"; -} -.mdi-oil-lamp::before { - content: "\FF36"; -} -.mdi-oil-level::before { - content: "\F0075"; -} -.mdi-oil-temperature::before { - content: "\F0019"; -} -.mdi-omega::before { - content: "\F3C9"; -} -.mdi-one-up::before { - content: "\FB89"; -} -.mdi-onedrive::before { - content: "\F3CA"; -} -.mdi-onenote::before { - content: "\F746"; -} -.mdi-onepassword::before { - content: "\F880"; -} -.mdi-opacity::before { - content: "\F5CC"; -} -.mdi-open-in-app::before { - content: "\F3CB"; -} -.mdi-open-in-new::before { - content: "\F3CC"; -} -.mdi-open-source-initiative::before { - content: "\FB8A"; -} -.mdi-openid::before { - content: "\F3CD"; -} -.mdi-opera::before { - content: "\F3CE"; -} -.mdi-orbit::before { - content: "\F018"; -} -.mdi-origin::before { - content: "\FB2B"; -} -.mdi-ornament::before { - content: "\F3CF"; -} -.mdi-ornament-variant::before { - content: "\F3D0"; -} -.mdi-outdoor-lamp::before { - content: "\F0076"; -} -.mdi-outlook::before { - content: "\FCFE"; -} -.mdi-overscan::before { - content: "\F0027"; -} -.mdi-owl::before { - content: "\F3D2"; -} -.mdi-pac-man::before { - content: "\FB8B"; -} -.mdi-package::before { - content: "\F3D3"; -} -.mdi-package-down::before { - content: "\F3D4"; -} -.mdi-package-up::before { - content: "\F3D5"; -} -.mdi-package-variant::before { - content: "\F3D6"; -} -.mdi-package-variant-closed::before { - content: "\F3D7"; -} -.mdi-page-first::before { - content: "\F600"; -} -.mdi-page-last::before { - content: "\F601"; -} -.mdi-page-layout-body::before { - content: "\F6F9"; -} -.mdi-page-layout-footer::before { - content: "\F6FA"; -} -.mdi-page-layout-header::before { - content: "\F6FB"; -} -.mdi-page-layout-header-footer::before { - content: "\FF9C"; -} -.mdi-page-layout-sidebar-left::before { - content: "\F6FC"; -} -.mdi-page-layout-sidebar-right::before { - content: "\F6FD"; -} -.mdi-page-next::before { - content: "\FB8C"; -} -.mdi-page-next-outline::before { - content: "\FB8D"; -} -.mdi-page-previous::before { - content: "\FB8E"; -} -.mdi-page-previous-outline::before { - content: "\FB8F"; -} -.mdi-palette::before { - content: "\F3D8"; -} -.mdi-palette-advanced::before { - content: "\F3D9"; -} -.mdi-palette-outline::before { - content: "\FE6C"; -} -.mdi-palette-swatch::before { - content: "\F8B4"; -} -.mdi-palette-swatch-outline::before { - content: "\F0387"; -} -.mdi-palm-tree::before { - content: "\F0077"; -} -.mdi-pan::before { - content: "\FB90"; -} -.mdi-pan-bottom-left::before { - content: "\FB91"; -} -.mdi-pan-bottom-right::before { - content: "\FB92"; -} -.mdi-pan-down::before { - content: "\FB93"; -} -.mdi-pan-horizontal::before { - content: "\FB94"; -} -.mdi-pan-left::before { - content: "\FB95"; -} -.mdi-pan-right::before { - content: "\FB96"; -} -.mdi-pan-top-left::before { - content: "\FB97"; -} -.mdi-pan-top-right::before { - content: "\FB98"; -} -.mdi-pan-up::before { - content: "\FB99"; -} -.mdi-pan-vertical::before { - content: "\FB9A"; -} -.mdi-panda::before { - content: "\F3DA"; -} -.mdi-pandora::before { - content: "\F3DB"; -} -.mdi-panorama::before { - content: "\F3DC"; -} -.mdi-panorama-fisheye::before { - content: "\F3DD"; -} -.mdi-panorama-horizontal::before { - content: "\F3DE"; -} -.mdi-panorama-vertical::before { - content: "\F3DF"; -} -.mdi-panorama-wide-angle::before { - content: "\F3E0"; -} -.mdi-paper-cut-vertical::before { - content: "\F3E1"; -} -.mdi-paper-roll::before { - content: "\F0182"; -} -.mdi-paper-roll-outline::before { - content: "\F0183"; -} -.mdi-paperclip::before { - content: "\F3E2"; -} -.mdi-parachute::before { - content: "\FC90"; -} -.mdi-parachute-outline::before { - content: "\FC91"; -} -.mdi-parking::before { - content: "\F3E3"; -} -.mdi-party-popper::before { - content: "\F0078"; -} -.mdi-passport::before { - content: "\F7E2"; -} -.mdi-passport-biometric::before { - content: "\FDBD"; -} -.mdi-pasta::before { - content: "\F018B"; -} -.mdi-patio-heater::before { - content: "\FF9D"; -} -.mdi-patreon::before { - content: "\F881"; -} -.mdi-pause::before { - content: "\F3E4"; -} -.mdi-pause-circle::before { - content: "\F3E5"; -} -.mdi-pause-circle-outline::before { - content: "\F3E6"; -} -.mdi-pause-octagon::before { - content: "\F3E7"; -} -.mdi-pause-octagon-outline::before { - content: "\F3E8"; -} -.mdi-paw::before { - content: "\F3E9"; -} -.mdi-paw-off::before { - content: "\F657"; -} -.mdi-paypal::before { - content: "\F882"; -} -.mdi-pdf-box::before { - content: "\FE39"; -} -.mdi-peace::before { - content: "\F883"; -} -.mdi-peanut::before { - content: "\F001E"; -} -.mdi-peanut-off::before { - content: "\F001F"; -} -.mdi-peanut-off-outline::before { - content: "\F0021"; -} -.mdi-peanut-outline::before { - content: "\F0020"; -} -.mdi-pen::before { - content: "\F3EA"; -} -.mdi-pen-lock::before { - content: "\FDBE"; -} -.mdi-pen-minus::before { - content: "\FDBF"; -} -.mdi-pen-off::before { - content: "\FDC0"; -} -.mdi-pen-plus::before { - content: "\FDC1"; -} -.mdi-pen-remove::before { - content: "\FDC2"; -} -.mdi-pencil::before { - content: "\F3EB"; -} -.mdi-pencil-box::before { - content: "\F3EC"; -} -.mdi-pencil-box-multiple::before { - content: "\F016F"; -} -.mdi-pencil-box-multiple-outline::before { - content: "\F0170"; -} -.mdi-pencil-box-outline::before { - content: "\F3ED"; -} -.mdi-pencil-circle::before { - content: "\F6FE"; -} -.mdi-pencil-circle-outline::before { - content: "\F775"; -} -.mdi-pencil-lock::before { - content: "\F3EE"; -} -.mdi-pencil-lock-outline::before { - content: "\FDC3"; -} -.mdi-pencil-minus::before { - content: "\FDC4"; -} -.mdi-pencil-minus-outline::before { - content: "\FDC5"; -} -.mdi-pencil-off::before { - content: "\F3EF"; -} -.mdi-pencil-off-outline::before { - content: "\FDC6"; -} -.mdi-pencil-outline::before { - content: "\FC92"; -} -.mdi-pencil-plus::before { - content: "\FDC7"; -} -.mdi-pencil-plus-outline::before { - content: "\FDC8"; -} -.mdi-pencil-remove::before { - content: "\FDC9"; -} -.mdi-pencil-remove-outline::before { - content: "\FDCA"; -} -.mdi-pencil-ruler::before { - content: "\F037E"; -} -.mdi-penguin::before { - content: "\FEDD"; -} -.mdi-pentagon::before { - content: "\F6FF"; -} -.mdi-pentagon-outline::before { - content: "\F700"; -} -.mdi-percent::before { - content: "\F3F0"; -} -.mdi-percent-outline::before { - content: "\F02A3"; -} -.mdi-periodic-table::before { - content: "\F8B5"; -} -.mdi-periodic-table-co::before { - content: "\F0329"; -} -.mdi-periodic-table-co2::before { - content: "\F7E3"; -} -.mdi-periscope::before { - content: "\F747"; -} -.mdi-perspective-less::before { - content: "\FCFF"; -} -.mdi-perspective-more::before { - content: "\FD00"; -} -.mdi-pharmacy::before { - content: "\F3F1"; -} -.mdi-phone::before { - content: "\F3F2"; -} -.mdi-phone-alert::before { - content: "\FF37"; -} -.mdi-phone-alert-outline::before { - content: "\F01B9"; -} -.mdi-phone-bluetooth::before { - content: "\F3F3"; -} -.mdi-phone-bluetooth-outline::before { - content: "\F01BA"; -} -.mdi-phone-cancel::before { - content: "\F00E7"; -} -.mdi-phone-cancel-outline::before { - content: "\F01BB"; -} -.mdi-phone-check::before { - content: "\F01D4"; -} -.mdi-phone-check-outline::before { - content: "\F01D5"; -} -.mdi-phone-classic::before { - content: "\F602"; -} -.mdi-phone-classic-off::before { - content: "\F02A4"; -} -.mdi-phone-forward::before { - content: "\F3F4"; -} -.mdi-phone-forward-outline::before { - content: "\F01BC"; -} -.mdi-phone-hangup::before { - content: "\F3F5"; -} -.mdi-phone-hangup-outline::before { - content: "\F01BD"; -} -.mdi-phone-in-talk::before { - content: "\F3F6"; -} -.mdi-phone-in-talk-outline::before { - content: "\F01AD"; -} -.mdi-phone-incoming::before { - content: "\F3F7"; -} -.mdi-phone-incoming-outline::before { - content: "\F01BE"; -} -.mdi-phone-lock::before { - content: "\F3F8"; -} -.mdi-phone-lock-outline::before { - content: "\F01BF"; -} -.mdi-phone-log::before { - content: "\F3F9"; -} -.mdi-phone-log-outline::before { - content: "\F01C0"; -} -.mdi-phone-message::before { - content: "\F01C1"; -} -.mdi-phone-message-outline::before { - content: "\F01C2"; -} -.mdi-phone-minus::before { - content: "\F658"; -} -.mdi-phone-minus-outline::before { - content: "\F01C3"; -} -.mdi-phone-missed::before { - content: "\F3FA"; -} -.mdi-phone-missed-outline::before { - content: "\F01D0"; -} -.mdi-phone-off::before { - content: "\FDCB"; -} -.mdi-phone-off-outline::before { - content: "\F01D1"; -} -.mdi-phone-outgoing::before { - content: "\F3FB"; -} -.mdi-phone-outgoing-outline::before { - content: "\F01C4"; -} -.mdi-phone-outline::before { - content: "\FDCC"; -} -.mdi-phone-paused::before { - content: "\F3FC"; -} -.mdi-phone-paused-outline::before { - content: "\F01C5"; -} -.mdi-phone-plus::before { - content: "\F659"; -} -.mdi-phone-plus-outline::before { - content: "\F01C6"; -} -.mdi-phone-return::before { - content: "\F82E"; -} -.mdi-phone-return-outline::before { - content: "\F01C7"; -} -.mdi-phone-ring::before { - content: "\F01D6"; -} -.mdi-phone-ring-outline::before { - content: "\F01D7"; -} -.mdi-phone-rotate-landscape::before { - content: "\F884"; -} -.mdi-phone-rotate-portrait::before { - content: "\F885"; -} -.mdi-phone-settings::before { - content: "\F3FD"; -} -.mdi-phone-settings-outline::before { - content: "\F01C8"; -} -.mdi-phone-voip::before { - content: "\F3FE"; -} -.mdi-pi::before { - content: "\F3FF"; -} -.mdi-pi-box::before { - content: "\F400"; -} -.mdi-pi-hole::before { - content: "\FDCD"; -} -.mdi-piano::before { - content: "\F67C"; -} -.mdi-pickaxe::before { - content: "\F8B6"; -} -.mdi-picture-in-picture-bottom-right::before { - content: "\FE3A"; -} -.mdi-picture-in-picture-bottom-right-outline::before { - content: "\FE3B"; -} -.mdi-picture-in-picture-top-right::before { - content: "\FE3C"; -} -.mdi-picture-in-picture-top-right-outline::before { - content: "\FE3D"; -} -.mdi-pier::before { - content: "\F886"; -} -.mdi-pier-crane::before { - content: "\F887"; -} -.mdi-pig::before { - content: "\F401"; -} -.mdi-pig-variant::before { - content: "\F0028"; -} -.mdi-piggy-bank::before { - content: "\F0029"; -} -.mdi-pill::before { - content: "\F402"; -} -.mdi-pillar::before { - content: "\F701"; -} -.mdi-pin::before { - content: "\F403"; -} -.mdi-pin-off::before { - content: "\F404"; -} -.mdi-pin-off-outline::before { - content: "\F92F"; -} -.mdi-pin-outline::before { - content: "\F930"; -} -.mdi-pine-tree::before { - content: "\F405"; -} -.mdi-pine-tree-box::before { - content: "\F406"; -} -.mdi-pinterest::before { - content: "\F407"; -} -.mdi-pinterest-box::before { - content: "\F408"; -} -.mdi-pinwheel::before { - content: "\FAD4"; -} -.mdi-pinwheel-outline::before { - content: "\FAD5"; -} -.mdi-pipe::before { - content: "\F7E4"; -} -.mdi-pipe-disconnected::before { - content: "\F7E5"; -} -.mdi-pipe-leak::before { - content: "\F888"; -} -.mdi-pipe-wrench::before { - content: "\F037F"; -} -.mdi-pirate::before { - content: "\FA07"; -} -.mdi-pistol::before { - content: "\F702"; -} -.mdi-piston::before { - content: "\F889"; -} -.mdi-pizza::before { - content: "\F409"; -} -.mdi-play::before { - content: "\F40A"; -} -.mdi-play-box::before { - content: "\F02A5"; -} -.mdi-play-box-outline::before { - content: "\F40B"; -} -.mdi-play-circle::before { - content: "\F40C"; -} -.mdi-play-circle-outline::before { - content: "\F40D"; -} -.mdi-play-network::before { - content: "\F88A"; -} -.mdi-play-network-outline::before { - content: "\FC93"; -} -.mdi-play-outline::before { - content: "\FF38"; -} -.mdi-play-pause::before { - content: "\F40E"; -} -.mdi-play-protected-content::before { - content: "\F40F"; -} -.mdi-play-speed::before { - content: "\F8FE"; -} -.mdi-playlist-check::before { - content: "\F5C7"; -} -.mdi-playlist-edit::before { - content: "\F8FF"; -} -.mdi-playlist-minus::before { - content: "\F410"; -} -.mdi-playlist-music::before { - content: "\FC94"; -} -.mdi-playlist-music-outline::before { - content: "\FC95"; -} -.mdi-playlist-play::before { - content: "\F411"; -} -.mdi-playlist-plus::before { - content: "\F412"; -} -.mdi-playlist-remove::before { - content: "\F413"; -} -.mdi-playlist-star::before { - content: "\FDCE"; -} -.mdi-playstation::before { - content: "\F414"; -} -.mdi-plex::before { - content: "\F6B9"; -} -.mdi-plus::before { - content: "\F415"; -} -.mdi-plus-box::before { - content: "\F416"; -} -.mdi-plus-box-multiple::before { - content: "\F334"; -} -.mdi-plus-box-multiple-outline::before { - content: "\F016E"; -} -.mdi-plus-box-outline::before { - content: "\F703"; -} -.mdi-plus-circle::before { - content: "\F417"; -} -.mdi-plus-circle-multiple-outline::before { - content: "\F418"; -} -.mdi-plus-circle-outline::before { - content: "\F419"; -} -.mdi-plus-minus::before { - content: "\F991"; -} -.mdi-plus-minus-box::before { - content: "\F992"; -} -.mdi-plus-network::before { - content: "\F41A"; -} -.mdi-plus-network-outline::before { - content: "\FC96"; -} -.mdi-plus-one::before { - content: "\F41B"; -} -.mdi-plus-outline::before { - content: "\F704"; -} -.mdi-plus-thick::before { - content: "\F0217"; -} -.mdi-pocket::before { - content: "\F41C"; -} -.mdi-podcast::before { - content: "\F993"; -} -.mdi-podium::before { - content: "\FD01"; -} -.mdi-podium-bronze::before { - content: "\FD02"; -} -.mdi-podium-gold::before { - content: "\FD03"; -} -.mdi-podium-silver::before { - content: "\FD04"; -} -.mdi-point-of-sale::before { - content: "\FD6E"; -} -.mdi-pokeball::before { - content: "\F41D"; -} -.mdi-pokemon-go::before { - content: "\FA08"; -} -.mdi-poker-chip::before { - content: "\F82F"; -} -.mdi-polaroid::before { - content: "\F41E"; -} -.mdi-police-badge::before { - content: "\F0192"; -} -.mdi-police-badge-outline::before { - content: "\F0193"; -} -.mdi-poll::before { - content: "\F41F"; -} -.mdi-poll-box::before { - content: "\F420"; -} -.mdi-poll-box-outline::before { - content: "\F02A6"; -} -.mdi-polymer::before { - content: "\F421"; -} -.mdi-pool::before { - content: "\F606"; -} -.mdi-popcorn::before { - content: "\F422"; -} -.mdi-post::before { - content: "\F002A"; -} -.mdi-post-outline::before { - content: "\F002B"; -} -.mdi-postage-stamp::before { - content: "\FC97"; -} -.mdi-pot::before { - content: "\F65A"; -} -.mdi-pot-mix::before { - content: "\F65B"; -} -.mdi-pound::before { - content: "\F423"; -} -.mdi-pound-box::before { - content: "\F424"; -} -.mdi-pound-box-outline::before { - content: "\F01AA"; -} -.mdi-power::before { - content: "\F425"; -} -.mdi-power-cycle::before { - content: "\F900"; -} -.mdi-power-off::before { - content: "\F901"; -} -.mdi-power-on::before { - content: "\F902"; -} -.mdi-power-plug::before { - content: "\F6A4"; -} -.mdi-power-plug-off::before { - content: "\F6A5"; -} -.mdi-power-settings::before { - content: "\F426"; -} -.mdi-power-sleep::before { - content: "\F903"; -} -.mdi-power-socket::before { - content: "\F427"; -} -.mdi-power-socket-au::before { - content: "\F904"; -} -.mdi-power-socket-de::before { - content: "\F0132"; -} -.mdi-power-socket-eu::before { - content: "\F7E6"; -} -.mdi-power-socket-fr::before { - content: "\F0133"; -} -.mdi-power-socket-jp::before { - content: "\F0134"; -} -.mdi-power-socket-uk::before { - content: "\F7E7"; -} -.mdi-power-socket-us::before { - content: "\F7E8"; -} -.mdi-power-standby::before { - content: "\F905"; -} -.mdi-powershell::before { - content: "\FA09"; -} -.mdi-prescription::before { - content: "\F705"; -} -.mdi-presentation::before { - content: "\F428"; -} -.mdi-presentation-play::before { - content: "\F429"; -} -.mdi-printer::before { - content: "\F42A"; -} -.mdi-printer-3d::before { - content: "\F42B"; -} -.mdi-printer-3d-nozzle::before { - content: "\FE3E"; -} -.mdi-printer-3d-nozzle-alert::before { - content: "\F01EB"; -} -.mdi-printer-3d-nozzle-alert-outline::before { - content: "\F01EC"; -} -.mdi-printer-3d-nozzle-outline::before { - content: "\FE3F"; -} -.mdi-printer-alert::before { - content: "\F42C"; -} -.mdi-printer-check::before { - content: "\F0171"; -} -.mdi-printer-off::before { - content: "\FE40"; -} -.mdi-printer-pos::before { - content: "\F0079"; -} -.mdi-printer-settings::before { - content: "\F706"; -} -.mdi-printer-wireless::before { - content: "\FA0A"; -} -.mdi-priority-high::before { - content: "\F603"; -} -.mdi-priority-low::before { - content: "\F604"; -} -.mdi-professional-hexagon::before { - content: "\F42D"; -} -.mdi-progress-alert::before { - content: "\FC98"; -} -.mdi-progress-check::before { - content: "\F994"; -} -.mdi-progress-clock::before { - content: "\F995"; -} -.mdi-progress-close::before { - content: "\F0135"; -} -.mdi-progress-download::before { - content: "\F996"; -} -.mdi-progress-upload::before { - content: "\F997"; -} -.mdi-progress-wrench::before { - content: "\FC99"; -} -.mdi-projector::before { - content: "\F42E"; -} -.mdi-projector-screen::before { - content: "\F42F"; -} -.mdi-propane-tank::before { - content: "\F0382"; -} -.mdi-propane-tank-outline::before { - content: "\F0383"; -} -.mdi-protocol::before { - content: "\FFF9"; -} -.mdi-publish::before { - content: "\F6A6"; -} -.mdi-pulse::before { - content: "\F430"; -} -.mdi-pumpkin::before { - content: "\FB9B"; -} -.mdi-purse::before { - content: "\FF39"; -} -.mdi-purse-outline::before { - content: "\FF3A"; -} -.mdi-puzzle::before { - content: "\F431"; -} -.mdi-puzzle-outline::before { - content: "\FA65"; -} -.mdi-qi::before { - content: "\F998"; -} -.mdi-qqchat::before { - content: "\F605"; -} -.mdi-qrcode::before { - content: "\F432"; -} -.mdi-qrcode-edit::before { - content: "\F8B7"; -} -.mdi-qrcode-minus::before { - content: "\F01B7"; -} -.mdi-qrcode-plus::before { - content: "\F01B6"; -} -.mdi-qrcode-remove::before { - content: "\F01B8"; -} -.mdi-qrcode-scan::before { - content: "\F433"; -} -.mdi-quadcopter::before { - content: "\F434"; -} -.mdi-quality-high::before { - content: "\F435"; -} -.mdi-quality-low::before { - content: "\FA0B"; -} -.mdi-quality-medium::before { - content: "\FA0C"; -} -.mdi-quicktime::before { - content: "\F436"; -} -.mdi-quora::before { - content: "\FD05"; -} -.mdi-rabbit::before { - content: "\F906"; -} -.mdi-racing-helmet::before { - content: "\FD6F"; -} -.mdi-racquetball::before { - content: "\FD70"; -} -.mdi-radar::before { - content: "\F437"; -} -.mdi-radiator::before { - content: "\F438"; -} -.mdi-radiator-disabled::before { - content: "\FAD6"; -} -.mdi-radiator-off::before { - content: "\FAD7"; -} -.mdi-radio::before { - content: "\F439"; -} -.mdi-radio-am::before { - content: "\FC9A"; -} -.mdi-radio-fm::before { - content: "\FC9B"; -} -.mdi-radio-handheld::before { - content: "\F43A"; -} -.mdi-radio-off::before { - content: "\F0247"; -} -.mdi-radio-tower::before { - content: "\F43B"; -} -.mdi-radioactive::before { - content: "\F43C"; -} -.mdi-radioactive-off::before { - content: "\FEDE"; -} -.mdi-radiobox-blank::before { - content: "\F43D"; -} -.mdi-radiobox-marked::before { - content: "\F43E"; -} -.mdi-radius::before { - content: "\FC9C"; -} -.mdi-radius-outline::before { - content: "\FC9D"; -} -.mdi-railroad-light::before { - content: "\FF3B"; -} -.mdi-raspberry-pi::before { - content: "\F43F"; -} -.mdi-ray-end::before { - content: "\F440"; -} -.mdi-ray-end-arrow::before { - content: "\F441"; -} -.mdi-ray-start::before { - content: "\F442"; -} -.mdi-ray-start-arrow::before { - content: "\F443"; -} -.mdi-ray-start-end::before { - content: "\F444"; -} -.mdi-ray-vertex::before { - content: "\F445"; -} -.mdi-react::before { - content: "\F707"; -} -.mdi-read::before { - content: "\F447"; -} -.mdi-receipt::before { - content: "\F449"; -} -.mdi-record::before { - content: "\F44A"; -} -.mdi-record-circle::before { - content: "\FEDF"; -} -.mdi-record-circle-outline::before { - content: "\FEE0"; -} -.mdi-record-player::before { - content: "\F999"; -} -.mdi-record-rec::before { - content: "\F44B"; -} -.mdi-rectangle::before { - content: "\FE41"; -} -.mdi-rectangle-outline::before { - content: "\FE42"; -} -.mdi-recycle::before { - content: "\F44C"; -} -.mdi-reddit::before { - content: "\F44D"; -} -.mdi-redhat::before { - content: "\F0146"; -} -.mdi-redo::before { - content: "\F44E"; -} -.mdi-redo-variant::before { - content: "\F44F"; -} -.mdi-reflect-horizontal::before { - content: "\FA0D"; -} -.mdi-reflect-vertical::before { - content: "\FA0E"; -} -.mdi-refresh::before { - content: "\F450"; -} -.mdi-refresh-circle::before { - content: "\F03A2"; -} -.mdi-regex::before { - content: "\F451"; -} -.mdi-registered-trademark::before { - content: "\FA66"; -} -.mdi-relative-scale::before { - content: "\F452"; -} -.mdi-reload::before { - content: "\F453"; -} -.mdi-reload-alert::before { - content: "\F0136"; -} -.mdi-reminder::before { - content: "\F88B"; -} -.mdi-remote::before { - content: "\F454"; -} -.mdi-remote-desktop::before { - content: "\F8B8"; -} -.mdi-remote-off::before { - content: "\FEE1"; -} -.mdi-remote-tv::before { - content: "\FEE2"; -} -.mdi-remote-tv-off::before { - content: "\FEE3"; -} -.mdi-rename-box::before { - content: "\F455"; -} -.mdi-reorder-horizontal::before { - content: "\F687"; -} -.mdi-reorder-vertical::before { - content: "\F688"; -} -.mdi-repeat::before { - content: "\F456"; -} -.mdi-repeat-off::before { - content: "\F457"; -} -.mdi-repeat-once::before { - content: "\F458"; -} -.mdi-replay::before { - content: "\F459"; -} -.mdi-reply::before { - content: "\F45A"; -} -.mdi-reply-all::before { - content: "\F45B"; -} -.mdi-reply-all-outline::before { - content: "\FF3C"; -} -.mdi-reply-circle::before { - content: "\F01D9"; -} -.mdi-reply-outline::before { - content: "\FF3D"; -} -.mdi-reproduction::before { - content: "\F45C"; -} -.mdi-resistor::before { - content: "\FB1F"; -} -.mdi-resistor-nodes::before { - content: "\FB20"; -} -.mdi-resize::before { - content: "\FA67"; -} -.mdi-resize-bottom-right::before { - content: "\F45D"; -} -.mdi-responsive::before { - content: "\F45E"; -} -.mdi-restart::before { - content: "\F708"; -} -.mdi-restart-alert::before { - content: "\F0137"; -} -.mdi-restart-off::before { - content: "\FD71"; -} -.mdi-restore::before { - content: "\F99A"; -} -.mdi-restore-alert::before { - content: "\F0138"; -} -.mdi-rewind::before { - content: "\F45F"; -} -.mdi-rewind-10::before { - content: "\FD06"; -} -.mdi-rewind-30::before { - content: "\FD72"; -} -.mdi-rewind-5::before { - content: "\F0224"; -} -.mdi-rewind-outline::before { - content: "\F709"; -} -.mdi-rhombus::before { - content: "\F70A"; -} -.mdi-rhombus-medium::before { - content: "\FA0F"; -} -.mdi-rhombus-outline::before { - content: "\F70B"; -} -.mdi-rhombus-split::before { - content: "\FA10"; -} -.mdi-ribbon::before { - content: "\F460"; -} -.mdi-rice::before { - content: "\F7E9"; -} -.mdi-ring::before { - content: "\F7EA"; -} -.mdi-rivet::before { - content: "\FE43"; -} -.mdi-road::before { - content: "\F461"; -} -.mdi-road-variant::before { - content: "\F462"; -} -.mdi-robber::before { - content: "\F007A"; -} -.mdi-robot::before { - content: "\F6A8"; -} -.mdi-robot-industrial::before { - content: "\FB21"; -} -.mdi-robot-mower::before { - content: "\F0222"; -} -.mdi-robot-mower-outline::before { - content: "\F021E"; -} -.mdi-robot-vacuum::before { - content: "\F70C"; -} -.mdi-robot-vacuum-variant::before { - content: "\F907"; -} -.mdi-rocket::before { - content: "\F463"; -} -.mdi-rodent::before { - content: "\F0352"; -} -.mdi-roller-skate::before { - content: "\FD07"; -} -.mdi-rollerblade::before { - content: "\FD08"; -} -.mdi-rollupjs::before { - content: "\FB9C"; -} -.mdi-roman-numeral-1::before { - content: "\F00B3"; -} -.mdi-roman-numeral-10::before { - content: "\F00BC"; -} -.mdi-roman-numeral-2::before { - content: "\F00B4"; -} -.mdi-roman-numeral-3::before { - content: "\F00B5"; -} -.mdi-roman-numeral-4::before { - content: "\F00B6"; -} -.mdi-roman-numeral-5::before { - content: "\F00B7"; -} -.mdi-roman-numeral-6::before { - content: "\F00B8"; -} -.mdi-roman-numeral-7::before { - content: "\F00B9"; -} -.mdi-roman-numeral-8::before { - content: "\F00BA"; -} -.mdi-roman-numeral-9::before { - content: "\F00BB"; -} -.mdi-room-service::before { - content: "\F88C"; -} -.mdi-room-service-outline::before { - content: "\FD73"; -} -.mdi-rotate-3d::before { - content: "\FEE4"; -} -.mdi-rotate-3d-variant::before { - content: "\F464"; -} -.mdi-rotate-left::before { - content: "\F465"; -} -.mdi-rotate-left-variant::before { - content: "\F466"; -} -.mdi-rotate-orbit::before { - content: "\FD74"; -} -.mdi-rotate-right::before { - content: "\F467"; -} -.mdi-rotate-right-variant::before { - content: "\F468"; -} -.mdi-rounded-corner::before { - content: "\F607"; -} -.mdi-router::before { - content: "\F020D"; -} -.mdi-router-wireless::before { - content: "\F469"; -} -.mdi-router-wireless-settings::before { - content: "\FA68"; -} -.mdi-routes::before { - content: "\F46A"; -} -.mdi-routes-clock::before { - content: "\F007B"; -} -.mdi-rowing::before { - content: "\F608"; -} -.mdi-rss::before { - content: "\F46B"; -} -.mdi-rss-box::before { - content: "\F46C"; -} -.mdi-rss-off::before { - content: "\FF3E"; -} -.mdi-ruby::before { - content: "\FD09"; -} -.mdi-rugby::before { - content: "\FD75"; -} -.mdi-ruler::before { - content: "\F46D"; -} -.mdi-ruler-square::before { - content: "\FC9E"; -} -.mdi-ruler-square-compass::before { - content: "\FEDB"; -} -.mdi-run::before { - content: "\F70D"; -} -.mdi-run-fast::before { - content: "\F46E"; -} -.mdi-rv-truck::before { - content: "\F01FF"; -} -.mdi-sack::before { - content: "\FD0A"; -} -.mdi-sack-percent::before { - content: "\FD0B"; -} -.mdi-safe::before { - content: "\FA69"; -} -.mdi-safe-square::before { - content: "\F02A7"; -} -.mdi-safe-square-outline::before { - content: "\F02A8"; -} -.mdi-safety-goggles::before { - content: "\FD0C"; -} -.mdi-sailing::before { - content: "\FEE5"; -} -.mdi-sale::before { - content: "\F46F"; -} -.mdi-salesforce::before { - content: "\F88D"; -} -.mdi-sass::before { - content: "\F7EB"; -} -.mdi-satellite::before { - content: "\F470"; -} -.mdi-satellite-uplink::before { - content: "\F908"; -} -.mdi-satellite-variant::before { - content: "\F471"; -} -.mdi-sausage::before { - content: "\F8B9"; -} -.mdi-saw-blade::before { - content: "\FE44"; -} -.mdi-saxophone::before { - content: "\F609"; -} -.mdi-scale::before { - content: "\F472"; -} -.mdi-scale-balance::before { - content: "\F5D1"; -} -.mdi-scale-bathroom::before { - content: "\F473"; -} -.mdi-scale-off::before { - content: "\F007C"; -} -.mdi-scanner::before { - content: "\F6AA"; -} -.mdi-scanner-off::before { - content: "\F909"; -} -.mdi-scatter-plot::before { - content: "\FEE6"; -} -.mdi-scatter-plot-outline::before { - content: "\FEE7"; -} -.mdi-school::before { - content: "\F474"; -} -.mdi-school-outline::before { - content: "\F01AB"; -} -.mdi-scissors-cutting::before { - content: "\FA6A"; -} -.mdi-scooter::before { - content: "\F0214"; -} -.mdi-scoreboard::before { - content: "\F02A9"; -} -.mdi-scoreboard-outline::before { - content: "\F02AA"; -} -.mdi-screen-rotation::before { - content: "\F475"; -} -.mdi-screen-rotation-lock::before { - content: "\F476"; -} -.mdi-screw-flat-top::before { - content: "\FDCF"; -} -.mdi-screw-lag::before { - content: "\FE54"; -} -.mdi-screw-machine-flat-top::before { - content: "\FE55"; -} -.mdi-screw-machine-round-top::before { - content: "\FE56"; -} -.mdi-screw-round-top::before { - content: "\FE57"; -} -.mdi-screwdriver::before { - content: "\F477"; -} -.mdi-script::before { - content: "\FB9D"; -} -.mdi-script-outline::before { - content: "\F478"; -} -.mdi-script-text::before { - content: "\FB9E"; -} -.mdi-script-text-outline::before { - content: "\FB9F"; -} -.mdi-sd::before { - content: "\F479"; -} -.mdi-seal::before { - content: "\F47A"; -} -.mdi-seal-variant::before { - content: "\FFFA"; -} -.mdi-search-web::before { - content: "\F70E"; -} -.mdi-seat::before { - content: "\FC9F"; -} -.mdi-seat-flat::before { - content: "\F47B"; -} -.mdi-seat-flat-angled::before { - content: "\F47C"; -} -.mdi-seat-individual-suite::before { - content: "\F47D"; -} -.mdi-seat-legroom-extra::before { - content: "\F47E"; -} -.mdi-seat-legroom-normal::before { - content: "\F47F"; -} -.mdi-seat-legroom-reduced::before { - content: "\F480"; -} -.mdi-seat-outline::before { - content: "\FCA0"; -} -.mdi-seat-passenger::before { - content: "\F0274"; -} -.mdi-seat-recline-extra::before { - content: "\F481"; -} -.mdi-seat-recline-normal::before { - content: "\F482"; -} -.mdi-seatbelt::before { - content: "\FCA1"; -} -.mdi-security::before { - content: "\F483"; -} -.mdi-security-network::before { - content: "\F484"; -} -.mdi-seed::before { - content: "\FE45"; -} -.mdi-seed-outline::before { - content: "\FE46"; -} -.mdi-segment::before { - content: "\FEE8"; -} -.mdi-select::before { - content: "\F485"; -} -.mdi-select-all::before { - content: "\F486"; -} -.mdi-select-color::before { - content: "\FD0D"; -} -.mdi-select-compare::before { - content: "\FAD8"; -} -.mdi-select-drag::before { - content: "\FA6B"; -} -.mdi-select-group::before { - content: "\FF9F"; -} -.mdi-select-inverse::before { - content: "\F487"; -} -.mdi-select-marker::before { - content: "\F02AB"; -} -.mdi-select-multiple::before { - content: "\F02AC"; -} -.mdi-select-multiple-marker::before { - content: "\F02AD"; -} -.mdi-select-off::before { - content: "\F488"; -} -.mdi-select-place::before { - content: "\FFFB"; -} -.mdi-select-search::before { - content: "\F022F"; -} -.mdi-selection::before { - content: "\F489"; -} -.mdi-selection-drag::before { - content: "\FA6C"; -} -.mdi-selection-ellipse::before { - content: "\FD0E"; -} -.mdi-selection-ellipse-arrow-inside::before { - content: "\FF3F"; -} -.mdi-selection-marker::before { - content: "\F02AE"; -} -.mdi-selection-multiple-marker::before { - content: "\F02AF"; -} -.mdi-selection-mutliple::before { - content: "\F02B0"; -} -.mdi-selection-off::before { - content: "\F776"; -} -.mdi-selection-search::before { - content: "\F0230"; -} -.mdi-semantic-web::before { - content: "\F0341"; -} -.mdi-send::before { - content: "\F48A"; -} -.mdi-send-check::before { - content: "\F018C"; -} -.mdi-send-check-outline::before { - content: "\F018D"; -} -.mdi-send-circle::before { - content: "\FE58"; -} -.mdi-send-circle-outline::before { - content: "\FE59"; -} -.mdi-send-clock::before { - content: "\F018E"; -} -.mdi-send-clock-outline::before { - content: "\F018F"; -} -.mdi-send-lock::before { - content: "\F7EC"; -} -.mdi-send-lock-outline::before { - content: "\F0191"; -} -.mdi-send-outline::before { - content: "\F0190"; -} -.mdi-serial-port::before { - content: "\F65C"; -} -.mdi-server::before { - content: "\F48B"; -} -.mdi-server-minus::before { - content: "\F48C"; -} -.mdi-server-network::before { - content: "\F48D"; -} -.mdi-server-network-off::before { - content: "\F48E"; -} -.mdi-server-off::before { - content: "\F48F"; -} -.mdi-server-plus::before { - content: "\F490"; -} -.mdi-server-remove::before { - content: "\F491"; -} -.mdi-server-security::before { - content: "\F492"; -} -.mdi-set-all::before { - content: "\F777"; -} -.mdi-set-center::before { - content: "\F778"; -} -.mdi-set-center-right::before { - content: "\F779"; -} -.mdi-set-left::before { - content: "\F77A"; -} -.mdi-set-left-center::before { - content: "\F77B"; -} -.mdi-set-left-right::before { - content: "\F77C"; -} -.mdi-set-none::before { - content: "\F77D"; -} -.mdi-set-right::before { - content: "\F77E"; -} -.mdi-set-top-box::before { - content: "\F99E"; -} -.mdi-settings::before { - content: "\F493"; -} -.mdi-settings-box::before { - content: "\F494"; -} -.mdi-settings-helper::before { - content: "\FA6D"; -} -.mdi-settings-outline::before { - content: "\F8BA"; -} -.mdi-settings-transfer::before { - content: "\F007D"; -} -.mdi-settings-transfer-outline::before { - content: "\F007E"; -} -.mdi-shaker::before { - content: "\F0139"; -} -.mdi-shaker-outline::before { - content: "\F013A"; -} -.mdi-shape::before { - content: "\F830"; -} -.mdi-shape-circle-plus::before { - content: "\F65D"; -} -.mdi-shape-outline::before { - content: "\F831"; -} -.mdi-shape-oval-plus::before { - content: "\F0225"; -} -.mdi-shape-plus::before { - content: "\F495"; -} -.mdi-shape-polygon-plus::before { - content: "\F65E"; -} -.mdi-shape-rectangle-plus::before { - content: "\F65F"; -} -.mdi-shape-square-plus::before { - content: "\F660"; -} -.mdi-share::before { - content: "\F496"; -} -.mdi-share-all::before { - content: "\F021F"; -} -.mdi-share-all-outline::before { - content: "\F0220"; -} -.mdi-share-circle::before { - content: "\F01D8"; -} -.mdi-share-off::before { - content: "\FF40"; -} -.mdi-share-off-outline::before { - content: "\FF41"; -} -.mdi-share-outline::before { - content: "\F931"; -} -.mdi-share-variant::before { - content: "\F497"; -} -.mdi-sheep::before { - content: "\FCA2"; -} -.mdi-shield::before { - content: "\F498"; -} -.mdi-shield-account::before { - content: "\F88E"; -} -.mdi-shield-account-outline::before { - content: "\FA11"; -} -.mdi-shield-airplane::before { - content: "\F6BA"; -} -.mdi-shield-airplane-outline::before { - content: "\FCA3"; -} -.mdi-shield-alert::before { - content: "\FEE9"; -} -.mdi-shield-alert-outline::before { - content: "\FEEA"; -} -.mdi-shield-car::before { - content: "\FFA0"; -} -.mdi-shield-check::before { - content: "\F565"; -} -.mdi-shield-check-outline::before { - content: "\FCA4"; -} -.mdi-shield-cross::before { - content: "\FCA5"; -} -.mdi-shield-cross-outline::before { - content: "\FCA6"; -} -.mdi-shield-edit::before { - content: "\F01CB"; -} -.mdi-shield-edit-outline::before { - content: "\F01CC"; -} -.mdi-shield-half::before { - content: "\F038B"; -} -.mdi-shield-half-full::before { - content: "\F77F"; -} -.mdi-shield-home::before { - content: "\F689"; -} -.mdi-shield-home-outline::before { - content: "\FCA7"; -} -.mdi-shield-key::before { - content: "\FBA0"; -} -.mdi-shield-key-outline::before { - content: "\FBA1"; -} -.mdi-shield-link-variant::before { - content: "\FD0F"; -} -.mdi-shield-link-variant-outline::before { - content: "\FD10"; -} -.mdi-shield-lock::before { - content: "\F99C"; -} -.mdi-shield-lock-outline::before { - content: "\FCA8"; -} -.mdi-shield-off::before { - content: "\F99D"; -} -.mdi-shield-off-outline::before { - content: "\F99B"; -} -.mdi-shield-outline::before { - content: "\F499"; -} -.mdi-shield-plus::before { - content: "\FAD9"; -} -.mdi-shield-plus-outline::before { - content: "\FADA"; -} -.mdi-shield-refresh::before { - content: "\F01CD"; -} -.mdi-shield-refresh-outline::before { - content: "\F01CE"; -} -.mdi-shield-remove::before { - content: "\FADB"; -} -.mdi-shield-remove-outline::before { - content: "\FADC"; -} -.mdi-shield-search::before { - content: "\FD76"; -} -.mdi-shield-star::before { - content: "\F0166"; -} -.mdi-shield-star-outline::before { - content: "\F0167"; -} -.mdi-shield-sun::before { - content: "\F007F"; -} -.mdi-shield-sun-outline::before { - content: "\F0080"; -} -.mdi-ship-wheel::before { - content: "\F832"; -} -.mdi-shoe-formal::before { - content: "\FB22"; -} -.mdi-shoe-heel::before { - content: "\FB23"; -} -.mdi-shoe-print::before { - content: "\FE5A"; -} -.mdi-shopify::before { - content: "\FADD"; -} -.mdi-shopping::before { - content: "\F49A"; -} -.mdi-shopping-music::before { - content: "\F49B"; -} -.mdi-shopping-outline::before { - content: "\F0200"; -} -.mdi-shopping-search::before { - content: "\FFA1"; -} -.mdi-shovel::before { - content: "\F70F"; -} -.mdi-shovel-off::before { - content: "\F710"; -} -.mdi-shower::before { - content: "\F99F"; -} -.mdi-shower-head::before { - content: "\F9A0"; -} -.mdi-shredder::before { - content: "\F49C"; -} -.mdi-shuffle::before { - content: "\F49D"; -} -.mdi-shuffle-disabled::before { - content: "\F49E"; -} -.mdi-shuffle-variant::before { - content: "\F49F"; -} -.mdi-shuriken::before { - content: "\F03AA"; -} -.mdi-sigma::before { - content: "\F4A0"; -} -.mdi-sigma-lower::before { - content: "\F62B"; -} -.mdi-sign-caution::before { - content: "\F4A1"; -} -.mdi-sign-direction::before { - content: "\F780"; -} -.mdi-sign-direction-minus::before { - content: "\F0022"; -} -.mdi-sign-direction-plus::before { - content: "\FFFD"; -} -.mdi-sign-direction-remove::before { - content: "\FFFE"; -} -.mdi-sign-real-estate::before { - content: "\F0143"; -} -.mdi-sign-text::before { - content: "\F781"; -} -.mdi-signal::before { - content: "\F4A2"; -} -.mdi-signal-2g::before { - content: "\F711"; -} -.mdi-signal-3g::before { - content: "\F712"; -} -.mdi-signal-4g::before { - content: "\F713"; -} -.mdi-signal-5g::before { - content: "\FA6E"; -} -.mdi-signal-cellular-1::before { - content: "\F8BB"; -} -.mdi-signal-cellular-2::before { - content: "\F8BC"; -} -.mdi-signal-cellular-3::before { - content: "\F8BD"; -} -.mdi-signal-cellular-outline::before { - content: "\F8BE"; -} -.mdi-signal-distance-variant::before { - content: "\FE47"; -} -.mdi-signal-hspa::before { - content: "\F714"; -} -.mdi-signal-hspa-plus::before { - content: "\F715"; -} -.mdi-signal-off::before { - content: "\F782"; -} -.mdi-signal-variant::before { - content: "\F60A"; -} -.mdi-signature::before { - content: "\FE5B"; -} -.mdi-signature-freehand::before { - content: "\FE5C"; -} -.mdi-signature-image::before { - content: "\FE5D"; -} -.mdi-signature-text::before { - content: "\FE5E"; -} -.mdi-silo::before { - content: "\FB24"; -} -.mdi-silverware::before { - content: "\F4A3"; -} -.mdi-silverware-clean::before { - content: "\FFFF"; -} -.mdi-silverware-fork::before { - content: "\F4A4"; -} -.mdi-silverware-fork-knife::before { - content: "\FA6F"; -} -.mdi-silverware-spoon::before { - content: "\F4A5"; -} -.mdi-silverware-variant::before { - content: "\F4A6"; -} -.mdi-sim::before { - content: "\F4A7"; -} -.mdi-sim-alert::before { - content: "\F4A8"; -} -.mdi-sim-off::before { - content: "\F4A9"; -} -.mdi-simple-icons::before { - content: "\F0348"; -} -.mdi-sina-weibo::before { - content: "\FADE"; -} -.mdi-sitemap::before { - content: "\F4AA"; -} -.mdi-skate::before { - content: "\FD11"; -} -.mdi-skew-less::before { - content: "\FD12"; -} -.mdi-skew-more::before { - content: "\FD13"; -} -.mdi-ski::before { - content: "\F032F"; -} -.mdi-ski-cross-country::before { - content: "\F0330"; -} -.mdi-ski-water::before { - content: "\F0331"; -} -.mdi-skip-backward::before { - content: "\F4AB"; -} -.mdi-skip-backward-outline::before { - content: "\FF42"; -} -.mdi-skip-forward::before { - content: "\F4AC"; -} -.mdi-skip-forward-outline::before { - content: "\FF43"; -} -.mdi-skip-next::before { - content: "\F4AD"; -} -.mdi-skip-next-circle::before { - content: "\F661"; -} -.mdi-skip-next-circle-outline::before { - content: "\F662"; -} -.mdi-skip-next-outline::before { - content: "\FF44"; -} -.mdi-skip-previous::before { - content: "\F4AE"; -} -.mdi-skip-previous-circle::before { - content: "\F663"; -} -.mdi-skip-previous-circle-outline::before { - content: "\F664"; -} -.mdi-skip-previous-outline::before { - content: "\FF45"; -} -.mdi-skull::before { - content: "\F68B"; -} -.mdi-skull-crossbones::before { - content: "\FBA2"; -} -.mdi-skull-crossbones-outline::before { - content: "\FBA3"; -} -.mdi-skull-outline::before { - content: "\FBA4"; -} -.mdi-skype::before { - content: "\F4AF"; -} -.mdi-skype-business::before { - content: "\F4B0"; -} -.mdi-slack::before { - content: "\F4B1"; -} -.mdi-slackware::before { - content: "\F90A"; -} -.mdi-slash-forward::before { - content: "\F0000"; -} -.mdi-slash-forward-box::before { - content: "\F0001"; -} -.mdi-sleep::before { - content: "\F4B2"; -} -.mdi-sleep-off::before { - content: "\F4B3"; -} -.mdi-slope-downhill::before { - content: "\FE5F"; -} -.mdi-slope-uphill::before { - content: "\FE60"; -} -.mdi-slot-machine::before { - content: "\F013F"; -} -.mdi-slot-machine-outline::before { - content: "\F0140"; -} -.mdi-smart-card::before { - content: "\F00E8"; -} -.mdi-smart-card-outline::before { - content: "\F00E9"; -} -.mdi-smart-card-reader::before { - content: "\F00EA"; -} -.mdi-smart-card-reader-outline::before { - content: "\F00EB"; -} -.mdi-smog::before { - content: "\FA70"; -} -.mdi-smoke-detector::before { - content: "\F392"; -} -.mdi-smoking::before { - content: "\F4B4"; -} -.mdi-smoking-off::before { - content: "\F4B5"; -} -.mdi-snapchat::before { - content: "\F4B6"; -} -.mdi-snowboard::before { - content: "\F0332"; -} -.mdi-snowflake::before { - content: "\F716"; -} -.mdi-snowflake-alert::before { - content: "\FF46"; -} -.mdi-snowflake-melt::before { - content: "\F02F6"; -} -.mdi-snowflake-variant::before { - content: "\FF47"; -} -.mdi-snowman::before { - content: "\F4B7"; -} -.mdi-soccer::before { - content: "\F4B8"; -} -.mdi-soccer-field::before { - content: "\F833"; -} -.mdi-sofa::before { - content: "\F4B9"; -} -.mdi-solar-panel::before { - content: "\FD77"; -} -.mdi-solar-panel-large::before { - content: "\FD78"; -} -.mdi-solar-power::before { - content: "\FA71"; -} -.mdi-soldering-iron::before { - content: "\F00BD"; -} -.mdi-solid::before { - content: "\F68C"; -} -.mdi-sort::before { - content: "\F4BA"; -} -.mdi-sort-alphabetical::before { - content: "\F4BB"; -} -.mdi-sort-alphabetical-ascending::before { - content: "\F0173"; -} -.mdi-sort-alphabetical-descending::before { - content: "\F0174"; -} -.mdi-sort-ascending::before { - content: "\F4BC"; -} -.mdi-sort-descending::before { - content: "\F4BD"; -} -.mdi-sort-numeric::before { - content: "\F4BE"; -} -.mdi-sort-variant::before { - content: "\F4BF"; -} -.mdi-sort-variant-lock::before { - content: "\FCA9"; -} -.mdi-sort-variant-lock-open::before { - content: "\FCAA"; -} -.mdi-sort-variant-remove::before { - content: "\F0172"; -} -.mdi-soundcloud::before { - content: "\F4C0"; -} -.mdi-source-branch::before { - content: "\F62C"; -} -.mdi-source-commit::before { - content: "\F717"; -} -.mdi-source-commit-end::before { - content: "\F718"; -} -.mdi-source-commit-end-local::before { - content: "\F719"; -} -.mdi-source-commit-local::before { - content: "\F71A"; -} -.mdi-source-commit-next-local::before { - content: "\F71B"; -} -.mdi-source-commit-start::before { - content: "\F71C"; -} -.mdi-source-commit-start-next-local::before { - content: "\F71D"; -} -.mdi-source-fork::before { - content: "\F4C1"; -} -.mdi-source-merge::before { - content: "\F62D"; -} -.mdi-source-pull::before { - content: "\F4C2"; -} -.mdi-source-repository::before { - content: "\FCAB"; -} -.mdi-source-repository-multiple::before { - content: "\FCAC"; -} -.mdi-soy-sauce::before { - content: "\F7ED"; -} -.mdi-spa::before { - content: "\FCAD"; -} -.mdi-spa-outline::before { - content: "\FCAE"; -} -.mdi-space-invaders::before { - content: "\FBA5"; -} -.mdi-space-station::before { - content: "\F03AE"; -} -.mdi-spade::before { - content: "\FE48"; -} -.mdi-speaker::before { - content: "\F4C3"; -} -.mdi-speaker-bluetooth::before { - content: "\F9A1"; -} -.mdi-speaker-multiple::before { - content: "\FD14"; -} -.mdi-speaker-off::before { - content: "\F4C4"; -} -.mdi-speaker-wireless::before { - content: "\F71E"; -} -.mdi-speedometer::before { - content: "\F4C5"; -} -.mdi-speedometer-medium::before { - content: "\FFA2"; -} -.mdi-speedometer-slow::before { - content: "\FFA3"; -} -.mdi-spellcheck::before { - content: "\F4C6"; -} -.mdi-spider::before { - content: "\F0215"; -} -.mdi-spider-thread::before { - content: "\F0216"; -} -.mdi-spider-web::before { - content: "\FBA6"; -} -.mdi-spotify::before { - content: "\F4C7"; -} -.mdi-spotlight::before { - content: "\F4C8"; -} -.mdi-spotlight-beam::before { - content: "\F4C9"; -} -.mdi-spray::before { - content: "\F665"; -} -.mdi-spray-bottle::before { - content: "\FADF"; -} -.mdi-sprinkler::before { - content: "\F0081"; -} -.mdi-sprinkler-variant::before { - content: "\F0082"; -} -.mdi-sprout::before { - content: "\FE49"; -} -.mdi-sprout-outline::before { - content: "\FE4A"; -} -.mdi-square::before { - content: "\F763"; -} -.mdi-square-edit-outline::before { - content: "\F90B"; -} -.mdi-square-inc::before { - content: "\F4CA"; -} -.mdi-square-inc-cash::before { - content: "\F4CB"; -} -.mdi-square-medium::before { - content: "\FA12"; -} -.mdi-square-medium-outline::before { - content: "\FA13"; -} -.mdi-square-off::before { - content: "\F0319"; -} -.mdi-square-off-outline::before { - content: "\F031A"; -} -.mdi-square-outline::before { - content: "\F762"; -} -.mdi-square-root::before { - content: "\F783"; -} -.mdi-square-root-box::before { - content: "\F9A2"; -} -.mdi-square-small::before { - content: "\FA14"; -} -.mdi-squeegee::before { - content: "\FAE0"; -} -.mdi-ssh::before { - content: "\F8BF"; -} -.mdi-stack-exchange::before { - content: "\F60B"; -} -.mdi-stack-overflow::before { - content: "\F4CC"; -} -.mdi-stackpath::before { - content: "\F359"; -} -.mdi-stadium::before { - content: "\F001A"; -} -.mdi-stadium-variant::before { - content: "\F71F"; -} -.mdi-stairs::before { - content: "\F4CD"; -} -.mdi-stairs-down::before { - content: "\F02E9"; -} -.mdi-stairs-up::before { - content: "\F02E8"; -} -.mdi-stamper::before { - content: "\FD15"; -} -.mdi-standard-definition::before { - content: "\F7EE"; -} -.mdi-star::before { - content: "\F4CE"; -} -.mdi-star-box::before { - content: "\FA72"; -} -.mdi-star-box-multiple::before { - content: "\F02B1"; -} -.mdi-star-box-multiple-outline::before { - content: "\F02B2"; -} -.mdi-star-box-outline::before { - content: "\FA73"; -} -.mdi-star-circle::before { - content: "\F4CF"; -} -.mdi-star-circle-outline::before { - content: "\F9A3"; -} -.mdi-star-face::before { - content: "\F9A4"; -} -.mdi-star-four-points::before { - content: "\FAE1"; -} -.mdi-star-four-points-outline::before { - content: "\FAE2"; -} -.mdi-star-half::before { - content: "\F4D0"; -} -.mdi-star-off::before { - content: "\F4D1"; -} -.mdi-star-outline::before { - content: "\F4D2"; -} -.mdi-star-three-points::before { - content: "\FAE3"; -} -.mdi-star-three-points-outline::before { - content: "\FAE4"; -} -.mdi-state-machine::before { - content: "\F021A"; -} -.mdi-steam::before { - content: "\F4D3"; -} -.mdi-steam-box::before { - content: "\F90C"; -} -.mdi-steering::before { - content: "\F4D4"; -} -.mdi-steering-off::before { - content: "\F90D"; -} -.mdi-step-backward::before { - content: "\F4D5"; -} -.mdi-step-backward-2::before { - content: "\F4D6"; -} -.mdi-step-forward::before { - content: "\F4D7"; -} -.mdi-step-forward-2::before { - content: "\F4D8"; -} -.mdi-stethoscope::before { - content: "\F4D9"; -} -.mdi-sticker::before { - content: "\F038F"; -} -.mdi-sticker-alert::before { - content: "\F0390"; -} -.mdi-sticker-alert-outline::before { - content: "\F0391"; -} -.mdi-sticker-check::before { - content: "\F0392"; -} -.mdi-sticker-check-outline::before { - content: "\F0393"; -} -.mdi-sticker-circle-outline::before { - content: "\F5D0"; -} -.mdi-sticker-emoji::before { - content: "\F784"; -} -.mdi-sticker-minus::before { - content: "\F0394"; -} -.mdi-sticker-minus-outline::before { - content: "\F0395"; -} -.mdi-sticker-outline::before { - content: "\F0396"; -} -.mdi-sticker-plus::before { - content: "\F0397"; -} -.mdi-sticker-plus-outline::before { - content: "\F0398"; -} -.mdi-sticker-remove::before { - content: "\F0399"; -} -.mdi-sticker-remove-outline::before { - content: "\F039A"; -} -.mdi-stocking::before { - content: "\F4DA"; -} -.mdi-stomach::before { - content: "\F00BE"; -} -.mdi-stop::before { - content: "\F4DB"; -} -.mdi-stop-circle::before { - content: "\F666"; -} -.mdi-stop-circle-outline::before { - content: "\F667"; -} -.mdi-store::before { - content: "\F4DC"; -} -.mdi-store-24-hour::before { - content: "\F4DD"; -} -.mdi-store-outline::before { - content: "\F038C"; -} -.mdi-storefront::before { - content: "\F00EC"; -} -.mdi-stove::before { - content: "\F4DE"; -} -.mdi-strategy::before { - content: "\F0201"; -} -.mdi-strava::before { - content: "\FB25"; -} -.mdi-stretch-to-page::before { - content: "\FF48"; -} -.mdi-stretch-to-page-outline::before { - content: "\FF49"; -} -.mdi-string-lights::before { - content: "\F02E5"; -} -.mdi-string-lights-off::before { - content: "\F02E6"; -} -.mdi-subdirectory-arrow-left::before { - content: "\F60C"; -} -.mdi-subdirectory-arrow-right::before { - content: "\F60D"; -} -.mdi-subtitles::before { - content: "\FA15"; -} -.mdi-subtitles-outline::before { - content: "\FA16"; -} -.mdi-subway::before { - content: "\F6AB"; -} -.mdi-subway-alert-variant::before { - content: "\FD79"; -} -.mdi-subway-variant::before { - content: "\F4DF"; -} -.mdi-summit::before { - content: "\F785"; -} -.mdi-sunglasses::before { - content: "\F4E0"; -} -.mdi-surround-sound::before { - content: "\F5C5"; -} -.mdi-surround-sound-2-0::before { - content: "\F7EF"; -} -.mdi-surround-sound-3-1::before { - content: "\F7F0"; -} -.mdi-surround-sound-5-1::before { - content: "\F7F1"; -} -.mdi-surround-sound-7-1::before { - content: "\F7F2"; -} -.mdi-svg::before { - content: "\F720"; -} -.mdi-swap-horizontal::before { - content: "\F4E1"; -} -.mdi-swap-horizontal-bold::before { - content: "\FBA9"; -} -.mdi-swap-horizontal-circle::before { - content: "\F0002"; -} -.mdi-swap-horizontal-circle-outline::before { - content: "\F0003"; -} -.mdi-swap-horizontal-variant::before { - content: "\F8C0"; -} -.mdi-swap-vertical::before { - content: "\F4E2"; -} -.mdi-swap-vertical-bold::before { - content: "\FBAA"; -} -.mdi-swap-vertical-circle::before { - content: "\F0004"; -} -.mdi-swap-vertical-circle-outline::before { - content: "\F0005"; -} -.mdi-swap-vertical-variant::before { - content: "\F8C1"; -} -.mdi-swim::before { - content: "\F4E3"; -} -.mdi-switch::before { - content: "\F4E4"; -} -.mdi-sword::before { - content: "\F4E5"; -} -.mdi-sword-cross::before { - content: "\F786"; -} -.mdi-syllabary-hangul::before { - content: "\F035E"; -} -.mdi-syllabary-hiragana::before { - content: "\F035F"; -} -.mdi-syllabary-katakana::before { - content: "\F0360"; -} -.mdi-syllabary-katakana-half-width::before { - content: "\F0361"; -} -.mdi-symfony::before { - content: "\FAE5"; -} -.mdi-sync::before { - content: "\F4E6"; -} -.mdi-sync-alert::before { - content: "\F4E7"; -} -.mdi-sync-circle::before { - content: "\F03A3"; -} -.mdi-sync-off::before { - content: "\F4E8"; -} -.mdi-tab::before { - content: "\F4E9"; -} -.mdi-tab-minus::before { - content: "\FB26"; -} -.mdi-tab-plus::before { - content: "\F75B"; -} -.mdi-tab-remove::before { - content: "\FB27"; -} -.mdi-tab-unselected::before { - content: "\F4EA"; -} -.mdi-table::before { - content: "\F4EB"; -} -.mdi-table-border::before { - content: "\FA17"; -} -.mdi-table-chair::before { - content: "\F0083"; -} -.mdi-table-column::before { - content: "\F834"; -} -.mdi-table-column-plus-after::before { - content: "\F4EC"; -} -.mdi-table-column-plus-before::before { - content: "\F4ED"; -} -.mdi-table-column-remove::before { - content: "\F4EE"; -} -.mdi-table-column-width::before { - content: "\F4EF"; -} -.mdi-table-edit::before { - content: "\F4F0"; -} -.mdi-table-eye::before { - content: "\F00BF"; -} -.mdi-table-headers-eye::before { - content: "\F0248"; -} -.mdi-table-headers-eye-off::before { - content: "\F0249"; -} -.mdi-table-large::before { - content: "\F4F1"; -} -.mdi-table-large-plus::before { - content: "\FFA4"; -} -.mdi-table-large-remove::before { - content: "\FFA5"; -} -.mdi-table-merge-cells::before { - content: "\F9A5"; -} -.mdi-table-of-contents::before { - content: "\F835"; -} -.mdi-table-plus::before { - content: "\FA74"; -} -.mdi-table-remove::before { - content: "\FA75"; -} -.mdi-table-row::before { - content: "\F836"; -} -.mdi-table-row-height::before { - content: "\F4F2"; -} -.mdi-table-row-plus-after::before { - content: "\F4F3"; -} -.mdi-table-row-plus-before::before { - content: "\F4F4"; -} -.mdi-table-row-remove::before { - content: "\F4F5"; -} -.mdi-table-search::before { - content: "\F90E"; -} -.mdi-table-settings::before { - content: "\F837"; -} -.mdi-table-tennis::before { - content: "\FE4B"; -} -.mdi-tablet::before { - content: "\F4F6"; -} -.mdi-tablet-android::before { - content: "\F4F7"; -} -.mdi-tablet-cellphone::before { - content: "\F9A6"; -} -.mdi-tablet-dashboard::before { - content: "\FEEB"; -} -.mdi-tablet-ipad::before { - content: "\F4F8"; -} -.mdi-taco::before { - content: "\F761"; -} -.mdi-tag::before { - content: "\F4F9"; -} -.mdi-tag-faces::before { - content: "\F4FA"; -} -.mdi-tag-heart::before { - content: "\F68A"; -} -.mdi-tag-heart-outline::before { - content: "\FBAB"; -} -.mdi-tag-minus::before { - content: "\F90F"; -} -.mdi-tag-minus-outline::before { - content: "\F024A"; -} -.mdi-tag-multiple::before { - content: "\F4FB"; -} -.mdi-tag-multiple-outline::before { - content: "\F0322"; -} -.mdi-tag-off::before { - content: "\F024B"; -} -.mdi-tag-off-outline::before { - content: "\F024C"; -} -.mdi-tag-outline::before { - content: "\F4FC"; -} -.mdi-tag-plus::before { - content: "\F721"; -} -.mdi-tag-plus-outline::before { - content: "\F024D"; -} -.mdi-tag-remove::before { - content: "\F722"; -} -.mdi-tag-remove-outline::before { - content: "\F024E"; -} -.mdi-tag-text::before { - content: "\F024F"; -} -.mdi-tag-text-outline::before { - content: "\F4FD"; -} -.mdi-tank::before { - content: "\FD16"; -} -.mdi-tanker-truck::before { - content: "\F0006"; -} -.mdi-tape-measure::before { - content: "\FB28"; -} -.mdi-target::before { - content: "\F4FE"; -} -.mdi-target-account::before { - content: "\FBAC"; -} -.mdi-target-variant::before { - content: "\FA76"; -} -.mdi-taxi::before { - content: "\F4FF"; -} -.mdi-tea::before { - content: "\FD7A"; -} -.mdi-tea-outline::before { - content: "\FD7B"; -} -.mdi-teach::before { - content: "\F88F"; -} -.mdi-teamviewer::before { - content: "\F500"; -} -.mdi-telegram::before { - content: "\F501"; -} -.mdi-telescope::before { - content: "\FB29"; -} -.mdi-television::before { - content: "\F502"; -} -.mdi-television-ambient-light::before { - content: "\F0381"; -} -.mdi-television-box::before { - content: "\F838"; -} -.mdi-television-classic::before { - content: "\F7F3"; -} -.mdi-television-classic-off::before { - content: "\F839"; -} -.mdi-television-clean::before { - content: "\F013B"; -} -.mdi-television-guide::before { - content: "\F503"; -} -.mdi-television-off::before { - content: "\F83A"; -} -.mdi-television-pause::before { - content: "\FFA6"; -} -.mdi-television-play::before { - content: "\FEEC"; -} -.mdi-television-stop::before { - content: "\FFA7"; -} -.mdi-temperature-celsius::before { - content: "\F504"; -} -.mdi-temperature-fahrenheit::before { - content: "\F505"; -} -.mdi-temperature-kelvin::before { - content: "\F506"; -} -.mdi-tennis::before { - content: "\FD7C"; -} -.mdi-tennis-ball::before { - content: "\F507"; -} -.mdi-tent::before { - content: "\F508"; -} -.mdi-terraform::before { - content: "\F0084"; -} -.mdi-terrain::before { - content: "\F509"; -} -.mdi-test-tube::before { - content: "\F668"; -} -.mdi-test-tube-empty::before { - content: "\F910"; -} -.mdi-test-tube-off::before { - content: "\F911"; -} -.mdi-text::before { - content: "\F9A7"; -} -.mdi-text-recognition::before { - content: "\F0168"; -} -.mdi-text-shadow::before { - content: "\F669"; -} -.mdi-text-short::before { - content: "\F9A8"; -} -.mdi-text-subject::before { - content: "\F9A9"; -} -.mdi-text-to-speech::before { - content: "\F50A"; -} -.mdi-text-to-speech-off::before { - content: "\F50B"; -} -.mdi-textarea::before { - content: "\F00C0"; -} -.mdi-textbox::before { - content: "\F60E"; -} -.mdi-textbox-lock::before { - content: "\F0388"; -} -.mdi-textbox-password::before { - content: "\F7F4"; -} -.mdi-texture::before { - content: "\F50C"; -} -.mdi-texture-box::before { - content: "\F0007"; -} -.mdi-theater::before { - content: "\F50D"; -} -.mdi-theme-light-dark::before { - content: "\F50E"; -} -.mdi-thermometer::before { - content: "\F50F"; -} -.mdi-thermometer-alert::before { - content: "\FE61"; -} -.mdi-thermometer-chevron-down::before { - content: "\FE62"; -} -.mdi-thermometer-chevron-up::before { - content: "\FE63"; -} -.mdi-thermometer-high::before { - content: "\F00ED"; -} -.mdi-thermometer-lines::before { - content: "\F510"; -} -.mdi-thermometer-low::before { - content: "\F00EE"; -} -.mdi-thermometer-minus::before { - content: "\FE64"; -} -.mdi-thermometer-plus::before { - content: "\FE65"; -} -.mdi-thermostat::before { - content: "\F393"; -} -.mdi-thermostat-box::before { - content: "\F890"; -} -.mdi-thought-bubble::before { - content: "\F7F5"; -} -.mdi-thought-bubble-outline::before { - content: "\F7F6"; -} -.mdi-thumb-down::before { - content: "\F511"; -} -.mdi-thumb-down-outline::before { - content: "\F512"; -} -.mdi-thumb-up::before { - content: "\F513"; -} -.mdi-thumb-up-outline::before { - content: "\F514"; -} -.mdi-thumbs-up-down::before { - content: "\F515"; -} -.mdi-ticket::before { - content: "\F516"; -} -.mdi-ticket-account::before { - content: "\F517"; -} -.mdi-ticket-confirmation::before { - content: "\F518"; -} -.mdi-ticket-outline::before { - content: "\F912"; -} -.mdi-ticket-percent::before { - content: "\F723"; -} -.mdi-tie::before { - content: "\F519"; -} -.mdi-tilde::before { - content: "\F724"; -} -.mdi-timelapse::before { - content: "\F51A"; -} -.mdi-timeline::before { - content: "\FBAD"; -} -.mdi-timeline-alert::before { - content: "\FFB2"; -} -.mdi-timeline-alert-outline::before { - content: "\FFB5"; -} -.mdi-timeline-clock::before { - content: "\F0226"; -} -.mdi-timeline-clock-outline::before { - content: "\F0227"; -} -.mdi-timeline-help::before { - content: "\FFB6"; -} -.mdi-timeline-help-outline::before { - content: "\FFB7"; -} -.mdi-timeline-outline::before { - content: "\FBAE"; -} -.mdi-timeline-plus::before { - content: "\FFB3"; -} -.mdi-timeline-plus-outline::before { - content: "\FFB4"; -} -.mdi-timeline-text::before { - content: "\FBAF"; -} -.mdi-timeline-text-outline::before { - content: "\FBB0"; -} -.mdi-timer::before { - content: "\F51B"; -} -.mdi-timer-10::before { - content: "\F51C"; -} -.mdi-timer-3::before { - content: "\F51D"; -} -.mdi-timer-off::before { - content: "\F51E"; -} -.mdi-timer-sand::before { - content: "\F51F"; -} -.mdi-timer-sand-empty::before { - content: "\F6AC"; -} -.mdi-timer-sand-full::before { - content: "\F78B"; -} -.mdi-timetable::before { - content: "\F520"; -} -.mdi-toaster::before { - content: "\F0085"; -} -.mdi-toaster-off::before { - content: "\F01E2"; -} -.mdi-toaster-oven::before { - content: "\FCAF"; -} -.mdi-toggle-switch::before { - content: "\F521"; -} -.mdi-toggle-switch-off::before { - content: "\F522"; -} -.mdi-toggle-switch-off-outline::before { - content: "\FA18"; -} -.mdi-toggle-switch-outline::before { - content: "\FA19"; -} -.mdi-toilet::before { - content: "\F9AA"; -} -.mdi-toolbox::before { - content: "\F9AB"; -} -.mdi-toolbox-outline::before { - content: "\F9AC"; -} -.mdi-tools::before { - content: "\F0086"; -} -.mdi-tooltip::before { - content: "\F523"; -} -.mdi-tooltip-account::before { - content: "\F00C"; -} -.mdi-tooltip-edit::before { - content: "\F524"; -} -.mdi-tooltip-edit-outline::before { - content: "\F02F0"; -} -.mdi-tooltip-image::before { - content: "\F525"; -} -.mdi-tooltip-image-outline::before { - content: "\FBB1"; -} -.mdi-tooltip-outline::before { - content: "\F526"; -} -.mdi-tooltip-plus::before { - content: "\FBB2"; -} -.mdi-tooltip-plus-outline::before { - content: "\F527"; -} -.mdi-tooltip-text::before { - content: "\F528"; -} -.mdi-tooltip-text-outline::before { - content: "\FBB3"; -} -.mdi-tooth::before { - content: "\F8C2"; -} -.mdi-tooth-outline::before { - content: "\F529"; -} -.mdi-toothbrush::before { - content: "\F0154"; -} -.mdi-toothbrush-electric::before { - content: "\F0157"; -} -.mdi-toothbrush-paste::before { - content: "\F0155"; -} -.mdi-tor::before { - content: "\F52A"; -} -.mdi-tortoise::before { - content: "\FD17"; -} -.mdi-toslink::before { - content: "\F02E3"; -} -.mdi-tournament::before { - content: "\F9AD"; -} -.mdi-tower-beach::before { - content: "\F680"; -} -.mdi-tower-fire::before { - content: "\F681"; -} -.mdi-towing::before { - content: "\F83B"; -} -.mdi-toy-brick::before { - content: "\F02B3"; -} -.mdi-toy-brick-marker::before { - content: "\F02B4"; -} -.mdi-toy-brick-marker-outline::before { - content: "\F02B5"; -} -.mdi-toy-brick-minus::before { - content: "\F02B6"; -} -.mdi-toy-brick-minus-outline::before { - content: "\F02B7"; -} -.mdi-toy-brick-outline::before { - content: "\F02B8"; -} -.mdi-toy-brick-plus::before { - content: "\F02B9"; -} -.mdi-toy-brick-plus-outline::before { - content: "\F02BA"; -} -.mdi-toy-brick-remove::before { - content: "\F02BB"; -} -.mdi-toy-brick-remove-outline::before { - content: "\F02BC"; -} -.mdi-toy-brick-search::before { - content: "\F02BD"; -} -.mdi-toy-brick-search-outline::before { - content: "\F02BE"; -} -.mdi-track-light::before { - content: "\F913"; -} -.mdi-trackpad::before { - content: "\F7F7"; -} -.mdi-trackpad-lock::before { - content: "\F932"; -} -.mdi-tractor::before { - content: "\F891"; -} -.mdi-trademark::before { - content: "\FA77"; -} -.mdi-traffic-cone::before { - content: "\F03A7"; -} -.mdi-traffic-light::before { - content: "\F52B"; -} -.mdi-train::before { - content: "\F52C"; -} -.mdi-train-car::before { - content: "\FBB4"; -} -.mdi-train-variant::before { - content: "\F8C3"; -} -.mdi-tram::before { - content: "\F52D"; -} -.mdi-tram-side::before { - content: "\F0008"; -} -.mdi-transcribe::before { - content: "\F52E"; -} -.mdi-transcribe-close::before { - content: "\F52F"; -} -.mdi-transfer::before { - content: "\F0087"; -} -.mdi-transfer-down::before { - content: "\FD7D"; -} -.mdi-transfer-left::before { - content: "\FD7E"; -} -.mdi-transfer-right::before { - content: "\F530"; -} -.mdi-transfer-up::before { - content: "\FD7F"; -} -.mdi-transit-connection::before { - content: "\FD18"; -} -.mdi-transit-connection-variant::before { - content: "\FD19"; -} -.mdi-transit-detour::before { - content: "\FFA8"; -} -.mdi-transit-transfer::before { - content: "\F6AD"; -} -.mdi-transition::before { - content: "\F914"; -} -.mdi-transition-masked::before { - content: "\F915"; -} -.mdi-translate::before { - content: "\F5CA"; -} -.mdi-translate-off::before { - content: "\FE66"; -} -.mdi-transmission-tower::before { - content: "\FD1A"; -} -.mdi-trash-can::before { - content: "\FA78"; -} -.mdi-trash-can-outline::before { - content: "\FA79"; -} -.mdi-tray::before { - content: "\F02BF"; -} -.mdi-tray-alert::before { - content: "\F02C0"; -} -.mdi-tray-full::before { - content: "\F02C1"; -} -.mdi-tray-minus::before { - content: "\F02C2"; -} -.mdi-tray-plus::before { - content: "\F02C3"; -} -.mdi-tray-remove::before { - content: "\F02C4"; -} -.mdi-treasure-chest::before { - content: "\F725"; -} -.mdi-tree::before { - content: "\F531"; -} -.mdi-tree-outline::before { - content: "\FE4C"; -} -.mdi-trello::before { - content: "\F532"; -} -.mdi-trending-down::before { - content: "\F533"; -} -.mdi-trending-neutral::before { - content: "\F534"; -} -.mdi-trending-up::before { - content: "\F535"; -} -.mdi-triangle::before { - content: "\F536"; -} -.mdi-triangle-outline::before { - content: "\F537"; -} -.mdi-triforce::before { - content: "\FBB5"; -} -.mdi-trophy::before { - content: "\F538"; -} -.mdi-trophy-award::before { - content: "\F539"; -} -.mdi-trophy-broken::before { - content: "\FD80"; -} -.mdi-trophy-outline::before { - content: "\F53A"; -} -.mdi-trophy-variant::before { - content: "\F53B"; -} -.mdi-trophy-variant-outline::before { - content: "\F53C"; -} -.mdi-truck::before { - content: "\F53D"; -} -.mdi-truck-check::before { - content: "\FCB0"; -} -.mdi-truck-check-outline::before { - content: "\F02C5"; -} -.mdi-truck-delivery::before { - content: "\F53E"; -} -.mdi-truck-delivery-outline::before { - content: "\F02C6"; -} -.mdi-truck-fast::before { - content: "\F787"; -} -.mdi-truck-fast-outline::before { - content: "\F02C7"; -} -.mdi-truck-outline::before { - content: "\F02C8"; -} -.mdi-truck-trailer::before { - content: "\F726"; -} -.mdi-trumpet::before { - content: "\F00C1"; -} -.mdi-tshirt-crew::before { - content: "\FA7A"; -} -.mdi-tshirt-crew-outline::before { - content: "\F53F"; -} -.mdi-tshirt-v::before { - content: "\FA7B"; -} -.mdi-tshirt-v-outline::before { - content: "\F540"; -} -.mdi-tumble-dryer::before { - content: "\F916"; -} -.mdi-tumble-dryer-alert::before { - content: "\F01E5"; -} -.mdi-tumble-dryer-off::before { - content: "\F01E6"; -} -.mdi-tumblr::before { - content: "\F541"; -} -.mdi-tumblr-box::before { - content: "\F917"; -} -.mdi-tumblr-reblog::before { - content: "\F542"; -} -.mdi-tune::before { - content: "\F62E"; -} -.mdi-tune-vertical::before { - content: "\F66A"; -} -.mdi-turnstile::before { - content: "\FCB1"; -} -.mdi-turnstile-outline::before { - content: "\FCB2"; -} -.mdi-turtle::before { - content: "\FCB3"; -} -.mdi-twitch::before { - content: "\F543"; -} -.mdi-twitter::before { - content: "\F544"; -} -.mdi-twitter-box::before { - content: "\F545"; -} -.mdi-twitter-circle::before { - content: "\F546"; -} -.mdi-twitter-retweet::before { - content: "\F547"; -} -.mdi-two-factor-authentication::before { - content: "\F9AE"; -} -.mdi-typewriter::before { - content: "\FF4A"; -} -.mdi-uber::before { - content: "\F748"; -} -.mdi-ubisoft::before { - content: "\FBB6"; -} -.mdi-ubuntu::before { - content: "\F548"; -} -.mdi-ufo::before { - content: "\F00EF"; -} -.mdi-ufo-outline::before { - content: "\F00F0"; -} -.mdi-ultra-high-definition::before { - content: "\F7F8"; -} -.mdi-umbraco::before { - content: "\F549"; -} -.mdi-umbrella::before { - content: "\F54A"; -} -.mdi-umbrella-closed::before { - content: "\F9AF"; -} -.mdi-umbrella-outline::before { - content: "\F54B"; -} -.mdi-undo::before { - content: "\F54C"; -} -.mdi-undo-variant::before { - content: "\F54D"; -} -.mdi-unfold-less-horizontal::before { - content: "\F54E"; -} -.mdi-unfold-less-vertical::before { - content: "\F75F"; -} -.mdi-unfold-more-horizontal::before { - content: "\F54F"; -} -.mdi-unfold-more-vertical::before { - content: "\F760"; -} -.mdi-ungroup::before { - content: "\F550"; -} -.mdi-unicode::before { - content: "\FEED"; -} -.mdi-unity::before { - content: "\F6AE"; -} -.mdi-unreal::before { - content: "\F9B0"; -} -.mdi-untappd::before { - content: "\F551"; -} -.mdi-update::before { - content: "\F6AF"; -} -.mdi-upload::before { - content: "\F552"; -} -.mdi-upload-lock::before { - content: "\F039E"; -} -.mdi-upload-lock-outline::before { - content: "\F039F"; -} -.mdi-upload-multiple::before { - content: "\F83C"; -} -.mdi-upload-network::before { - content: "\F6F5"; -} -.mdi-upload-network-outline::before { - content: "\FCB4"; -} -.mdi-upload-off::before { - content: "\F00F1"; -} -.mdi-upload-off-outline::before { - content: "\F00F2"; -} -.mdi-upload-outline::before { - content: "\FE67"; -} -.mdi-usb::before { - content: "\F553"; -} -.mdi-usb-flash-drive::before { - content: "\F02C9"; -} -.mdi-usb-flash-drive-outline::before { - content: "\F02CA"; -} -.mdi-usb-port::before { - content: "\F021B"; -} -.mdi-valve::before { - content: "\F0088"; -} -.mdi-valve-closed::before { - content: "\F0089"; -} -.mdi-valve-open::before { - content: "\F008A"; -} -.mdi-van-passenger::before { - content: "\F7F9"; -} -.mdi-van-utility::before { - content: "\F7FA"; -} -.mdi-vanish::before { - content: "\F7FB"; -} -.mdi-vanity-light::before { - content: "\F020C"; -} -.mdi-variable::before { - content: "\FAE6"; -} -.mdi-variable-box::before { - content: "\F013C"; -} -.mdi-vector-arrange-above::before { - content: "\F554"; -} -.mdi-vector-arrange-below::before { - content: "\F555"; -} -.mdi-vector-bezier::before { - content: "\FAE7"; -} -.mdi-vector-circle::before { - content: "\F556"; -} -.mdi-vector-circle-variant::before { - content: "\F557"; -} -.mdi-vector-combine::before { - content: "\F558"; -} -.mdi-vector-curve::before { - content: "\F559"; -} -.mdi-vector-difference::before { - content: "\F55A"; -} -.mdi-vector-difference-ab::before { - content: "\F55B"; -} -.mdi-vector-difference-ba::before { - content: "\F55C"; -} -.mdi-vector-ellipse::before { - content: "\F892"; -} -.mdi-vector-intersection::before { - content: "\F55D"; -} -.mdi-vector-line::before { - content: "\F55E"; -} -.mdi-vector-link::before { - content: "\F0009"; -} -.mdi-vector-point::before { - content: "\F55F"; -} -.mdi-vector-polygon::before { - content: "\F560"; -} -.mdi-vector-polyline::before { - content: "\F561"; -} -.mdi-vector-polyline-edit::before { - content: "\F0250"; -} -.mdi-vector-polyline-minus::before { - content: "\F0251"; -} -.mdi-vector-polyline-plus::before { - content: "\F0252"; -} -.mdi-vector-polyline-remove::before { - content: "\F0253"; -} -.mdi-vector-radius::before { - content: "\F749"; -} -.mdi-vector-rectangle::before { - content: "\F5C6"; -} -.mdi-vector-selection::before { - content: "\F562"; -} -.mdi-vector-square::before { - content: "\F001"; -} -.mdi-vector-triangle::before { - content: "\F563"; -} -.mdi-vector-union::before { - content: "\F564"; -} -.mdi-venmo::before { - content: "\F578"; -} -.mdi-vhs::before { - content: "\FA1A"; -} -.mdi-vibrate::before { - content: "\F566"; -} -.mdi-vibrate-off::before { - content: "\FCB5"; -} -.mdi-video::before { - content: "\F567"; -} -.mdi-video-3d::before { - content: "\F7FC"; -} -.mdi-video-3d-variant::before { - content: "\FEEE"; -} -.mdi-video-4k-box::before { - content: "\F83D"; -} -.mdi-video-account::before { - content: "\F918"; -} -.mdi-video-check::before { - content: "\F008B"; -} -.mdi-video-check-outline::before { - content: "\F008C"; -} -.mdi-video-image::before { - content: "\F919"; -} -.mdi-video-input-antenna::before { - content: "\F83E"; -} -.mdi-video-input-component::before { - content: "\F83F"; -} -.mdi-video-input-hdmi::before { - content: "\F840"; -} -.mdi-video-input-scart::before { - content: "\FFA9"; -} -.mdi-video-input-svideo::before { - content: "\F841"; -} -.mdi-video-minus::before { - content: "\F9B1"; -} -.mdi-video-off::before { - content: "\F568"; -} -.mdi-video-off-outline::before { - content: "\FBB7"; -} -.mdi-video-outline::before { - content: "\FBB8"; -} -.mdi-video-plus::before { - content: "\F9B2"; -} -.mdi-video-stabilization::before { - content: "\F91A"; -} -.mdi-video-switch::before { - content: "\F569"; -} -.mdi-video-vintage::before { - content: "\FA1B"; -} -.mdi-video-wireless::before { - content: "\FEEF"; -} -.mdi-video-wireless-outline::before { - content: "\FEF0"; -} -.mdi-view-agenda::before { - content: "\F56A"; -} -.mdi-view-agenda-outline::before { - content: "\F0203"; -} -.mdi-view-array::before { - content: "\F56B"; -} -.mdi-view-carousel::before { - content: "\F56C"; -} -.mdi-view-column::before { - content: "\F56D"; -} -.mdi-view-comfy::before { - content: "\FE4D"; -} -.mdi-view-compact::before { - content: "\FE4E"; -} -.mdi-view-compact-outline::before { - content: "\FE4F"; -} -.mdi-view-dashboard::before { - content: "\F56E"; -} -.mdi-view-dashboard-outline::before { - content: "\FA1C"; -} -.mdi-view-dashboard-variant::before { - content: "\F842"; -} -.mdi-view-day::before { - content: "\F56F"; -} -.mdi-view-grid::before { - content: "\F570"; -} -.mdi-view-grid-outline::before { - content: "\F0204"; -} -.mdi-view-grid-plus::before { - content: "\FFAA"; -} -.mdi-view-grid-plus-outline::before { - content: "\F0205"; -} -.mdi-view-headline::before { - content: "\F571"; -} -.mdi-view-list::before { - content: "\F572"; -} -.mdi-view-module::before { - content: "\F573"; -} -.mdi-view-parallel::before { - content: "\F727"; -} -.mdi-view-quilt::before { - content: "\F574"; -} -.mdi-view-sequential::before { - content: "\F728"; -} -.mdi-view-split-horizontal::before { - content: "\FBA7"; -} -.mdi-view-split-vertical::before { - content: "\FBA8"; -} -.mdi-view-stream::before { - content: "\F575"; -} -.mdi-view-week::before { - content: "\F576"; -} -.mdi-vimeo::before { - content: "\F577"; -} -.mdi-violin::before { - content: "\F60F"; -} -.mdi-virtual-reality::before { - content: "\F893"; -} -.mdi-visual-studio::before { - content: "\F610"; -} -.mdi-visual-studio-code::before { - content: "\FA1D"; -} -.mdi-vk::before { - content: "\F579"; -} -.mdi-vk-box::before { - content: "\F57A"; -} -.mdi-vk-circle::before { - content: "\F57B"; -} -.mdi-vlc::before { - content: "\F57C"; -} -.mdi-voice::before { - content: "\F5CB"; -} -.mdi-voice-off::before { - content: "\FEF1"; -} -.mdi-voicemail::before { - content: "\F57D"; -} -.mdi-volleyball::before { - content: "\F9B3"; -} -.mdi-volume-high::before { - content: "\F57E"; -} -.mdi-volume-low::before { - content: "\F57F"; -} -.mdi-volume-medium::before { - content: "\F580"; -} -.mdi-volume-minus::before { - content: "\F75D"; -} -.mdi-volume-mute::before { - content: "\F75E"; -} -.mdi-volume-off::before { - content: "\F581"; -} -.mdi-volume-plus::before { - content: "\F75C"; -} -.mdi-volume-source::before { - content: "\F014B"; -} -.mdi-volume-variant-off::before { - content: "\FE68"; -} -.mdi-volume-vibrate::before { - content: "\F014C"; -} -.mdi-vote::before { - content: "\FA1E"; -} -.mdi-vote-outline::before { - content: "\FA1F"; -} -.mdi-vpn::before { - content: "\F582"; -} -.mdi-vuejs::before { - content: "\F843"; -} -.mdi-vuetify::before { - content: "\FE50"; -} -.mdi-walk::before { - content: "\F583"; -} -.mdi-wall::before { - content: "\F7FD"; -} -.mdi-wall-sconce::before { - content: "\F91B"; -} -.mdi-wall-sconce-flat::before { - content: "\F91C"; -} -.mdi-wall-sconce-variant::before { - content: "\F91D"; -} -.mdi-wallet::before { - content: "\F584"; -} -.mdi-wallet-giftcard::before { - content: "\F585"; -} -.mdi-wallet-membership::before { - content: "\F586"; -} -.mdi-wallet-outline::before { - content: "\FBB9"; -} -.mdi-wallet-plus::before { - content: "\FFAB"; -} -.mdi-wallet-plus-outline::before { - content: "\FFAC"; -} -.mdi-wallet-travel::before { - content: "\F587"; -} -.mdi-wallpaper::before { - content: "\FE69"; -} -.mdi-wan::before { - content: "\F588"; -} -.mdi-wardrobe::before { - content: "\FFAD"; -} -.mdi-wardrobe-outline::before { - content: "\FFAE"; -} -.mdi-warehouse::before { - content: "\FFBB"; -} -.mdi-washing-machine::before { - content: "\F729"; -} -.mdi-washing-machine-alert::before { - content: "\F01E7"; -} -.mdi-washing-machine-off::before { - content: "\F01E8"; -} -.mdi-watch::before { - content: "\F589"; -} -.mdi-watch-export::before { - content: "\F58A"; -} -.mdi-watch-export-variant::before { - content: "\F894"; -} -.mdi-watch-import::before { - content: "\F58B"; -} -.mdi-watch-import-variant::before { - content: "\F895"; -} -.mdi-watch-variant::before { - content: "\F896"; -} -.mdi-watch-vibrate::before { - content: "\F6B0"; -} -.mdi-watch-vibrate-off::before { - content: "\FCB6"; -} -.mdi-water::before { - content: "\F58C"; -} -.mdi-water-boiler::before { - content: "\FFAF"; -} -.mdi-water-boiler-alert::before { - content: "\F01DE"; -} -.mdi-water-boiler-off::before { - content: "\F01DF"; -} -.mdi-water-off::before { - content: "\F58D"; -} -.mdi-water-outline::before { - content: "\FE6A"; -} -.mdi-water-percent::before { - content: "\F58E"; -} -.mdi-water-polo::before { - content: "\F02CB"; -} -.mdi-water-pump::before { - content: "\F58F"; -} -.mdi-water-pump-off::before { - content: "\FFB0"; -} -.mdi-water-well::before { - content: "\F008D"; -} -.mdi-water-well-outline::before { - content: "\F008E"; -} -.mdi-watermark::before { - content: "\F612"; -} -.mdi-wave::before { - content: "\FF4B"; -} -.mdi-waves::before { - content: "\F78C"; -} -.mdi-waze::before { - content: "\FBBA"; -} -.mdi-weather-cloudy::before { - content: "\F590"; -} -.mdi-weather-cloudy-alert::before { - content: "\FF4C"; -} -.mdi-weather-cloudy-arrow-right::before { - content: "\FE51"; -} -.mdi-weather-fog::before { - content: "\F591"; -} -.mdi-weather-hail::before { - content: "\F592"; -} -.mdi-weather-hazy::before { - content: "\FF4D"; -} -.mdi-weather-hurricane::before { - content: "\F897"; -} -.mdi-weather-lightning::before { - content: "\F593"; -} -.mdi-weather-lightning-rainy::before { - content: "\F67D"; -} -.mdi-weather-night::before { - content: "\F594"; -} -.mdi-weather-night-partly-cloudy::before { - content: "\FF4E"; -} -.mdi-weather-partly-cloudy::before { - content: "\F595"; -} -.mdi-weather-partly-lightning::before { - content: "\FF4F"; -} -.mdi-weather-partly-rainy::before { - content: "\FF50"; -} -.mdi-weather-partly-snowy::before { - content: "\FF51"; -} -.mdi-weather-partly-snowy-rainy::before { - content: "\FF52"; -} -.mdi-weather-pouring::before { - content: "\F596"; -} -.mdi-weather-rainy::before { - content: "\F597"; -} -.mdi-weather-snowy::before { - content: "\F598"; -} -.mdi-weather-snowy-heavy::before { - content: "\FF53"; -} -.mdi-weather-snowy-rainy::before { - content: "\F67E"; -} -.mdi-weather-sunny::before { - content: "\F599"; -} -.mdi-weather-sunny-alert::before { - content: "\FF54"; -} -.mdi-weather-sunset::before { - content: "\F59A"; -} -.mdi-weather-sunset-down::before { - content: "\F59B"; -} -.mdi-weather-sunset-up::before { - content: "\F59C"; -} -.mdi-weather-tornado::before { - content: "\FF55"; -} -.mdi-weather-windy::before { - content: "\F59D"; -} -.mdi-weather-windy-variant::before { - content: "\F59E"; -} -.mdi-web::before { - content: "\F59F"; -} -.mdi-web-box::before { - content: "\FFB1"; -} -.mdi-web-clock::before { - content: "\F0275"; -} -.mdi-webcam::before { - content: "\F5A0"; -} -.mdi-webhook::before { - content: "\F62F"; -} -.mdi-webpack::before { - content: "\F72A"; -} -.mdi-webrtc::before { - content: "\F0273"; -} -.mdi-wechat::before { - content: "\F611"; -} -.mdi-weight::before { - content: "\F5A1"; -} -.mdi-weight-gram::before { - content: "\FD1B"; -} -.mdi-weight-kilogram::before { - content: "\F5A2"; -} -.mdi-weight-lifter::before { - content: "\F0188"; -} -.mdi-weight-pound::before { - content: "\F9B4"; -} -.mdi-whatsapp::before { - content: "\F5A3"; -} -.mdi-wheelchair-accessibility::before { - content: "\F5A4"; -} -.mdi-whistle::before { - content: "\F9B5"; -} -.mdi-whistle-outline::before { - content: "\F02E7"; -} -.mdi-white-balance-auto::before { - content: "\F5A5"; -} -.mdi-white-balance-incandescent::before { - content: "\F5A6"; -} -.mdi-white-balance-iridescent::before { - content: "\F5A7"; -} -.mdi-white-balance-sunny::before { - content: "\F5A8"; -} -.mdi-widgets::before { - content: "\F72B"; -} -.mdi-widgets-outline::before { - content: "\F0380"; -} -.mdi-wifi::before { - content: "\F5A9"; -} -.mdi-wifi-off::before { - content: "\F5AA"; -} -.mdi-wifi-star::before { - content: "\FE6B"; -} -.mdi-wifi-strength-1::before { - content: "\F91E"; -} -.mdi-wifi-strength-1-alert::before { - content: "\F91F"; -} -.mdi-wifi-strength-1-lock::before { - content: "\F920"; -} -.mdi-wifi-strength-2::before { - content: "\F921"; -} -.mdi-wifi-strength-2-alert::before { - content: "\F922"; -} -.mdi-wifi-strength-2-lock::before { - content: "\F923"; -} -.mdi-wifi-strength-3::before { - content: "\F924"; -} -.mdi-wifi-strength-3-alert::before { - content: "\F925"; -} -.mdi-wifi-strength-3-lock::before { - content: "\F926"; -} -.mdi-wifi-strength-4::before { - content: "\F927"; -} -.mdi-wifi-strength-4-alert::before { - content: "\F928"; -} -.mdi-wifi-strength-4-lock::before { - content: "\F929"; -} -.mdi-wifi-strength-alert-outline::before { - content: "\F92A"; -} -.mdi-wifi-strength-lock-outline::before { - content: "\F92B"; -} -.mdi-wifi-strength-off::before { - content: "\F92C"; -} -.mdi-wifi-strength-off-outline::before { - content: "\F92D"; -} -.mdi-wifi-strength-outline::before { - content: "\F92E"; -} -.mdi-wii::before { - content: "\F5AB"; -} -.mdi-wiiu::before { - content: "\F72C"; -} -.mdi-wikipedia::before { - content: "\F5AC"; -} -.mdi-wind-turbine::before { - content: "\FD81"; -} -.mdi-window-close::before { - content: "\F5AD"; -} -.mdi-window-closed::before { - content: "\F5AE"; -} -.mdi-window-closed-variant::before { - content: "\F0206"; -} -.mdi-window-maximize::before { - content: "\F5AF"; -} -.mdi-window-minimize::before { - content: "\F5B0"; -} -.mdi-window-open::before { - content: "\F5B1"; -} -.mdi-window-open-variant::before { - content: "\F0207"; -} -.mdi-window-restore::before { - content: "\F5B2"; -} -.mdi-window-shutter::before { - content: "\F0147"; -} -.mdi-window-shutter-alert::before { - content: "\F0148"; -} -.mdi-window-shutter-open::before { - content: "\F0149"; -} -.mdi-windows::before { - content: "\F5B3"; -} -.mdi-windows-classic::before { - content: "\FA20"; -} -.mdi-wiper::before { - content: "\FAE8"; -} -.mdi-wiper-wash::before { - content: "\FD82"; -} -.mdi-wordpress::before { - content: "\F5B4"; -} -.mdi-worker::before { - content: "\F5B5"; -} -.mdi-wrap::before { - content: "\F5B6"; -} -.mdi-wrap-disabled::before { - content: "\FBBB"; -} -.mdi-wrench::before { - content: "\F5B7"; -} -.mdi-wrench-outline::before { - content: "\FBBC"; -} -.mdi-wunderlist::before { - content: "\F5B8"; -} -.mdi-xamarin::before { - content: "\F844"; -} -.mdi-xamarin-outline::before { - content: "\F845"; -} -.mdi-xaml::before { - content: "\F673"; -} -.mdi-xbox::before { - content: "\F5B9"; -} -.mdi-xbox-controller::before { - content: "\F5BA"; -} -.mdi-xbox-controller-battery-alert::before { - content: "\F74A"; -} -.mdi-xbox-controller-battery-charging::before { - content: "\FA21"; -} -.mdi-xbox-controller-battery-empty::before { - content: "\F74B"; -} -.mdi-xbox-controller-battery-full::before { - content: "\F74C"; -} -.mdi-xbox-controller-battery-low::before { - content: "\F74D"; -} -.mdi-xbox-controller-battery-medium::before { - content: "\F74E"; -} -.mdi-xbox-controller-battery-unknown::before { - content: "\F74F"; -} -.mdi-xbox-controller-menu::before { - content: "\FE52"; -} -.mdi-xbox-controller-off::before { - content: "\F5BB"; -} -.mdi-xbox-controller-view::before { - content: "\FE53"; -} -.mdi-xda::before { - content: "\F5BC"; -} -.mdi-xing::before { - content: "\F5BD"; -} -.mdi-xing-box::before { - content: "\F5BE"; -} -.mdi-xing-circle::before { - content: "\F5BF"; -} -.mdi-xml::before { - content: "\F5C0"; -} -.mdi-xmpp::before { - content: "\F7FE"; -} -.mdi-yahoo::before { - content: "\FB2A"; -} -.mdi-yammer::before { - content: "\F788"; -} -.mdi-yeast::before { - content: "\F5C1"; -} -.mdi-yelp::before { - content: "\F5C2"; -} -.mdi-yin-yang::before { - content: "\F67F"; -} -.mdi-yoga::before { - content: "\F01A7"; -} -.mdi-youtube::before { - content: "\F5C3"; -} -.mdi-youtube-creator-studio::before { - content: "\F846"; -} -.mdi-youtube-gaming::before { - content: "\F847"; -} -.mdi-youtube-subscription::before { - content: "\FD1C"; -} -.mdi-youtube-tv::before { - content: "\F448"; -} -.mdi-z-wave::before { - content: "\FAE9"; -} -.mdi-zend::before { - content: "\FAEA"; -} -.mdi-zigbee::before { - content: "\FD1D"; -} -.mdi-zip-box::before { - content: "\F5C4"; -} -.mdi-zip-box-outline::before { - content: "\F001B"; -} -.mdi-zip-disk::before { - content: "\FA22"; -} -.mdi-zodiac-aquarius::before { - content: "\FA7C"; -} -.mdi-zodiac-aries::before { - content: "\FA7D"; -} -.mdi-zodiac-cancer::before { - content: "\FA7E"; -} -.mdi-zodiac-capricorn::before { - content: "\FA7F"; -} -.mdi-zodiac-gemini::before { - content: "\FA80"; -} -.mdi-zodiac-leo::before { - content: "\FA81"; -} -.mdi-zodiac-libra::before { - content: "\FA82"; -} -.mdi-zodiac-pisces::before { - content: "\FA83"; -} -.mdi-zodiac-sagittarius::before { - content: "\FA84"; -} -.mdi-zodiac-scorpio::before { - content: "\FA85"; -} -.mdi-zodiac-taurus::before { - content: "\FA86"; -} -.mdi-zodiac-virgo::before { - content: "\FA87"; -} -.mdi-blank::before { - content: "\F68C"; - visibility: hidden; -} -.mdi-18px.mdi-set, -.mdi-18px.mdi:before { - font-size: 18px; -} -.mdi-24px.mdi-set, -.mdi-24px.mdi:before { - font-size: 24px; -} -.mdi-36px.mdi-set, -.mdi-36px.mdi:before { - font-size: 36px; -} -.mdi-48px.mdi-set, -.mdi-48px.mdi:before { - font-size: 48px; -} -.mdi-dark:before { - color: rgba(0, 0, 0, 0.54); -} -.mdi-dark.mdi-inactive:before { - color: rgba(0, 0, 0, 0.26); -} -.mdi-light:before { - color: #fff; -} -.mdi-light.mdi-inactive:before { - color: rgba(255, 255, 255, 0.3); -} -.mdi-rotate-45:before { - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); -} -.mdi-rotate-90:before { - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); -} -.mdi-rotate-135:before { - -webkit-transform: rotate(135deg); - -ms-transform: rotate(135deg); - transform: rotate(135deg); -} -.mdi-rotate-180:before { - -webkit-transform: rotate(180deg); - -ms-transform: rotate(180deg); - transform: rotate(180deg); -} -.mdi-rotate-225:before { - -webkit-transform: rotate(225deg); - -ms-transform: rotate(225deg); - transform: rotate(225deg); -} -.mdi-rotate-270:before { - -webkit-transform: rotate(270deg); - -ms-transform: rotate(270deg); - transform: rotate(270deg); -} -.mdi-rotate-315:before { - -webkit-transform: rotate(315deg); - -ms-transform: rotate(315deg); - transform: rotate(315deg); -} -.mdi-flip-h:before { - -webkit-transform: scaleX(-1); - transform: scaleX(-1); - filter: FlipH; - -ms-filter: "FlipH"; -} -.mdi-flip-v:before { - -webkit-transform: scaleY(-1); - transform: scaleY(-1); - filter: FlipV; - -ms-filter: "FlipV"; -} -.mdi-spin:before { - -webkit-animation: mdi-spin 2s infinite linear; - animation: mdi-spin 2s infinite linear; -} -@-webkit-keyframes mdi-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes mdi-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} - -/*# sourceMappingURL=materialdesignicons.css.map */ diff --git a/packages/demobank-ui/src/scss/libs/_all.scss b/packages/demobank-ui/src/scss/libs/_all.scss deleted file mode 100644 index d33f8acc4..000000000 --- a/packages/demobank-ui/src/scss/libs/_all.scss +++ /dev/null @@ -1,29 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -@import "node_modules/bulma-radio/bulma-radio"; -// @import "node_modules/bulma-responsive-tables/bulma-responsive-tables"; -@import "node_modules/bulma-checkbox/bulma-checkbox"; -// @import "node_modules/bulma-switch-control/bulma-switch-control"; -// @import "node_modules/bulma-upload-control/bulma-upload-control"; - -/* Bulma */ -@import "node_modules/bulma/bulma"; diff --git a/packages/demobank-ui/src/scss/main.css b/packages/demobank-ui/src/scss/main.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/packages/demobank-ui/src/scss/main.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/demobank-ui/src/scss/main.scss b/packages/demobank-ui/src/scss/main.scss deleted file mode 100644 index b9a46718f..000000000 --- a/packages/demobank-ui/src/scss/main.scss +++ /dev/null @@ -1,5 +0,0 @@ -@use "pure"; -@use "bank"; -@use "demo"; -@use "toggle"; -@use "colors-bank"; diff --git a/packages/demobank-ui/src/scss/pure.scss b/packages/demobank-ui/src/scss/pure.scss deleted file mode 100644 index 25a261a5f..000000000 --- a/packages/demobank-ui/src/scss/pure.scss +++ /dev/null @@ -1,1397 +0,0 @@ -/*! -Pure v2.2.0 -Copyright 2013 Yahoo! -Licensed under the BSD License. -https://github.com/pure-css/pure/blob/master/LICENSE -*/ -/*! -normalize.css v | MIT License | https://necolas.github.io/normalize.css/ -Copyright (c) Nicolas Gallagher and Jonathan Neal -*/ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ - -/* Document - ========================================================================== */ - -/** - * 1. Correct the line height in all browsers. - * 2. Prevent adjustments of font size after orientation changes in iOS. - */ - -html { - line-height: 1.15; /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/* Sections - ========================================================================== */ - -/** - * Remove the margin in all browsers. - */ - -body { - margin: 0; -} - -/** - * Render the `main` element consistently in IE. - */ - -main { - display: block; -} - -/** - * Correct the font size and margin on `h1` elements within `section` and - * `article` contexts in Chrome, Firefox, and Safari. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/* Grouping content - ========================================================================== */ - -/** - * 1. Add the correct box sizing in Firefox. - * 2. Show the overflow in Edge and IE. - */ - -hr { - -webkit-box-sizing: content-box; - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -pre { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Remove the gray background on active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * 1. Remove the bottom border in Chrome 57- - * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. - */ - -abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; /* 2 */ -} - -/** - * Add the correct font weight in Chrome, Edge, and Safari. - */ - -b, -strong { - font-weight: bolder; -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -code, -kbd, -samp { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/** - * Add the correct font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` elements from affecting the line height in - * all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove the border on images inside links in IE 10. - */ - -img { - border-style: none; -} - -/* Forms - ========================================================================== */ - -/** - * 1. Change the font styles in all browsers. - * 2. Remove the margin in Firefox and Safari. - */ - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 1 */ - line-height: 1.15; /* 1 */ - margin: 0; /* 2 */ -} - -/** - * Show the overflow in IE. - * 1. Show the overflow in Edge. - */ - -button, -input { - /* 1 */ - overflow: visible; -} - -/** - * Remove the inheritance of text transform in Edge, Firefox, and IE. - * 1. Remove the inheritance of text transform in Firefox. - */ - -button, -select { - /* 1 */ - text-transform: none; -} - -/** - * Correct the inability to style clickable types in iOS and Safari. - */ - -button, -[type="button"], -[type="reset"], -[type="submit"] { - -webkit-appearance: button; -} - -/** - * Remove the inner border and padding in Firefox. - */ - -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -/** - * Restore the focus styles unset by the previous rule. - */ - -button:-moz-focusring, -[type="button"]:-moz-focusring, -[type="reset"]:-moz-focusring, -[type="submit"]:-moz-focusring { - outline: 1px dotted ButtonText; -} - -/** - * Correct the padding in Firefox. - */ - -fieldset { - padding: 0.35em 0.75em 0.625em; -} - -/** - * 1. Correct the text wrapping in Edge and IE. - * 2. Correct the color inheritance from `fieldset` elements in IE. - * 3. Remove the padding so developers are not caught out when they zero out - * `fieldset` elements in all browsers. - */ - -legend { - -webkit-box-sizing: border-box; - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} - -/** - * Add the correct vertical alignment in Chrome, Firefox, and Opera. - */ - -progress { - vertical-align: baseline; -} - -/** - * Remove the default vertical scrollbar in IE 10+. - */ - -textarea { - overflow: auto; -} - -/** - * 1. Add the correct box sizing in IE 10. - * 2. Remove the padding in IE 10. - */ - -[type="checkbox"], -[type="radio"] { - -webkit-box-sizing: border-box; - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Correct the cursor style of increment and decrement buttons in Chrome. - */ - -[type="number"]::-webkit-inner-spin-button, -[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Correct the odd appearance in Chrome and Safari. - * 2. Correct the outline style in Safari. - */ - -[type="search"] { - -webkit-appearance: textfield; /* 1 */ - outline-offset: -2px; /* 2 */ -} - -/** - * Remove the inner padding in Chrome and Safari on macOS. - */ - -[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * 1. Correct the inability to style clickable types in iOS and Safari. - * 2. Change font properties to `inherit` in Safari. - */ - -::-webkit-file-upload-button { - -webkit-appearance: button; /* 1 */ - font: inherit; /* 2 */ -} - -/* Interactive - ========================================================================== */ - -/* - * Add the correct display in Edge, IE 10+, and Firefox. - */ - -details { - display: block; -} - -/* - * Add the correct display in all browsers. - */ - -summary { - display: list-item; -} - -/* Misc - ========================================================================== */ - -/** - * Add the correct display in IE 10+. - */ - -template { - display: none; -} - -/** - * Add the correct display in IE 10. - */ - -[hidden] { - display: none; -} - -/*csslint important:false*/ - -/* ========================================================================== - Pure Base Extras - ========================================================================== */ - -/** - * Extra rules that Pure adds on top of Normalize.css - */ - -html { - font-family: sans-serif; -} - -/** - * Always hide an element when it has the `hidden` HTML attribute. - */ - -.hidden, -[hidden] { - display: none !important; -} - -/** - * Add this class to an image to make it fit within it's fluid parent wrapper while maintaining - * aspect ratio. - */ -.pure-img { - max-width: 100%; - height: auto; - display: block; -} - -/*csslint regex-selectors:false, known-properties:false, duplicate-properties:false*/ - -.pure-g { - letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ - text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ - - /* - Sets the font stack to fonts known to work properly with the above letter - and word spacings. See: https://github.com/pure-css/pure/issues/41/ - - The following font stack makes Pure Grids work on all known environments. - - * FreeSans: Ships with many Linux distros, including Ubuntu - - * Arimo: Ships with Chrome OS. Arimo has to be defined before Helvetica and - Arial to get picked up by the browser, even though neither is available - in Chrome OS. - - * Droid Sans: Ships with all versions of Android. - - * Helvetica, Arial, sans-serif: Common font stack on OS X and Windows. - */ - font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; - - /* Use flexbox when possible to avoid `letter-spacing` side-effects. */ - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-flow: row wrap; - flex-flow: row wrap; - - /* Prevents distributing space between rows */ - -ms-flex-line-pack: start; - align-content: flex-start; -} - -/* IE10 display: -ms-flexbox (and display: flex in IE 11) does not work inside a table; fall back to block and rely on font hack */ -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { - table .pure-g { - display: block; - } -} - -/* Opera as of 12 on Windows needs word-spacing. - The ".opera-only" selector is used to prevent actual prefocus styling - and is not required in markup. -*/ -.opera-only :-o-prefocus, -.pure-g { - word-spacing: -0.43em; -} - -.pure-u { - display: inline-block; - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto; -} - -/* -Resets the font family back to the OS/browser's default sans-serif font, -this the same font stack that Normalize.css sets for the `body`. -*/ -.pure-g [class*="pure-u"] { - font-family: sans-serif; -} - -.pure-u-1, -.pure-u-1-1, -.pure-u-1-2, -.pure-u-1-3, -.pure-u-2-3, -.pure-u-1-4, -.pure-u-3-4, -.pure-u-1-5, -.pure-u-2-5, -.pure-u-3-5, -.pure-u-4-5, -.pure-u-5-5, -.pure-u-1-6, -.pure-u-5-6, -.pure-u-1-8, -.pure-u-3-8, -.pure-u-5-8, -.pure-u-7-8, -.pure-u-1-12, -.pure-u-5-12, -.pure-u-7-12, -.pure-u-11-12, -.pure-u-1-24, -.pure-u-2-24, -.pure-u-3-24, -.pure-u-4-24, -.pure-u-5-24, -.pure-u-6-24, -.pure-u-7-24, -.pure-u-8-24, -.pure-u-9-24, -.pure-u-10-24, -.pure-u-11-24, -.pure-u-12-24, -.pure-u-13-24, -.pure-u-14-24, -.pure-u-15-24, -.pure-u-16-24, -.pure-u-17-24, -.pure-u-18-24, -.pure-u-19-24, -.pure-u-20-24, -.pure-u-21-24, -.pure-u-22-24, -.pure-u-23-24, -.pure-u-24-24 { - display: inline-block; - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto; -} - -.pure-u-1-24 { - width: 4.1667%; -} - -.pure-u-1-12, -.pure-u-2-24 { - width: 8.3333%; -} - -.pure-u-1-8, -.pure-u-3-24 { - width: 12.5%; -} - -.pure-u-1-6, -.pure-u-4-24 { - width: 16.6667%; -} - -.pure-u-1-5 { - width: 20%; -} - -.pure-u-5-24 { - width: 20.8333%; -} - -.pure-u-1-4, -.pure-u-6-24 { - width: 25%; -} - -.pure-u-7-24 { - width: 29.1667%; -} - -.pure-u-1-3, -.pure-u-8-24 { - width: 33.3333%; -} - -.pure-u-3-8, -.pure-u-9-24 { - width: 37.5%; -} - -.pure-u-2-5 { - width: 40%; -} - -.pure-u-5-12, -.pure-u-10-24 { - width: 41.6667%; -} - -.pure-u-11-24 { - width: 45.8333%; -} - -.pure-u-1-2, -.pure-u-12-24 { - width: 50%; -} - -.pure-u-13-24 { - width: 54.1667%; -} - -.pure-u-7-12, -.pure-u-14-24 { - width: 58.3333%; -} - -.pure-u-3-5 { - width: 60%; -} - -.pure-u-5-8, -.pure-u-15-24 { - width: 62.5%; -} - -.pure-u-2-3, -.pure-u-16-24 { - width: 66.6667%; -} - -.pure-u-17-24 { - width: 70.8333%; -} - -.pure-u-3-4, -.pure-u-18-24 { - width: 75%; -} - -.pure-u-19-24 { - width: 79.1667%; -} - -.pure-u-4-5 { - width: 80%; -} - -.pure-u-5-6, -.pure-u-20-24 { - width: 83.3333%; -} - -.pure-u-7-8, -.pure-u-21-24 { - width: 87.5%; -} - -.pure-u-11-12, -.pure-u-22-24 { - width: 91.6667%; -} - -.pure-u-23-24 { - width: 95.8333%; -} - -.pure-u-1, -.pure-u-1-1, -.pure-u-5-5, -.pure-u-24-24 { - width: 100%; -} -.pure-button { - /* Structure */ - display: inline-block; - line-height: normal; - white-space: nowrap; - vertical-align: middle; - text-align: center; - cursor: pointer; - -webkit-user-drag: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -/* Firefox: Get rid of the inner focus border */ -.pure-button::-moz-focus-inner { - padding: 0; - border: 0; -} - -/* Inherit .pure-g styles */ -.pure-button-group { - letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ - text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ -} - -.opera-only :-o-prefocus, -.pure-button-group { - word-spacing: -0.43em; -} - -.pure-button-group .pure-button { - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto; -} - -/*csslint outline-none:false*/ - -.pure-button { - font-family: inherit; - font-size: 100%; - padding: 0.5em 1em; - color: rgba(0, 0, 0, 0.8); - border: none rgba(0, 0, 0, 0); - background-color: #e6e6e6; - text-decoration: none; - border-radius: 2px; -} - -.pure-button-hover, -.pure-button:hover, -.pure-button:focus { - background-image: -webkit-gradient( - linear, - left top, - left bottom, - from(transparent), - color-stop(40%, rgba(0, 0, 0, 0.05)), - to(rgba(0, 0, 0, 0.1)) - ); - background-image: linear-gradient( - transparent, - rgba(0, 0, 0, 0.05) 40%, - rgba(0, 0, 0, 0.1) - ); -} -.pure-button:focus { - outline: 0; -} -.pure-button-active, -.pure-button:active { - -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, - 0 0 6px rgba(0, 0, 0, 0.2) inset; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, - 0 0 6px rgba(0, 0, 0, 0.2) inset; - border-color: #000; -} - -.pure-button[disabled], -.pure-button-disabled, -.pure-button-disabled:hover, -.pure-button-disabled:focus, -.pure-button-disabled:active { - border: none; - background-image: none; - opacity: 0.4; - cursor: not-allowed; - -webkit-box-shadow: none; - box-shadow: none; - pointer-events: none; -} - -.pure-button-hidden { - display: none; -} - -.pure-button-primary, -.pure-button-selected, -a.pure-button-primary, -a.pure-button-selected { - background-color: rgb(0, 120, 231); - color: #fff; -} - -/* Button Groups */ -.pure-button-group .pure-button { - margin: 0; - border-radius: 0; - border-right: 1px solid rgba(0, 0, 0, 0.2); -} - -.pure-button-group .pure-button:first-child { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} -.pure-button-group .pure-button:last-child { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - border-right: none; -} - -/*csslint box-model:false*/ -/* -Box-model set to false because we're setting a height on select elements, which -also have border and padding. This is done because some browsers don't render -the padding. We explicitly set the box-model for select elements to border-box, -so we can ignore the csslint warning. -*/ - -.pure-form input[type="text"], -.pure-form input[type="password"], -.pure-form input[type="email"], -.pure-form input[type="url"], -.pure-form input[type="date"], -.pure-form input[type="month"], -.pure-form input[type="time"], -.pure-form input[type="datetime"], -.pure-form input[type="datetime-local"], -.pure-form input[type="week"], -.pure-form input[type="number"], -.pure-form input[type="search"], -.pure-form input[type="tel"], -.pure-form input[type="color"], -.pure-form select, -.pure-form textarea { - padding: 0.5em 0.6em; - display: inline-block; - border: 1px solid #ccc; - -webkit-box-shadow: inset 0 1px 3px #ddd; - box-shadow: inset 0 1px 3px #ddd; - border-radius: 4px; - vertical-align: middle; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -/* -Need to separate out the :not() selector from the rest of the CSS 2.1 selectors -since IE8 won't execute CSS that contains a CSS3 selector. -*/ -.pure-form input:not([type]) { - padding: 0.5em 0.6em; - display: inline-block; - border: 1px solid #ccc; - -webkit-box-shadow: inset 0 1px 3px #ddd; - box-shadow: inset 0 1px 3px #ddd; - border-radius: 4px; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -/* Chrome (as of v.32/34 on OS X) needs additional room for color to display. */ -/* May be able to remove this tweak as color inputs become more standardized across browsers. */ -.pure-form input[type="color"] { - padding: 0.2em 0.5em; -} - -.pure-form input[type="text"]:focus, -.pure-form input[type="password"]:focus, -.pure-form input[type="email"]:focus, -.pure-form input[type="url"]:focus, -.pure-form input[type="date"]:focus, -.pure-form input[type="month"]:focus, -.pure-form input[type="time"]:focus, -.pure-form input[type="datetime"]:focus, -.pure-form input[type="datetime-local"]:focus, -.pure-form input[type="week"]:focus, -.pure-form input[type="number"]:focus, -.pure-form input[type="search"]:focus, -.pure-form input[type="tel"]:focus, -.pure-form input[type="color"]:focus, -.pure-form select:focus, -.pure-form textarea:focus { - outline: 0; - border-color: #129fea; -} - -/* -Need to separate out the :not() selector from the rest of the CSS 2.1 selectors -since IE8 won't execute CSS that contains a CSS3 selector. -*/ -.pure-form input:not([type]):focus { - outline: 0; - border-color: #129fea; -} - -.pure-form input[type="file"]:focus, -.pure-form input[type="radio"]:focus, -.pure-form input[type="checkbox"]:focus { - outline: thin solid #129fea; - outline: 1px auto #129fea; -} -.pure-form .pure-checkbox, -.pure-form .pure-radio { - margin: 0.5em 0; - display: block; -} - -.pure-form input[type="text"][disabled], -.pure-form input[type="password"][disabled], -.pure-form input[type="email"][disabled], -.pure-form input[type="url"][disabled], -.pure-form input[type="date"][disabled], -.pure-form input[type="month"][disabled], -.pure-form input[type="time"][disabled], -.pure-form input[type="datetime"][disabled], -.pure-form input[type="datetime-local"][disabled], -.pure-form input[type="week"][disabled], -.pure-form input[type="number"][disabled], -.pure-form input[type="search"][disabled], -.pure-form input[type="tel"][disabled], -.pure-form input[type="color"][disabled], -.pure-form select[disabled], -.pure-form textarea[disabled] { - cursor: not-allowed; - background-color: #eaeded; - color: #cad2d3; -} - -/* -Need to separate out the :not() selector from the rest of the CSS 2.1 selectors -since IE8 won't execute CSS that contains a CSS3 selector. -*/ -.pure-form input:not([type])[disabled] { - cursor: not-allowed; - background-color: #eaeded; - color: #cad2d3; -} -.pure-form input[readonly], -.pure-form select[readonly], -.pure-form textarea[readonly] { - background-color: #eee; /* menu hover bg color */ - color: #777; /* menu text color */ - border-color: #ccc; -} - -/** - * Even if we add novalidate property - * in the form, the styles are applied for - * invalid elements so we need to remove this styles - * - */ -// .pure-form input:focus:invalid, -// .pure-form textarea:focus:invalid, -// .pure-form select:focus:invalid { -// color: #b94a48; -// border-color: #e9322d; -// } -// .pure-form input[type="file"]:focus:invalid:focus, -// .pure-form input[type="radio"]:focus:invalid:focus, -// .pure-form input[type="checkbox"]:focus:invalid:focus { -// outline-color: #e9322d; -// } -.pure-form select { - /* Normalizes the height; padding is not sufficient. */ - height: 2.25em; - border: 1px solid #ccc; - background-color: white; -} -.pure-form select[multiple] { - height: auto; -} -.pure-form label { - margin: 0.5em 0 0.2em; -} -.pure-form fieldset { - margin: 0; - padding: 0.35em 0 0.75em; - border: 0; -} -.pure-form legend { - display: block; - width: 100%; - padding: 0.3em 0; - margin-bottom: 0.3em; - color: #333; - border-bottom: 1px solid #e5e5e5; -} - -.pure-form-stacked input[type="text"], -.pure-form-stacked input[type="password"], -.pure-form-stacked input[type="email"], -.pure-form-stacked input[type="url"], -.pure-form-stacked input[type="date"], -.pure-form-stacked input[type="month"], -.pure-form-stacked input[type="time"], -.pure-form-stacked input[type="datetime"], -.pure-form-stacked input[type="datetime-local"], -.pure-form-stacked input[type="week"], -.pure-form-stacked input[type="number"], -.pure-form-stacked input[type="search"], -.pure-form-stacked input[type="tel"], -.pure-form-stacked input[type="color"], -.pure-form-stacked input[type="file"], -.pure-form-stacked select, -.pure-form-stacked label, -.pure-form-stacked textarea { - display: block; - margin: 0.25em 0; -} - -/* -Need to separate out the :not() selector from the rest of the CSS 2.1 selectors -since IE8 won't execute CSS that contains a CSS3 selector. -*/ -.pure-form-stacked input:not([type]) { - display: block; - margin: 0.25em 0; -} -.pure-form-aligned input, -.pure-form-aligned textarea, -.pure-form-aligned select, -.pure-form-message-inline { - display: inline-block; - vertical-align: middle; -} -.pure-form-aligned textarea { - vertical-align: top; -} - -/* Aligned Forms */ -.pure-form-aligned .pure-control-group { - margin-bottom: 0.5em; -} -.pure-form-aligned .pure-control-group label { - text-align: right; - display: inline-block; - vertical-align: middle; - width: 10em; - margin: 0 1em 0 0; -} -.pure-form-aligned .pure-controls { - margin: 1.5em 0 0 11em; -} - -/* Rounded Inputs */ -.pure-form input.pure-input-rounded, -.pure-form .pure-input-rounded { - border-radius: 2em; - padding: 0.5em 1em; -} - -/* Grouped Inputs */ -.pure-form .pure-group fieldset { - margin-bottom: 10px; -} -.pure-form .pure-group input, -.pure-form .pure-group textarea { - display: block; - padding: 10px; - margin: 0 0 -1px; - border-radius: 0; - position: relative; - top: -1px; -} -.pure-form .pure-group input:focus, -.pure-form .pure-group textarea:focus { - z-index: 3; -} -.pure-form .pure-group input:first-child, -.pure-form .pure-group textarea:first-child { - top: 1px; - border-radius: 4px 4px 0 0; - margin: 0; -} -.pure-form .pure-group input:first-child:last-child, -.pure-form .pure-group textarea:first-child:last-child { - top: 1px; - border-radius: 4px; - margin: 0; -} -.pure-form .pure-group input:last-child, -.pure-form .pure-group textarea:last-child { - top: -2px; - border-radius: 0 0 4px 4px; - margin: 0; -} -.pure-form .pure-group button { - margin: 0.35em 0; -} - -.pure-form .pure-input-1 { - width: 100%; -} -.pure-form .pure-input-3-4 { - width: 75%; -} -.pure-form .pure-input-2-3 { - width: 66%; -} -.pure-form .pure-input-1-2 { - width: 50%; -} -.pure-form .pure-input-1-3 { - width: 33%; -} -.pure-form .pure-input-1-4 { - width: 25%; -} - -/* Inline help for forms */ -.pure-form-message-inline { - display: inline-block; - padding-left: 0.3em; - color: #666; - vertical-align: middle; - font-size: 0.875em; -} - -/* Block help for forms */ -.pure-form-message { - display: block; - color: #666; - font-size: 0.875em; -} - -@media only screen and (max-width: 480px) { - // .pure-form button[type="submit"] { - // margin: 0.7em 0 0; - // } - - // .pure-form input:not([type]), - // .pure-form input[type="text"], - // .pure-form input[type="password"], - // .pure-form input[type="email"], - // .pure-form input[type="url"], - // .pure-form input[type="date"], - // .pure-form input[type="month"], - // .pure-form input[type="time"], - // .pure-form input[type="datetime"], - // .pure-form input[type="datetime-local"], - // .pure-form input[type="week"], - // .pure-form input[type="number"], - // .pure-form input[type="search"], - // .pure-form input[type="tel"], - // .pure-form input[type="color"], - // .pure-form label { - // margin-bottom: 0.3em; - // display: block; - // } - - .pure-group input:not([type]), - .pure-group input[type="text"], - .pure-group input[type="password"], - .pure-group input[type="email"], - .pure-group input[type="url"], - .pure-group input[type="date"], - .pure-group input[type="month"], - .pure-group input[type="time"], - .pure-group input[type="datetime"], - .pure-group input[type="datetime-local"], - .pure-group input[type="week"], - .pure-group input[type="number"], - .pure-group input[type="search"], - .pure-group input[type="tel"], - .pure-group input[type="color"] { - margin-bottom: 0; - } - - .pure-form-aligned .pure-control-group label { - margin-bottom: 0.3em; - text-align: left; - display: block; - width: 100%; - } - - .pure-form-aligned .pure-controls { - margin: 1.5em 0 0 0; - } - - .pure-form-message-inline, - .pure-form-message { - display: block; - font-size: 0.75em; - /* Increased bottom padding to make it group with its related input element. */ - padding: 0.2em 0 0.8em; - } -} - -/*csslint adjoining-classes: false, box-model:false*/ -.pure-menu { - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -.pure-menu-fixed { - position: fixed; - left: 0; - top: 0; - z-index: 3; -} - -.pure-menu-list, -.pure-menu-item { - position: relative; -} - -.pure-menu-list { - list-style: none; - margin: 0; - padding: 0; -} - -.pure-menu-item { - padding: 0; - margin: 0; - height: 100%; -} - -.pure-menu-link, -.pure-menu-heading { - display: block; - text-decoration: none; - white-space: nowrap; -} - -/* HORIZONTAL MENU */ -.pure-menu-horizontal { - width: 100%; - white-space: nowrap; -} - -.pure-menu-horizontal .pure-menu-list { - display: inline-block; -} - -/* Initial menus should be inline-block so that they are horizontal */ -.pure-menu-horizontal .pure-menu-item, -.pure-menu-horizontal .pure-menu-heading, -.pure-menu-horizontal .pure-menu-separator { - display: inline-block; - vertical-align: middle; -} - -/* Submenus should still be display: block; */ -.pure-menu-item .pure-menu-item { - display: block; -} - -.pure-menu-children { - display: none; - position: absolute; - left: 100%; - top: 0; - margin: 0; - padding: 0; - z-index: 3; -} - -.pure-menu-horizontal .pure-menu-children { - left: 0; - top: auto; - width: inherit; -} - -.pure-menu-allow-hover:hover > .pure-menu-children, -.pure-menu-active > .pure-menu-children { - display: block; - position: absolute; -} - -/* Vertical Menus - show the dropdown arrow */ -.pure-menu-has-children > .pure-menu-link:after { - padding-left: 0.5em; - content: "\25B8"; - font-size: small; -} - -/* Horizontal Menus - show the dropdown arrow */ -.pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after { - content: "\25BE"; -} - -/* scrollable menus */ -.pure-menu-scrollable { - overflow-y: scroll; - overflow-x: hidden; -} - -.pure-menu-scrollable .pure-menu-list { - display: block; -} - -.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list { - display: inline-block; -} - -.pure-menu-horizontal.pure-menu-scrollable { - white-space: nowrap; - overflow-y: hidden; - overflow-x: auto; - /* a little extra padding for this style to allow for scrollbars */ - padding: 0.5em 0; -} - -/* misc default styling */ - -.pure-menu-separator, -.pure-menu-horizontal .pure-menu-children .pure-menu-separator { - background-color: #ccc; - height: 1px; - margin: 0.3em 0; -} - -.pure-menu-horizontal .pure-menu-separator { - width: 1px; - height: 1.3em; - margin: 0 0.3em; -} - -/* Need to reset the separator since submenu is vertical */ -.pure-menu-horizontal .pure-menu-children .pure-menu-separator { - display: block; - width: auto; -} - -.pure-menu-heading { - text-transform: uppercase; - color: #565d64; -} - -.pure-menu-link { - color: #777; -} - -.pure-menu-children { - background-color: #fff; -} - -.pure-menu-link, -.pure-menu-heading { - padding: 0.5em 1em; -} - -.pure-menu-disabled { - opacity: 0.5; -} - -.pure-menu-disabled .pure-menu-link:hover { - background-color: transparent; - cursor: default; -} - -.pure-menu-active > .pure-menu-link, -.pure-menu-link:hover, -.pure-menu-link:focus { - background-color: #eee; -} - -.pure-menu-selected > .pure-menu-link, -.pure-menu-selected > .pure-menu-link:visited { - color: #000; -} - -.pure-table { - /* Remove spacing between table cells (from Normalize.css) */ - border-collapse: collapse; - border-spacing: 0; - empty-cells: show; - border: 1px solid #cbcbcb; -} - -.pure-table caption { - color: #000; - font: italic 85%/1 arial, sans-serif; - padding: 1em 0; - text-align: center; -} - -.pure-table td, -.pure-table th { - border-left: 1px solid #cbcbcb; /* inner column border */ - border-width: 0 0 0 1px; - font-size: inherit; - margin: 0; - overflow: visible; /*to make ths where the title is really long work*/ - padding: 0.5em 1em; /* cell padding */ -} - -.pure-table thead { - background-color: #e0e0e0; - color: #000; - text-align: left; - vertical-align: bottom; -} - -/* -striping: - even - #fff (white) - odd - #f2f2f2 (light gray) -*/ -.pure-table td { - background-color: transparent; -} -.pure-table-odd td { - background-color: #f2f2f2; -} - -/* nth-child selector for modern browsers */ -.pure-table-striped tr:nth-child(2n-1) td { - background-color: #f2f2f2; -} - -/* BORDERED TABLES */ -.pure-table-bordered td { - border-bottom: 1px solid #cbcbcb; -} -.pure-table-bordered tbody > tr:last-child > td { - border-bottom-width: 0; -} - -/* HORIZONTAL BORDERED TABLES */ - -.pure-table-horizontal td, -.pure-table-horizontal th { - border-width: 0 0 1px 0; - border-bottom: 1px solid #cbcbcb; -} -.pure-table-horizontal tbody > tr:last-child > td { - border-bottom-width: 0; -} diff --git a/packages/demobank-ui/src/scss/toggle.scss b/packages/demobank-ui/src/scss/toggle.scss deleted file mode 100644 index 24636da2f..000000000 --- a/packages/demobank-ui/src/scss/toggle.scss +++ /dev/null @@ -1,51 +0,0 @@ -$green: #56c080; - -.toggle { - cursor: pointer; - display: inline-block; -} -.toggle-switch { - display: inline-block; - background: #ccc; - border-radius: 16px; - width: 58px; - height: 32px; - position: relative; - vertical-align: middle; - transition: background 0.25s; - &:before, - &:after { - content: ""; - } - &:before { - display: block; - background: linear-gradient(to bottom, #fff 0%, #eee 100%); - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25); - width: 24px; - height: 24px; - position: absolute; - top: 4px; - left: 4px; - transition: left 0.25s; - } - .toggle:hover &:before { - background: linear-gradient(to bottom, #fff 0%, #fff 100%); - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5); - } - .toggle-checkbox:checked + & { - background: $green; - &:before { - left: 30px; - } - } -} -.toggle-checkbox { - position: absolute; - visibility: hidden; -} -.toggle-label { - margin-left: 5px; - position: relative; - top: 2px; -} diff --git a/packages/demobank-ui/src/stories.tsx b/packages/demobank-ui/src/stories.tsx index c6e8eb9ba..87848cb09 100644 --- a/packages/demobank-ui/src/stories.tsx +++ b/packages/demobank-ui/src/stories.tsx @@ -25,8 +25,6 @@ import * as components from "./components/index.examples.js"; import { renderStories } from "@gnu-taler/web-util/browser"; -import "./scss/main.scss"; - function main(): void { renderStories( { pages, components }, diff --git a/packages/demobank-ui/tailwind.config.js b/packages/demobank-ui/tailwind.config.js new file mode 100644 index 000000000..01f058b2e --- /dev/null +++ b/packages/demobank-ui/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ["./src/**/*.{html,tsx}"], + theme: { + extend: {}, + }, + plugins: [require("@tailwindcss/typography"), require("@tailwindcss/forms")], +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c5946a38..295074f61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -259,6 +259,12 @@ importers: '@gnu-taler/pogen': specifier: ^0.0.5 version: link:../pogen + '@tailwindcss/forms': + specifier: ^0.5.3 + version: 0.5.3(tailwindcss@3.3.2) + '@tailwindcss/typography': + specifier: ^0.5.9 + version: 0.5.9(tailwindcss@3.3.2) '@types/chai': specifier: ^4.3.0 version: 4.3.3 @@ -277,6 +283,9 @@ importers: '@typescript-eslint/parser': specifier: ^5.41.0 version: 5.41.0(eslint@8.48.0)(typescript@5.2.2) + autoprefixer: + specifier: ^10.4.14 + version: 10.4.14(postcss@8.4.23) bulma: specifier: ^0.9.4 version: 0.9.4 @@ -307,6 +316,9 @@ importers: sass: specifier: 1.56.1 version: 1.56.1 + tailwindcss: + specifier: ^3.3.2 + version: 3.3.2 typescript: specifier: 5.2.2 version: 5.2.2 @@ -319,7 +331,7 @@ importers: optionalDependencies: better-sqlite3: specifier: ^8.4.0 - version: 8.6.0 + version: 8.4.0 devDependencies: '@types/better-sqlite3': specifier: ^7.6.4 @@ -1122,7 +1134,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.4 - '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.17 jsesc: 2.5.2 @@ -4925,15 +4937,15 @@ packages: engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/sourcemap-codec': 1.4.15 /@jridgewell/gen-mapping@0.3.2: resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} @@ -4942,7 +4954,6 @@ packages: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.19 - dev: true /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} @@ -4960,8 +4971,8 @@ packages: /@jridgewell/source-map@0.3.2: resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} dependencies: - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.19 dev: true /@jridgewell/source-map@0.3.5: @@ -4976,7 +4987,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true /@jridgewell/trace-mapping@0.3.17: resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} @@ -4987,15 +4997,14 @@ packages: /@jridgewell/trace-mapping@0.3.19: resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} dependencies: - '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 dev: true /@leichtgewicht/ip-codec@2.0.4: @@ -5466,7 +5475,7 @@ packages: builtin-modules: 3.3.0 deepmerge: 4.2.2 is-module: 1.0.0 - resolve: 1.22.1 + resolve: 1.22.2 rollup: 2.79.1 dev: true @@ -7003,7 +7012,7 @@ packages: dependencies: '@babel/runtime': 7.19.4 cosmiconfig: 7.0.1 - resolve: 1.22.1 + resolve: 1.22.2 dev: true /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.18.9): @@ -7159,8 +7168,8 @@ packages: tweetnacl: 0.14.5 dev: true - /better-sqlite3@8.6.0: - resolution: {integrity: sha512-jwAudeiTMTSyby+/SfbHDebShbmC2MCH8mU2+DXi0WJfv13ypEJm47cd3kljmy/H130CazEvkf2Li//ewcMJ1g==} + /better-sqlite3@8.4.0: + resolution: {integrity: sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==} requiresBuild: true dependencies: bindings: 1.5.0 @@ -8210,7 +8219,7 @@ packages: webpack: ^4.37.0 || ^5.0.0 dependencies: cacache: 15.3.0 - fast-glob: 3.2.12 + fast-glob: 3.3.1 find-cache-dir: 3.3.2 glob-parent: 5.1.2 globby: 11.1.0 @@ -8487,6 +8496,7 @@ packages: /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} + hasBin: true /cssnano-preset-default@4.0.8: resolution: {integrity: sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==} @@ -8610,7 +8620,7 @@ packages: postcss: ^8.2.15 dependencies: cssnano-preset-default: 5.2.12(postcss@8.4.23) - lilconfig: 2.0.6 + lilconfig: 2.1.0 postcss: 8.4.23 yaml: 1.10.2 dev: true @@ -9449,7 +9459,7 @@ packages: resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} dependencies: debug: 3.2.7 - resolve: 1.22.1 + resolve: 1.22.2 transitivePeerDependencies: - supports-color dev: true @@ -10885,7 +10895,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.12 + fast-glob: 3.3.1 ignore: 5.2.0 merge2: 1.4.1 slash: 3.0.0 @@ -11969,6 +11979,7 @@ packages: /jiti@1.18.2: resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} + hasBin: true /js-sdsl@4.1.5: resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==} @@ -12232,11 +12243,6 @@ packages: type-check: 0.4.0 dev: true - /lilconfig@2.0.6: - resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} - engines: {node: '>=10'} - dev: true - /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -13966,7 +13972,7 @@ packages: ts-node: optional: true dependencies: - lilconfig: 2.0.6 + lilconfig: 2.1.0 postcss: 8.4.23 yaml: 1.10.2 dev: true @@ -15309,6 +15315,7 @@ packages: /resolve@1.22.2: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + hasBin: true dependencies: is-core-module: 2.11.0 path-parse: 1.0.7 @@ -16256,6 +16263,7 @@ packages: /sucrase@3.32.0: resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} engines: {node: '>=8'} + hasBin: true dependencies: '@jridgewell/gen-mapping': 0.3.2 commander: 4.1.1 @@ -16368,6 +16376,7 @@ packages: /tailwindcss@3.3.2: resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} engines: {node: '>=14.0.0'} + hasBin: true dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 -- cgit v1.2.3 From e39d5c488e2e425bd7febf694caadc17d5126401 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 20 Sep 2023 15:18:36 -0300 Subject: more ui --- packages/demobank-ui/src/assets/logo-2021.svg | 9 + packages/demobank-ui/src/components/QR.tsx | 5 +- .../src/components/Transactions/views.tsx | 14 +- packages/demobank-ui/src/forms/simplest.ts | 66 + packages/demobank-ui/src/hooks/notification.ts | 54 - .../demobank-ui/src/pages/AccountPage/index.ts | 4 +- .../demobank-ui/src/pages/AccountPage/state.ts | 10 +- packages/demobank-ui/src/pages/AdminPage.tsx | 150 +- packages/demobank-ui/src/pages/BankFrame.tsx | 409 +-- packages/demobank-ui/src/pages/BusinessAccount.tsx | 106 +- packages/demobank-ui/src/pages/HomePage.tsx | 40 +- packages/demobank-ui/src/pages/LoginForm.tsx | 335 +-- packages/demobank-ui/src/pages/PaymentOptions.tsx | 48 +- .../src/pages/PaytoWireTransferForm.tsx | 221 +- packages/demobank-ui/src/pages/QrCodeSection.tsx | 126 +- .../demobank-ui/src/pages/RegistrationPage.tsx | 311 ++- .../demobank-ui/src/pages/WalletWithdrawForm.tsx | 319 ++- .../src/pages/WithdrawalConfirmationQuestion.tsx | 383 ++- .../demobank-ui/src/pages/WithdrawalQRCode.tsx | 23 +- packages/demobank-ui/src/pages/rnd.ts | 2890 ++++++++++++++++++++ packages/demobank-ui/src/utils.ts | 18 +- pnpm-lock.yaml | 10 +- 22 files changed, 4451 insertions(+), 1100 deletions(-) create mode 100644 packages/demobank-ui/src/assets/logo-2021.svg create mode 100644 packages/demobank-ui/src/forms/simplest.ts delete mode 100644 packages/demobank-ui/src/hooks/notification.ts create mode 100644 packages/demobank-ui/src/pages/rnd.ts (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/src/assets/logo-2021.svg b/packages/demobank-ui/src/assets/logo-2021.svg new file mode 100644 index 000000000..8c5ff3e5b --- /dev/null +++ b/packages/demobank-ui/src/assets/logo-2021.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/demobank-ui/src/components/QR.tsx b/packages/demobank-ui/src/components/QR.tsx index c1c159ef8..945a08867 100644 --- a/packages/demobank-ui/src/components/QR.tsx +++ b/packages/demobank-ui/src/components/QR.tsx @@ -33,7 +33,6 @@ export function QR({ text }: { text: string }): VNode { return (
Latest transactions
-
+
- - - - + + + + {transactions.map((item, idx) => { return ( - - + ); })} diff --git a/packages/demobank-ui/src/forms/simplest.ts b/packages/demobank-ui/src/forms/simplest.ts new file mode 100644 index 000000000..54b6b1c65 --- /dev/null +++ b/packages/demobank-ui/src/forms/simplest.ts @@ -0,0 +1,66 @@ +import { + AbsoluteTime, + AmountJson, + TranslatedString +} from "@gnu-taler/taler-util"; +import { DoubleColumnForm, FormState } from "@gnu-taler/web-util/browser"; + +export namespace Data { + export interface WithResolution { + when: AbsoluteTime; + threshold: AmountJson; + state: string; + } + export interface Form extends WithResolution { + comment: string; + } +} + +const design: DoubleColumnForm = [ + { + title: "Simple form" as TranslatedString, + fields: [ + { + type: "textArea", + props: { + name: "comment", + label: "Comments" as TranslatedString, + }, + }, + ], + }, + { + title: "Resolution" as TranslatedString, + description: `Current state is and threshold at ` as TranslatedString, + fields: [ + { + type: "date", + props: { + name: "when", + label: "Decision Time" as TranslatedString, + }, + }, + { + type: "amount", + props: { + name: "threshold", + label: "New threshold" as TranslatedString, + }, + }, + ], + } + , +]; + +function formBehavior(v: Partial): FormState { + return { + when: { + disabled: true, + }, + threshold: { + // disabled: v.state === AmlExchangeBackend.AmlState.frozen, + }, + }; +} + + diff --git a/packages/demobank-ui/src/hooks/notification.ts b/packages/demobank-ui/src/hooks/notification.ts deleted file mode 100644 index 9bf621b41..000000000 --- a/packages/demobank-ui/src/hooks/notification.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { memoryMap } from "@gnu-taler/web-util/browser"; -import { StateUpdater, useEffect, useState } from "preact/hooks"; - -export type NotificationMessage = ErrorNotification | InfoNotification; - -//FIXME: this should not be exported since every notification -// goes throw notify function -export interface ErrorMessage { - description?: string; - title: TranslatedString; - debug?: string; -} - -interface ErrorNotification { - type: "error"; - error: ErrorMessage; -} -interface InfoNotification { - type: "info"; - info: TranslatedString; -} - -const storage = memoryMap(); -const NOTIFICATION_KEY = "notification"; - -export function onNotificationUpdate( - handler: (newValue: NotificationMessage | undefined) => void, -) { - return storage.onUpdate(NOTIFICATION_KEY, () => { - const newValue = storage.get(NOTIFICATION_KEY); - handler(newValue); - }); -} - -export function notifyError(error: ErrorMessage) { - storage.set(NOTIFICATION_KEY, { type: "error", error }); -} -export function notifyInfo(info: TranslatedString) { - storage.set(NOTIFICATION_KEY, { type: "info", info }); -} - -export function useNotifications(): [ - NotificationMessage | undefined, - StateUpdater, -] { - const [value, setter] = useState(); - useEffect(() => { - return storage.onUpdate(NOTIFICATION_KEY, () => { - setter(storage.get(NOTIFICATION_KEY)); - }); - }); - return [value, setter]; -} diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts index 28fb7cb0c..ed6945f84 100644 --- a/packages/demobank-ui/src/pages/AccountPage/index.ts +++ b/packages/demobank-ui/src/pages/AccountPage/index.ts @@ -15,7 +15,7 @@ */ import { HttpError, HttpResponseOk, HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser"; -import { AbsoluteTime, AmountJson, PaytoUriIBAN } from "@gnu-taler/taler-util"; +import { AbsoluteTime, AmountJson, PaytoUriIBAN, PaytoUriTalerBank } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { useComponentState } from "./state.js"; import { ReadyView, InvalidIbanView} from "./views.js"; @@ -51,7 +51,7 @@ export namespace State { status: "ready"; error: undefined; account: string, - payto: PaytoUriIBAN, + payto: PaytoUriIBAN | PaytoUriTalerBank, balance: AmountJson, balanceIsDebit: boolean, limit: AmountJson, diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts index bc59c9374..2249b743e 100644 --- a/packages/demobank-ui/src/pages/AccountPage/state.ts +++ b/packages/demobank-ui/src/pages/AccountPage/state.ts @@ -15,10 +15,9 @@ */ import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util"; -import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; import { useBackendContext } from "../../context/backend.js"; import { useAccountDetails } from "../../hooks/access.js"; -import { notifyError } from "../../hooks/notification.js"; import { Props, State } from "./index.js"; export function useComponentState({ account, onLoadNotOk }: Props): State { @@ -43,9 +42,7 @@ export function useComponentState({ account, onLoadNotOk }: Props): State { //logout if there is any error, not if loading backend.logOut(); if (result.status === HttpStatusCode.NotFound) { - notifyError({ - title: i18n.str`Username or account label "${account}" not found`, - }); + notifyError(i18n.str`Username or account label "${account}" not found`, undefined); return { status: "error-user-not-found", error: result, @@ -62,7 +59,8 @@ export function useComponentState({ account, onLoadNotOk }: Props): State { const debitThreshold = Amounts.parseOrThrow(data.debitThreshold); const payto = parsePaytoUri(data.paytoUri); - if (!payto || !payto.isKnown || payto.targetType !== "iban") { + if (!payto || !payto.isKnown || (payto.targetType !== "iban" && payto.targetType !== "x-taler-bank")) { + console.log(payto) return { status: "invalid-iban", error: result diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx index 73a4f9ca3..18462bdc3 100644 --- a/packages/demobank-ui/src/pages/AdminPage.tsx +++ b/packages/demobank-ui/src/pages/AdminPage.tsx @@ -14,11 +14,14 @@ GNU Taler; see the file COPYING. If not, see */ -import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util"; +import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri } from "@gnu-taler/taler-util"; import { ErrorType, HttpResponsePaginated, RequestError, + notify, + notifyError, + notifyInfo, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; @@ -39,12 +42,10 @@ import { validateIBAN, WithIntermediate, } from "../utils.js"; -import { ErrorBannerFloat } from "./BankFrame.js"; import { ShowCashoutDetails } from "./BusinessAccount.js"; import { handleNotOkResult } from "./HomePage.js"; import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; -import { ErrorMessage, notifyInfo } from "../hooks/notification.js"; const charset = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; @@ -362,6 +363,7 @@ function AdminAccount({ onRegister }: { onRegister: () => void }): VNode { onSuccess={() => { notifyInfo(i18n.str`Wire transfer created!`); }} + onCancel={undefined} /> ); @@ -414,7 +416,6 @@ export function UpdateAccountPassword({ const { changePassword } = useAdminAccountAPI(); const [password, setPassword] = useState(); const [repeat, setRepeat] = useState(); - const [error, saveError] = useState(); if (!result.ok) { if (result.loading || result.type === ErrorType.TIMEOUT) { @@ -431,8 +432,8 @@ export function UpdateAccountPassword({ repeat: !repeat ? i18n.str`required` : password !== repeat - ? i18n.str`password doesn't match` - : undefined, + ? i18n.str`password doesn't match` + : undefined, }); return ( @@ -442,9 +443,6 @@ export function UpdateAccountPassword({ Update password for {account} - {error && ( - saveError(undefined)} /> - )}
@@ -507,15 +505,11 @@ export function UpdateAccountPassword({ onUpdateSuccess(); } catch (error) { if (error instanceof RequestError) { - saveError(buildRequestErrorMessage(i18n, error.cause)); + notify(buildRequestErrorMessage(i18n, error.cause)); } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); + notifyError(i18n.str`Operation failed, please report`, (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString) } } }} @@ -540,7 +534,6 @@ function CreateNewAccount({ const [submitAccount, setSubmitAccount] = useState< SandboxBackend.Circuit.CircuitAccountData | undefined >(); - const [error, saveError] = useState(); return (
@@ -548,9 +541,6 @@ function CreateNewAccount({ New account
- {error && ( - saveError(undefined)} /> - )}
status === HttpStatusCode.Forbidden ? i18n.str`The rights to perform the operation are not sufficient` : status === HttpStatusCode.BadRequest - ? i18n.str`Input data was invalid` - : status === HttpStatusCode.Conflict - ? i18n.str`At least one registration detail was not available` - : undefined, + ? i18n.str`Input data was invalid` + : status === HttpStatusCode.Conflict + ? i18n.str`At least one registration detail was not available` + : undefined, }), ); } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) } } }} @@ -654,7 +643,6 @@ export function ShowAccountDetails({ const [submitAccount, setSubmitAccount] = useState< SandboxBackend.Circuit.CircuitAccountData | undefined >(); - const [error, saveError] = useState(); if (!result.ok) { if (result.loading || result.type === ErrorType.TIMEOUT) { @@ -673,9 +661,6 @@ export function ShowAccountDetails({ Business account details
- {error && ( - saveError(undefined)} /> - )}
status === HttpStatusCode.Forbidden ? i18n.str`The rights to change the account are not sufficient` : status === HttpStatusCode.NotFound - ? i18n.str`The username was not found` - : undefined, + ? i18n.str`The username was not found` + : undefined, }), ); } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) } } } @@ -788,7 +772,6 @@ function RemoveAccount({ const { i18n } = useTranslationContext(); const result = useAccountDetails(account); const { deleteAccount } = useAdminAccountAPI(); - const [error, saveError] = useState(); if (!result.ok) { if (result.loading || result.type === ErrorType.TIMEOUT) { @@ -812,7 +795,8 @@ function RemoveAccount({ Remove account: {account}
- {!isBalanceEmpty && ( + {/* {FXME: SHOW WARNING} */} + {/* {!isBalanceEmpty && ( saveError(undefined)} /> - )} - {error && ( - saveError(undefined)} /> - )} + )} */}

@@ -852,26 +833,23 @@ function RemoveAccount({ onUpdateSuccess(); } catch (error) { if (error instanceof RequestError) { - saveError( + notify( buildRequestErrorMessage(i18n, error.cause, { onClientError: (status) => status === HttpStatusCode.Forbidden ? i18n.str`The administrator specified a institutional username` : status === HttpStatusCode.NotFound - ? i18n.str`The username was not found` - : status === HttpStatusCode.PreconditionFailed - ? i18n.str`Balance was not zero` - : undefined, + ? i18n.str`The username was not found` + : status === HttpStatusCode.PreconditionFailed + ? i18n.str`Balance was not zero` + : undefined, }), ); } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error + notifyError(i18n.str`Operation failed, please report`, + (error instanceof Error ? error.message - : JSON.stringify(error), - }); + : JSON.stringify(error)) as TranslatedString); } } }} @@ -915,31 +893,31 @@ function AccountForm({ cashout_address: !newForm.cashout_address ? i18n.str`required` : !parsed - ? i18n.str`does not follow the pattern` - : !parsed.isKnown || parsed.targetType !== "iban" - ? i18n.str`only "IBAN" target are supported` - : !IBAN_REGEX.test(parsed.iban) - ? i18n.str`IBAN should have just uppercased letters and numbers` - : validateIBAN(parsed.iban, i18n), + ? i18n.str`does not follow the pattern` + : !parsed.isKnown || parsed.targetType !== "iban" + ? i18n.str`only "IBAN" target are supported` + : !IBAN_REGEX.test(parsed.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(parsed.iban, i18n), contact_data: undefinedIfEmpty({ email: !newForm.contact_data?.email ? i18n.str`required` : !EMAIL_REGEX.test(newForm.contact_data.email) - ? i18n.str`it should be an email` - : undefined, + ? i18n.str`it should be an email` + : undefined, phone: !newForm.contact_data?.phone ? i18n.str`required` : !newForm.contact_data.phone.startsWith("+") - ? i18n.str`should start with +` - : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone) - ? i18n.str`phone number can't have other than numbers` - : undefined, + ? i18n.str`should start with +` + : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone) + ? i18n.str`phone number can't have other than numbers` + : undefined, }), iban: !newForm.iban ? undefined //optional field : !IBAN_REGEX.test(newForm.iban) - ? i18n.str`IBAN should have just uppercased letters and numbers` - : validateIBAN(newForm.iban, i18n), + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(newForm.iban, i18n), name: !newForm.name ? i18n.str`required` : undefined, username: !newForm.username ? i18n.str`required` : undefined, }); diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 5b6d95ade..d234845a0 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -15,17 +15,16 @@ */ import { Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { StateUpdater, useEffect, useState } from "preact/hooks"; -import talerLogo from "../assets/logo-white.svg"; import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js"; import { useBackendContext } from "../context/backend.js"; import { useBusinessAccountDetails } from "../hooks/circuit.js"; import { bankUiSettings } from "../settings.js"; import { useSettings } from "../hooks/settings.js"; -import { ErrorMessage, onNotificationUpdate } from "../hooks/notification.js"; import { CopyButton, CopyIcon } from "../components/CopyButton.js"; +import logo from "../assets/logo-2021.svg"; const IS_PUBLIC_ACCOUNT_ENABLED = false; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; @@ -81,16 +80,23 @@ export function BankFrame({ , ); - return (
+ return (
-
-
+
- {/* */} + {children}
@@ -207,15 +282,15 @@ export function BankFrame({ // /> // ) : undefined} - // + // // { - // backend.logOut(); - // updateSettings("currentWithdrawalOperationId", undefined); - // }} + // onClick={() => { + // backend.logOut(); + // updateSettings("currentWithdrawalOperationId", undefined); + // }} // >{i18n.str`Logout`} // // ) : undefined} @@ -225,149 +300,110 @@ export function BankFrame({ // // {children} // - // // ); } -function maybeDemoContent(content: VNode): VNode { - if (bankUiSettings.showDemoNav) { - return content; - } - return ; -} +// function maybeDemoContent(content: VNode): VNode { +// if (bankUiSettings.showDemoNav) { +// return content; +// } +// return ; +// } -export function ErrorBannerFloat({ - error, - onClear, -}: { - error: ErrorMessage; - onClear?: () => void; -}): VNode { - return ( -
- -
- ); -} +// export function ErrorBannerFloat({ +// error, +// onClear, +// }: { +// error: ErrorMessage; +// onClear?: () => void; +// }): VNode { +// return ( +//
+// +//
+// ); +// } -function ErrorBanner({ - error, - onClear, -}: { - error: ErrorMessage; - onClear?: () => void; -}): VNode { - return ( -
-
-

- {error.title} -

-
- {onClear && ( - { - e.preventDefault(); - onClear(); - }} - /> - )} -
-
-

{error.description}

-
- ); -} - -function StatusBanner(): VNode | null { - const [info, setInfo] = useState(); - const [error, setError] = useState(); - useEffect(() => { - return onNotificationUpdate((newValue) => { - if (newValue === undefined) { - setInfo(undefined); - setError(undefined); - } else { - if (newValue.type === "error") { - setError(newValue.error); - } else { - setInfo(newValue.info); - } - } - }); - }, []); - return ( -
- {!info ? undefined : ( -
-
-

- {info} -

-
- { - setInfo(undefined); - }} - /> +function StatusBanner(): VNode { + const notifs = useNotifications() + return
{ + notifs.map(n => { + const info = n.message.type === "info" ? n.message : undefined + const error = n.message.type === "error" ? n.message : undefined + switch (n.message.type) { + case "error": + return
+
+
+ +
+
+

{n.message.title}

+

+ +

+
+ {n.message.description && +
+ {n.message.description} +
+ }
-
- )} - {!error ? undefined : ( - { - setError(undefined); - }} - /> - )} -
- ); + case "info": + return
+
+
+ +
+
+

{n.message.title}

+ +

+ +

+
+ +
+
+ } + })} +
+ } function TestingTag(): VNode { @@ -392,12 +428,12 @@ function TestingTag(): VNode { function Footer() { return ( -
+

You can learn more about GNU Taler on our{" "} - main website. + main website.

@@ -418,4 +454,9 @@ function WelcomeAccount(): VNode { Welcome, {account} ({payto.iban})! stringifyPaytoUri(payto)} /> -} \ No newline at end of file +} + +function AccountBalance(): VNode { + + return
KUDOS 100.00
+} diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx index 2faf83a1c..ec71ceca6 100644 --- a/packages/demobank-ui/src/pages/BusinessAccount.tsx +++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx @@ -17,17 +17,21 @@ import { AmountJson, Amounts, HttpStatusCode, - TranslatedString, + TranslatedString } from "@gnu-taler/taler-util"; import { HttpResponse, HttpResponsePaginated, RequestError, + notify, + notifyError, + notifyInfo, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { StateUpdater, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { Cashouts } from "../components/Cashouts/index.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { useBackendContext } from "../context/backend.js"; import { useAccountDetails } from "../hooks/access.js"; import { @@ -42,12 +46,9 @@ import { undefinedIfEmpty, } from "../utils.js"; import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js"; -import { ErrorBannerFloat } from "./BankFrame.js"; -import { LoginForm } from "./LoginForm.js"; -import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { handleNotOkResult } from "./HomePage.js"; -import { ErrorMessage, notifyInfo } from "../hooks/notification.js"; -import { Amount } from "./WalletWithdrawForm.js"; +import { LoginForm } from "./LoginForm.js"; +import { Amount } from "./PaytoWireTransferForm.js"; interface Props { onClose: () => void; @@ -225,7 +226,6 @@ function CreateCashout({ const { i18n } = useTranslationContext(); const ratiosResult = useRatiosAndFeeConfig(); const result = useAccountDetails(account); - const [error, saveError] = useState(); const { estimateByCredit: calculateFromCredit, estimateByDebit: calculateFromDebit, @@ -268,15 +268,15 @@ function CreateCashout({ calculateFromDebit(amount, sellFee, sellRate) .then((r) => { setCalc(r); - saveError(undefined); }) .catch((error) => { - saveError( + notify( error instanceof RequestError ? buildRequestErrorMessage(i18n, error.cause) : { + type: "error", title: i18n.str`Could not estimate the cashout`, - description: error.message, + description: error.message as TranslatedString }, ); }); @@ -284,13 +284,13 @@ function CreateCashout({ calculateFromCredit(amount, sellFee, sellRate) .then((r) => { setCalc(r); - saveError(undefined); }) .catch((error) => { - saveError( + notify( error instanceof RequestError ? buildRequestErrorMessage(i18n, error.cause) : { + type: "error", title: i18n.str`Could not estimate the cashout`, description: error.message, }, @@ -321,9 +321,6 @@ function CreateCashout({ return (
- {error && ( - saveError(undefined)} /> - )}

New cashout

@@ -341,13 +338,15 @@ function CreateCashout({ />
-
- +
-
- + @@ -401,16 +403,18 @@ function CreateCashout({ {Amounts.isZero(sellFee) ? undefined : (
- +
- + @@ -418,10 +422,11 @@ function CreateCashout({ )}
- @@ -511,7 +516,7 @@ function CreateCashout({ onComplete(res.data.uuid); } catch (error) { if (error instanceof RequestError) { - saveError( + notify( buildRequestErrorMessage(i18n, error.cause, { onClientError: (status) => status === HttpStatusCode.BadRequest @@ -530,14 +535,13 @@ function CreateCashout({ }), ); } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); - } + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) + } } }} > @@ -565,7 +569,6 @@ export function ShowCashoutDetails({ const result = useCashoutDetails(id); const { abortCashout, confirmCashout } = useCircuitAccountAPI(); const [code, setCode] = useState(undefined); - const [error, saveError] = useState(); if (!result.ok) return onLoadNotOk(result); const errors = undefinedIfEmpty({ code: !code ? i18n.str`required` : undefined, @@ -574,9 +577,6 @@ export function ShowCashoutDetails({ return (

Cashout details {id}

- {error && ( - saveError(undefined)} /> - )}
+ + ); } diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index cf3f41deb..c82c1b28d 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -15,10 +15,9 @@ */ import { AmountJson } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { notifyInfo } from "../hooks/notification.js"; import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js"; import { WalletWithdrawForm } from "./WalletWithdrawForm.js"; import { useSettings } from "../hooks/settings.js"; @@ -31,7 +30,8 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode { const { i18n } = useTranslationContext(); const [settings, updateSettings] = useSettings(); - const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined); + const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(); + // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined); return (
@@ -41,34 +41,32 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
{/* */} - {/* */}
) - {/* return ( -
-
-
- - -
-
-
- ); */} } diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index 1107360bd..5e0624cbf 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -19,17 +19,21 @@ import { Amounts, HttpStatusCode, Logger, - parsePaytoUri + TranslatedString, + buildPayto, + parsePaytoUri, + stringifyPaytoUri } from "@gnu-taler/taler-util"; import { RequestError, + notify, + notifyError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { h, VNode, Fragment } from "preact"; +import { h, VNode, Fragment, Ref } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { useAccessAPI } from "../hooks/access.js"; -import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage, undefinedIfEmpty, @@ -41,10 +45,12 @@ const logger = new Logger("PaytoWireTransferForm"); export function PaytoWireTransferForm({ focus, onSuccess, + onCancel, limit, }: { focus?: boolean; onSuccess: () => void; + onCancel: (() => void) | undefined; limit: AmountJson; }): VNode { const [isRawPayto, setIsRawPayto] = useState(false); @@ -105,7 +111,51 @@ export function PaytoWireTransferForm({ ? i18n.str`IBAN should have just uppercased letters and numbers` : validateIBAN(parsed.iban, i18n), }); - // if (!isRawPayto) { + + async function doSend() { + let paytoUri: string | undefined; + + if (rawPaytoInput) { + paytoUri = rawPaytoInput + } else { + if (!iban || !subject) return; + const ibanPayto = buildPayto("iban", iban, undefined); + ibanPayto.params.message = encodeURIComponent(subject); + paytoUri = stringifyPaytoUri(ibanPayto); + } + + try { + await createTransaction({ + paytoUri, + amount: `${limit.currency}:${amount}`, + }); + onSuccess(); + setAmount(undefined); + setIban(undefined); + setSubject(undefined); + rawPaytoInputSetter(undefined) + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.BadRequest + ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) +} + } + + } + return (

Transfer details

@@ -118,7 +168,7 @@ export function PaytoWireTransferForm({ }} /> - + form @@ -133,7 +183,7 @@ export function PaytoWireTransferForm({ }} /> - + payto:// @@ -143,23 +193,31 @@ export function PaytoWireTransferForm({
-
+ { + e.preventDefault() + }} + >
{!isRawPayto ? -
- +
+
{ @@ -171,21 +229,18 @@ export function PaytoWireTransferForm({ isDirty={iban !== undefined} />
-

the receiver of the money

-
- -
+

the receiver of the money

-
- +
+
-
-

some text to identify the transfer

- -
- -
+

some text to identify the transfer

-
- -
- -
+
+ + { + setAmount(d) + }} + /> + +

amount to transfer

-
-
:
- +
{ rawPaytoInputSetter(e.currentTarget.value); }} @@ -244,9 +302,23 @@ export function PaytoWireTransferForm({ }
-
- - + :
+ } +
@@ -262,8 +334,6 @@ export function PaytoWireTransferForm({ // onSubmit={(e) => { // e.preventDefault(); // }} - // autoCapitalize="none" - // autoCorrect="off" // > //   @@ -318,39 +388,7 @@ export function PaytoWireTransferForm({ // if (!(iban && subject && amount)) { // return; // } - // const ibanPayto = buildPayto("iban", iban, undefined); - // ibanPayto.params.message = encodeURIComponent(subject); - // const paytoUri = stringifyPaytoUri(ibanPayto); - - // try { - // await createTransaction({ - // paytoUri, - // amount: `${limit.currency}:${amount}`, - // }); - // onSuccess(); - // setAmount(undefined); - // setIban(undefined); - // setSubject(undefined); - // } catch (error) { - // if (error instanceof RequestError) { - // notifyError( - // buildRequestErrorMessage(i18n, error.cause, { - // onClientError: (status) => - // status === HttpStatusCode.BadRequest - // ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.` - // : undefined, - // }), - // ); - // } else { - // notifyError({ - // title: i18n.str`Operation failed, please report`, - // description: - // error instanceof Error - // ? error.message - // : JSON.stringify(error), - // }); - // } - // } + // }} // /> // // ); } +export function Amount( + { + currency, + name, + value, + error, + onChange, + }: { + error?: string; + currency: string; + name: string; + value: string | undefined; + onChange?: (s: string) => void; + }, + ref: Ref, +): VNode { + return ( +
+
+
+ {currency} +
+ { + if (onChange) { + onChange(e.currentTarget.value); + } + }} + /> +
+ +
+ ); +} diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx index c27984569..7c1b3bdc5 100644 --- a/packages/demobank-ui/src/pages/QrCodeSection.tsx +++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx @@ -17,17 +17,19 @@ import { HttpStatusCode, stringifyWithdrawUri, + TranslatedString, WithdrawUriResult, } from "@gnu-taler/taler-util"; import { + notify, + notifyError, RequestError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; import { useEffect } from "preact/hooks"; import { QR } from "../components/QR.js"; import { useAccessAnonAPI } from "../hooks/access.js"; -import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage } from "../utils.js"; export function QrCodeSection({ @@ -49,47 +51,87 @@ export function QrCodeSection({ const talerWithdrawUri = stringifyWithdrawUri(withdrawUri); const { abortWithdrawal } = useAccessAnonAPI(); + + async function doAbort() { + try { + await abortWithdrawal(withdrawUri.withdrawalOperationId); + onAborted(); + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Conflict + ? i18n.str`The reserve operation has been confirmed previously and can't be aborted` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) + } + } + } + return ( -
-

{i18n.str`Charge your GNU Taler wallet`}

-
-
- - Continue with GNU Taler - -

{i18n.str`Or scan this QR code with your mobile to receive the coin in another device:`}

- - { - e.preventDefault(); - try { - await abortWithdrawal(withdrawUri.withdrawalOperationId); - onAborted(); - } catch (error) { - if (error instanceof RequestError) { - notifyError( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.Conflict - ? i18n.str`The reserve operation has been confirmed previously and can't be aborted` - : undefined, - }), - ); - } else { - notifyError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); - } - } - }} - >{i18n.str`Cancel`} + +
+
+

+ If you have a Taler wallet installed in this device +

+ +
+

+ You will see the details of the operation in your wallet including the fees (if applies). + If you still one you can install it from here. +

+
+
+
+ +
+
+
+ +
+
+

+ Or if you have the wallet in another device +

+
+ Scan the QR below to start the withdrawal +
+
+ +
+
+
+
-
-
+
+ +
); } diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index e52a5b11b..b912b9060 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -13,19 +13,21 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ -import { HttpStatusCode, Logger } from "@gnu-taler/taler-util"; +import { HttpStatusCode, Logger, TranslatedString } from "@gnu-taler/taler-util"; import { RequestError, + notify, + notifyError, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBackendContext } from "../context/backend.js"; import { useTestingAPI } from "../hooks/access.js"; -import { notifyError } from "../hooks/notification.js"; import { bankUiSettings } from "../settings.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; +import { getRandomPassword, getRandomUsername } from "./rnd.js"; const logger = new Logger("RegistrationPage"); @@ -61,147 +63,214 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode { username: !username ? i18n.str`Missing username` : !USERNAME_REGEX.test(username) - ? i18n.str`Use letters and numbers only, and start with a lowercase letter` - : undefined, + ? i18n.str`Use letters and numbers only, and start with a lowercase letter` + : undefined, password: !password ? i18n.str`Missing password` : undefined, repeatPassword: !repeatPassword ? i18n.str`Missing password` : repeatPassword !== password - ? i18n.str`Passwords don't match` - : undefined, + ? i18n.str`Passwords don't match` + : undefined, }); + async function doRegistrationStep() { + if (!username || !password) return; + try { + await register({ username, password }); + setUsername(undefined); + backend.logIn({ username, password }); + onComplete(); + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Conflict + ? i18n.str`That username is already taken` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) +} + } + setPassword(undefined); + setRepeatPassword(undefined); +} + + async function delay(ms: number):Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve(undefined); + }, ms) + }) + } + async function doRandomRegistration(tries: number = 3) { + const user = getRandomUsername(); + const pass = getRandomPassword(); + try { + setUsername(undefined); + setPassword(undefined); + setRepeatPassword(undefined); + await register({ username: user, password: pass }); + backend.logIn({ username: user, password: pass }); + onComplete(); + } catch (error) { + if (error instanceof RequestError) { + if (tries > 0) { + await delay(200) + await doRandomRegistration(tries-1) + } else { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Conflict + ? i18n.str`Could not create a random user` + : undefined, + }), + ); + } + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) +} + } + } + return ( -

{i18n.str`Welcome to ${bankUiSettings.bankName}!`}

-
-
- + +
+
+

{i18n.str`Sign up!`}

+
+ +
+ { e.preventDefault(); }} autoCapitalize="none" autoCorrect="off" > -
-

{i18n.str`Please register!`}

-

- -

- { - setUsername(e.currentTarget.value); - }} - /> - -

- -

- { - setPassword(e.currentTarget.value); - }} - /> - -

- -

- { - setRepeatPassword(e.currentTarget.value); - }} - /> - -
-
+ +
+
+ +
+
+ { + setPassword(e.currentTarget.value); + }} + /> + +
+
+ +
+
+ +
+
+ { + setRepeatPassword(e.currentTarget.value); + }} + /> + +
+
+ +
+ - {/* FIXME: should use a different color */} -
+ + +

+ +

-
+
+ ); } diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index da624f61b..6574ec934 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -19,19 +19,21 @@ import { Amounts, HttpStatusCode, Logger, + TranslatedString, parseWithdrawUri, } from "@gnu-taler/taler-util"; import { RequestError, + notify, + notifyError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { Ref, VNode, h } from "preact"; +import { VNode, h } from "preact"; +import { forwardRef } from "preact/compat"; import { useEffect, useRef, useState } from "preact/hooks"; import { useAccessAPI } from "../hooks/access.js"; -import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; -import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; -import { forwardRef } from "preact/compat"; +import { Amount } from "./PaytoWireTransferForm.js"; const logger = new Logger("WalletWithdrawForm"); const RefAmount = forwardRef(Amount); @@ -40,10 +42,12 @@ export function WalletWithdrawForm({ focus, limit, onSuccess, + onCancel, }: { limit: AmountJson; focus?: boolean; onSuccess: (operationId: string) => void; + onCancel: () => void; }): VNode { const { i18n } = useTranslationContext(); const { createWithdrawal } = useAccessAPI(); @@ -71,136 +75,195 @@ export function WalletWithdrawForm({ : undefined, }); - return ( + async function doStart() { + if (!parsedAmount) return; + try { + const result = await createWithdrawal({ + amount: Amounts.stringify(parsedAmount), + }); + const uri = parseWithdrawUri(result.data.taler_withdraw_uri); + if (!uri) { + return notifyError( + i18n.str`Server responded with an invalid withdraw URI`, + i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`); + } else { + onSuccess(uri.withdrawalOperationId); + } + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Forbidden + ? i18n.str`The operation was rejected due to insufficient funds` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) +} + } + + } + + return (
+
+

Prepare your wallet

+

+ Upon starting you will receive the money in your digital wallet, if you don't have one please install one from here. +

+

+ After using your wallet you will be redirected here to confirm or cancel the operation. +

+
{ - e.preventDefault(); - }} + class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2" autoCapitalize="none" autoCorrect="off" + onSubmit={e => { + e.preventDefault() + }} > -

- -   - { - setAmountStr(v); - }} - error={errors?.amount} - ref={ref} - /> -

-

-

- { - e.preventDefault(); - if (!parsedAmount) return; - try { - const result = await createWithdrawal({ - amount: Amounts.stringify(parsedAmount), - }); - const uri = parseWithdrawUri(result.data.taler_withdraw_uri); - if (!uri) { - return notifyError({ - title: i18n.str`Server responded with an invalid withdraw URI`, - description: i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`, - }); - } else { - onSuccess(uri.withdrawalOperationId); - } - } catch (error) { - if (error instanceof RequestError) { - notifyError( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.Forbidden - ? i18n.str`The operation was rejected due to insufficient funds` - : undefined, - }), - ); - } else { - notifyError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); - } - } - }} - /> -
-

- - ); -} +
+
+
+ + { + setAmountStr(v); + }} + error={errors?.amount} + ref={ref} + /> +
+
+ + + + + + +
+ +
+
+
+ +
- -
+ + +
); } + +// export function Amount( +// { +// currency, +// value, +// error, +// onChange, +// }: { +// error?: string; +// currency: string; +// value: string | undefined; +// onChange?: (s: string) => void; +// }, +// ref: Ref, +// ): VNode { +// return ( +//
+//
+// +// { +// if (onChange) { +// onChange(e.currentTarget.value); +// } +// }} +// /> +//
+// +//
+// ); +// } diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 2fa8e51b5..28f00169d 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -15,26 +15,40 @@ */ import { + AmountJson, + Amounts, HttpStatusCode, Logger, + PaytoUri, + PaytoUriGeneric, + PaytoUriIBAN, + PaytoUriTalerBank, + TranslatedString, WithdrawUriResult, } from "@gnu-taler/taler-util"; import { RequestError, + notify, + notifyError, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useMemo, useState } from "preact/hooks"; import { useAccessAnonAPI } from "../hooks/access.js"; -import { notifyError } from "../hooks/notification.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; +import { Amount } from "./PaytoWireTransferForm.js"; const logger = new Logger("WithdrawalConfirmationQuestion"); interface Props { onAborted: () => void; withdrawUri: WithdrawUriResult; + details: { + account: PaytoUri, + reserve: string, + amount: AmountJson, + } } /** * Additional authentication required to complete the operation. @@ -42,6 +56,7 @@ interface Props { */ export function WithdrawalConfirmationQuestion({ onAborted, + details, withdrawUri, }: Props): VNode { const { i18n } = useTranslationContext(); @@ -60,135 +75,257 @@ export function WithdrawalConfirmationQuestion({ answer: !captchaAnswer ? i18n.str`Answer the question before continue` : Number.isNaN(answer) - ? i18n.str`The answer should be a number` - : answer !== captchaNumbers.a + captchaNumbers.b - ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.` - : undefined, + ? i18n.str`The answer should be a number` + : answer !== captchaNumbers.a + captchaNumbers.b + ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.` + : undefined, }); + + async function doTransfer() { + try { + await confirmWithdrawal( + withdrawUri.withdrawalOperationId, + ); + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Conflict + ? i18n.str`The withdrawal has been aborted previously and can't be confirmed` + : status === HttpStatusCode.UnprocessableEntity + ? i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) + } + } + } + + async function doCancel() { + try { + await abortWithdrawal(withdrawUri.withdrawalOperationId); + onAborted(); + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Conflict + ? i18n.str`The reserve operation has been confirmed previously and can't be aborted` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) + } + } + } + return ( -

{i18n.str`Confirm Withdrawal`}

-
-
-
{ - e.preventDefault(); - }} - autoCapitalize="none" - autoCorrect="off" - > -
-

{i18n.str`Authorize withdrawal by solving challenge`}

-

- -   - { - setCaptchaAnswer(e.currentTarget.value); - }} - /> - -

-

- -   - -

+ })()} +
+
Withdrawal identification
+
{details.reserve}
+
+
+
Amount
+
{Amounts.stringifyValue(details.amount)}
+
+ +
+
+ +
+
+ + + + + + + +
+
+
+ +
+
+

Answer the next question to authorize the wire transfer

+
+ { + e.preventDefault() + }} + > +
+ +
+
+ { + // if (onChange) { + // onChange(e.currentTarget.value); + // } + // }} + /> +
+ +
+
+
+ + +
+ +
- -
-

- - A this point, a real bank would ask for an additional - authentication proof (PIN/TAN, one time password, ..), instead - of a simple calculation. - -

- +
+ ); } diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx index 80fdac3c8..3b983c2d4 100644 --- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx @@ -15,15 +15,16 @@ */ import { + Amounts, HttpStatusCode, Logger, WithdrawUriResult, + parsePaytoUri, } from "@gnu-taler/taler-util"; -import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { Loading } from "../components/Loading.js"; import { useWithdrawalDetails } from "../hooks/access.js"; -import { notifyInfo } from "../hooks/notification.js"; import { useSettings } from "../hooks/settings.js"; import { handleNotOkResult } from "./HomePage.js"; import { QrCodeSection } from "./QrCodeSection.js"; @@ -127,6 +128,19 @@ export function WithdrawalQRCode({ } + if (!data.selected_reserve_pub) { + return
+ the exchange is selcted but no reserve pub +
+ } + + const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account) + + if (!account) { + return
+ the exchange is selcted but no account +
+ } if (!data.selection_done) { return ( @@ -144,6 +158,11 @@ export function WithdrawalQRCode({ return ( { notifyInfo(i18n.str`Operation canceled`); clearCurrentWithdrawal() diff --git a/packages/demobank-ui/src/pages/rnd.ts b/packages/demobank-ui/src/pages/rnd.ts new file mode 100644 index 000000000..8c9bae875 --- /dev/null +++ b/packages/demobank-ui/src/pages/rnd.ts @@ -0,0 +1,2890 @@ +import { createEddsaKeyPair, encodeCrock, getRandomBytes } from "@gnu-taler/taler-util" + + +const noun = [ + "people", + "history", + "way", + "art", + "world", + "information", + "map", + "two", + "family", + "government", + "health", + "system", + "computer", + "meat", + "year", + "thanks", + "music", + "person", + "reading", + "method", + "data", + "food", + "understanding", + "theory", + "law", + "bird", + "literature", + "problem", + "software", + "control", + "knowledge", + "power", + "ability", + "economics", + "love", + "internet", + "television", + "science", + "library", + "nature", + "fact", + "product", + "idea", + "temperature", + "investment", + "area", + "society", + "activity", + "story", + "industry", + "media", + "thing", + "oven", + "community", + "definition", + "safety", + "quality", + "development", + "language", + "management", + "player", + "variety", + "video", + "week", + "security", + "country", + "exam", + "movie", + "organization", + "equipment", + "physics", + "analysis", + "policy", + "series", + "thought", + "basis", + "boyfriend", + "direction", + "strategy", + "technology", + "army", + "camera", + "freedom", + "paper", + "environment", + "child", + "instance", + "month", + "truth", + "marketing", + "university", + "writing", + "article", + "department", + "difference", + "goal", + "news", + "audience", + "fishing", + "growth", + "income", + "marriage", + "user", + "combination", + "failure", + "meaning", + "medicine", + "philosophy", + "teacher", + "communication", + "night", + "chemistry", + "disease", + "disk", + "energy", + "nation", + "road", + "role", + "soup", + "advertising", + "location", + "success", + "addition", + "apartment", + "education", + "math", + "moment", + "painting", + "politics", + "attention", + "decision", + "event", + "property", + "shopping", + "student", + "wood", + "competition", + "distribution", + "entertainment", + "office", + "population", + "president", + "unit", + "category", + "cigarette", + "context", + "introduction", + "opportunity", + "performance", + "driver", + "flight", + "length", + "magazine", + "newspaper", + "relationship", + "teaching", + "cell", + "dealer", + "finding", + "lake", + "member", + "message", + "phone", + "scene", + "appearance", + "association", + "concept", + "customer", + "death", + "discussion", + "housing", + "inflation", + "insurance", + "mood", + "woman", + "advice", + "blood", + "effort", + "expression", + "importance", + "opinion", + "payment", + "reality", + "responsibility", + "situation", + "skill", + "statement", + "wealth", + "application", + "city", + "county", + "depth", + "estate", + "foundation", + "grandmother", + "heart", + "perspective", + "photo", + "recipe", + "studio", + "topic", + "collection", + "depression", + "imagination", + "passion", + "percentage", + "resource", + "setting", + "ad", + "agency", + "college", + "connection", + "criticism", + "debt", + "description", + "memory", + "patience", + "secretary", + "solution", + "administration", + "aspect", + "attitude", + "director", + "personality", + "psychology", + "recommendation", + "response", + "selection", + "storage", + "version", + "alcohol", + "argument", + "complaint", + "contract", + "emphasis", + "highway", + "loss", + "membership", + "possession", + "preparation", + "steak", + "union", + "agreement", + "cancer", + "currency", + "employment", + "engineering", + "entry", + "interaction", + "mixture", + "preference", + "region", + "republic", + "tradition", + "virus", + "actor", + "classroom", + "delivery", + "device", + "difficulty", + "drama", + "election", + "engine", + "football", + "guidance", + "hotel", + "owner", + "priority", + "protection", + "suggestion", + "tension", + "variation", + "anxiety", + "atmosphere", + "awareness", + "bath", + "bread", + "candidate", + "climate", + "comparison", + "confusion", + "construction", + "elevator", + "emotion", + "employee", + "employer", + "guest", + "height", + "leadership", + "mall", + "manager", + "operation", + "recording", + "sample", + "transportation", + "charity", + "cousin", + "disaster", + "editor", + "efficiency", + "excitement", + "extent", + "feedback", + "guitar", + "homework", + "leader", + "mom", + "outcome", + "permission", + "presentation", + "promotion", + "reflection", + "refrigerator", + "resolution", + "revenue", + "session", + "singer", + "tennis", + "basket", + "bonus", + "cabinet", + "childhood", + "church", + "clothes", + "coffee", + "dinner", + "drawing", + "hair", + "hearing", + "initiative", + "judgment", + "lab", + "measurement", + "mode", + "mud", + "orange", + "poetry", + "police", + "possibility", + "procedure", + "queen", + "ratio", + "relation", + "restaurant", + "satisfaction", + "sector", + "signature", + "significance", + "song", + "tooth", + "town", + "vehicle", + "volume", + "wife", + "accident", + "airport", + "appointment", + "arrival", + "assumption", + "baseball", + "chapter", + "committee", + "conversation", + "database", + "enthusiasm", + "error", + "explanation", + "farmer", + "gate", + "girl", + "hall", + "historian", + "hospital", + "injury", + "instruction", + "maintenance", + "manufacturer", + "meal", + "perception", + "pie", + "poem", + "presence", + "proposal", + "reception", + "replacement", + "revolution", + "river", + "son", + "speech", + "tea", + "village", + "warning", + "winner", + "worker", + "writer", + "assistance", + "breath", + "buyer", + "chest", + "chocolate", + "conclusion", + "contribution", + "cookie", + "courage", + "dad", + "desk", + "drawer", + "establishment", + "examination", + "garbage", + "grocery", + "honey", + "impression", + "improvement", + "independence", + "insect", + "inspection", + "inspector", + "king", + "ladder", + "menu", + "penalty", + "piano", + "potato", + "profession", + "professor", + "quantity", + "reaction", + "requirement", + "salad", + "sister", + "supermarket", + "tongue", + "weakness", + "wedding", + "affair", + "ambition", + "analyst", + "apple", + "assignment", + "assistant", + "bathroom", + "bedroom", + "beer", + "birthday", + "celebration", + "championship", + "cheek", + "client", + "consequence", + "departure", + "diamond", + "dirt", + "ear", + "fortune", + "friendship", + "funeral", + "gene", + "girlfriend", + "hat", + "indication", + "intention", + "lady", + "midnight", + "negotiation", + "obligation", + "passenger", + "pizza", + "platform", + "poet", + "pollution", + "recognition", + "reputation", + "shirt", + "sir", + "speaker", + "stranger", + "surgery", + "sympathy", + "tale", + "throat", + "trainer", + "uncle", + "youth", + "time", + "work", + "film", + "water", + "money", + "example", + "while", + "business", + "study", + "game", + "life", + "form", + "air", + "day", + "place", + "number", + "part", + "field", + "fish", + "back", + "process", + "heat", + "hand", + "experience", + "job", + "book", + "end", + "point", + "type", + "home", + "economy", + "value", + "body", + "market", + "guide", + "interest", + "state", + "radio", + "course", + "company", + "price", + "size", + "card", + "list", + "mind", + "trade", + "line", + "care", + "group", + "risk", + "word", + "fat", + "force", + "key", + "light", + "training", + "name", + "school", + "top", + "amount", + "level", + "order", + "practice", + "research", + "sense", + "service", + "piece", + "web", + "boss", + "sport", + "fun", + "house", + "page", + "term", + "test", + "answer", + "sound", + "focus", + "matter", + "kind", + "soil", + "board", + "oil", + "picture", + "access", + "garden", + "range", + "rate", + "reason", + "future", + "site", + "demand", + "exercise", + "image", + "case", + "cause", + "coast", + "action", + "age", + "bad", + "boat", + "record", + "result", + "section", + "building", + "mouse", + "cash", + "class", + "nothing", + "period", + "plan", + "store", + "tax", + "side", + "subject", + "space", + "rule", + "stock", + "weather", + "chance", + "figure", + "man", + "model", + "source", + "beginning", + "earth", + "program", + "chicken", + "design", + "feature", + "head", + "material", + "purpose", + "question", + "rock", + "salt", + "act", + "birth", + "car", + "dog", + "object", + "scale", + "sun", + "note", + "profit", + "rent", + "speed", + "style", + "war", + "bank", + "craft", + "half", + "inside", + "outside", + "standard", + "bus", + "exchange", + "eye", + "fire", + "position", + "pressure", + "stress", + "advantage", + "benefit", + "box", + "frame", + "issue", + "step", + "cycle", + "face", + "item", + "metal", + "paint", + "review", + "room", + "screen", + "structure", + "view", + "account", + "ball", + "discipline", + "medium", + "share", + "balance", + "bit", + "black", + "bottom", + "choice", + "gift", + "impact", + "machine", + "shape", + "tool", + "wind", + "address", + "average", + "career", + "culture", + "morning", + "pot", + "sign", + "table", + "task", + "condition", + "contact", + "credit", + "egg", + "hope", + "ice", + "network", + "north", + "square", + "attempt", + "date", + "effect", + "link", + "post", + "star", + "voice", + "capital", + "challenge", + "friend", + "self", + "shot", + "brush", + "couple", + "debate", + "exit", + "front", + "function", + "lack", + "living", + "plant", + "plastic", + "spot", + "summer", + "taste", + "theme", + "track", + "wing", + "brain", + "button", + "click", + "desire", + "foot", + "gas", + "influence", + "notice", + "rain", + "wall", + "base", + "damage", + "distance", + "feeling", + "pair", + "savings", + "staff", + "sugar", + "target", + "text", + "animal", + "author", + "budget", + "discount", + "file", + "ground", + "lesson", + "minute", + "officer", + "phase", + "reference", + "register", + "sky", + "stage", + "stick", + "title", + "trouble", + "bowl", + "bridge", + "campaign", + "character", + "club", + "edge", + "evidence", + "fan", + "letter", + "lock", + "maximum", + "novel", + "option", + "pack", + "park", + "plenty", + "quarter", + "skin", + "sort", + "weight", + "baby", + "background", + "carry", + "dish", + "factor", + "fruit", + "glass", + "joint", + "master", + "muscle", + "red", + "strength", + "traffic", + "trip", + "vegetable", + "appeal", + "chart", + "gear", + "ideal", + "kitchen", + "land", + "log", + "mother", + "net", + "party", + "principle", + "relative", + "sale", + "season", + "signal", + "spirit", + "street", + "tree", + "wave", + "belt", + "bench", + "commission", + "copy", + "drop", + "minimum", + "path", + "progress", + "project", + "sea", + "south", + "status", + "stuff", + "ticket", + "tour", + "angle", + "blue", + "breakfast", + "confidence", + "daughter", + "degree", + "doctor", + "dot", + "dream", + "duty", + "essay", + "father", + "fee", + "finance", + "hour", + "juice", + "limit", + "luck", + "milk", + "mouth", + "peace", + "pipe", + "seat", + "stable", + "storm", + "substance", + "team", + "trick", + "afternoon", + "bat", + "beach", + "blank", + "catch", + "chain", + "consideration", + "cream", + "crew", + "detail", + "gold", + "interview", + "kid", + "mark", + "match", + "mission", + "pain", + "pleasure", + "score", + "screw", + "sex", + "shop", + "shower", + "suit", + "tone", + "window", + "agent", + "band", + "block", + "bone", + "calendar", + "cap", + "coat", + "contest", + "corner", + "court", + "cup", + "district", + "door", + "east", + "finger", + "garage", + "guarantee", + "hole", + "hook", + "implement", + "layer", + "lecture", + "lie", + "manner", + "meeting", + "nose", + "parking", + "partner", + "profile", + "respect", + "rice", + "routine", + "schedule", + "swimming", + "telephone", + "tip", + "winter", + "airline", + "bag", + "battle", + "bed", + "bill", + "bother", + "cake", + "code", + "curve", + "designer", + "dimension", + "dress", + "ease", + "emergency", + "evening", + "extension", + "farm", + "fight", + "gap", + "grade", + "holiday", + "horror", + "horse", + "host", + "husband", + "loan", + "mistake", + "mountain", + "nail", + "noise", + "occasion", + "package", + "patient", + "pause", + "phrase", + "proof", + "race", + "relief", + "sand", + "sentence", + "shoulder", + "smoke", + "stomach", + "string", + "tourist", + "towel", + "vacation", + "west", + "wheel", + "wine", + "arm", + "aside", + "associate", + "bet", + "blow", + "border", + "branch", + "breast", + "brother", + "buddy", + "bunch", + "chip", + "coach", + "cross", + "document", + "draft", + "dust", + "expert", + "floor", + "god", + "golf", + "habit", + "iron", + "judge", + "knife", + "landscape", + "league", + "mail", + "mess", + "native", + "opening", + "parent", + "pattern", + "pin", + "pool", + "pound", + "request", + "salary", + "shame", + "shelter", + "shoe", + "silver", + "tackle", + "tank", + "trust", + "assist", + "bake", + "bar", + "bell", + "bike", + "blame", + "boy", + "brick", + "chair", + "closet", + "clue", + "collar", + "comment", + "conference", + "devil", + "diet", + "fear", + "fuel", + "glove", + "jacket", + "lunch", + "monitor", + "mortgage", + "nurse", + "pace", + "panic", + "peak", + "plane", + "reward", + "row", + "sandwich", + "shock", + "spite", + "spray", + "surprise", + "till", + "transition", + "weekend", + "welcome", + "yard", + "alarm", + "bend", + "bicycle", + "bite", + "blind", + "bottle", + "cable", + "candle", + "clerk", + "cloud", + "concert", + "counter", + "flower", + "grandfather", + "harm", + "knee", + "lawyer", + "leather", + "load", + "mirror", + "neck", + "pension", + "plate", + "purple", + "ruin", + "ship", + "skirt", + "slice", + "snow", + "specialist", + "stroke", + "switch", + "trash", + "tune", + "zone", + "anger", + "award", + "bid", + "bitter", + "boot", + "bug", + "camp", + "candy", + "carpet", + "cat", + "champion", + "channel", + "clock", + "comfort", + "cow", + "crack", + "engineer", + "entrance", + "fault", + "grass", + "guy", + "hell", + "highlight", + "incident", + "island", + "joke", + "jury", + "leg", + "lip", + "mate", + "motor", + "nerve", + "passage", + "pen", + "pride", + "priest", + "prize", + "promise", + "resident", + "resort", + "ring", + "roof", + "rope", + "sail", + "scheme", + "script", + "sock", + "station", + "toe", + "tower", + "truck", + "witness", + "a", + "you", + "it", + "can", + "will", + "if", + "one", + "many", + "most", + "other", + "use", + "make", + "good", + "look", + "help", + "go", + "great", + "being", + "few", + "might", + "still", + "public", + "read", + "keep", + "start", + "give", + "human", + "local", + "general", + "she", + "specific", + "long", + "play", + "feel", + "high", + "tonight", + "put", + "common", + "set", + "change", + "simple", + "past", + "big", + "possible", + "particular", + "today", + "major", + "personal", + "current", + "national", + "cut", + "natural", + "physical", + "show", + "try", + "check", + "second", + "call", + "move", + "pay", + "let", + "increase", + "single", + "individual", + "turn", + "ask", + "buy", + "guard", + "hold", + "main", + "offer", + "potential", + "professional", + "international", + "travel", + "cook", + "alternative", + "following", + "special", + "working", + "whole", + "dance", + "excuse", + "cold", + "commercial", + "low", + "purchase", + "deal", + "primary", + "worth", + "fall", + "necessary", + "positive", + "produce", + "search", + "present", + "spend", + "talk", + "creative", + "tell", + "cost", + "drive", + "green", + "support", + "glad", + "remove", + "return", + "run", + "complex", + "due", + "effective", + "middle", + "regular", + "reserve", + "independent", + "leave", + "original", + "reach", + "rest", + "serve", + "watch", + "beautiful", + "charge", + "active", + "break", + "negative", + "safe", + "stay", + "visit", + "visual", + "affect", + "cover", + "report", + "rise", + "walk", + "white", + "beyond", + "junior", + "pick", + "unique", + "anything", + "classic", + "final", + "lift", + "mix", + "private", + "stop", + "teach", + "western", + "concern", + "familiar", + "fly", + "official", + "broad", + "comfortable", + "gain", + "maybe", + "rich", + "save", + "stand", + "young", + "fail", + "heavy", + "hello", + "lead", + "listen", + "valuable", + "worry", + "handle", + "leading", + "meet", + "release", + "sell", + "finish", + "normal", + "press", + "ride", + "secret", + "spread", + "spring", + "tough", + "wait", + "brown", + "deep", + "display", + "flow", + "hit", + "objective", + "shoot", + "touch", + "cancel", + "chemical", + "cry", + "dump", + "extreme", + "push", + "conflict", + "eat", + "fill", + "formal", + "jump", + "kick", + "opposite", + "pass", + "pitch", + "remote", + "total", + "treat", + "vast", + "abuse", + "beat", + "burn", + "deposit", + "print", + "raise", + "sleep", + "somewhere", + "advance", + "anywhere", + "consist", + "dark", + "double", + "draw", + "equal", + "fix", + "hire", + "internal", + "join", + "kill", + "sensitive", + "tap", + "win", + "attack", + "claim", + "constant", + "drag", + "drink", + "guess", + "minor", + "pull", + "raw", + "soft", + "solid", + "wear", + "weird", + "wonder", + "annual", + "count", + "dead", + "doubt", + "feed", + "forever", + "impress", + "nobody", + "repeat", + "round", + "sing", + "slide", + "strip", + "whereas", + "wish", + "combine", + "command", + "dig", + "divide", + "equivalent", + "hang", + "hunt", + "initial", + "march", + "mention", + "smell", + "spiritual", + "survey", + "tie", + "adult", + "brief", + "crazy", + "escape", + "gather", + "hate", + "prior", + "repair", + "rough", + "sad", + "scratch", + "sick", + "strike", + "employ", + "external", + "hurt", + "illegal", + "laugh", + "lay", + "mobile", + "nasty", + "ordinary", + "respond", + "royal", + "senior", + "split", + "strain", + "struggle", + "swim", + "train", + "upper", + "wash", + "yellow", + "convert", + "crash", + "dependent", + "fold", + "funny", + "grab", + "hide", + "miss", + "permit", + "quote", + "recover", + "resolve", + "roll", + "sink", + "slip", + "spare", + "suspect", + "sweet", + "swing", + "twist", + "upstairs", + "usual", + "abroad", + "brave", + "calm", + "concentrate", + "estimate", + "grand", + "male", + "mine", + "prompt", + "quiet", + "refuse", + "regret", + "reveal", + "rush", + "shake", + "shift", + "shine", + "steal", + "suck", + "surround", + "anybody", + "bear", + "brilliant", + "dare", + "dear", + "delay", + "drunk", + "female", + "hurry", + "inevitable", + "invite", + "kiss", + "neat", + "pop", + "punch", + "quit", + "reply", + "representative", + "resist", + "rip", + "rub", + "silly", + "smile", + "spell", + "stretch", + "stupid", + "tear", + "temporary", + "tomorrow", + "wake", + "wrap", + "yesterday" +] + +const adj = [ + "abandoned", + "able", + "absolute", + "adorable", + "adventurous", + "academic", + "acceptable", + "acclaimed", + "accomplished", + "accurate", + "aching", + "acidic", + "acrobatic", + "active", + "actual", + "adept", + "admirable", + "admired", + "adolescent", + "adorable", + "adored", + "advanced", + "afraid", + "affectionate", + "aged", + "aggravating", + "aggressive", + "agile", + "agitated", + "agonizing", + "agreeable", + "ajar", + "alarmed", + "alarming", + "alert", + "alienated", + "alive", + "all", + "altruistic", + "amazing", + "ambitious", + "ample", + "amused", + "amusing", + "anchored", + "ancient", + "angelic", + "angry", + "anguished", + "animated", + "annual", + "another", + "antique", + "anxious", + "any", + "apprehensive", + "appropriate", + "apt", + "arctic", + "arid", + "aromatic", + "artistic", + "ashamed", + "assured", + "astonishing", + "athletic", + "attached", + "attentive", + "attractive", + "austere", + "authentic", + "authorized", + "automatic", + "avaricious", + "average", + "aware", + "awesome", + "awful", + "awkward", + "babyish", + "bad", + "back", + "baggy", + "bare", + "barren", + "basic", + "beautiful", + "belated", + "beloved", + "beneficial", + "better", + "best", + "bewitched", + "big", + "big-hearted", + "biodegradable", + "bite-sized", + "bitter", + "black", + "black-and-white", + "bland", + "blank", + "blaring", + "bleak", + "blind", + "blissful", + "blond", + "blue", + "blushing", + "bogus", + "boiling", + "bold", + "bony", + "boring", + "bossy", + "both", + "bouncy", + "bountiful", + "bowed", + "brave", + "breakable", + "brief", + "bright", + "brilliant", + "brisk", + "broken", + "bronze", + "brown", + "bruised", + "bubbly", + "bulky", + "bumpy", + "buoyant", + "burdensome", + "burly", + "bustling", + "busy", + "buttery", + "buzzing", + "calculating", + "calm", + "candid", + "canine", + "capital", + "carefree", + "careful", + "careless", + "caring", + "cautious", + "cavernous", + "celebrated", + "charming", + "cheap", + "cheerful", + "cheery", + "chief", + "chilly", + "chubby", + "circular", + "classic", + "clean", + "clear", + "clear-cut", + "clever", + "close", + "closed", + "cloudy", + "clueless", + "clumsy", + "cluttered", + "coarse", + "cold", + "colorful", + "colorless", + "colossal", + "comfortable", + "common", + "compassionate", + "competent", + "complete", + "complex", + "complicated", + "composed", + "concerned", + "concrete", + "confused", + "conscious", + "considerate", + "constant", + "content", + "conventional", + "cooked", + "cool", + "cooperative", + "coordinated", + "corny", + "corrupt", + "costly", + "courageous", + "courteous", + "crafty", + "crazy", + "creamy", + "creative", + "creepy", + "criminal", + "crisp", + "critical", + "crooked", + "crowded", + "cruel", + "crushing", + "cuddly", + "cultivated", + "cultured", + "cumbersome", + "curly", + "curvy", + "cute", + "cylindrical", + "damaged", + "damp", + "dangerous", + "dapper", + "daring", + "darling", + "dark", + "dazzling", + "dead", + "deadly", + "deafening", + "dear", + "dearest", + "decent", + "decimal", + "decisive", + "deep", + "defenseless", + "defensive", + "defiant", + "deficient", + "definite", + "definitive", + "delayed", + "delectable", + "delicious", + "delightful", + "delirious", + "demanding", + "dense", + "dental", + "dependable", + "dependent", + "descriptive", + "deserted", + "detailed", + "determined", + "devoted", + "different", + "difficult", + "digital", + "diligent", + "dim", + "dimpled", + "dimwitted", + "direct", + "disastrous", + "discrete", + "disfigured", + "disgusting", + "disloyal", + "dismal", + "distant", + "downright", + "dreary", + "dirty", + "disguised", + "dishonest", + "dismal", + "distant", + "distinct", + "distorted", + "dizzy", + "dopey", + "doting", + "double", + "downright", + "drab", + "drafty", + "dramatic", + "dreary", + "droopy", + "dry", + "dual", + "dull", + "dutiful", + "each", + "eager", + "earnest", + "early", + "easy", + "easy-going", + "ecstatic", + "edible", + "educated", + "elaborate", + "elastic", + "elated", + "elderly", + "electric", + "elegant", + "elementary", + "elliptical", + "embarrassed", + "embellished", + "eminent", + "emotional", + "empty", + "enchanted", + "enchanting", + "energetic", + "enlightened", + "enormous", + "enraged", + "entire", + "envious", + "equal", + "equatorial", + "essential", + "esteemed", + "ethical", + "euphoric", + "even", + "evergreen", + "everlasting", + "every", + "evil", + "exalted", + "excellent", + "exemplary", + "exhausted", + "excitable", + "excited", + "exciting", + "exotic", + "expensive", + "experienced", + "expert", + "extraneous", + "extroverted", + "extra-large", + "extra-small", + "fabulous", + "failing", + "faint", + "fair", + "faithful", + "fake", + "false", + "familiar", + "famous", + "fancy", + "fantastic", + "far", + "faraway", + "far-flung", + "far-off", + "fast", + "fat", + "fatal", + "fatherly", + "favorable", + "favorite", + "fearful", + "fearless", + "feisty", + "feline", + "female", + "feminine", + "few", + "fickle", + "filthy", + "fine", + "finished", + "firm", + "first", + "firsthand", + "fitting", + "fixed", + "flaky", + "flamboyant", + "flashy", + "flat", + "flawed", + "flawless", + "flickering", + "flimsy", + "flippant", + "flowery", + "fluffy", + "fluid", + "flustered", + "focused", + "fond", + "foolhardy", + "foolish", + "forceful", + "forked", + "formal", + "forsaken", + "forthright", + "fortunate", + "fragrant", + "frail", + "frank", + "frayed", + "free", + "French", + "fresh", + "frequent", + "friendly", + "frightened", + "frightening", + "frigid", + "frilly", + "frizzy", + "frivolous", + "front", + "frosty", + "frozen", + "frugal", + "fruitful", + "full", + "fumbling", + "functional", + "funny", + "fussy", + "fuzzy", + "gargantuan", + "gaseous", + "general", + "generous", + "gentle", + "genuine", + "giant", + "giddy", + "gigantic", + "gifted", + "giving", + "glamorous", + "glaring", + "glass", + "gleaming", + "gleeful", + "glistening", + "glittering", + "gloomy", + "glorious", + "glossy", + "glum", + "golden", + "good", + "good-natured", + "gorgeous", + "graceful", + "gracious", + "grand", + "grandiose", + "granular", + "grateful", + "grave", + "gray", + "great", + "greedy", + "green", + "gregarious", + "grim", + "grimy", + "gripping", + "grizzled", + "gross", + "grotesque", + "grouchy", + "grounded", + "growing", + "growling", + "grown", + "grubby", + "gruesome", + "grumpy", + "guilty", + "gullible", + "gummy", + "hairy", + "half", + "handmade", + "handsome", + "handy", + "happy", + "happy-go-lucky", + "hard", + "hard-to-find", + "harmful", + "harmless", + "harmonious", + "harsh", + "hasty", + "hateful", + "haunting", + "healthy", + "heartfelt", + "hearty", + "heavenly", + "heavy", + "hefty", + "helpful", + "helpless", + "hidden", + "hideous", + "high", + "high-level", + "hilarious", + "hoarse", + "hollow", + "homely", + "honest", + "honorable", + "honored", + "hopeful", + "horrible", + "hospitable", + "hot", + "huge", + "humble", + "humiliating", + "humming", + "humongous", + "hungry", + "hurtful", + "husky", + "icky", + "icy", + "ideal", + "idealistic", + "identical", + "idle", + "idiotic", + "idolized", + "ignorant", + "ill", + "illegal", + "ill-fated", + "ill-informed", + "illiterate", + "illustrious", + "imaginary", + "imaginative", + "immaculate", + "immaterial", + "immediate", + "immense", + "impassioned", + "impeccable", + "impartial", + "imperfect", + "imperturbable", + "impish", + "impolite", + "important", + "impossible", + "impractical", + "impressionable", + "impressive", + "improbable", + "impure", + "inborn", + "incomparable", + "incompatible", + "incomplete", + "inconsequential", + "incredible", + "indelible", + "inexperienced", + "indolent", + "infamous", + "infantile", + "infatuated", + "inferior", + "infinite", + "informal", + "innocent", + "insecure", + "insidious", + "insignificant", + "insistent", + "instructive", + "insubstantial", + "intelligent", + "intent", + "intentional", + "interesting", + "internal", + "international", + "intrepid", + "ironclad", + "irresponsible", + "irritating", + "itchy", + "jaded", + "jagged", + "jam-packed", + "jaunty", + "jealous", + "jittery", + "joint", + "jolly", + "jovial", + "joyful", + "joyous", + "jubilant", + "judicious", + "juicy", + "jumbo", + "junior", + "jumpy", + "juvenile", + "kaleidoscopic", + "keen", + "key", + "kind", + "kindhearted", + "kindly", + "klutzy", + "knobby", + "knotty", + "knowledgeable", + "knowing", + "known", + "kooky", + "kosher", + "lame", + "lanky", + "large", + "last", + "lasting", + "late", + "lavish", + "lawful", + "lazy", + "leading", + "lean", + "leafy", + "left", + "legal", + "legitimate", + "light", + "lighthearted", + "likable", + "likely", + "limited", + "limp", + "limping", + "linear", + "lined", + "liquid", + "little", + "live", + "lively", + "livid", + "loathsome", + "lone", + "lonely", + "long", + "long-term", + "loose", + "lopsided", + "lost", + "loud", + "lovable", + "lovely", + "loving", + "low", + "loyal", + "lucky", + "lumbering", + "luminous", + "lumpy", + "lustrous", + "luxurious", + "mad", + "made-up", + "magnificent", + "majestic", + "major", + "male", + "mammoth", + "married", + "marvelous", + "masculine", + "massive", + "mature", + "meager", + "mealy", + "mean", + "measly", + "meaty", + "medical", + "mediocre", + "medium", + "meek", + "mellow", + "melodic", + "memorable", + "menacing", + "merry", + "messy", + "metallic", + "mild", + "milky", + "mindless", + "miniature", + "minor", + "minty", + "miserable", + "miserly", + "misguided", + "misty", + "mixed", + "modern", + "modest", + "moist", + "monstrous", + "monthly", + "monumental", + "moral", + "mortified", + "motherly", + "motionless", + "mountainous", + "muddy", + "muffled", + "multicolored", + "mundane", + "murky", + "mushy", + "musty", + "muted", + "mysterious", + "naive", + "narrow", + "nasty", + "natural", + "naughty", + "nautical", + "near", + "neat", + "necessary", + "needy", + "negative", + "neglected", + "negligible", + "neighboring", + "nervous", + "new", + "next", + "nice", + "nifty", + "nimble", + "nippy", + "nocturnal", + "noisy", + "nonstop", + "normal", + "notable", + "noted", + "noteworthy", + "novel", + "noxious", + "numb", + "nutritious", + "nutty", + "obedient", + "obese", + "oblong", + "oily", + "oblong", + "obvious", + "occasional", + "odd", + "oddball", + "offbeat", + "offensive", + "official", + "old", + "old-fashioned", + "only", + "open", + "optimal", + "optimistic", + "opulent", + "orange", + "orderly", + "organic", + "ornate", + "ornery", + "ordinary", + "original", + "other", + "our", + "outlying", + "outgoing", + "outlandish", + "outrageous", + "outstanding", + "oval", + "overcooked", + "overdue", + "overjoyed", + "overlooked", + "palatable", + "pale", + "paltry", + "parallel", + "parched", + "partial", + "passionate", + "past", + "pastel", + "peaceful", + "peppery", + "perfect", + "perfumed", + "periodic", + "perky", + "personal", + "pertinent", + "pesky", + "pessimistic", + "petty", + "phony", + "physical", + "piercing", + "pink", + "pitiful", + "plain", + "plaintive", + "plastic", + "playful", + "pleasant", + "pleased", + "pleasing", + "plump", + "plush", + "polished", + "polite", + "political", + "pointed", + "pointless", + "poised", + "poor", + "popular", + "portly", + "posh", + "positive", + "possible", + "potable", + "powerful", + "powerless", + "practical", + "precious", + "present", + "prestigious", + "pretty", + "precious", + "previous", + "pricey", + "prickly", + "primary", + "prime", + "pristine", + "private", + "prize", + "probable", + "productive", + "profitable", + "profuse", + "proper", + "proud", + "prudent", + "punctual", + "pungent", + "puny", + "pure", + "purple", + "pushy", + "putrid", + "puzzled", + "puzzling", + "quaint", + "qualified", + "quarrelsome", + "quarterly", + "queasy", + "querulous", + "questionable", + "quick", + "quick-witted", + "quiet", + "quintessential", + "quirky", + "quixotic", + "quizzical", + "radiant", + "ragged", + "rapid", + "rare", + "rash", + "raw", + "recent", + "reckless", + "rectangular", + "ready", + "real", + "realistic", + "reasonable", + "red", + "reflecting", + "regal", + "regular", + "reliable", + "relieved", + "remarkable", + "remorseful", + "remote", + "repentant", + "required", + "respectful", + "responsible", + "repulsive", + "revolving", + "rewarding", + "rich", + "rigid", + "right", + "ringed", + "ripe", + "roasted", + "robust", + "rosy", + "rotating", + "rotten", + "rough", + "round", + "rowdy", + "royal", + "rubbery", + "rundown", + "ruddy", + "rude", + "runny", + "rural", + "rusty", + "sad", + "safe", + "salty", + "same", + "sandy", + "sane", + "sarcastic", + "sardonic", + "satisfied", + "scaly", + "scarce", + "scared", + "scary", + "scented", + "scholarly", + "scientific", + "scornful", + "scratchy", + "scrawny", + "second", + "secondary", + "second-hand", + "secret", + "self-assured", + "self-reliant", + "selfish", + "sentimental", + "separate", + "serene", + "serious", + "serpentine", + "several", + "severe", + "shabby", + "shadowy", + "shady", + "shallow", + "shameful", + "shameless", + "sharp", + "shimmering", + "shiny", + "shocked", + "shocking", + "shoddy", + "short", + "short-term", + "showy", + "shrill", + "shy", + "sick", + "silent", + "silky", + "silly", + "silver", + "similar", + "simple", + "simplistic", + "sinful", + "single", + "sizzling", + "skeletal", + "skinny", + "sleepy", + "slight", + "slim", + "slimy", + "slippery", + "slow", + "slushy", + "small", + "smart", + "smoggy", + "smooth", + "smug", + "snappy", + "snarling", + "sneaky", + "sniveling", + "snoopy", + "sociable", + "soft", + "soggy", + "solid", + "somber", + "some", + "spherical", + "sophisticated", + "sore", + "sorrowful", + "soulful", + "soupy", + "sour", + "Spanish", + "sparkling", + "sparse", + "specific", + "spectacular", + "speedy", + "spicy", + "spiffy", + "spirited", + "spiteful", + "splendid", + "spotless", + "spotted", + "spry", + "square", + "squeaky", + "squiggly", + "stable", + "staid", + "stained", + "stale", + "standard", + "starchy", + "stark", + "starry", + "steep", + "sticky", + "stiff", + "stimulating", + "stingy", + "stormy", + "straight", + "strange", + "steel", + "strict", + "strident", + "striking", + "striped", + "strong", + "studious", + "stunning", + "stupendous", + "stupid", + "sturdy", + "stylish", + "subdued", + "submissive", + "substantial", + "subtle", + "suburban", + "sudden", + "sugary", + "sunny", + "super", + "superb", + "superficial", + "superior", + "supportive", + "sure-footed", + "surprised", + "suspicious", + "svelte", + "sweaty", + "sweet", + "sweltering", + "swift", + "sympathetic", + "tall", + "talkative", + "tame", + "tan", + "tangible", + "tart", + "tasty", + "tattered", + "taut", + "tedious", + "teeming", + "tempting", + "tender", + "tense", + "tepid", + "terrible", + "terrific", + "testy", + "thankful", + "that", + "these", + "thick", + "thin", + "third", + "thirsty", + "this", + "thorough", + "thorny", + "those", + "thoughtful", + "threadbare", + "thrifty", + "thunderous", + "tidy", + "tight", + "timely", + "tinted", + "tiny", + "tired", + "torn", + "total", + "tough", + "traumatic", + "treasured", + "tremendous", + "tragic", + "trained", + "tremendous", + "triangular", + "tricky", + "trifling", + "trim", + "trivial", + "troubled", + "true", + "trusting", + "trustworthy", + "trusty", + "truthful", + "tubby", + "turbulent", + "twin", + "ugly", + "ultimate", + "unacceptable", + "unaware", + "uncomfortable", + "uncommon", + "unconscious", + "understated", + "unequaled", + "uneven", + "unfinished", + "unfit", + "unfolded", + "unfortunate", + "unhappy", + "unhealthy", + "uniform", + "unimportant", + "unique", + "united", + "unkempt", + "unknown", + "unlawful", + "unlined", + "unlucky", + "unnatural", + "unpleasant", + "unrealistic", + "unripe", + "unruly", + "unselfish", + "unsightly", + "unsteady", + "unsung", + "untidy", + "untimely", + "untried", + "untrue", + "unused", + "unusual", + "unwelcome", + "unwieldy", + "unwilling", + "unwitting", + "unwritten", + "upbeat", + "upright", + "upset", + "urban", + "usable", + "used", + "useful", + "useless", + "utilized", + "utter", + "vacant", + "vague", + "vain", + "valid", + "valuable", + "vapid", + "variable", + "vast", + "velvety", + "venerated", + "vengeful", + "verifiable", + "vibrant", + "vicious", + "victorious", + "vigilant", + "vigorous", + "villainous", + "violet", + "violent", + "virtual", + "virtuous", + "visible", + "vital", + "vivacious", + "vivid", + "voluminous", + "wan", + "warlike", + "warm", + "warmhearted", + "warped", + "wary", + "wasteful", + "watchful", + "waterlogged", + "watery", + "wavy", + "wealthy", + "weak", + "weary", + "webbed", + "wee", + "weekly", + "weepy", + "weighty", + "weird", + "welcome", + "well-documented", + "well-groomed", + "well-informed", + "well-lit", + "well-made", + "well-off", + "well-to-do", + "well-worn", + "wet", + "which", + "whimsical", + "whirlwind", + "whispered", + "white", + "whole", + "whopping", + "wicked", + "wide", + "wide-eyed", + "wiggly", + "wild", + "willing", + "wilted", + "winding", + "windy", + "winged", + "wiry", + "wise", + "witty", + "wobbly", + "woeful", + "wonderful", + "wooden", + "woozy", + "wordy", + "worldly", + "worn", + "worried", + "worrisome", + "worse", + "worst", + "worthless", + "worthwhile", + "worthy", + "wrathful", + "wretched", + "writhing", + "wrong", + "wry", + "yawning", + "yearly", + "yellow", + "yellowish", + "young", + "youthful", + "yummy", + "zany", + "zealous", + "zesty", + "zigzag", +] + +export function getRandomUsername(): string { + const n = Math.random() * noun.length + const a = Math.random() * adj.length + return `tmp-${a}-${n}` +} + +export function getRandomPassword(): string { + return encodeCrock(getRandomBytes(16)) +} \ No newline at end of file diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts index 4ce0f140e..c13b9a3cb 100644 --- a/packages/demobank-ui/src/utils.ts +++ b/packages/demobank-ui/src/utils.ts @@ -16,11 +16,12 @@ import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; import { + ErrorNotification, ErrorType, HttpError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { ErrorMessage } from "./hooks/notification.js"; + /** * Validate (the number part of) an amount. If needed, @@ -120,11 +121,12 @@ export function buildRequestErrorMessage( onClientError?: (status: HttpStatusCode) => TranslatedString | undefined; onServerError?: (status: HttpStatusCode) => TranslatedString | undefined; } = {}, -): ErrorMessage { - let result: ErrorMessage; +): ErrorNotification { + let result: ErrorNotification; switch (cause.type) { case ErrorType.TIMEOUT: { result = { + type: "error", title: i18n.str`Request timeout`, }; break; @@ -133,8 +135,9 @@ export function buildRequestErrorMessage( const title = specialCases.onClientError && specialCases.onClientError(cause.status); result = { + type: "error", title: title ? title : i18n.str`The server didn't accept the request`, - description: cause?.payload?.error?.description, + description: cause?.payload?.error?.description as TranslatedString, debug: JSON.stringify(cause), }; break; @@ -143,24 +146,27 @@ export function buildRequestErrorMessage( const title = specialCases.onServerError && specialCases.onServerError(cause.status); result = { + type: "error", title: title ? title : i18n.str`The server had problems processing the request`, - description: cause?.payload?.error?.description, + description: cause?.payload?.error?.description as TranslatedString, debug: JSON.stringify(cause), }; break; } case ErrorType.UNREADABLE: { result = { + type: "error", title: i18n.str`Unexpected error`, - description: `Response from ${cause?.info?.url} is unreadable, status: ${cause?.status}`, + description: `Response from ${cause?.info?.url} is unreadable, status: ${cause?.status}` as TranslatedString, debug: JSON.stringify(cause), }; break; } case ErrorType.UNEXPECTED: { result = { + type: "error", title: i18n.str`Unexpected error`, debug: JSON.stringify(cause), }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 295074f61..392f34981 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -877,6 +877,9 @@ importers: '@gnu-taler/taler-util': specifier: workspace:* version: link:../taler-util + '@heroicons/react': + specifier: ^2.0.17 + version: 2.0.17(react@18.2.0) '@linaria/babel-preset': specifier: 4.4.5 version: 4.4.5 @@ -907,6 +910,9 @@ importers: chokidar: specifier: ^3.5.3 version: 3.5.3 + date-fns: + specifier: 2.29.3 + version: 2.29.3 esbuild: specifier: ^0.17.7 version: 0.17.7 @@ -4872,7 +4878,6 @@ packages: react: '>= 16' dependencies: react: 18.2.0 - dev: false /@humanwhocodes/config-array@0.11.11: resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} @@ -8680,7 +8685,6 @@ packages: /date-fns@2.29.3: resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} engines: {node: '>=0.11'} - dev: false /date-time@3.1.0: resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} -- cgit v1.2.3 From 7d4c5a71aaa6c4e781af124fe821d8be4ed101ed Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 20 Sep 2023 16:10:32 -0300 Subject: more ui --- packages/demobank-ui/src/hooks/backend.ts | 16 +- packages/demobank-ui/src/hooks/settings.ts | 10 ++ packages/demobank-ui/src/pages/BankFrame.tsx | 50 +++++-- packages/demobank-ui/src/pages/HomePage.tsx | 9 +- .../src/pages/WithdrawalConfirmationQuestion.tsx | 165 +++++++++++---------- .../demobank-ui/src/pages/WithdrawalQRCode.tsx | 117 ++++++++++----- 6 files changed, 221 insertions(+), 146 deletions(-) (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts index 4b60d1b6c..c05ab33e9 100644 --- a/packages/demobank-ui/src/hooks/backend.ts +++ b/packages/demobank-ui/src/hooks/backend.ts @@ -85,18 +85,26 @@ export function getInitialBackendBaseURL(): string { typeof localStorage !== "undefined" ? localStorage.getItem("bank-base-url") : undefined; + let result: string; if (!overrideUrl) { //normal path if (!bankUiSettings.backendBaseURL) { console.error( "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", ); - return canonicalizeBaseUrl(window.origin); + result = window.origin } - return canonicalizeBaseUrl(bankUiSettings.backendBaseURL); + result = bankUiSettings.backendBaseURL; + } else { + // testing/development path + result = overrideUrl + } + try { + return canonicalizeBaseUrl(result) + } catch (e) { + //fall back + return canonicalizeBaseUrl(window.origin) } - // testing/development path - return canonicalizeBaseUrl(overrideUrl); } export const defaultState: BackendState = { diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts index 46b31bf2a..43e803726 100644 --- a/packages/demobank-ui/src/hooks/settings.ts +++ b/packages/demobank-ui/src/hooks/settings.ts @@ -15,8 +15,12 @@ */ import { + AmountString, Codec, buildCodecForObject, + codecForAmountString, + codecForBoolean, + codecForNumber, codecForString, codecOptional, } from "@gnu-taler/taler-util"; @@ -24,15 +28,21 @@ import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; interface Settings { currentWithdrawalOperationId: string | undefined; + showWithdrawalSuccess: boolean; + maxWithdrawalAmount: number; } export const codecForSettings = (): Codec => buildCodecForObject() .property("currentWithdrawalOperationId", codecOptional(codecForString())) + .property("showWithdrawalSuccess", (codecForBoolean())) + .property("maxWithdrawalAmount", codecForNumber()) .build("Settings"); const defaultSettings: Settings = { currentWithdrawalOperationId: undefined, + showWithdrawalSuccess: true, + maxWithdrawalAmount: 25 }; const DEMOBANK_SETTINGS_KEY = buildStorageKey( diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index d234845a0..e682085ae 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -183,8 +183,28 @@ export function BankFrame({ {/* */} +
  • +
    + + + Show withdrawal confirmation + + + +
    +
  • + + +
  • Sites @@ -343,14 +363,14 @@ function StatusBanner(): VNode { switch (n.message.type) { case "error": return
    -
    -
    - -
    -
    -

    {n.message.title}

    +
    +
    + +
    +
    +

    {n.message.title}

    -

    +

    +
    + {n.message.description && +
    + {n.message.description} +
    + }
    - {n.message.description && -
    - {n.message.description} -
    - } -
    case "info": return
    diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx index e00daf278..e82e46eb2 100644 --- a/packages/demobank-ui/src/pages/HomePage.tsx +++ b/packages/demobank-ui/src/pages/HomePage.tsx @@ -95,8 +95,9 @@ export function WithdrawalOperationPage({ }): VNode { //FIXME: libeufin sandbox should return show to create the integration api endpoint //or return withdrawal uri from response + const baseUrl = getInitialBackendBaseURL() const uri = stringifyWithdrawUri({ - bankIntegrationApiBaseUrl: `${getInitialBackendBaseURL()}/integration-api`, + bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`, withdrawalOperationId: operationId, }); const parsedUri = parseWithdrawUri(uri); @@ -155,7 +156,7 @@ export function handleNotOkResult( } case ErrorType.SERVER: { notify({ - type: "error", + type: "error", title: i18n.str`Server returned with error`, description: result.payload.error.description as TranslatedString, debug: JSON.stringify(result.payload), @@ -164,7 +165,7 @@ export function handleNotOkResult( } case ErrorType.UNREADABLE: { notify({ - type:"error", + type: "error", title: i18n.str`Unexpected error.`, description: i18n.str`Response from ${result.info?.url} is unreadable, http status: ${result.status}`, debug: JSON.stringify(result), @@ -173,7 +174,7 @@ export function handleNotOkResult( } case ErrorType.UNEXPECTED: { notify({ - type:"error", + type: "error", title: i18n.str`Unexpected error.`, description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`, debug: JSON.stringify(result), diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 28f00169d..80e7a78ac 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -20,24 +20,23 @@ import { HttpStatusCode, Logger, PaytoUri, - PaytoUriGeneric, PaytoUriIBAN, PaytoUriTalerBank, TranslatedString, - WithdrawUriResult, + WithdrawUriResult } from "@gnu-taler/taler-util"; import { RequestError, notify, notifyError, + notifyInfo, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useMemo, useState } from "preact/hooks"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { useAccessAnonAPI } from "../hooks/access.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; -import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; -import { Amount } from "./PaytoWireTransferForm.js"; const logger = new Logger("WithdrawalConfirmationQuestion"); @@ -71,6 +70,7 @@ export function WithdrawalConfirmationQuestion({ const { confirmWithdrawal, abortWithdrawal } = useAccessAnonAPI(); const [captchaAnswer, setCaptchaAnswer] = useState(); const answer = parseInt(captchaAnswer ?? "", 10); + const [busy, setBusy] = useState>() const errors = undefinedIfEmpty({ answer: !captchaAnswer ? i18n.str`Answer the question before continue` @@ -79,13 +79,15 @@ export function WithdrawalConfirmationQuestion({ : answer !== captchaNumbers.a + captchaNumbers.b ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.` : undefined, - }); + }) ?? busy; async function doTransfer() { try { + setBusy({}) await confirmWithdrawal( withdrawUri.withdrawalOperationId, ); + notifyInfo(i18n.str`Wire transfer completed!`) } catch (error) { if (error instanceof RequestError) { notify( @@ -107,10 +109,12 @@ export function WithdrawalConfirmationQuestion({ ) } } + setBusy(undefined) } async function doCancel() { try { + setBusy({}) await abortWithdrawal(withdrawUri.withdrawalOperationId); onAborted(); } catch (error) { @@ -132,6 +136,7 @@ export function WithdrawalConfirmationQuestion({ ) } } + setBusy(undefined) } return ( @@ -142,68 +147,6 @@ export function WithdrawalConfirmationQuestion({ Confirm the withdrawal operation
    -
    -
    -
    -

    Wire transfer details

    -
    -
    -
    - {((): VNode => { - switch (details.account.targetType) { - case "iban": { - const p = details.account as PaytoUriIBAN - const name = p.params["receiver-name"] - return -
    -
    Exchange account
    -
    {p.iban}
    -
    - {name && -
    -
    Exchange name
    -
    {p.params["receiver-name"]}
    -
    - } -
    - } - case "x-taler-bank": { - const p = details.account as PaytoUriTalerBank - const name = p.params["receiver-name"] - return -
    -
    Exchange account
    -
    {p.account}
    -
    - {name && -
    -
    Exchange name
    -
    {p.params["receiver-name"]}
    -
    - } -
    - } - default: - return
    -
    Exchange account
    -
    {details.account.targetPath}
    -
    - - } - })()} -
    -
    Withdrawal identification
    -
    {details.reserve}
    -
    -
    -
    Amount
    -
    {Amounts.stringifyValue(details.amount)}
    -
    -
    -
    -
    - -
    - +
    @@ -323,6 +262,68 @@ export function WithdrawalConfirmationQuestion({
    +
    +
    +
    +

    Wire transfer details

    +
    +
    +
    + {((): VNode => { + switch (details.account.targetType) { + case "iban": { + const p = details.account as PaytoUriIBAN + const name = p.params["receiver-name"] + return +
    +
    Exchange account
    +
    {p.iban}
    +
    + {name && +
    +
    Exchange name
    +
    {p.params["receiver-name"]}
    +
    + } +
    + } + case "x-taler-bank": { + const p = details.account as PaytoUriTalerBank + const name = p.params["receiver-name"] + return +
    +
    Exchange account
    +
    {p.account}
    +
    + {name && +
    +
    Exchange name
    +
    {p.params["receiver-name"]}
    +
    + } +
    + } + default: + return
    +
    Exchange account
    +
    {details.account.targetPath}
    +
    + + } + })()} +
    +
    Withdrawal identification
    +
    {details.reserve}
    +
    +
    +
    Amount
    +
    {Amounts.stringifyValue(details.amount)}
    +
    +
    +
    +
    + +
    diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx index 3b983c2d4..b48e3b1dc 100644 --- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx @@ -62,9 +62,10 @@ export function WithdrawalQRCode({ result.type === ErrorType.CLIENT && result.status === HttpStatusCode.NotFound ) { + clearCurrentWithdrawal() return
    operation not found
    ; } - onLoadNotOk(); + // onLoadNotOk(); return handleNotOkResult(i18n)(result); } const { data } = result; @@ -85,12 +86,12 @@ export function WithdrawalQRCode({

    { e.preventDefault(); clearCurrentWithdrawal() onContinue() - }}> + }}> {i18n.str`Continue`} @@ -99,49 +100,69 @@ export function WithdrawalQRCode({ } if (data.confirmation_done) { - return
    -

    {i18n.str`Operation completed`}

    - -
    -

    - - The wire transfer to the GNU Taler Exchange bank's account is completed, now the - exchange will send the requested amount into your GNU Taler wallet. - -

    -

    - - You can close this page now or continue to the account page. - -

    -
    + if (!settings.showWithdrawalSuccess) { + clearCurrentWithdrawal() + onContinue() + } + return
    +
    +
    + +
    +
    + +
    +

    + + The wire transfer to the Taler exchange bank's account is completed, now the + exchange will send the requested amount into your GNU Taler wallet. + +

    +
    +
    +

    + + You can close this page now or continue to the account page. + +

    +
    +
    +
    +
    +
    + + + Do not show this again + + +
    -
    -
    - } - if (!data.selected_reserve_pub) { - return
    - the exchange is selcted but no reserve pub +
    +
    + +
  • - } - const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account) - if (!account) { - return
    - the exchange is selcted but no account -
    } - if (!data.selection_done) { return ( ); } + if (!data.selected_reserve_pub) { + return
    + the exchange is selcted but no reserve pub +
    + } + + const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account) + + if (!account) { + return
    + the exchange is selcted but no account +
    + } + return ( ); } \ No newline at end of file -- cgit v1.2.3 From b3c747151bb3f50d28bf6205cafa4b7dd6ae2b1c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 21 Sep 2023 09:41:07 -0300 Subject: more ui --- packages/demobank-ui/src/components/CopyButton.tsx | 4 +- packages/demobank-ui/src/components/Routing.tsx | 66 +++++- .../src/components/Transactions/state.ts | 2 +- packages/demobank-ui/src/pages/BankFrame.tsx | 227 +++++++++++---------- .../src/pages/PaytoWireTransferForm.tsx | 111 +--------- .../demobank-ui/src/pages/WalletWithdrawForm.tsx | 70 +------ .../src/pages/WithdrawalConfirmationQuestion.tsx | 8 +- 7 files changed, 193 insertions(+), 295 deletions(-) (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/src/components/CopyButton.tsx b/packages/demobank-ui/src/components/CopyButton.tsx index 97ccbf2bf..b36de770e 100644 --- a/packages/demobank-ui/src/components/CopyButton.tsx +++ b/packages/demobank-ui/src/components/CopyButton.tsx @@ -45,14 +45,14 @@ export function CopyButton({ getContent }: { getContent: () => string }): VNode if (!copied) { return ( - ); } return (
    -
    diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx index 2c532e863..890058a9b 100644 --- a/packages/demobank-ui/src/components/Routing.tsx +++ b/packages/demobank-ui/src/components/Routing.tsx @@ -24,12 +24,50 @@ import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js"; import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js"; import { RegistrationPage } from "../pages/RegistrationPage.js"; import { Test } from "../pages/Test.js"; +import { useBackendContext } from "../context/backend.js"; +import { LoginForm } from "../pages/LoginForm.js"; +import { AdminPage } from "../pages/AdminPage.js"; export function Routing(): VNode { const history = createHashHistory(); + const backend = useBackendContext(); + + if (backend.state.status === "loggedOut") { + return { + route("/business"); + }} + > + + ( + { + route("/register"); + }} + /> + )} + /> + ( + { + route("/account"); + }} + /> + )} + /> + + + + } + const isAdmin = backend.state.isUserAdministrator return ( { route("/business"); }} @@ -69,16 +107,24 @@ export function Routing(): VNode { /> ( - { - route(`/operation/${wopid}`); - }} - onRegister={() => { - route("/register"); - }} - /> - )} + component={() => { + if (isAdmin) { + return { + route("/register"); + }} + />; + } else { + return { + route(`/operation/${wopid}`); + }} + onRegister={() => { + route("/register"); + }} + /> + } + }} /> */ -import { Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; import { useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { StateUpdater, useEffect, useState } from "preact/hooks"; @@ -25,6 +25,7 @@ import { bankUiSettings } from "../settings.js"; import { useSettings } from "../hooks/settings.js"; import { CopyButton, CopyIcon } from "../components/CopyButton.js"; import logo from "../assets/logo-2021.svg"; +import { useAccountDetails } from "../hooks/access.js"; const IS_PUBLIC_ACCOUNT_ENABLED = false; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; @@ -63,7 +64,9 @@ function MaybeBusinessButton({ export function BankFrame({ children, goToBusinessAccount, + account, }: { + account: string | undefined, children: ComponentChildren; goToBusinessAccount?: () => void; }): VNode { @@ -122,123 +125,118 @@ export function BankFrame({
    {open && - -
    {i18n.str`Date`}{i18n.str`Amount`}{i18n.str`Counterpart`}{i18n.str`Subject`}{i18n.str`Date`}{i18n.str`Amount`}{i18n.str`Counterpart`}{i18n.str`Subject`}
    +
    {item.when.t_ms === "never" ? "" : format(item.when.t_ms, "dd/MM/yyyy HH:mm:ss")}
    @@ -66,7 +66,7 @@ export function ReadyView({ transactions }: State.Ready): VNode { <{i18n.str`invalid value`}> )}
    {item.counterpart}{item.subject}{item.subject}
    - - - - - - - - - - {customers.map((item, idx) => { - const balance = !item.balance - ? undefined - : Amounts.parse(item.balance.amount); - const balanceIsDebit = - item.balance && - item.balance.credit_debit_indicator == "debit"; - return ( - - - - - - - ); - })} - -
    {i18n.str`Username`}{i18n.str`Name`}{i18n.str`Balance`}{i18n.str`Actions`}
    - { - e.preventDefault(); - setShowDetails(item.username); - }} - > - {item.username} - - {item.name} - {!balance ? ( - i18n.str`unknown` - ) : ( - - {balanceIsDebit ? - : null} - {`${Amounts.stringifyValue( - balance, - )}`} -   - {`${balance.currency}`} - - )} - - { - e.preventDefault(); - setUpdatePassword(item.username); - }} - > - change password - -   - { - e.preventDefault(); - setShowCashouts(item.username); - }} - > - cashouts - -   - { - e.preventDefault(); - setRemoveAccount(item.username); - }} - > - remove - -
    -
    - - )} - - - ); -} - -function AdminAccount({ onRegister }: { onRegister: () => void }): VNode { - const { i18n } = useTranslationContext(); - const r = useBackendContext(); - const account = r.state.status === "loggedIn" ? r.state.username : "admin"; - const result = useAccountDetails(account); - - if (!result.ok) { - return handleNotOkResult(i18n, onRegister)(result); - } - const { data } = result; - const balance = Amounts.parseOrThrow(data.balance.amount); - const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold); - const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; - const limit = balanceIsDebit - ? Amounts.sub(debitThreshold, balance).amount - : Amounts.add(balance, debitThreshold).amount; - if (!balance) return ; - return ( - -
    -
    -

    {i18n.str`Bank account balance`}

    - {!balance ? ( -
    - Waiting server response... -
    - ) : ( -
    - {balanceIsDebit ? - : null} - {`${Amounts.stringifyValue(balance)}`} -   - {`${balance.currency}`} -
    - )} -
    -
    - { - notifyInfo(i18n.str`Wire transfer created!`); - }} - onCancel={undefined} - /> -
    - ); -} - -const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/; -const EMAIL_REGEX = - /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; -const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/; - -function initializeFromTemplate( - account: SandboxBackend.Circuit.CircuitAccountData | undefined, -): WithIntermediate { - const emptyAccount = { - cashout_address: undefined, - iban: undefined, - name: undefined, - username: undefined, - contact_data: undefined, - }; - const emptyContact = { - email: undefined, - phone: undefined, - }; - - const initial: PartialButDefined = - structuredClone(account) ?? emptyAccount; - if (typeof initial.contact_data === "undefined") { - initial.contact_data = emptyContact; - } - initial.contact_data.email; - return initial as any; -} - -export function UpdateAccountPassword({ - account, - onClear, - onUpdateSuccess, - onLoadNotOk, -}: { - onLoadNotOk: ( - error: HttpResponsePaginated, - ) => VNode; - onClear: () => void; - onUpdateSuccess: () => void; - account: string; -}): VNode { - const { i18n } = useTranslationContext(); - const result = useBusinessAccountDetails(account); - const { changePassword } = useAdminAccountAPI(); - const [password, setPassword] = useState(); - const [repeat, setRepeat] = useState(); - - if (!result.ok) { - if (result.loading || result.type === ErrorType.TIMEOUT) { - return onLoadNotOk(result); - } - if (result.status === HttpStatusCode.NotFound) { - return
    account not found
    ; - } - return onLoadNotOk(result); - } - - const errors = undefinedIfEmpty({ - password: !password ? i18n.str`required` : undefined, - repeat: !repeat - ? i18n.str`required` - : password !== repeat - ? i18n.str`password doesn't match` - : undefined, - }); - - return ( -
    -
    -

    - Update password for {account} -

    -
    - -
    - -
    - - { - setPassword(e.currentTarget.value); - }} - /> - -
    -
    - - { - setRepeat(e.currentTarget.value); - }} - /> - -
    - -

    -

    -
    - { - e.preventDefault(); - onClear(); - }} - /> -
    -
    - { - e.preventDefault(); - if (!!errors || !password) return; - try { - const r = await changePassword(account, { - new_password: password, - }); - onUpdateSuccess(); - } catch (error) { - if (error instanceof RequestError) { - notify(buildRequestErrorMessage(i18n, error.cause)); - } else { - notifyError(i18n.str`Operation failed, please report`, (error instanceof Error - ? error.message - : JSON.stringify(error)) as TranslatedString) - } - } - }} - /> -
    -
    -

    -
    -
    - ); -} - -function CreateNewAccount({ - onClose, - onCreateSuccess, -}: { - onClose: () => void; - onCreateSuccess: (password: string) => void; -}): VNode { - const { i18n } = useTranslationContext(); - const { createAccount } = useAdminAccountAPI(); - const [submitAccount, setSubmitAccount] = useState< - SandboxBackend.Circuit.CircuitAccountData | undefined - >(); - return ( -
    -
    -

    - New account -

    -
    - -
    - { - setSubmitAccount(a); - }} - /> - -

    -

    -
    - { - e.preventDefault(); - onClose(); - }} - /> -
    -
    - { - e.preventDefault(); - - if (!submitAccount) return; - try { - const account: SandboxBackend.Circuit.CircuitAccountRequest = - { - cashout_address: submitAccount.cashout_address, - contact_data: submitAccount.contact_data, - internal_iban: submitAccount.iban, - name: submitAccount.name, - username: submitAccount.username, - password: randomPassword(), - }; - - await createAccount(account); - onCreateSuccess(account.password); - } catch (error) { - if (error instanceof RequestError) { - notify( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.Forbidden - ? i18n.str`The rights to perform the operation are not sufficient` - : status === HttpStatusCode.BadRequest - ? i18n.str`Input data was invalid` - : status === HttpStatusCode.Conflict - ? i18n.str`At least one registration detail was not available` - : undefined, - }), - ); - } else { - notifyError( - i18n.str`Operation failed, please report`, - (error instanceof Error - ? error.message - : JSON.stringify(error)) as TranslatedString - ) - } - } - }} - /> -
    -
    -

    -
    -
    - ); -} - -export function ShowAccountDetails({ - account, - onClear, - onUpdateSuccess, - onLoadNotOk, - onChangePassword, -}: { - onLoadNotOk: ( - error: HttpResponsePaginated, - ) => VNode; - onClear?: () => void; - onChangePassword: () => void; - onUpdateSuccess: () => void; - account: string; -}): VNode { - const { i18n } = useTranslationContext(); - const result = useBusinessAccountDetails(account); - const { updateAccount } = useAdminAccountAPI(); - const [update, setUpdate] = useState(false); - const [submitAccount, setSubmitAccount] = useState< - SandboxBackend.Circuit.CircuitAccountData | undefined - >(); - - if (!result.ok) { - if (result.loading || result.type === ErrorType.TIMEOUT) { - return onLoadNotOk(result); - } - if (result.status === HttpStatusCode.NotFound) { - return
    account not found
    ; - } - return onLoadNotOk(result); - } - - return ( -
    -
    -

    - Business account details -

    -
    -
    - setSubmitAccount(a)} - /> - -
    -
    - {onClear ? ( - { - e.preventDefault(); - onClear(); - }} - /> - ) : undefined} -
    -
    -
    - { - e.preventDefault(); - onChangePassword(); - }} - /> -
    -
    - { - e.preventDefault(); - - if (!update) { - setUpdate(true); - } else { - if (!submitAccount) return; - try { - await updateAccount(account, { - cashout_address: submitAccount.cashout_address, - contact_data: submitAccount.contact_data, - }); - onUpdateSuccess(); - } catch (error) { - if (error instanceof RequestError) { - notify( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.Forbidden - ? i18n.str`The rights to change the account are not sufficient` - : status === HttpStatusCode.NotFound - ? i18n.str`The username was not found` - : undefined, - }), - ); - } else { - notifyError( - i18n.str`Operation failed, please report`, - (error instanceof Error - ? error.message - : JSON.stringify(error)) as TranslatedString - ) - } - } - } - }} - /> -
    -
    -
    -

    -
    -
    - ); -} - -function RemoveAccount({ - account, - onClear, - onUpdateSuccess, - onLoadNotOk, -}: { - onLoadNotOk: ( - error: HttpResponsePaginated, - ) => VNode; - onClear: () => void; - onUpdateSuccess: () => void; - account: string; -}): VNode { - const { i18n } = useTranslationContext(); - const result = useAccountDetails(account); - const { deleteAccount } = useAdminAccountAPI(); - - if (!result.ok) { - if (result.loading || result.type === ErrorType.TIMEOUT) { - return onLoadNotOk(result); - } - if (result.status === HttpStatusCode.NotFound) { - return
    account not found
    ; - } - return onLoadNotOk(result); - } - - const balance = Amounts.parse(result.data.balance.amount); - if (!balance) { - return
    there was an error reading the balance
    ; - } - const isBalanceEmpty = Amounts.isZero(balance); - return ( -
    -
    -

    - Remove account: {account} -

    -
    - {/* {FXME: SHOW WARNING} */} - {/* {!isBalanceEmpty && ( - saveError(undefined)} - /> - )} */} - -

    -

    -
    - { - e.preventDefault(); - onClear(); - }} - /> -
    -
    - { - e.preventDefault(); - try { - const r = await deleteAccount(account); - onUpdateSuccess(); - } catch (error) { - if (error instanceof RequestError) { - notify( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.Forbidden - ? i18n.str`The administrator specified a institutional username` - : status === HttpStatusCode.NotFound - ? i18n.str`The username was not found` - : status === HttpStatusCode.PreconditionFailed - ? i18n.str`Balance was not zero` - : undefined, - }), - ); - } else { - notifyError(i18n.str`Operation failed, please report`, - (error instanceof Error - ? error.message - : JSON.stringify(error)) as TranslatedString); - } - } - }} - /> -
    -
    -

    -
    - ); -} -/** - * Create valid account object to update or create - * Take template as initial values for the form - * Purpose indicate if all field al read only (show), part of them (update) - * or none (create) - * @param param0 - * @returns - */ -function AccountForm({ - template, - purpose, - onChange, -}: { - template: SandboxBackend.Circuit.CircuitAccountData | undefined; - onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void; - purpose: "create" | "update" | "show"; -}): VNode { - const initial = initializeFromTemplate(template); - const [form, setForm] = useState(initial); - const [errors, setErrors] = useState< - RecursivePartial | undefined - >(undefined); - const { i18n } = useTranslationContext(); - - function updateForm(newForm: typeof initial): void { - const parsed = !newForm.cashout_address - ? undefined - : parsePaytoUri(newForm.cashout_address); - - const errors = undefinedIfEmpty>({ - cashout_address: !newForm.cashout_address - ? i18n.str`required` - : !parsed - ? i18n.str`does not follow the pattern` - : !parsed.isKnown || parsed.targetType !== "iban" - ? i18n.str`only "IBAN" target are supported` - : !IBAN_REGEX.test(parsed.iban) - ? i18n.str`IBAN should have just uppercased letters and numbers` - : validateIBAN(parsed.iban, i18n), - contact_data: undefinedIfEmpty({ - email: !newForm.contact_data?.email - ? i18n.str`required` - : !EMAIL_REGEX.test(newForm.contact_data.email) - ? i18n.str`it should be an email` - : undefined, - phone: !newForm.contact_data?.phone - ? i18n.str`required` - : !newForm.contact_data.phone.startsWith("+") - ? i18n.str`should start with +` - : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone) - ? i18n.str`phone number can't have other than numbers` - : undefined, - }), - iban: !newForm.iban - ? undefined //optional field - : !IBAN_REGEX.test(newForm.iban) - ? i18n.str`IBAN should have just uppercased letters and numbers` - : validateIBAN(newForm.iban, i18n), - name: !newForm.name ? i18n.str`required` : undefined, - username: !newForm.username ? i18n.str`required` : undefined, - }); - setErrors(errors); - setForm(newForm); - onChange(errors === undefined ? (newForm as any) : undefined); - } - - return ( -
    -
    - - { - form.username = e.currentTarget.value; - updateForm(structuredClone(form)); - }} - />{" "} - -
    -
    - - { - form.name = e.currentTarget.value; - updateForm(structuredClone(form)); - }} - /> - -
    - {purpose !== "create" && ( -
    - - { - form.iban = e.currentTarget.value; - updateForm(structuredClone(form)); - }} - /> - -
    - )} -
    - - { - form.contact_data.email = e.currentTarget.value; - updateForm(structuredClone(form)); - }} - /> - -
    -
    - - { - form.contact_data.phone = e.currentTarget.value; - updateForm(structuredClone(form)); - }} - /> - -
    -
    - - { - form.cashout_address = "payto://iban/" + e.currentTarget.value; - updateForm(structuredClone(form)); - }} - /> - -
    -
    - ); -} diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx deleted file mode 100644 index ec71ceca6..000000000 --- a/packages/demobank-ui/src/pages/BusinessAccount.tsx +++ /dev/null @@ -1,758 +0,0 @@ -/* - 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 - */ -import { - AmountJson, - Amounts, - HttpStatusCode, - TranslatedString -} from "@gnu-taler/taler-util"; -import { - HttpResponse, - HttpResponsePaginated, - RequestError, - notify, - notifyError, - notifyInfo, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; -import { Fragment, VNode, h } from "preact"; -import { useEffect, useState } from "preact/hooks"; -import { Cashouts } from "../components/Cashouts/index.js"; -import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; -import { useBackendContext } from "../context/backend.js"; -import { useAccountDetails } from "../hooks/access.js"; -import { - useCashoutDetails, - useCircuitAccountAPI, - useEstimator, - useRatiosAndFeeConfig, -} from "../hooks/circuit.js"; -import { - TanChannel, - buildRequestErrorMessage, - undefinedIfEmpty, -} from "../utils.js"; -import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js"; -import { handleNotOkResult } from "./HomePage.js"; -import { LoginForm } from "./LoginForm.js"; -import { Amount } from "./PaytoWireTransferForm.js"; - -interface Props { - onClose: () => void; - onRegister: () => void; - onLoadNotOk: () => void; -} -export function BusinessAccount({ - onClose, - onLoadNotOk, - onRegister, -}: Props): VNode { - const { i18n } = useTranslationContext(); - const backend = useBackendContext(); - const [updatePassword, setUpdatePassword] = useState(false); - const [newCashout, setNewcashout] = useState(false); - const [showCashoutDetails, setShowCashoutDetails] = useState< - string | undefined - >(); - - if (backend.state.status === "loggedOut") { - return ; - } - - if (newCashout) { - return ( - { - setNewcashout(false); - }} - onComplete={(id) => { - notifyInfo( - i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`, - ); - setNewcashout(false); - setShowCashoutDetails(id); - }} - /> - ); - } - if (showCashoutDetails) { - return ( - { - setShowCashoutDetails(undefined); - }} - /> - ); - } - if (updatePassword) { - return ( - { - notifyInfo(i18n.str`Password changed`); - setUpdatePassword(false); - }} - onClear={() => { - setUpdatePassword(false); - }} - /> - ); - } - return ( -
    - { - notifyInfo(i18n.str`Account updated`); - }} - onChangePassword={() => { - setUpdatePassword(true); - }} - onClear={onClose} - /> -
    -
    -

    {i18n.str`Latest cashouts`}

    - { - setShowCashoutDetails(id); - }} - /> -
    -
    -
    -
    - { - e.preventDefault(); - setNewcashout(true); - }} - /> -
    -
    -
    - ); -} - -interface PropsCashout { - account: string; - onComplete: (id: string) => void; - onCancel: () => void; - onLoadNotOk: ( - error: - | HttpResponsePaginated - | HttpResponse, - ) => VNode; -} - -type FormType = { - isDebit: boolean; - amount: string; - subject: string; - channel: TanChannel; -}; -type ErrorFrom = { - [P in keyof T]+?: string; -}; - -// check #7719 -function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse< - SandboxBackend.Circuit.Config & { hasChanged?: boolean }, - SandboxBackend.SandboxError -> { - const result = useRatiosAndFeeConfig(); - const [oldResult, setOldResult] = useState< - SandboxBackend.Circuit.Config | undefined - >(undefined); - const dataFromBackend = result.ok ? result.data : undefined; - useEffect(() => { - // save only the first result of /config to the backend - if (!dataFromBackend || oldResult !== undefined) return; - setOldResult(dataFromBackend); - }, [dataFromBackend]); - - if (!result.ok) return result; - - const data = !oldResult ? result.data : oldResult; - const hasChanged = - oldResult && - (result.data.name !== oldResult.name || - result.data.version !== oldResult.version || - result.data.ratios_and_fees.buy_at_ratio !== - oldResult.ratios_and_fees.buy_at_ratio || - result.data.ratios_and_fees.buy_in_fee !== - oldResult.ratios_and_fees.buy_in_fee || - result.data.ratios_and_fees.sell_at_ratio !== - oldResult.ratios_and_fees.sell_at_ratio || - result.data.ratios_and_fees.sell_out_fee !== - oldResult.ratios_and_fees.sell_out_fee || - result.data.fiat_currency !== oldResult.fiat_currency); - - return { - ...result, - data: { ...data, hasChanged }, - }; -} - -function CreateCashout({ - account, - onComplete, - onCancel, - onLoadNotOk, -}: PropsCashout): VNode { - const { i18n } = useTranslationContext(); - const ratiosResult = useRatiosAndFeeConfig(); - const result = useAccountDetails(account); - const { - estimateByCredit: calculateFromCredit, - estimateByDebit: calculateFromDebit, - } = useEstimator(); - const [form, setForm] = useState>({ isDebit: true }); - - const { createCashout } = useCircuitAccountAPI(); - if (!result.ok) return onLoadNotOk(result); - if (!ratiosResult.ok) return onLoadNotOk(ratiosResult); - const config = ratiosResult.data; - - const balance = Amounts.parseOrThrow(result.data.balance.amount); - const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold); - const zero = Amounts.zeroOfCurrency(balance.currency); - const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; - const limit = balanceIsDebit - ? Amounts.sub(debitThreshold, balance).amount - : Amounts.add(balance, debitThreshold).amount; - - const zeroCalc = { debit: zero, credit: zero, beforeFee: zero }; - const [calc, setCalc] = useState(zeroCalc); - const sellRate = config.ratios_and_fees.sell_at_ratio; - const sellFee = !config.ratios_and_fees.sell_out_fee - ? zero - : Amounts.parseOrThrow( - `${balance.currency}:${config.ratios_and_fees.sell_out_fee}`, - ); - const fiatCurrency = config.fiat_currency; - - if (!sellRate || sellRate < 0) return
    error rate
    ; - - const amount = Amounts.parseOrThrow( - `${!form.isDebit ? fiatCurrency : balance.currency}:${ - !form.amount ? "0" : form.amount - }`, - ); - - useEffect(() => { - if (form.isDebit) { - calculateFromDebit(amount, sellFee, sellRate) - .then((r) => { - setCalc(r); - }) - .catch((error) => { - notify( - error instanceof RequestError - ? buildRequestErrorMessage(i18n, error.cause) - : { - type: "error", - title: i18n.str`Could not estimate the cashout`, - description: error.message as TranslatedString - }, - ); - }); - } else { - calculateFromCredit(amount, sellFee, sellRate) - .then((r) => { - setCalc(r); - }) - .catch((error) => { - notify( - error instanceof RequestError - ? buildRequestErrorMessage(i18n, error.cause) - : { - type: "error", - title: i18n.str`Could not estimate the cashout`, - description: error.message, - }, - ); - }); - } - }, [form.amount, form.isDebit]); - - const balanceAfter = Amounts.sub(balance, calc.debit).amount; - - function updateForm(newForm: typeof form): void { - setForm(newForm); - } - const errors = undefinedIfEmpty>({ - amount: !form.amount - ? i18n.str`required` - : !amount - ? i18n.str`could not be parsed` - : Amounts.cmp(limit, calc.debit) === -1 - ? i18n.str`balance is not enough` - : Amounts.cmp(calc.beforeFee, sellFee) === -1 - ? i18n.str`the total amount to transfer does not cover the fees` - : Amounts.isZero(calc.credit) - ? i18n.str`the total transfer at destination will be zero` - : undefined, - channel: !form.channel ? i18n.str`required` : undefined, - }); - - return ( -
    -

    New cashout

    -
    -
    - - { - form.subject = e.currentTarget.value; - updateForm(structuredClone(form)); - }} - /> - -
    -
    - -
    - { - form.amount = v; - updateForm(structuredClone(form)); - }} - error={errors?.amount} - /> - -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    {" "} - {Amounts.isZero(sellFee) ? undefined : ( - -
    - - -
    - -
    - - -
    -
    - )} -
    - - -
    -
    - - -
    - { - e.preventDefault(); - form.channel = TanChannel.EMAIL; - updateForm(structuredClone(form)); - }} - /> - { - e.preventDefault(); - form.channel = TanChannel.SMS; - updateForm(structuredClone(form)); - }} - /> - { - e.preventDefault(); - form.channel = TanChannel.FILE; - updateForm(structuredClone(form)); - }} - /> -
    - -
    -
    -
    - - - -
    -
    -
    - ); -} - -interface ShowCashoutProps { - id: string; - onCancel: () => void; - onLoadNotOk: ( - error: HttpResponsePaginated, - ) => VNode; -} -export function ShowCashoutDetails({ - id, - onCancel, - onLoadNotOk, -}: ShowCashoutProps): VNode { - const { i18n } = useTranslationContext(); - const result = useCashoutDetails(id); - const { abortCashout, confirmCashout } = useCircuitAccountAPI(); - const [code, setCode] = useState(undefined); - if (!result.ok) return onLoadNotOk(result); - const errors = undefinedIfEmpty({ - code: !code ? i18n.str`required` : undefined, - }); - const isPending = String(result.data.status).toUpperCase() === "PENDING"; - return ( -
    -

    Cashout details {id}

    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    - {isPending ? ( -
    - - { - setCode(e.currentTarget.value); - }} - /> - -
    - ) : undefined} -
    -
    -
    - - {isPending ? ( -
    - -   - -
    - ) : ( -
    - )} -
    -
    - ); -} - -const MAX_AMOUNT_DIGIT = 2; -/** - * Truncate the amount of digits to display - * in the form based on the fee calculations - * - * Backend must have the same truncation - * @param a - * @returns - */ -function truncate(a: AmountJson): AmountJson { - const str = Amounts.stringify(a); - const idx = str.indexOf("."); - if (idx === -1) { - return a; - } - const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT); - return Amounts.parseOrThrow(truncated); -} - -export function assertUnreachable(x: never): never { - throw new Error("Didn't expect to get here"); -} diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx index e82e46eb2..a911f347c 100644 --- a/packages/demobank-ui/src/pages/HomePage.tsx +++ b/packages/demobank-ui/src/pages/HomePage.tsx @@ -35,7 +35,7 @@ import { useBackendContext } from "../context/backend.js"; import { getInitialBackendBaseURL } from "../hooks/backend.js"; import { useSettings } from "../hooks/settings.js"; import { AccountPage } from "./AccountPage/index.js"; -import { AdminPage } from "./AdminPage.js"; +import { AdminHome } from "./admin/Home.js"; import { LoginForm } from "./LoginForm.js"; import { WithdrawalQRCode } from "./WithdrawalQRCode.js"; import { error } from "console"; @@ -54,31 +54,24 @@ const logger = new Logger("AccountPage"); */ export function HomePage({ onRegister, + account, onPendingOperationFound, }: { + account: string, onPendingOperationFound: (id: string) => void; onRegister: () => void; }): VNode { - const backend = useBackendContext(); const [settings] = useSettings(); const { i18n } = useTranslationContext(); - if (backend.state.status === "loggedOut") { - return ; - } - if (settings.currentWithdrawalOperationId) { onPendingOperationFound(settings.currentWithdrawalOperationId); return ; } - if (backend.state.isUserAdministrator) { - return ; - } - return ( ); @@ -105,8 +98,8 @@ export function WithdrawalOperationPage({ if (!parsedUri) { notifyError( - i18n.str`The Withdrawal URI is not valid: "${uri}"`, - undefined + i18n.str`The Withdrawal URI is not valid`, + uri as TranslatedString ); return ; } diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx new file mode 100644 index 000000000..91b50b84c --- /dev/null +++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx @@ -0,0 +1,143 @@ +import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { VNode,h } from "preact"; +import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js"; +import { useState } from "preact/hooks"; +import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; +import { buildRequestErrorMessage } from "../utils.js"; +import { AccountForm } from "./admin/AccountForm.js"; + +export function ShowAccountDetails({ + account, + onClear, + onUpdateSuccess, + onLoadNotOk, + onChangePassword, + }: { + onLoadNotOk: ( + error: HttpResponsePaginated, + ) => VNode; + onClear?: () => void; + onChangePassword: () => void; + onUpdateSuccess: () => void; + account: string; + }): VNode { + const { i18n } = useTranslationContext(); + const result = useBusinessAccountDetails(account); + const { updateAccount } = useAdminAccountAPI(); + const [update, setUpdate] = useState(false); + const [submitAccount, setSubmitAccount] = useState< + SandboxBackend.Circuit.CircuitAccountData | undefined + >(); + + if (!result.ok) { + if (result.loading || result.type === ErrorType.TIMEOUT) { + return onLoadNotOk(result); + } + if (result.status === HttpStatusCode.NotFound) { + return
    account not found
    ; + } + return onLoadNotOk(result); + } + + return ( +
    +
    +

    + Business account details +

    +
    +
    + setSubmitAccount(a)} + /> + +
    +
    + {onClear ? ( + { + e.preventDefault(); + onClear(); + }} + /> + ) : undefined} +
    +
    +
    + { + e.preventDefault(); + onChangePassword(); + }} + /> +
    +
    + { + e.preventDefault(); + + if (!update) { + setUpdate(true); + } else { + if (!submitAccount) return; + try { + await updateAccount(account, { + cashout_address: submitAccount.cashout_address, + contact_data: submitAccount.contact_data, + }); + onUpdateSuccess(); + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Forbidden + ? i18n.str`The rights to change the account are not sufficient` + : status === HttpStatusCode.NotFound + ? i18n.str`The username was not found` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) + } + } + } + }} + /> +
    +
    +
    +

    +
    +
    + ); + } + \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx new file mode 100644 index 000000000..084a5b643 --- /dev/null +++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx @@ -0,0 +1,131 @@ +import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js"; +import { useState } from "preact/hooks"; +import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; +import { VNode,h ,Fragment} from "preact"; +import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; +import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; + +export function UpdateAccountPassword({ + account, + onClear, + onUpdateSuccess, + onLoadNotOk, + }: { + onLoadNotOk: ( + error: HttpResponsePaginated, + ) => VNode; + onClear: () => void; + onUpdateSuccess: () => void; + account: string; + }): VNode { + const { i18n } = useTranslationContext(); + const result = useBusinessAccountDetails(account); + const { changePassword } = useAdminAccountAPI(); + const [password, setPassword] = useState(); + const [repeat, setRepeat] = useState(); + + if (!result.ok) { + if (result.loading || result.type === ErrorType.TIMEOUT) { + return onLoadNotOk(result); + } + if (result.status === HttpStatusCode.NotFound) { + return
    account not found
    ; + } + return onLoadNotOk(result); + } + + const errors = undefinedIfEmpty({ + password: !password ? i18n.str`required` : undefined, + repeat: !repeat + ? i18n.str`required` + : password !== repeat + ? i18n.str`password doesn't match` + : undefined, + }); + + return ( +
    +
    +

    + Update password for {account} +

    +
    + +
    +
    +
    + + { + setPassword(e.currentTarget.value); + }} + /> + +
    +
    + + { + setRepeat(e.currentTarget.value); + }} + /> + +
    +
    +

    +

    +
    + { + e.preventDefault(); + onClear(); + }} + /> +
    +
    + { + e.preventDefault(); + if (!!errors || !password) return; + try { + const r = await changePassword(account, { + new_password: password, + }); + onUpdateSuccess(); + } catch (error) { + if (error instanceof RequestError) { + notify(buildRequestErrorMessage(i18n, error.cause)); + } else { + notifyError(i18n.str`Operation failed, please report`, (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString) + } + } + }} + /> +
    +
    +

    +
    +
    + ); + } \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index ced152feb..30fcbdff7 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -317,7 +317,8 @@ export function WithdrawalConfirmationQuestion({
    Amount
    -
    {Amounts.stringifyValue(details.amount)}
    +
    To be added
    + {/* Amounts.stringifyValue(details.amount) */}
    diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx index b48e3b1dc..2a3a1ec2c 100644 --- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx @@ -100,10 +100,6 @@ export function WithdrawalQRCode({ } if (data.confirmation_done) { - if (!settings.showWithdrawalSuccess) { - clearCurrentWithdrawal() - onContinue() - } return
    diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx new file mode 100644 index 000000000..8ab3e1323 --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/Account.tsx @@ -0,0 +1,56 @@ +import { Amounts } from "@gnu-taler/taler-util"; +import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js"; +import { handleNotOkResult } from "../HomePage.js"; +import { useAccountDetails } from "../../hooks/access.js"; +import { useBackendContext } from "../../context/backend.js"; +import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, h, VNode } from "preact"; + +export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode { + const { i18n } = useTranslationContext(); + const r = useBackendContext(); + const account = r.state.status === "loggedIn" ? r.state.username : "admin"; + const result = useAccountDetails(account); + + if (!result.ok) { + return handleNotOkResult(i18n, onRegister)(result); + } + const { data } = result; + const balance = Amounts.parseOrThrow(data.balance.amount); + const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold); + const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; + const limit = balanceIsDebit + ? Amounts.sub(debitThreshold, balance).amount + : Amounts.add(balance, debitThreshold).amount; + if (!balance) return ; + return ( + +
    +
    +

    {i18n.str`Bank account balance`}

    + {!balance ? ( +
    + Waiting server response... +
    + ) : ( +
    + {balanceIsDebit ? - : null} + {`${Amounts.stringifyValue(balance)}`} +   + {`${balance.currency}`} +
    + )} +
    +
    + { + notifyInfo(i18n.str`Wire transfer created!`); + }} + onCancel={undefined} + /> +
    + ); + } + \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx new file mode 100644 index 000000000..9ca0323a1 --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx @@ -0,0 +1,219 @@ +import { VNode,h } from "preact"; +import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js"; +import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js"; +import { useState } from "preact/hooks"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { parsePaytoUri } from "@gnu-taler/taler-util"; + +const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/; +const EMAIL_REGEX = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; +const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/; + +/** + * Create valid account object to update or create + * Take template as initial values for the form + * Purpose indicate if all field al read only (show), part of them (update) + * or none (create) + * @param param0 + * @returns + */ +export function AccountForm({ + template, + purpose, + onChange, + }: { + template: SandboxBackend.Circuit.CircuitAccountData | undefined; + onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void; + purpose: "create" | "update" | "show"; + }): VNode { + const initial = initializeFromTemplate(template); + const [form, setForm] = useState(initial); + const [errors, setErrors] = useState< + RecursivePartial | undefined + >(undefined); + const { i18n } = useTranslationContext(); + + function updateForm(newForm: typeof initial): void { + const parsed = !newForm.cashout_address + ? undefined + : parsePaytoUri(newForm.cashout_address); + + const errors = undefinedIfEmpty>({ + cashout_address: !newForm.cashout_address + ? i18n.str`required` + : !parsed + ? i18n.str`does not follow the pattern` + : !parsed.isKnown || parsed.targetType !== "iban" + ? i18n.str`only "IBAN" target are supported` + : !IBAN_REGEX.test(parsed.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(parsed.iban, i18n), + contact_data: undefinedIfEmpty({ + email: !newForm.contact_data?.email + ? i18n.str`required` + : !EMAIL_REGEX.test(newForm.contact_data.email) + ? i18n.str`it should be an email` + : undefined, + phone: !newForm.contact_data?.phone + ? i18n.str`required` + : !newForm.contact_data.phone.startsWith("+") + ? i18n.str`should start with +` + : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone) + ? i18n.str`phone number can't have other than numbers` + : undefined, + }), + iban: !newForm.iban + ? undefined //optional field + : !IBAN_REGEX.test(newForm.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` + : validateIBAN(newForm.iban, i18n), + name: !newForm.name ? i18n.str`required` : undefined, + username: !newForm.username ? i18n.str`required` : undefined, + }); + setErrors(errors); + setForm(newForm); + onChange(errors === undefined ? (newForm as any) : undefined); + } + + return ( +
    +
    + + { + form.username = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + />{" "} + +
    +
    + + { + form.name = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + +
    + {purpose !== "create" && ( +
    + + { + form.iban = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + +
    + )} +
    + + { + form.contact_data.email = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + +
    +
    + + { + form.contact_data.phone = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + +
    +
    + + { + form.cashout_address = "payto://iban/" + e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + +
    +
    + ); + } + + function initializeFromTemplate( + account: SandboxBackend.Circuit.CircuitAccountData | undefined, + ): WithIntermediate { + const emptyAccount = { + cashout_address: undefined, + iban: undefined, + name: undefined, + username: undefined, + contact_data: undefined, + }; + const emptyContact = { + email: undefined, + phone: undefined, + }; + + const initial: PartialButDefined = + structuredClone(account) ?? emptyAccount; + if (typeof initial.contact_data === "undefined") { + initial.contact_data = emptyContact; + } + initial.contact_data.email; + return initial as any; + } + + + \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx new file mode 100644 index 000000000..56b15818b --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -0,0 +1,120 @@ +import { h, VNode } from "preact"; +import { useBusinessAccounts } from "../../hooks/circuit.js"; +import { handleNotOkResult } from "../HomePage.js"; +import { AccountAction } from "./Home.js"; +import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; + +interface Props { + onAction: (type: AccountAction, account: string) => void; + account: string | undefined; + onRegister: () => void; + +} + +export function AccountList({ account, onAction, onRegister }: Props): VNode { + const result = useBusinessAccounts({ account }); + const { i18n } = useTranslationContext(); + + if (result.loading) return
    ; + if (!result.ok) { + return handleNotOkResult(i18n, onRegister)(result); + } + + const { customers } = result.data; + return
    + {!customers.length ? ( +
    + ) : ( +
    +

    {i18n.str`Accounts:`}

    +
    + + + + + + + + + + + {customers.map((item, idx) => { + const balance = !item.balance + ? undefined + : Amounts.parse(item.balance.amount); + const balanceIsDebit = + item.balance && + item.balance.credit_debit_indicator == "debit"; + return ( + + + + + + + ); + })} + +
    {i18n.str`Username`}{i18n.str`Name`}{i18n.str`Balance`}{i18n.str`Actions`}
    + { + e.preventDefault(); + onAction("show-details", item.username) + }} + > + {item.username} + + {item.name} + {!balance ? ( + i18n.str`unknown` + ) : ( + + {balanceIsDebit ? - : null} + {`${Amounts.stringifyValue( + balance, + )}`} +   + {`${balance.currency}`} + + )} + + { + e.preventDefault(); + onAction("update-password", item.username) + }} + > + change password + +   + { + e.preventDefault(); + onAction("show-cashout", item.username) + }} + > + cashouts + +   + { + e.preventDefault(); + onAction("remove-account", item.username) + }} + > + remove + +
    +
    +
    + )} +
    +} \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx new file mode 100644 index 000000000..90835d52b --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx @@ -0,0 +1,107 @@ +import { RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { VNode, h, Fragment } from "preact"; +import { useAdminAccountAPI } from "../../hooks/circuit.js"; +import { useState } from "preact/hooks"; +import { buildRequestErrorMessage } from "../../utils.js"; +import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; +import { getRandomPassword } from "../rnd.js"; +import { AccountForm } from "./AccountForm.js"; + +export function CreateNewAccount({ + onClose, + onCreateSuccess, +}: { + onClose: () => void; + onCreateSuccess: (password: string) => void; +}): VNode { + const { i18n } = useTranslationContext(); + const { createAccount } = useAdminAccountAPI(); + const [submitAccount, setSubmitAccount] = useState< + SandboxBackend.Circuit.CircuitAccountData | undefined + >(); + return ( +
    +
    +

    + New account +

    +
    + +
    + { + setSubmitAccount(a); + }} + /> + +

    +

    +
    + { + e.preventDefault(); + onClose(); + }} + /> +
    +
    + { + e.preventDefault(); + + if (!submitAccount) return; + try { + const account: SandboxBackend.Circuit.CircuitAccountRequest = + { + cashout_address: submitAccount.cashout_address, + contact_data: submitAccount.contact_data, + internal_iban: submitAccount.iban, + name: submitAccount.name, + username: submitAccount.username, + password: getRandomPassword(), + }; + + await createAccount(account); + onCreateSuccess(account.password); + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Forbidden + ? i18n.str`The rights to perform the operation are not sufficient` + : status === HttpStatusCode.BadRequest + ? i18n.str`Input data was invalid` + : status === HttpStatusCode.Conflict + ? i18n.str`At least one registration detail was not available` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) + } + } + }} + /> +
    +
    +

    +
    +
    + ); +} diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx new file mode 100644 index 000000000..e1ec6cfe0 --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/Home.tsx @@ -0,0 +1,162 @@ +import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { useState } from "preact/hooks"; +import { Cashouts } from "../../components/Cashouts/index.js"; +import { ShowCashoutDetails } from "../business/Home.js"; +import { handleNotOkResult } from "../HomePage.js"; +import { ShowAccountDetails } from "../ShowAccountDetails.js"; +import { UpdateAccountPassword } from "../UpdateAccountPassword.js"; +import { AdminAccount } from "./Account.js"; +import { AccountList } from "./AccountList.js"; +import { CreateNewAccount } from "./CreateNewAccount.js"; +import { RemoveAccount } from "./RemoveAccount.js"; + +/** + * Query account information and show QR code if there is pending withdrawal + */ +interface Props { + onRegister: () => void; +} +export type AccountAction = "show-details" | + "show-cashout" | + "update-password" | + "remove-account" | + "show-cashouts-details"; + +export function AdminHome({ onRegister }: Props): VNode { + const [action, setAction] = useState<{ + type: AccountAction, + account: string + }>() + + const [createAccount, setCreateAccount] = useState(false); + + const { i18n } = useTranslationContext(); + + if (action) { + switch (action.type) { + case "show-details": return { + setAction(undefined); + }} + /> + case "show-cashout": return ( +
    +
    +

    + Cashout for account {action.account} +

    +
    + { + setAction({ + type: "show-cashouts-details", + account: action.account + }); + }} + /> +

    + { + e.preventDefault(); + setAction(undefined); + }} + /> +

    +
    + ) + case "update-password": return { + notifyInfo(i18n.str`Password changed`); + setAction(undefined); + }} + onClear={() => { + setAction(undefined); + }} + /> + case "remove-account": return { + notifyInfo(i18n.str`Account removed`); + setAction(undefined); + }} + onClear={() => { + setAction(undefined); + }} + /> + case "show-cashouts-details": return { + setAction({ + type: "update-password", + account: action.account, + }) + }} + onUpdateSuccess={() => { + notifyInfo(i18n.str`Account updated`); + setAction(undefined); + }} + onClear={() => { + setAction(undefined); + }} + /> + } + } + + if (createAccount) { + return ( + setCreateAccount(false)} + onCreateSuccess={(password) => { + notifyInfo( + i18n.str`Account created with password "${password}". The user must change the password on the next login.`, + ); + setCreateAccount(false); + }} + /> + ); + } + + return ( + +
    +

    + Admin panel +

    +
    + +

    +

    +
    +
    + { + e.preventDefault(); + + setCreateAccount(true); + }} + /> +
    +
    +

    + + + + setAction({account, type})} onRegister={onRegister}/> + +
    + ); +} \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx new file mode 100644 index 000000000..2900db9d2 --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx @@ -0,0 +1,112 @@ +import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { VNode,h,Fragment } from "preact"; +import { useAccountDetails } from "../../hooks/access.js"; +import { useAdminAccountAPI } from "../../hooks/circuit.js"; +import { Amounts, HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util"; +import { buildRequestErrorMessage } from "../../utils.js"; + +export function RemoveAccount({ + account, + onClear, + onUpdateSuccess, + onLoadNotOk, + }: { + onLoadNotOk: ( + error: HttpResponsePaginated, + ) => VNode; + onClear: () => void; + onUpdateSuccess: () => void; + account: string; + }): VNode { + const { i18n } = useTranslationContext(); + const result = useAccountDetails(account); + const { deleteAccount } = useAdminAccountAPI(); + + if (!result.ok) { + if (result.loading || result.type === ErrorType.TIMEOUT) { + return onLoadNotOk(result); + } + if (result.status === HttpStatusCode.NotFound) { + return
    account not found
    ; + } + return onLoadNotOk(result); + } + + const balance = Amounts.parse(result.data.balance.amount); + if (!balance) { + return
    there was an error reading the balance
    ; + } + const isBalanceEmpty = Amounts.isZero(balance); + return ( +
    +
    +

    + Remove account: {account} +

    +
    + {/* {FXME: SHOW WARNING} */} + {/* {!isBalanceEmpty && ( + saveError(undefined)} + /> + )} */} + +

    +

    +
    + { + e.preventDefault(); + onClear(); + }} + /> +
    +
    + { + e.preventDefault(); + try { + const r = await deleteAccount(account); + onUpdateSuccess(); + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Forbidden + ? i18n.str`The administrator specified a institutional username` + : status === HttpStatusCode.NotFound + ? i18n.str`The username was not found` + : status === HttpStatusCode.PreconditionFailed + ? i18n.str`Balance was not zero` + : undefined, + }), + ); + } else { + notifyError(i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString); + } + } + }} + /> +
    +
    +

    +
    + ); + } + \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx new file mode 100644 index 000000000..8beea640a --- /dev/null +++ b/packages/demobank-ui/src/pages/business/Home.tsx @@ -0,0 +1,757 @@ +/* + 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 + */ +import { + AmountJson, + Amounts, + HttpStatusCode, + TranslatedString +} from "@gnu-taler/taler-util"; +import { + HttpResponse, + HttpResponsePaginated, + RequestError, + notify, + notifyError, + notifyInfo, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { useEffect, useState } from "preact/hooks"; +import { Cashouts } from "../../components/Cashouts/index.js"; +import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js"; +import { useBackendContext } from "../../context/backend.js"; +import { useAccountDetails } from "../../hooks/access.js"; +import { + useCashoutDetails, + useCircuitAccountAPI, + useEstimator, + useRatiosAndFeeConfig, +} from "../../hooks/circuit.js"; +import { + TanChannel, + buildRequestErrorMessage, + undefinedIfEmpty, +} from "../../utils.js"; +import { handleNotOkResult } from "../HomePage.js"; +import { LoginForm } from "../LoginForm.js"; +import { Amount } from "../PaytoWireTransferForm.js"; +import { ShowAccountDetails } from "../ShowAccountDetails.js"; +import { UpdateAccountPassword } from "../UpdateAccountPassword.js"; + +interface Props { + account: string, + onClose: () => void; + onRegister: () => void; + onLoadNotOk: () => void; +} +export function BusinessAccount({ + onClose, + account, + onLoadNotOk, + onRegister, +}: Props): VNode { + const { i18n } = useTranslationContext(); + const [updatePassword, setUpdatePassword] = useState(false); + const [newCashout, setNewcashout] = useState(false); + const [showCashoutDetails, setShowCashoutDetails] = useState< + string | undefined + >(); + + + if (newCashout) { + return ( + { + setNewcashout(false); + }} + onComplete={(id) => { + notifyInfo( + i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`, + ); + setNewcashout(false); + setShowCashoutDetails(id); + }} + /> + ); + } + if (showCashoutDetails) { + return ( + { + setShowCashoutDetails(undefined); + }} + /> + ); + } + if (updatePassword) { + return ( + { + notifyInfo(i18n.str`Password changed`); + setUpdatePassword(false); + }} + onClear={() => { + setUpdatePassword(false); + }} + /> + ); + } + return ( +
    + { + notifyInfo(i18n.str`Account updated`); + }} + onChangePassword={() => { + setUpdatePassword(true); + }} + onClear={onClose} + /> +
    +
    +

    {i18n.str`Latest cashouts`}

    + { + setShowCashoutDetails(id); + }} + /> +
    +
    +
    +
    + { + e.preventDefault(); + setNewcashout(true); + }} + /> +
    +
    +
    + ); +} + +interface PropsCashout { + account: string; + onComplete: (id: string) => void; + onCancel: () => void; + onLoadNotOk: ( + error: + | HttpResponsePaginated + | HttpResponse, + ) => VNode; +} + +type FormType = { + isDebit: boolean; + amount: string; + subject: string; + channel: TanChannel; +}; +type ErrorFrom = { + [P in keyof T]+?: string; +}; + +// check #7719 +function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse< + SandboxBackend.Circuit.Config & { hasChanged?: boolean }, + SandboxBackend.SandboxError +> { + const result = useRatiosAndFeeConfig(); + const [oldResult, setOldResult] = useState< + SandboxBackend.Circuit.Config | undefined + >(undefined); + const dataFromBackend = result.ok ? result.data : undefined; + useEffect(() => { + // save only the first result of /config to the backend + if (!dataFromBackend || oldResult !== undefined) return; + setOldResult(dataFromBackend); + }, [dataFromBackend]); + + if (!result.ok) return result; + + const data = !oldResult ? result.data : oldResult; + const hasChanged = + oldResult && + (result.data.name !== oldResult.name || + result.data.version !== oldResult.version || + result.data.ratios_and_fees.buy_at_ratio !== + oldResult.ratios_and_fees.buy_at_ratio || + result.data.ratios_and_fees.buy_in_fee !== + oldResult.ratios_and_fees.buy_in_fee || + result.data.ratios_and_fees.sell_at_ratio !== + oldResult.ratios_and_fees.sell_at_ratio || + result.data.ratios_and_fees.sell_out_fee !== + oldResult.ratios_and_fees.sell_out_fee || + result.data.fiat_currency !== oldResult.fiat_currency); + + return { + ...result, + data: { ...data, hasChanged }, + }; +} + +function CreateCashout({ + account, + onComplete, + onCancel, + onLoadNotOk, +}: PropsCashout): VNode { + const { i18n } = useTranslationContext(); + const ratiosResult = useRatiosAndFeeConfig(); + const result = useAccountDetails(account); + const { + estimateByCredit: calculateFromCredit, + estimateByDebit: calculateFromDebit, + } = useEstimator(); + const [form, setForm] = useState>({ isDebit: true }); + + const { createCashout } = useCircuitAccountAPI(); + if (!result.ok) return onLoadNotOk(result); + if (!ratiosResult.ok) return onLoadNotOk(ratiosResult); + const config = ratiosResult.data; + + const balance = Amounts.parseOrThrow(result.data.balance.amount); + const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold); + const zero = Amounts.zeroOfCurrency(balance.currency); + const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit"; + const limit = balanceIsDebit + ? Amounts.sub(debitThreshold, balance).amount + : Amounts.add(balance, debitThreshold).amount; + + const zeroCalc = { debit: zero, credit: zero, beforeFee: zero }; + const [calc, setCalc] = useState(zeroCalc); + const sellRate = config.ratios_and_fees.sell_at_ratio; + const sellFee = !config.ratios_and_fees.sell_out_fee + ? zero + : Amounts.parseOrThrow( + `${balance.currency}:${config.ratios_and_fees.sell_out_fee}`, + ); + const fiatCurrency = config.fiat_currency; + + if (!sellRate || sellRate < 0) return
    error rate
    ; + + const amount = Amounts.parseOrThrow( + `${!form.isDebit ? fiatCurrency : balance.currency}:${ + !form.amount ? "0" : form.amount + }`, + ); + + useEffect(() => { + if (form.isDebit) { + calculateFromDebit(amount, sellFee, sellRate) + .then((r) => { + setCalc(r); + }) + .catch((error) => { + notify( + error instanceof RequestError + ? buildRequestErrorMessage(i18n, error.cause) + : { + type: "error", + title: i18n.str`Could not estimate the cashout`, + description: error.message as TranslatedString + }, + ); + }); + } else { + calculateFromCredit(amount, sellFee, sellRate) + .then((r) => { + setCalc(r); + }) + .catch((error) => { + notify( + error instanceof RequestError + ? buildRequestErrorMessage(i18n, error.cause) + : { + type: "error", + title: i18n.str`Could not estimate the cashout`, + description: error.message, + }, + ); + }); + } + }, [form.amount, form.isDebit]); + + const balanceAfter = Amounts.sub(balance, calc.debit).amount; + + function updateForm(newForm: typeof form): void { + setForm(newForm); + } + const errors = undefinedIfEmpty>({ + amount: !form.amount + ? i18n.str`required` + : !amount + ? i18n.str`could not be parsed` + : Amounts.cmp(limit, calc.debit) === -1 + ? i18n.str`balance is not enough` + : Amounts.cmp(calc.beforeFee, sellFee) === -1 + ? i18n.str`the total amount to transfer does not cover the fees` + : Amounts.isZero(calc.credit) + ? i18n.str`the total transfer at destination will be zero` + : undefined, + channel: !form.channel ? i18n.str`required` : undefined, + }); + + return ( +
    +

    New cashout

    +
    +
    + + { + form.subject = e.currentTarget.value; + updateForm(structuredClone(form)); + }} + /> + +
    +
    + +
    + { + form.amount = v; + updateForm(structuredClone(form)); + }} + error={errors?.amount} + /> + +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    {" "} + {Amounts.isZero(sellFee) ? undefined : ( + +
    + + +
    + +
    + + +
    +
    + )} +
    + + +
    +
    + + +
    + { + e.preventDefault(); + form.channel = TanChannel.EMAIL; + updateForm(structuredClone(form)); + }} + /> + { + e.preventDefault(); + form.channel = TanChannel.SMS; + updateForm(structuredClone(form)); + }} + /> + { + e.preventDefault(); + form.channel = TanChannel.FILE; + updateForm(structuredClone(form)); + }} + /> +
    + +
    +
    +
    + + + +
    +
    +
    + ); +} + +interface ShowCashoutProps { + id: string; + onCancel: () => void; + onLoadNotOk: ( + error: HttpResponsePaginated, + ) => VNode; +} +export function ShowCashoutDetails({ + id, + onCancel, + onLoadNotOk, +}: ShowCashoutProps): VNode { + const { i18n } = useTranslationContext(); + const result = useCashoutDetails(id); + const { abortCashout, confirmCashout } = useCircuitAccountAPI(); + const [code, setCode] = useState(undefined); + if (!result.ok) return onLoadNotOk(result); + const errors = undefinedIfEmpty({ + code: !code ? i18n.str`required` : undefined, + }); + const isPending = String(result.data.status).toUpperCase() === "PENDING"; + return ( +
    +

    Cashout details {id}

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + {isPending ? ( +
    + + { + setCode(e.currentTarget.value); + }} + /> + +
    + ) : undefined} +
    +
    +
    + + {isPending ? ( +
    + +   + +
    + ) : ( +
    + )} +
    +
    + ); +} + +const MAX_AMOUNT_DIGIT = 2; +/** + * Truncate the amount of digits to display + * in the form based on the fee calculations + * + * Backend must have the same truncation + * @param a + * @returns + */ +function truncate(a: AmountJson): AmountJson { + const str = Amounts.stringify(a); + const idx = str.indexOf("."); + if (idx === -1) { + return a; + } + const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT); + return Amounts.parseOrThrow(truncated); +} + +export function assertUnreachable(x: never): never { + throw new Error("Didn't expect to get here"); +} -- cgit v1.2.3 From 0b7bbed99d155ba030a1328e357ab6751bdbb835 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 21 Sep 2023 13:10:16 -0300 Subject: more ui: business and admin --- packages/demobank-ui/src/components/Routing.tsx | 17 +- .../src/components/ShowInputErrorLabel.tsx | 4 +- .../demobank-ui/src/pages/AccountPage/index.ts | 5 +- .../demobank-ui/src/pages/AccountPage/state.ts | 7 +- .../demobank-ui/src/pages/AccountPage/views.tsx | 29 +- packages/demobank-ui/src/pages/BankFrame.tsx | 32 +- packages/demobank-ui/src/pages/HomePage.tsx | 6 +- packages/demobank-ui/src/pages/PaymentOptions.tsx | 131 +++--- .../src/pages/PaytoWireTransferForm.tsx | 6 +- .../demobank-ui/src/pages/RegistrationPage.tsx | 2 +- .../demobank-ui/src/pages/ShowAccountDetails.tsx | 278 ++++++------ .../src/pages/UpdateAccountPassword.tsx | 278 +++++++----- packages/demobank-ui/src/pages/admin/Account.tsx | 72 ++- .../demobank-ui/src/pages/admin/AccountForm.tsx | 494 +++++++++++++-------- .../demobank-ui/src/pages/admin/AccountList.tsx | 182 +++++--- .../src/pages/admin/CreateNewAccount.tsx | 174 ++++---- packages/demobank-ui/src/pages/admin/Home.tsx | 51 +-- .../demobank-ui/src/pages/admin/RemoveAccount.tsx | 302 +++++++++---- packages/demobank-ui/src/pages/business/Home.tsx | 2 +- 19 files changed, 1181 insertions(+), 891 deletions(-) (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx index ef11af76e..b8e39948b 100644 --- a/packages/demobank-ui/src/components/Routing.tsx +++ b/packages/demobank-ui/src/components/Routing.tsx @@ -33,12 +33,7 @@ export function Routing(): VNode { const backend = useBackendContext(); if (backend.state.status === "loggedOut") { - return { - route("/business"); - }} - > + return { - route("/business"); - }} - > + { route(`/operation/${wopid}`); }} + goToBusinessAccount={() => { + route("/business"); + }} onRegister={() => { route("/register"); }} diff --git a/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx b/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx index dacffe20a..c5840cad9 100644 --- a/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx +++ b/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx @@ -24,6 +24,6 @@ export function ShowInputErrorLabel({ isDirty: boolean; }): VNode { if (message && isDirty) - return
    {message}
    ; - return ; + return
    {message}
    ; + return
    ; } diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts index ed6945f84..128a6d30f 100644 --- a/packages/demobank-ui/src/pages/AccountPage/index.ts +++ b/packages/demobank-ui/src/pages/AccountPage/index.ts @@ -28,6 +28,7 @@ export interface Props { onLoadNotOk: ( error: HttpResponsePaginated, ) => VNode; + goToBusinessAccount: () => void; } export type State = State.Loading | State.LoadingError | State.Ready | State.InvalidIban | State.UserNotFound; @@ -51,10 +52,8 @@ export namespace State { status: "ready"; error: undefined; account: string, - payto: PaytoUriIBAN | PaytoUriTalerBank, - balance: AmountJson, - balanceIsDebit: boolean, limit: AmountJson, + goToBusinessAccount: () => void; } export interface InvalidIban { diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts index 2249b743e..a57e19901 100644 --- a/packages/demobank-ui/src/pages/AccountPage/state.ts +++ b/packages/demobank-ui/src/pages/AccountPage/state.ts @@ -20,7 +20,7 @@ import { useBackendContext } from "../../context/backend.js"; import { useAccountDetails } from "../../hooks/access.js"; import { Props, State } from "./index.js"; -export function useComponentState({ account, onLoadNotOk }: Props): State { +export function useComponentState({ account, goToBusinessAccount }: Props): State { const result = useAccountDetails(account); const backend = useBackendContext(); const { i18n } = useTranslationContext(); @@ -60,7 +60,6 @@ export function useComponentState({ account, onLoadNotOk }: Props): State { const payto = parsePaytoUri(data.paytoUri); if (!payto || !payto.isKnown || (payto.targetType !== "iban" && payto.targetType !== "x-taler-bank")) { - console.log(payto) return { status: "invalid-iban", error: result @@ -75,11 +74,9 @@ export function useComponentState({ account, onLoadNotOk }: Props): State { return { status: "ready", + goToBusinessAccount, error: undefined, account, - balance, - balanceIsDebit, limit, - payto }; } diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx index f2cbbba6c..abd14848f 100644 --- a/packages/demobank-ui/src/pages/AccountPage/views.tsx +++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx @@ -22,6 +22,7 @@ import { PaymentOptions } from "../PaymentOptions.js"; import { State } from "./index.js"; import { CopyButton } from "../../components/CopyButton.js"; import { bankUiSettings } from "../../settings.js"; +import { useBusinessAccountDetails } from "../../hooks/circuit.js"; export function InvalidIbanView({ error }: State.InvalidIban) { return ( @@ -77,11 +78,35 @@ function ImportantMessage(): VNode { } -export function ReadyView({ account, balance, balanceIsDebit, limit, payto }: State.Ready): VNode<{}> { - const { i18n } = useTranslationContext(); +export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> { return + ; } +function MaybeBusinessButton({ + account, + onClick, +}: { + account: string; + onClick: () => void; +}): VNode { + const { i18n } = useTranslationContext(); + const result = useBusinessAccountDetails(account); + if (!result.ok) return ; + return ( +
    + +
    + ); +} diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 80a8224d4..4b23686d6 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -39,36 +39,12 @@ const versionText = VERSION const logger = new Logger("BankFrame"); -function MaybeBusinessButton({ - account, - onClick, -}: { - account: string; - onClick: () => void; -}): VNode { - const { i18n } = useTranslationContext(); - const result = useBusinessAccountDetails(account); - if (!result.ok) return ; - return ( - { - e.preventDefault(); - onClick(); - }} - >{i18n.str`Business Profile`} - ); -} - export function BankFrame({ children, - goToBusinessAccount, account, }: { - account: string | undefined, + account?: string, children: ComponentChildren; - goToBusinessAccount?: () => void; }): VNode { const { i18n } = useTranslationContext(); const backend = useBackendContext(); @@ -489,5 +465,9 @@ function AccountBalance({ account }: { account: string }): VNode { const result = useAccountDetails(account); if (!result.ok) return
    - return
    {result.data.balance.credit_debit_indicator === "debit" ? "-" : ""} {Amounts.currencyOf(result.data.balance.amount)} {Amounts.stringifyValue(result.data.balance.amount)}
    + return
    + {Amounts.currencyOf(result.data.balance.amount)} +  {result.data.balance.credit_debit_indicator === "debit" ? "-" : ""} + {Amounts.stringifyValue(result.data.balance.amount)} +
    } diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx index a911f347c..40cc147a6 100644 --- a/packages/demobank-ui/src/pages/HomePage.tsx +++ b/packages/demobank-ui/src/pages/HomePage.tsx @@ -31,14 +31,11 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { Loading } from "../components/Loading.js"; -import { useBackendContext } from "../context/backend.js"; import { getInitialBackendBaseURL } from "../hooks/backend.js"; import { useSettings } from "../hooks/settings.js"; import { AccountPage } from "./AccountPage/index.js"; -import { AdminHome } from "./admin/Home.js"; import { LoginForm } from "./LoginForm.js"; import { WithdrawalQRCode } from "./WithdrawalQRCode.js"; -import { error } from "console"; const logger = new Logger("AccountPage"); @@ -56,10 +53,12 @@ export function HomePage({ onRegister, account, onPendingOperationFound, + goToBusinessAccount, }: { account: string, onPendingOperationFound: (id: string) => void; onRegister: () => void; + goToBusinessAccount: () => void; }): VNode { const [settings] = useSettings(); const { i18n } = useTranslationContext(); @@ -72,6 +71,7 @@ export function HomePage({ return ( ); diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index c82c1b28d..5cb09a5d4 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -33,76 +33,79 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode { const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(); // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined); - return (
    - - Send money to - + return ( +
    + + Send money to + -
    - {/* */} - +
    + {tab === "charge-wallet" && ( + { + updateSettings("currentWithdrawalOperationId", id); + }} + onCancel={() => { + setTab(undefined) + }} + /> + )} + {tab === "wire-transfer" && ( + { + notifyInfo(i18n.str`Wire transfer created!`); + }} + onCancel={() => { + setTab(undefined) + }} + /> + )} -
    ) +
    + ) } diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index cdaf363e3..af6f7caee 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -44,10 +44,12 @@ const logger = new Logger("PaytoWireTransferForm"); export function PaytoWireTransferForm({ focus, + title, onSuccess, onCancel, limit, }: { + title: TranslatedString, focus?: boolean; onSuccess: () => void; onCancel: (() => void) | undefined; @@ -158,7 +160,9 @@ export function PaytoWireTransferForm({ return (
    -

    Transfer details

    +

    + {title} +