diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index b259a08..c66eb6d 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -42,12 +42,26 @@ pub async fn has_saved_session() -> Result { #[tauri::command] pub async fn start_bridge( + email: Option, password: Option, state: State<'_, BridgeState>, ) -> Result<(), String> { let mut cfg = match config::load_config() { Ok(Some(cfg)) if !cfg.email.is_empty() => cfg, - _ => return Err("No config found — save config first".into()), + // First run: no account yet. Bootstrap a default config from the email + // the user just entered on the dashboard, instead of erroring out. + _ => { + let email = email.unwrap_or_default().trim().to_string(); + if email.is_empty() { + return Err("Enter your Tuta email to get started".into()); + } + let cfg = Config { + email, + ..Default::default() + }; + config::save_config(&cfg).map_err(|e| format!("Failed to save config: {e}"))?; + cfg + } }; config::ensure_bridge_password(&mut cfg) diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 4d92b06..eb80760 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -70,6 +70,7 @@ function App() { Promise; + onStart: (password?: string, email?: string) => Promise; onStop: () => Promise; onClearLogs: () => void; } @@ -24,6 +25,7 @@ function formatUptime(secs: number): string { export function Dashboard({ status, stats, + config, hasSavedSession, loading, logs, @@ -32,6 +34,7 @@ export function Dashboard({ onClearLogs, }: Props) { const [password, setPassword] = useState(""); + const [email, setEmail] = useState(""); const logEndRef = useRef(null); useEffect(() => { @@ -50,6 +53,8 @@ export function Dashboard({ const isStarting = status === "Starting"; const errored = isError(status); const isStopped = status === "Stopped" || errored; + const hasAccount = !!config?.email; + const needsEmail = isStopped && !hasAccount; const needsPassword = isStopped && !hasSavedSession; const wsConnected = stats.ws_status === "Connected"; @@ -76,15 +81,16 @@ export function Dashboard({ ? "Signing in and syncing your mailbox" : errored ? "See the activity log below" - : "Start the bridge to connect your mail client"; + : needsEmail + ? "Sign in with your Tuta account to get started" + : "Start the bridge to connect your mail client"; const handleStart = async () => { - if (needsPassword) { - await onStart(password); - setPassword(""); - } else { - await onStart(); - } + await onStart( + needsPassword ? password : undefined, + needsEmail ? email.trim() : undefined, + ); + setPassword(""); }; return ( @@ -106,6 +112,18 @@ export function Dashboard({ {isStopped && (
+ {needsEmail && ( +
+ + setEmail(e.target.value)} + placeholder="you@tuta.com" + autoFocus + /> +
+ )} {needsPassword && (
@@ -114,14 +132,23 @@ export function Dashboard({ value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Enter your Tuta password" - onKeyDown={(e) => e.key === "Enter" && password && handleStart()} + onKeyDown={(e) => + e.key === "Enter" && + password && + (!needsEmail || email.trim()) && + handleStart() + } />
)} diff --git a/ui/src/hooks/useBridge.ts b/ui/src/hooks/useBridge.ts index d748317..729aea0 100644 --- a/ui/src/hooks/useBridge.ts +++ b/ui/src/hooks/useBridge.ts @@ -104,10 +104,10 @@ export function useBridge() { setConfig(cfg); }, []); - const startBridge = useCallback(async (password?: string) => { + const startBridge = useCallback(async (password?: string, email?: string) => { setLoading(true); try { - await invoke("start_bridge", { password: password || null }); + await invoke("start_bridge", { password: password || null, email: email || null }); refresh(); invoke("get_bridge_password").then(setBridgePassword); } catch (e) {