Path: blob/master/src/packages/frontend/account/settings/email-address-setting.tsx
6055 views
/*1* This file is part of CoCalc: Copyright © 2026 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { Button, Input, Modal, Space } from "antd";6import { useState } from "react";7import { defineMessage, FormattedMessage, useIntl } from "react-intl";89import { alert_message } from "@cocalc/frontend/alerts";10import { ErrorDisplay, LabeledRow, Saving } from "@cocalc/frontend/components";11import { labels } from "@cocalc/frontend/i18n";12import { log } from "@cocalc/frontend/user-tracking";13import { webapp_client } from "@cocalc/frontend/webapp-client";14import { COLORS } from "@cocalc/util/theme";15import { MIN_PASSWORD_LENGTH } from "@cocalc/util/auth";1617const sendWelcomeEmailError = defineMessage({18id: "account.settings.email_address.send_welcome_email_error",19defaultMessage: "Problem sending welcome email: {error}",20description:21"Error shown when the welcome or verification email could not be sent.",22});2324const passwordTooShortError = defineMessage({25id: "account.settings.email_address.password_too_short_error",26defaultMessage: "Password must be at least {length} characters long.",27description: "Shown when the entered password is too short.",28});2930interface Props {31email_address?: string;32disabled?: boolean;33is_anonymous?: boolean;34verify_emails?: boolean;35}3637export const EmailAddressSetting = ({38email_address: email_address0,39disabled,40is_anonymous,41verify_emails,42}: Props) => {43const intl = useIntl();44const [state, setState] = useState<"view" | "edit" | "saving">("view");45const [password, setPassword] = useState<string>("");46const [email_address, set_email_address] = useState<string>(47email_address0 ?? "",48);49const [error, setError] = useState<string>("");50const savedEmailAddress = email_address0 ?? "";5152function start_editing() {53setState("edit");54set_email_address(savedEmailAddress);55setError("");56setPassword("");57}5859function cancel_editing() {60setState("view");61set_email_address(savedEmailAddress);62setPassword("");63}6465async function save_editing(): Promise<void> {66if (password.length < MIN_PASSWORD_LENGTH) {67setState("edit");68setError(69intl.formatMessage(passwordTooShortError, {70length: MIN_PASSWORD_LENGTH,71}),72);73return;74}75setState("saving");76try {77await webapp_client.account_client.change_email(email_address, password);78} catch (error) {79setState("edit");80setError(`Error -- ${error}`);81return;82}83if (is_anonymous) {84log("email_sign_up", { source: "anonymous_account" });85}86setState("view");87setError("");88setPassword("");89// if email verification is enabled, send out a token90// in any case, send a welcome email to an anonymous user, possibly91// including an email verification link92if (!(verify_emails || is_anonymous)) {93return;94}95try {96// Do a round of UI refresh, because maybe the "verify email" dialog will pop up97await new Promise((resolve) => {98setTimeout(resolve, 0);99});100// anonymous users will get the "welcome" email101await webapp_client.account_client.send_verification_email(!is_anonymous);102} catch (error) {103const err_msg = intl.formatMessage(sendWelcomeEmailError, {104error: String(error),105});106console.log(err_msg);107alert_message({ type: "error", message: err_msg });108}109}110111function is_submittable(): boolean {112return !!(password !== "" && email_address !== savedEmailAddress);113}114115function render_error() {116if (error) {117return (118<ErrorDisplay119error={error}120onClose={() => setError("")}121style={{ marginTop: "15px" }}122/>123);124}125}126127function render_edit() {128const password_label = intl.formatMessage(129{130id: "account.settings.email_address.password_label",131defaultMessage:132"{have_email, select, true {Current password} other {Choose a password}}",133},134{135have_email: savedEmailAddress !== "",136},137);138return (139<Modal140closable={state !== "saving"}141footer={null}142onCancel={state !== "saving" ? cancel_editing : undefined}143open={state !== "view"}144title={button_label()}145width={520}146>147<div style={{ marginBottom: "15px" }}>148<FormattedMessage149id="account.settings.email_address.new_email_address_label"150defaultMessage="New email address"151/>152<Input153autoFocus154placeholder="[email protected]"155value={email_address}156onChange={(e) => {157set_email_address(e.target.value);158}}159maxLength={254}160/>161</div>162{password_label}163<Input.Password164value={password}165placeholder={password_label}166onChange={(e) => {167const pw = e.target.value;168if (pw != null) {169setPassword(pw);170}171}}172onPressEnter={() => {173if (is_submittable()) {174return save_editing();175}176}}177/>178<Space style={{ marginTop: "15px" }}>179<Button onClick={cancel_editing} disabled={state === "saving"}>180{intl.formatMessage(labels.cancel)}181</Button>182<Button183disabled={!is_submittable() || state === "saving"}184loading={state === "saving"}185onClick={save_editing}186type="primary"187>188{button_label()}189</Button>190{render_saving()}191</Space>192{render_error()}193</Modal>194);195}196197function render_saving() {198if (state === "saving") {199return <Saving />;200}201}202203function button_label(): string {204return intl.formatMessage(205{206id: "account.settings.email_address.button_label",207defaultMessage: `{type, select,208anonymous {Sign up using an email address and password}209have_email {Change email address}210other {Set email address and password}}`,211},212{213type: is_anonymous214? "anonymous"215: savedEmailAddress216? "have_email"217: "",218},219);220}221222const label = is_anonymous ? (223<h5 style={{ color: COLORS.GRAY_M }}>224Sign up using an email address and password225</h5>226) : (227intl.formatMessage(labels.email_address)228);229230return (231<LabeledRow232label={label}233style={disabled ? { color: COLORS.GRAY_M } : undefined}234>235<div>236{savedEmailAddress}237{state === "view" ? (238<Button239disabled={disabled}240className="pull-right"241onClick={start_editing}242>243{button_label()}...244</Button>245) : undefined}246</div>247{state !== "view" ? render_edit() : undefined}248</LabeledRow>249);250};251252253