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]
|
#[tauri::command]
|
||||||
pub async fn start_bridge(
|
pub async fn start_bridge(
|
||||||
|
email: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
state: State<'_, BridgeState>,
|
state: State<'_, BridgeState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut cfg = match config::load_config() {
|
let mut cfg = match config::load_config() {
|
||||||
Ok(Some(cfg)) if !cfg.email.is_empty() => cfg,
|
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)
|
config::ensure_bridge_password(&mut cfg)
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ function App() {
|
|||||||
<Dashboard
|
<Dashboard
|
||||||
status={bridge.status}
|
status={bridge.status}
|
||||||
stats={bridge.stats}
|
stats={bridge.stats}
|
||||||
|
config={bridge.config}
|
||||||
hasSavedSession={bridge.hasSavedSession}
|
hasSavedSession={bridge.hasSavedSession}
|
||||||
loading={bridge.loading}
|
loading={bridge.loading}
|
||||||
logs={bridge.logs}
|
logs={bridge.logs}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import type { BridgeStatus, BridgeStats } from "../types";
|
import type { Config, BridgeStatus, BridgeStats } from "../types";
|
||||||
import { isError } from "../types";
|
import { isError } from "../types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
status: BridgeStatus | null;
|
status: BridgeStatus | null;
|
||||||
stats: BridgeStats;
|
stats: BridgeStats;
|
||||||
|
config: Config | null;
|
||||||
hasSavedSession: boolean;
|
hasSavedSession: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
logs: string[];
|
logs: string[];
|
||||||
onStart: (password?: string) => Promise<void>;
|
onStart: (password?: string, email?: string) => Promise<void>;
|
||||||
onStop: () => Promise<void>;
|
onStop: () => Promise<void>;
|
||||||
onClearLogs: () => void;
|
onClearLogs: () => void;
|
||||||
}
|
}
|
||||||
@@ -24,6 +25,7 @@ function formatUptime(secs: number): string {
|
|||||||
export function Dashboard({
|
export function Dashboard({
|
||||||
status,
|
status,
|
||||||
stats,
|
stats,
|
||||||
|
config,
|
||||||
hasSavedSession,
|
hasSavedSession,
|
||||||
loading,
|
loading,
|
||||||
logs,
|
logs,
|
||||||
@@ -32,6 +34,7 @@ export function Dashboard({
|
|||||||
onClearLogs,
|
onClearLogs,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
const logEndRef = useRef<HTMLDivElement>(null);
|
const logEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -50,6 +53,8 @@ export function Dashboard({
|
|||||||
const isStarting = status === "Starting";
|
const isStarting = status === "Starting";
|
||||||
const errored = isError(status);
|
const errored = isError(status);
|
||||||
const isStopped = status === "Stopped" || errored;
|
const isStopped = status === "Stopped" || errored;
|
||||||
|
const hasAccount = !!config?.email;
|
||||||
|
const needsEmail = isStopped && !hasAccount;
|
||||||
const needsPassword = isStopped && !hasSavedSession;
|
const needsPassword = isStopped && !hasSavedSession;
|
||||||
const wsConnected = stats.ws_status === "Connected";
|
const wsConnected = stats.ws_status === "Connected";
|
||||||
|
|
||||||
@@ -76,15 +81,16 @@ export function Dashboard({
|
|||||||
? "Signing in and syncing your mailbox"
|
? "Signing in and syncing your mailbox"
|
||||||
: errored
|
: errored
|
||||||
? "See the activity log below"
|
? "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 () => {
|
const handleStart = async () => {
|
||||||
if (needsPassword) {
|
await onStart(
|
||||||
await onStart(password);
|
needsPassword ? password : undefined,
|
||||||
setPassword("");
|
needsEmail ? email.trim() : undefined,
|
||||||
} else {
|
);
|
||||||
await onStart();
|
setPassword("");
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -106,6 +112,18 @@ export function Dashboard({
|
|||||||
|
|
||||||
{isStopped && (
|
{isStopped && (
|
||||||
<div className="start-section">
|
<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 && (
|
{needsPassword && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Tuta Password</label>
|
<label>Tuta Password</label>
|
||||||
@@ -114,14 +132,23 @@ export function Dashboard({
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Enter your Tuta password"
|
placeholder="Enter your Tuta password"
|
||||||
onKeyDown={(e) => e.key === "Enter" && password && handleStart()}
|
onKeyDown={(e) =>
|
||||||
|
e.key === "Enter" &&
|
||||||
|
password &&
|
||||||
|
(!needsEmail || email.trim()) &&
|
||||||
|
handleStart()
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="primary start-btn"
|
className="primary start-btn"
|
||||||
onClick={handleStart}
|
onClick={handleStart}
|
||||||
disabled={loading || (needsPassword && !password)}
|
disabled={
|
||||||
|
loading ||
|
||||||
|
(needsPassword && !password) ||
|
||||||
|
(needsEmail && !email.trim())
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{loading ? "Connecting…" : "Start Bridge"}
|
{loading ? "Connecting…" : "Start Bridge"}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ export function useBridge() {
|
|||||||
setConfig(cfg);
|
setConfig(cfg);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const startBridge = useCallback(async (password?: string) => {
|
const startBridge = useCallback(async (password?: string, email?: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await invoke("start_bridge", { password: password || null });
|
await invoke("start_bridge", { password: password || null, email: email || null });
|
||||||
refresh();
|
refresh();
|
||||||
invoke<string | null>("get_bridge_password").then(setBridgePassword);
|
invoke<string | null>("get_bridge_password").then(setBridgePassword);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user