5252 :key =" field.name"
5353 class =" flex flex-col gap-1"
5454 >
55+ <div
56+ v-if =" field.name === 'domain'"
57+ class =" flex flex-col gap-2"
58+ >
59+ <Link
60+ v-model =" customState.domain"
61+ :label =" field.label"
62+ :placeholder =" field.placeholder"
63+ doctype =" Email Domain"
64+ :onCreate =" handleCreateDomainClick"
65+ />
66+ <div v-if =" customState.domain" class =" flex gap-2" >
67+ <Button
68+ size =" sm"
69+ variant =" subtle"
70+ theme =" gray"
71+ :label =" __('Edit Domain')"
72+ @click =" handleEditDomainClick"
73+ />
74+ </div >
75+ </div >
5576 <FormControl
77+ v-else
5678 v-model =" state[field.name]"
5779 :label =" field.label"
5880 :name =" field.name"
6183 />
6284 </div >
6385 </div >
86+ <div
87+ v-if =" isCustomSelected"
88+ class =" grid grid-cols-1 md:grid-cols-2 gap-4"
89+ >
90+ <div class =" flex flex-col gap-4" >
91+ <div
92+ v-for =" field in customIncomingFields"
93+ :key =" field.name"
94+ class =" flex flex-col gap-1"
95+ >
96+ <FormControl
97+ v-model =" customState[field.name]"
98+ :label =" field.label"
99+ :name =" field.name"
100+ :type =" field.type"
101+ :placeholder =" field.placeholder"
102+ />
103+ </div >
104+ </div >
105+ <div class =" flex flex-col gap-4" >
106+ <div
107+ v-for =" field in customOutgoingFields"
108+ :key =" field.name"
109+ class =" flex flex-col gap-1"
110+ >
111+ <FormControl
112+ v-model =" customState[field.name]"
113+ :label =" field.label"
114+ :name =" field.name"
115+ :type =" field.type"
116+ :placeholder =" field.placeholder"
117+ />
118+ </div >
119+ </div >
120+ </div >
64121 <div class =" grid grid-cols-2 gap-4" >
65122 <div
66123 v-for =" field in incomingOutgoingFields"
@@ -109,10 +166,13 @@ import { capture } from "@/telemetry";
109166import { EmailAccount , EmailService , EmailStep } from " @/types" ;
110167import { call , createResource , toast } from " frappe-ui" ;
111168import { useOnboarding } from " frappe-ui/frappe" ;
112- import { computed , Reactive , reactive , Ref , ref } from " vue" ;
169+ import { computed , reactive , Ref , ref , watch } from " vue" ;
113170import CircleAlert from " ~icons/lucide/circle-alert" ;
114171import {
115- customProviderFields ,
172+ customIncomingFields ,
173+ customOutgoingFields ,
174+ customProviderTopFields ,
175+ frappeMailFields ,
116176 incomingOutgoingFields ,
117177 popularProviderFields ,
118178 services ,
@@ -125,11 +185,45 @@ interface E {
125185 (event : " update:step" , value : EmailStep ): void ;
126186}
127187
188+ interface EmailAccountBaseState {
189+ email_account_name: string ;
190+ email_id: string ;
191+ service: string ;
192+ enable_incoming: boolean ;
193+ enable_outgoing: boolean ;
194+ default_incoming: boolean ;
195+ default_outgoing: boolean ;
196+ }
197+
198+ interface EmailAccountProviderAuthState extends EmailAccountBaseState {
199+ password? : string ;
200+ api_key? : string ;
201+ api_secret? : string ;
202+ frappe_mail_site? : string ;
203+ }
204+
205+ type CustomEmailAccountState = {
206+ domain? : string ;
207+ email_server? : string ;
208+ incoming_port? : string | number ;
209+ smtp_server? : string ;
210+ smtp_port? : string | number ;
211+ use_ssl? : boolean | number ;
212+ use_starttls? : boolean | number ;
213+ use_tls? : boolean | number ;
214+ use_ssl_for_outgoing? : boolean | number ;
215+ validate_ssl_certificate? : boolean | number ;
216+ validate_ssl_certificate_for_outgoing? : boolean | number ;
217+ attachment_limit? : string | number ;
218+ append_emails_to_sent_folder? : boolean | number ;
219+ sent_folder_name? : string ;
220+ };
221+
128222const emit = defineEmits <E >();
129223
130224const { updateOnboardingStep } = useOnboarding (" helpdesk" );
131225
132- const state: Reactive < EmailAccount > = reactive ({
226+ const state = reactive < EmailAccountProviderAuthState > ({
133227 service: " " ,
134228 email_account_name: " " ,
135229 email_id: " " ,
@@ -143,23 +237,74 @@ const state: Reactive<EmailAccount> = reactive({
143237 default_outgoing: false ,
144238});
145239
146- const selectedService: Ref <EmailService > = ref (null );
147- const fields = computed (() =>
148- selectedService .value .custom ? customProviderFields : popularProviderFields
240+ const getDefaultCustomState = (): CustomEmailAccountState => ({
241+ domain: " " ,
242+ email_server: " " ,
243+ smtp_server: " " ,
244+ incoming_port: " " ,
245+ smtp_port: " " ,
246+ use_ssl: false ,
247+ use_starttls: false ,
248+ use_tls: false ,
249+ use_ssl_for_outgoing: false ,
250+ validate_ssl_certificate: true ,
251+ validate_ssl_certificate_for_outgoing: true ,
252+ attachment_limit: " " ,
253+ append_emails_to_sent_folder: false ,
254+ sent_folder_name: " " ,
255+ });
256+
257+ const customState = reactive <CustomEmailAccountState >(getDefaultCustomState ());
258+
259+ function resetCustomState() {
260+ Object .assign (customState , getDefaultCustomState ());
261+ }
262+
263+ const selectedService: Ref <EmailService | null > = ref (null );
264+ const fields = computed (() => {
265+ if (! selectedService .value ) return [];
266+ if (selectedService .value .name === " Frappe Mail" ) {
267+ return frappeMailFields ;
268+ }
269+ if (selectedService .value .custom ) {
270+ return customProviderTopFields ;
271+ }
272+ return popularProviderFields ;
273+ });
274+
275+ const isCustomSelected = computed (
276+ () => selectedService .value ?.name === " Custom"
277+ );
278+
279+ watch (
280+ () => customState .domain ,
281+ async (val ) => {
282+ if (state .service !== " Custom" ) return ;
283+ if (! isCustomSelected .value || ! val ) return ;
284+ try {
285+ const doc = await call (" frappe.client.get" , {
286+ doctype: " Email Domain" ,
287+ name: val ,
288+ });
289+ syncAccountFieldsFromDomain (doc );
290+ } catch (err ) {
291+ console .warn (" Failed to load Email Domain" , err );
292+ }
293+ }
149294);
150295
151296function handleSelect(service : EmailService ) {
152297 selectedService .value = service ;
153298 state .service = service .name ;
299+ if (service .name !== " Custom" ) {
300+ resetCustomState ();
301+ }
154302}
155303
156304const addEmailRes = createResource ({
157305 url: " helpdesk.api.settings.email.create_email_account" ,
158- makeParams : (val : EmailAccount ) => {
159- return {
160- ... val ,
161- };
162- },
306+ makeParams : (val : EmailAccount ) => ({ ... val }),
307+
163308 onSuccess : () => {
164309 toast .success (__ (" Email account created" ));
165310 emit (" update:step" , " email-list" );
@@ -173,10 +318,94 @@ const addEmailRes = createResource({
173318
174319const error = ref <string | undefined >();
175320function createEmailAccount() {
176- error .value = validateInputs (state , selectedService .value .custom );
321+ const validationState = { ... state , ... customState };
322+ error .value = validateInputs (validationState , state .service );
177323 if (error .value ) return ;
178324
179- addEmailRes .submit ({ data: state });
325+ addEmailRes .submit ({ data: buildCreatePayload () });
326+ }
327+
328+ function buildCreatePayload() {
329+ const commonPayload = {
330+ email_account_name: state .email_account_name ,
331+ email_id: state .email_id ,
332+ service: state .service ,
333+ enable_incoming: state .enable_incoming ,
334+ enable_outgoing: state .enable_outgoing ,
335+ default_incoming: state .default_incoming ,
336+ default_outgoing: state .default_outgoing ,
337+ };
338+
339+ if (state .service === " Frappe Mail" ) {
340+ return {
341+ ... commonPayload ,
342+ frappe_mail_site: state .frappe_mail_site ,
343+ api_key: state .api_key ,
344+ api_secret: state .api_secret ,
345+ };
346+ }
347+
348+ if (state .service === " Custom" ) {
349+ return {
350+ ... commonPayload ,
351+ password: state .password ,
352+ ... customState ,
353+ };
354+ }
355+
356+ return {
357+ ... commonPayload ,
358+ password: state .password ,
359+ };
360+ }
361+
362+ function syncAccountFieldsFromDomain(payload : Record <string , any >) {
363+ customState .email_server = payload .email_server ?? customState .email_server ;
364+ customState .smtp_server = payload .smtp_server ?? customState .smtp_server ;
365+ customState .incoming_port =
366+ payload .incoming_port ?? customState .incoming_port ;
367+ customState .smtp_port = payload .smtp_port ?? customState .smtp_port ;
368+ customState .use_ssl = Boolean (payload .use_ssl ?? customState .use_ssl );
369+ customState .use_starttls = Boolean (
370+ payload .use_starttls ?? customState .use_starttls
371+ );
372+ customState .use_tls = Boolean (payload .use_tls ?? customState .use_tls );
373+ customState .use_ssl_for_outgoing = Boolean (
374+ payload .use_ssl_for_outgoing ?? customState .use_ssl_for_outgoing
375+ );
376+ customState .validate_ssl_certificate = Boolean (
377+ payload .validate_ssl_certificate ?? customState .validate_ssl_certificate
378+ );
379+ customState .validate_ssl_certificate_for_outgoing = Boolean (
380+ payload .validate_ssl_certificate_for_outgoing ??
381+ customState .validate_ssl_certificate_for_outgoing
382+ );
383+ customState .append_emails_to_sent_folder = Boolean (
384+ payload .append_emails_to_sent_folder ??
385+ customState .append_emails_to_sent_folder
386+ );
387+ customState .sent_folder_name =
388+ payload .sent_folder_name || customState .sent_folder_name ;
389+ customState .attachment_limit =
390+ payload .attachment_limit || customState .attachment_limit ;
391+ }
392+
393+ function handleCreateDomainClick(value : any , close ? : () => void ) {
394+ close ?.();
395+ window .open (
396+ " /desk/email-domain/new-email-domain" ,
397+ " _blank" ,
398+ " noopener,noreferrer"
399+ );
400+ }
401+
402+ function handleEditDomainClick() {
403+ if (! customState .domain ) return ;
404+ window .open (
405+ ` /desk/email-domain/${encodeURIComponent (customState .domain )} ` ,
406+ " _blank" ,
407+ " noopener,noreferrer"
408+ );
180409}
181410 </script >
182411
0 commit comments