diff options
Diffstat (limited to 'packages/bank-ui/src/pages/account/ShowAccountDetails.tsx')
-rw-r--r-- | packages/bank-ui/src/pages/account/ShowAccountDetails.tsx | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/packages/bank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/bank-ui/src/pages/account/ShowAccountDetails.tsx new file mode 100644 index 000000000..62c8df7f8 --- /dev/null +++ b/packages/bank-ui/src/pages/account/ShowAccountDetails.tsx @@ -0,0 +1,244 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +import { + AbsoluteTime, + HttpStatusCode, + TalerCorebankApi, + TalerError, + TalerErrorCode, + TranslatedString, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { + Loading, + LocalNotificationBanner, + notifyInfo, + useLocalNotification, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { useState } from "preact/hooks"; +import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; +import { useBankCoreApiContext } from "../../context/config.js"; +import { useAccountDetails } from "../../hooks/account.js"; +import { useSessionState } from "../../hooks/session.js"; +import { useBankState } from "../../hooks/bank-state.js"; +import { RouteDefinition } from "../../route.js"; +import { LoginForm } from "../LoginForm.js"; +import { ProfileNavigation } from "../ProfileNavigation.js"; +import { AccountForm } from "../admin/AccountForm.js"; + +export function ShowAccountDetails({ + account, + routeClose, + onUpdateSuccess, + onAuthorizationRequired, + routeMyAccountCashout, + routeMyAccountDelete, + routeMyAccountDetails, + routeHere, + routeMyAccountPassword, + routeConversionConfig, +}: { + routeClose: RouteDefinition; + routeHere: RouteDefinition<{ account: string }>; + routeMyAccountDetails: RouteDefinition; + routeMyAccountDelete: RouteDefinition; + routeMyAccountPassword: RouteDefinition; + routeMyAccountCashout: RouteDefinition; + routeConversionConfig: RouteDefinition; + onUpdateSuccess: () => void; + onAuthorizationRequired: () => void; + account: string; +}): VNode { + const { i18n } = useTranslationContext(); + const { state: credentials } = useSessionState(); + const creds = credentials.status !== "loggedIn" ? undefined : credentials; + const { bank: api } = useBankCoreApiContext(); + const accountIsTheCurrentUser = + credentials.status === "loggedIn" + ? credentials.username === account + : false; + + const [submitAccount, setSubmitAccount] = useState< + TalerCorebankApi.AccountReconfiguration | undefined + >(); + const [notification, notify, handleError] = useLocalNotification(); + const [, updateBankState] = useBankState(); + + const result = useAccountDetails(account); + if (!result) { + return <Loading />; + } + if (result instanceof TalerError) { + return <ErrorLoadingWithDebug error={result} />; + } + if (result.type === "fail") { + switch (result.case) { + case HttpStatusCode.Unauthorized: + case HttpStatusCode.NotFound: + return <LoginForm currentUser={account} />; + default: + assertUnreachable(result); + } + } + + async function doUpdate() { + if (!submitAccount || !creds) return; + await handleError(async () => { + const resp = await api.updateAccount( + { + token: creds.token, + username: account, + }, + submitAccount, + ); + + if (resp.type === "ok") { + notifyInfo(i18n.str`Account updated`); + onUpdateSuccess(); + } else { + switch (resp.case) { + case HttpStatusCode.Unauthorized: + return notify({ + type: "error", + title: i18n.str`The rights to change the account are not sufficient`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + case HttpStatusCode.NotFound: + return notify({ + type: "error", + title: i18n.str`The username was not found`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME: + return notify({ + type: "error", + title: i18n.str`You can't change the legal name, please contact the your account administrator.`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: + return notify({ + type: "error", + title: i18n.str`You can't change the debt limit, please contact the your account administrator.`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: + return notify({ + type: "error", + title: i18n.str`You can't change the cashout address, please contact the your account administrator.`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + case TalerErrorCode.BANK_MISSING_TAN_INFO: + return notify({ + type: "error", + title: i18n.str`No information for the selected authentication channel.`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + case HttpStatusCode.Accepted: { + updateBankState("currentChallenge", { + operation: "update-account", + id: String(resp.body.challenge_id), + location: routeHere.url({ account }), + sent: AbsoluteTime.never(), + request: submitAccount, + }); + return onAuthorizationRequired(); + } + case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: { + return notify({ + type: "error", + title: i18n.str`Authentication channel is not supported.`, + description: resp.detail.hint as TranslatedString, + debug: resp.detail, + }); + } + default: + assertUnreachable(resp); + } + } + }); + } + + return ( + <Fragment> + <LocalNotificationBanner notification={notification} showDebug={true} /> + {accountIsTheCurrentUser ? ( + <ProfileNavigation current="details" + routeMyAccountCashout={routeMyAccountCashout} + routeMyAccountDelete={routeMyAccountDelete} + routeConversionConfig={routeConversionConfig} + routeMyAccountDetails={routeMyAccountDetails} + routeMyAccountPassword={routeMyAccountPassword} + /> + ) : ( + <h1 class="text-base font-semibold leading-6 text-gray-900"> + <i18n.Translate>Account "{account}"</i18n.Translate> + </h1> + )} + + <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg"> + <div class="px-4 sm:px-0"> + <h2 class="text-base font-semibold leading-7 text-gray-900"> + <div class="flex items-center justify-between"> + <span class="flex flex-grow flex-col"> + <span + class="text-sm text-black font-semibold leading-6 " + id="availability-label" + > + <i18n.Translate>Change details</i18n.Translate> + </span> + </span> + </div> + </h2> + </div> + + <AccountForm + focus={true} + username={account} + template={result.body} + purpose="update" + onChange={(a) => setSubmitAccount(a)} + > + <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> + <a + href={routeClose.url({})} + name="cancel" + class="text-sm font-semibold leading-6 text-gray-900" + > + <i18n.Translate>Cancel</i18n.Translate> + </a> + <button + type="submit" + name="update" + class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + disabled={!submitAccount} + onClick={doUpdate} + > + <i18n.Translate>Update</i18n.Translate> + </button> + </div> + </AccountForm> + </div> + </Fragment> + ); +} |