1 import { useState } from 'react';
3 import { c } from 'ttag';
5 import { Button, Href } from '@proton/atoms';
6 import Copy from '@proton/components/components/button/Copy';
7 import PrimaryButton from '@proton/components/components/button/PrimaryButton';
8 import Form from '@proton/components/components/form/Form';
9 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
10 import ModalTwo from '@proton/components/components/modalTwo/Modal';
11 import ModalTwoContent from '@proton/components/components/modalTwo/ModalContent';
12 import ModalTwoFooter from '@proton/components/components/modalTwo/ModalFooter';
13 import ModalTwoHeader from '@proton/components/components/modalTwo/ModalHeader';
14 import Option from '@proton/components/components/option/Option';
15 import SelectTwo from '@proton/components/components/selectTwo/SelectTwo';
16 import InputFieldTwo from '@proton/components/components/v2/field/InputField';
17 import useFormErrors from '@proton/components/components/v2/useFormErrors';
18 import useApi from '@proton/components/hooks/useApi';
19 import useNotifications from '@proton/components/hooks/useNotifications';
20 import { useLoading } from '@proton/hooks';
21 import { createToken } from '@proton/shared/lib/api/smtptokens';
22 import { ADDRESS_TYPE } from '@proton/shared/lib/constants';
23 import { maxLengthValidator, requiredValidator } from '@proton/shared/lib/helpers/formValidators';
24 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
25 import type { Address } from '@proton/shared/lib/interfaces';
26 import noop from '@proton/utils/noop';
28 import './SMTPTokenModal.scss';
35 const SMTP_SERVER = 'smtp.protonmail.ch';
36 const SMTP_PORT = '587';
38 interface Props extends ModalProps {
40 onCreate: () => void; // fetch new tokens
43 const SMTPTokenModal = ({ addresses, onCreate, ...rest }: Props) => {
44 const { onClose } = rest;
45 const { validator, onFormSubmit } = useFormErrors();
46 const [step, setStep] = useState(Steps.TokenForm);
47 const [loading, withLoading] = useLoading();
48 const { createNotification } = useNotifications();
49 const [tokenName, setTokenName] = useState('');
50 const [token, setToken] = useState('');
51 // This modal can be open only if it has at least one custom address
52 const customAddresses = addresses.filter(({ Type }) => Type === ADDRESS_TYPE.TYPE_CUSTOM_DOMAIN);
53 const [addressID, setAddressID] = useState(customAddresses[0].ID);
55 const title = step === Steps.TokenForm ? c('Title').t`Generate SMTP token` : c('Title').t`Your SMTP token`;
56 const emailAddress = addresses.find(({ ID }) => ID === addressID)?.Email || '';
58 const handleClose = loading ? noop : onClose;
60 const handleSubmit = async () => {
61 if (step === Steps.TokenForm) {
62 if (loading || !onFormSubmit()) {
65 const { SmtpTokenCode } = await api(createToken(addressID, tokenName));
66 setToken(SmtpTokenCode);
67 setStep(Steps.TokenValue);
72 const handleCopyEmail = () => {
73 createNotification({ type: 'success', text: c('Success').t`Email address copied to clipboard` });
76 const handleCopyToken = () => {
77 createNotification({ type: 'success', text: c('Success').t`Token copied to clipboard` });
80 const handleCopyServer = () => {
81 createNotification({ type: 'success', text: c('Success').t`Server copied to clipboard` });
84 const handleCopyPort = () => {
85 createNotification({ type: 'success', text: c('Success').t`Port copied to clipboard` });
88 const content = () => {
89 if (step === Steps.TokenForm) {
93 .t`Give the token a descriptive name, such as the service you plan to use it with, and select one of your active custom domain addresses. Only this address will be able to send emails with this token.`}</p>
95 label={c('Label').t`Token name`}
99 error={validator([requiredValidator(tokenName), maxLengthValidator(tokenName, 100)])}
100 placeholder={c('Placeholder').t`Printer`}
102 onValue={(value: string) => setTokenName(value)}
109 label={c('Label').t`Email address`}
111 onValue={(value: unknown) => setAddressID(value as string)}
114 {customAddresses.map(({ ID, Email }) => (
115 <Option key={ID} value={ID} title={Email} />
125 .t`Use the selected email address as the SMTP username in the external service, and the generated token as the SMTP password.`}
127 <Href href={getKnowledgeBaseUrl('/smtp-submission')}>{c('Link').t`Learn more`}</Href>
129 <div className="flex items-center flex-nowrap mb-4">
132 label={c('Label').t`SMTP username`}
135 inputClassName="bg-weak"
141 className="smtp-token-copy relative shrink-0 ml-2"
142 onCopy={handleCopyEmail}
145 <div className="flex items-center flex-nowrap mb-4">
148 label={c('Label').t`SMTP token`}
151 inputClassName="bg-weak"
157 className="smtp-token-copy relative shrink-0 ml-2"
158 onCopy={handleCopyToken}
161 <p className="color-danger">{c('Info')
162 .t`This token won’t be available after you close this window, and you should not share it with anyone.`}</p>
163 <div className="flex items-center flex-nowrap mb-4">
166 label={c('Label').t`SMTP server`}
169 inputClassName="bg-weak"
175 className="smtp-token-copy relative shrink-0 ml-2"
176 onCopy={handleCopyServer}
179 <div className="flex items-center flex-nowrap mb-4">
182 label={c('Label').t`SMTP port`}
185 inputClassName="bg-weak"
191 className="smtp-token-copy relative shrink-0 ml-2"
192 onCopy={handleCopyPort}
195 <p>{c('Info').t`Enable TLS or SSL on the external service if it is supported.`}</p>
200 const footer = () => {
201 if (step === Steps.TokenForm) {
204 <Button onClick={handleClose}>{c('Action').t`Cancel`}</Button>
205 <PrimaryButton type="submit" loading={loading}>
206 {c('Action').t`Generate`}
213 <Button shape="outline" color="norm" className="ml-auto" onClick={handleClose}>{c('Action')
220 <ModalTwo as={Form} onSubmit={() => withLoading(handleSubmit())} onClose={handleClose} {...rest}>
221 <ModalTwoHeader closeButtonProps={{ disabled: loading }} title={title} />
222 <ModalTwoContent>{content()}</ModalTwoContent>
223 <ModalTwoFooter>{footer()}</ModalTwoFooter>
228 export default SMTPTokenModal;