From 67a76d2f3f5a5809fddd04028126c87c74953b10 Mon Sep 17 00:00:00 2001 From: saknarajapakshe Date: Wed, 15 Jul 2026 11:53:47 +0530 Subject: [PATCH] feat: implement AddAccountForm component for adding email accounts --- src/view/moles/AddAccountForm.tsx | 399 ++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 src/view/moles/AddAccountForm.tsx diff --git a/src/view/moles/AddAccountForm.tsx b/src/view/moles/AddAccountForm.tsx new file mode 100644 index 0000000..79e2213 --- /dev/null +++ b/src/view/moles/AddAccountForm.tsx @@ -0,0 +1,399 @@ +import React, { useState } from "react"; +import { + Box, + Stack, + TextField, + Button, + CircularProgress, + MenuItem, + Typography, + MobileStepper, +} from "@mui/material"; +import ErrorMessage from "../atoms/ErrorMessage"; +import type { LinkedAccount, RegistrationData } from "../../nonview/core/accountStorage"; + +// Email service provider configurations +// smtpSecure=true means implicit TLS (port 465); +// smtpSecure=false means STARTTLS on the submission port (587). +// Gmail/Outlook/Yahoo all support both +// we default to 465+implicit-TLS because it's a single negotiation and works +// even when a network rewrites STARTTLS responses. +const EMAIL_PROVIDERS = { + gmail: { + name: "Gmail", + imapHost: "imap.gmail.com", + imapPort: 993, + imapSecure: true, + smtpHost: "smtp.gmail.com", + smtpPort: 465, + smtpSecure: true, + }, + outlook: { + name: "Outlook", + imapHost: "outlook.office365.com", + imapPort: 993, + imapSecure: true, + smtpHost: "smtp.office365.com", + smtpPort: 587, + smtpSecure: false, + }, + yahoo: { + name: "Yahoo Mail", + imapHost: "imap.mail.yahoo.com", + imapPort: 993, + imapSecure: true, + smtpHost: "smtp.mail.yahoo.com", + smtpPort: 465, + smtpSecure: true, + }, + custom: { + name: "Custom", + imapHost: "", + imapPort: 993, + imapSecure: true, + smtpHost: "", + smtpPort: 465, + smtpSecure: true, + }, +}; + +type Step = 1 | 2 | "success"; + +interface AddAccountFormProps { + onSubmit: (data: RegistrationData) => Promise; + loading?: boolean; + onSuccess: (account: LinkedAccount) => void; + successCtaLabel?: string; +} + +// Two-step "add another account" form +const AddAccountForm = ({ + onSubmit, + loading = false, + onSuccess, + successCtaLabel = "Continue", +}: AddAccountFormProps) => { + const [step, setStep] = useState(1); + const [formData, setFormData] = useState({ + name: "", + email: "", + emailServiceProvider: "gmail", + emailAddress: "", + emailPassword: "", + imapHost: EMAIL_PROVIDERS.gmail.imapHost, + imapPort: EMAIL_PROVIDERS.gmail.imapPort as number, + imapSecure: EMAIL_PROVIDERS.gmail.imapSecure, + smtpHost: EMAIL_PROVIDERS.gmail.smtpHost, + smtpPort: EMAIL_PROVIDERS.gmail.smtpPort as number, + smtpSecure: EMAIL_PROVIDERS.gmail.smtpSecure, + }); + const [errors, setErrors] = useState>({}); + const [formError, setFormError] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [createdAccount, setCreatedAccount] = useState(null); + + const isBusy = loading || submitting; + + const handleChange = (field: string) => (e: React.ChangeEvent) => { + const value = e.target.value; + let updates: Record = { [field]: value }; + + // Auto-populate server settings when provider changes. + if (field === "emailServiceProvider" && EMAIL_PROVIDERS[value as keyof typeof EMAIL_PROVIDERS]) { + const provider = EMAIL_PROVIDERS[value as keyof typeof EMAIL_PROVIDERS]; + updates = { + ...updates, + imapHost: provider.imapHost, + imapPort: provider.imapPort, + imapSecure: provider.imapSecure, + smtpHost: provider.smtpHost, + smtpPort: provider.smtpPort, + smtpSecure: provider.smtpSecure, + }; + } + + setFormData((prev) => ({ ...prev, ...updates })); + if (errors[field]) { + setErrors((prev) => ({ ...prev, [field]: "" })); + } + }; + + const validateStep1 = (): boolean => { + const newErrors: Record = {}; + if (!formData.name.trim()) { + newErrors.name = "Name is required"; + } + if (!formData.email) { + newErrors.email = "Email is required"; + } else if (!/\S+@\S+\.\S+/.test(formData.email)) { + newErrors.email = "Email is invalid"; + } + setErrors((prev) => ({ ...prev, ...newErrors })); + return Object.keys(newErrors).length === 0; + }; + + const validateStep2 = (): boolean => { + const newErrors: Record = {}; + if (!formData.emailAddress) { + newErrors.emailAddress = "Email address for service is required"; + } else if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) { + newErrors.emailAddress = "Email address is invalid"; + } + if (!formData.emailPassword) { + newErrors.emailPassword = "Email service password is required"; + } + if (!formData.imapHost) { + newErrors.imapHost = "IMAP host is required"; + } + if (!formData.smtpHost) { + newErrors.smtpHost = "SMTP host is required"; + } + setErrors((prev) => ({ ...prev, ...newErrors })); + return Object.keys(newErrors).length === 0; + }; + + const handleContinue = () => { + setFormError(""); + if (validateStep1()) { + setStep(2); + } + }; + + const handleBack = () => { + setFormError(""); + setStep(1); + }; + + const handleComplete = async () => { + setFormError(""); + if (!validateStep2()) return; + + // Prepare the payload for submission + const payload = { + name: formData.name, + email: formData.email, + emailServiceProvider: formData.emailServiceProvider, + emailAddress: formData.emailAddress, + emailPassword: formData.emailPassword, + imapHost: formData.imapHost, + imapPort: parseInt(String(formData.imapPort), 10), + imapSecure: formData.imapSecure, + smtpHost: formData.smtpHost, + smtpPort: parseInt(String(formData.smtpPort), 10), + smtpSecure: formData.smtpSecure, + }; + + setSubmitting(true); + try { + const account = await onSubmit(payload as RegistrationData); + setCreatedAccount(account); + setStep("success"); + } catch (error) { + setFormError((error as Error).message || "Registration failed. Please try again."); + } finally { + setSubmitting(false); + } + }; + + return ( + + {step !== "success" && ( + } + nextButton={} + sx={{ justifyContent: "center", bgcolor: "transparent", px: 0, mb: 3 }} + /> + )} + + {formError && step !== "success" && ( + + setFormError("")} /> + + )} + + {step === 1 && ( + + + Account Details + + + + + + + + )} + + {step === 2 && ( + + + Email Service Configuration + + + Configure your email service connection (Gmail, Outlook, etc.) + + + + {Object.entries(EMAIL_PROVIDERS).map(([key, provider]) => ( + + {provider.name} + + ))} + + + + + + {formData.emailServiceProvider === "custom" && ( + <> + IMAP Settings (Incoming Mail) + + + + SMTP Settings (Outgoing Mail) + + + + )} + + + + + + + )} + + {step === "success" && createdAccount && ( + + Account added successfully! + + {createdAccount.email} is now available in the account switcher. + + + + )} + + ); +}; + +export default AddAccountForm;