1 import { useRef, useState } from 'react';
3 import { c } from 'ttag';
5 import { useUser } from '@proton/account/user/hooks';
6 import { Button } from '@proton/atoms';
7 import Editor from '@proton/components/components/editor/Editor';
8 import { useToolbar } from '@proton/components/components/editor/hooks/useToolbar';
9 import type { EditorActions } from '@proton/components/components/editor/interface';
10 import Toggle from '@proton/components/components/toggle/Toggle';
11 import SettingsLayout from '@proton/components/containers/account/SettingsLayout';
12 import SettingsLayoutLeft from '@proton/components/containers/account/SettingsLayoutLeft';
13 import SettingsLayoutRight from '@proton/components/containers/account/SettingsLayoutRight';
14 import SettingsParagraph from '@proton/components/containers/account/SettingsParagraph';
15 import SettingsSectionWide from '@proton/components/containers/account/SettingsSectionWide';
16 import useApi from '@proton/components/hooks/useApi';
17 import useErrorHandler from '@proton/components/hooks/useErrorHandler';
18 import useEventManager from '@proton/components/hooks/useEventManager';
19 import useNotifications from '@proton/components/hooks/useNotifications';
20 import { useLoading } from '@proton/hooks';
21 import { useMailSettings } from '@proton/mail/mailSettings/hooks';
22 import { PLANS, PLAN_NAMES } from '@proton/payments';
23 import { updateAutoresponder } from '@proton/shared/lib/api/mailSettings';
30 } from '@proton/shared/lib/constants';
31 import { getUpsellRef } from '@proton/shared/lib/helpers/upsell';
32 import { DEFAULT_MAILSETTINGS } from '@proton/shared/lib/mail/mailSettings';
33 import { removeImagesFromContent } from '@proton/shared/lib/sanitize/purify';
35 import { useHotkeys } from '../../hooks/useHotkeys';
36 import UpgradeBanner from '../account/UpgradeBanner';
37 import AutoReplyFormDaily from './AutoReplyForm/AutoReplyFormDaily';
38 import AutoReplyFormFixed from './AutoReplyForm/AutoReplyFormFixed';
39 import AutoReplyFormMonthly from './AutoReplyForm/AutoReplyFormMonthly';
40 import AutoReplyFormPermanent from './AutoReplyForm/AutoReplyFormPermanent';
41 import AutoReplyFormWeekly from './AutoReplyForm/AutoReplyFormWeekly';
42 import DurationField from './AutoReplyForm/fields/DurationField';
43 import useAutoReplyForm, { getDefaultAutoResponder } from './AutoReplyForm/useAutoReplyForm';
45 const AUTO_REPLY_MAX_LENGTH = 4096;
47 export const AutoReplySection = () => {
48 const errorHandler = useErrorHandler();
49 const [{ hasPaidMail }] = useUser();
50 const [mailSettings] = useMailSettings();
51 const { Shortcuts } = mailSettings || DEFAULT_MAILSETTINGS;
52 const AutoResponder = mailSettings?.AutoResponder || getDefaultAutoResponder();
54 const { call } = useEventManager();
55 const [enablingLoading, withEnablingLoading] = useLoading();
56 const [updatingLoading, withUpdatingLoading] = useLoading();
57 const [isEnabled, setIsEnabled] = useState(AutoResponder.IsEnabled);
58 const { createNotification } = useNotifications();
59 const { model, updateModel, toAutoResponder } = useAutoReplyForm(AutoResponder);
61 const editorActionsRef = useRef<EditorActions>();
62 const composerRef = useRef<HTMLDivElement>(null);
64 const messageLimitReached = model.message.length > AUTO_REPLY_MAX_LENGTH;
66 const handleToggle = async (enable: boolean) => {
68 const error: any = new Error(
69 c('Error').t`Automatic replies is a paid feature. Please upgrade to a paid account to use this feature.`
77 const isDisablingExistingAutoResponder =
78 !enable && mailSettings?.AutoResponder && mailSettings?.AutoResponder.IsEnabled;
80 if (enable || !isDisablingExistingAutoResponder) {
85 ...updateAutoresponder({ ...AutoResponder, IsEnabled: enable }),
90 text: c('Success').t`Auto-reply disabled`,
94 const handleSubmit = async () => {
95 // Remove images from the composer in autoreply
96 const { message, containsImages } = removeImagesFromContent(model.message);
100 text: c('Info').t`Images have been removed because they are not allowed in auto-reply`,
103 updateModel('message').bind(message);
104 // update the composer to remove the image from it
105 if (editorActionsRef.current) {
106 editorActionsRef.current.setContent(model.message);
110 await api(updateAutoresponder(toAutoResponder({ ...model, message })));
112 createNotification({ text: c('Success').t`Auto-reply updated` });
115 const handleEditorReady = (actions: EditorActions) => {
116 editorActionsRef.current = actions;
117 actions.setContent(model.message);
120 const formRenderer = (duration: AutoReplyDuration) => {
122 case AutoReplyDuration.FIXED:
123 return <AutoReplyFormFixed model={model} updateModel={updateModel} />;
124 case AutoReplyDuration.DAILY:
125 return <AutoReplyFormDaily model={model} updateModel={updateModel} />;
126 case AutoReplyDuration.MONTHLY:
127 return <AutoReplyFormMonthly model={model} updateModel={updateModel} />;
128 case AutoReplyDuration.WEEKLY:
129 return <AutoReplyFormWeekly model={model} updateModel={updateModel} />;
130 case AutoReplyDuration.PERMANENT:
131 return <AutoReplyFormPermanent />;
137 useHotkeys(composerRef, [
142 await withUpdatingLoading(handleSubmit());
148 const { openEmojiPickerRef, toolbarConfig, setToolbarConfig, modalLink, modalImage, modalDefaultFont } = useToolbar(
152 const plus = PLAN_NAMES[PLANS.MAIL];
153 const bundle = PLAN_NAMES[PLANS.BUNDLE];
155 const upsellRef = getUpsellRef({
156 app: APP_UPSELL_REF_PATH.MAIL_UPSELL_REF_PATH,
157 component: UPSELL_COMPONENT.BANNER,
158 feature: MAIL_UPSELL_PATHS.AUTO_REPLY,
162 <SettingsSectionWide className="overflow-hidden">
163 <SettingsParagraph className="mt-0 mb-4">
164 {c('new_plans: info')
165 .t`Set automatic replies to inform senders you are out of the office or unable to respond.`}
170 <label htmlFor="autoReplyToggle" className="text-semibold">
171 {c('Label').t`Auto-reply`}
173 </SettingsLayoutLeft>
174 <SettingsLayoutRight isToggleContainer>
177 loading={enablingLoading}
179 onChange={({ target: { checked } }) =>
180 withEnablingLoading(handleToggle(checked).catch(errorHandler))
183 </SettingsLayoutRight>
189 onSubmit={async (e) => {
191 await withUpdatingLoading(handleSubmit());
194 <DurationField value={model.duration} onChange={updateModel('duration')} />
196 {formRenderer(model.duration)}
200 <label className="text-semibold" onClick={() => editorActionsRef.current?.focus()}>
201 {c('Label').t`Message`}
203 </SettingsLayoutLeft>
204 <SettingsLayoutRight>
205 <div ref={composerRef} tabIndex={-1} className="w-full">
207 metadata={{ supportImages: false }}
208 onReady={handleEditorReady}
209 onChange={updateModel('message')}
211 openEmojiPickerRef={openEmojiPickerRef}
212 toolbarConfig={toolbarConfig}
213 setToolbarConfig={setToolbarConfig}
214 modalLink={modalLink}
215 modalImage={modalImage}
216 modalDefaultFont={modalDefaultFont}
217 mailSettings={mailSettings}
220 {messageLimitReached && (
221 <p className="mt-1 mb-0 color-danger">{c('Error')
222 .t`Auto-reply message exceeds the allowed length. Please shorten it to fit within the limit.`}</p>
228 disabled={updatingLoading || messageLimitReached}
229 loading={updatingLoading}
232 {c('Action').t`Save`}
234 </SettingsLayoutRight>
239 <UpgradeBanner className="mt-8" upsellPath={upsellRef}>
240 {c('new_plans: upgrade').t`Included with ${plus}, ${bundle}, and ${BRAND_NAME} for Business.`}
243 </SettingsSectionWide>