mirror of
https://github.com/spartanz51/tutabridge.git
synced 2026-06-24 10:54:32 +02:00
GUI: fix first-run onboarding when no account exists
The dashboard only ever showed a password field, and start_bridge errored with "No config found" when nothing was configured yet, so a brand-new user could never get past the start screen. Now the dashboard shows a Tuta email field too when no account is set up, and start_bridge bootstraps the config from that email (with defaults) instead of failing. Once an account exists the email field disappears and only the password is asked (until a keyring session is saved).
This commit is contained in:
@@ -42,12 +42,26 @@ pub async fn has_saved_session() -> Result<bool, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_bridge(
|
||||
email: Option<String>,
|
||||
password: Option<String>,
|
||||
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)
|
||||
|
||||
@@ -70,6 +70,7 @@ function App() {
|
||||
<Dashboard
|
||||
status={bridge.status}
|
||||
stats={bridge.stats}
|
||||
config={bridge.config}
|
||||
hasSavedSession={bridge.hasSavedSession}
|
||||
loading={bridge.loading}
|
||||
logs={bridge.logs}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import type { BridgeStatus, BridgeStats } from "../types";
|
||||
import type { Config, BridgeStatus, BridgeStats } from "../types";
|
||||
import { isError } from "../types";
|
||||
|
||||
interface Props {
|
||||
status: BridgeStatus | null;
|
||||
stats: BridgeStats;
|
||||
config: Config | null;
|
||||
hasSavedSession: boolean;
|
||||
loading: boolean;
|
||||
logs: string[];
|
||||
onStart: (password?: string) => Promise<void>;
|
||||
onStart: (password?: string, email?: string) => Promise<void>;
|
||||
onStop: () => Promise<void>;
|
||||
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<HTMLDivElement>(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 && (
|
||||
<div className="start-section">
|
||||
{needsEmail && (
|
||||
<div className="form-group">
|
||||
<label>Tuta Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@tuta.com"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{needsPassword && (
|
||||
<div className="form-group">
|
||||
<label>Tuta Password</label>
|
||||
@@ -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()
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className="primary start-btn"
|
||||
onClick={handleStart}
|
||||
disabled={loading || (needsPassword && !password)}
|
||||
disabled={
|
||||
loading ||
|
||||
(needsPassword && !password) ||
|
||||
(needsEmail && !email.trim())
|
||||
}
|
||||
>
|
||||
{loading ? "Connecting…" : "Start Bridge"}
|
||||
</button>
|
||||
|
||||
@@ -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<string | null>("get_bridge_password").then(setBridgePassword);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user