agentic-os/interface/hq-dashboard/components/GmailAgentUI.jsx

236 lines
10 KiB
JavaScript

"use client";
import React, { useState, useEffect } from "react";
import { Mail, Plus, CheckCircle, RefreshCw, AlertCircle } from "lucide-react";
export default function GmailAgentUI() {
const [accounts, setAccounts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [newAccountName, setNewAccountName] = useState("");
const [authUrl, setAuthUrl] = useState("");
const [authCode, setAuthCode] = useState("");
const [authStep, setAuthStep] = useState(0); // 0 = list, 1 = get url, 2 = enter code
const fetchAccounts = async () => {
setLoading(true);
try {
const res = await fetch("/proxy/gmail/api/accounts");
const data = await res.json();
if (data.success) {
setAccounts(data.accounts || []);
} else {
setError(data.error);
}
} catch (err) {
setError("Failed to connect to Gmail Agent backend: " + err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchAccounts();
// Check for OAuth redirect
const params = new URLSearchParams(window.location.search);
const codeParam = params.get('code');
const stateParam = params.get('state');
if (codeParam && stateParam) {
setAuthStep(2);
setAuthCode(codeParam);
setNewAccountName(stateParam);
// Auto-submit the token verification
const verifyRedirect = async () => {
try {
const res = await fetch("/proxy/gmail/api/auth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ accountName: stateParam.trim(), code: codeParam.trim() })
});
const data = await res.json();
if (data.success) {
alert(`Account '${stateParam}' added successfully!`);
setAuthStep(0);
setNewAccountName("");
setAuthCode("");
window.history.replaceState({}, document.title, window.location.pathname);
fetchAccounts();
} else {
alert("OAuth verification failed: " + data.error);
}
} catch (err) {
alert("Error verifying OAuth: " + err.message);
}
};
verifyRedirect();
}
}, []);
const handleGenerateUrl = async (e) => {
e.preventDefault();
if (!newAccountName.trim()) return;
try {
const res = await fetch("/proxy/gmail/api/auth/url", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ accountName: newAccountName.trim() })
});
const data = await res.json();
if (data.success) {
setAuthUrl(data.url);
setAuthStep(2);
} else {
alert("Error generating URL: " + data.error);
}
} catch (err) {
alert("Error: " + err.message);
}
};
const handleVerifyCode = async (e) => {
e.preventDefault();
if (!authCode.trim()) return;
try {
const res = await fetch("/proxy/gmail/api/auth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ accountName: newAccountName.trim(), code: authCode.trim() })
});
const data = await res.json();
if (data.success) {
alert("Account added successfully!");
setAuthStep(0);
setNewAccountName("");
setAuthCode("");
fetchAccounts();
} else {
alert("Verification failed: " + data.error);
}
} catch (err) {
alert("Error: " + err.message);
}
};
return (
<div className="fade-in" style={{ display: "flex", flexDirection: "column", gap: "24px", color: "var(--text-primary)" }}>
{authStep === 0 && (
<>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<h2>Connected Gmail Accounts</h2>
<div style={{ display: "flex", gap: "10px" }}>
<button
onClick={fetchAccounts}
style={{ background: "rgba(255,255,255,0.05)", border: "none", color: "white", padding: "8px 16px", borderRadius: "8px", cursor: "pointer", display: "flex", alignItems: "center", gap: "8px" }}
>
<RefreshCw size={16} /> Refresh
</button>
<button
onClick={() => setAuthStep(1)}
style={{ background: "var(--accent-cyan)", border: "none", color: "black", padding: "8px 16px", borderRadius: "8px", cursor: "pointer", display: "flex", alignItems: "center", gap: "8px", fontWeight: "bold" }}
>
<Plus size={16} /> Add Account
</button>
</div>
</div>
{error && (
<div style={{ background: "rgba(239, 68, 68, 0.1)", color: "#ef4444", padding: "16px", borderRadius: "8px", display: "flex", alignItems: "center", gap: "12px" }}>
<AlertCircle size={20} />
{error}
</div>
)}
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: "20px" }}>
{loading ? (
<div style={{ padding: "20px", color: "var(--text-muted)" }}>Loading...</div>
) : accounts.length === 0 ? (
<div className="glass" style={{ padding: "40px 20px", textAlign: "center", color: "var(--text-muted)" }}>
<Mail size={48} style={{ opacity: 0.5, marginBottom: "16px", margin: "0 auto" }} />
<p>No Gmail accounts connected yet.</p>
</div>
) : (
accounts.map((acc, idx) => (
<div key={idx} className="glass glass-interactive" style={{ padding: "24px", display: "flex", alignItems: "center", gap: "16px" }}>
<div style={{ width: "48px", height: "48px", borderRadius: "12px", background: "rgba(6, 182, 212, 0.15)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--accent-cyan)" }}>
<Mail size={24} />
</div>
<div>
<h3 style={{ margin: 0, fontSize: "18px" }}>{acc.name}</h3>
<div style={{ display: "flex", alignItems: "center", gap: "6px", color: "var(--accent-emerald)", fontSize: "12px", marginTop: "4px" }}>
<CheckCircle size={14} /> Active & Syncing
</div>
</div>
</div>
))
)}
</div>
</>
)}
{authStep === 1 && (
<div className="glass" style={{ padding: "30px", maxWidth: "500px", margin: "0 auto" }}>
<h2 style={{ marginBottom: "20px", display: "flex", alignItems: "center", gap: "10px" }}><Plus size={24} color="var(--accent-cyan)" /> Connect New Gmail</h2>
<form onSubmit={handleGenerateUrl} style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<div>
<label style={{ display: "block", marginBottom: "8px", color: "var(--text-secondary)" }}>Account Label (e.g. "Work" or "Personal")</label>
<input
type="text"
value={newAccountName}
onChange={(e) => setNewAccountName(e.target.value)}
required
style={{ width: "100%", padding: "12px", borderRadius: "8px", border: "1px solid rgba(255,255,255,0.1)", background: "rgba(0,0,0,0.2)", color: "white" }}
placeholder="Enter account label..."
/>
</div>
<div style={{ display: "flex", gap: "10px", marginTop: "10px" }}>
<button type="button" onClick={() => setAuthStep(0)} style={{ flex: 1, padding: "12px", background: "transparent", border: "1px solid rgba(255,255,255,0.2)", color: "white", borderRadius: "8px", cursor: "pointer" }}>Cancel</button>
<button type="submit" style={{ flex: 1, padding: "12px", background: "var(--accent-cyan)", border: "none", color: "black", fontWeight: "bold", borderRadius: "8px", cursor: "pointer" }}>Get Auth Link</button>
</div>
</form>
</div>
)}
{authStep === 2 && (
<div className="glass" style={{ padding: "30px", maxWidth: "600px", margin: "0 auto" }}>
<h2 style={{ marginBottom: "20px", color: "var(--accent-cyan)" }}>Step 2: Authenticate</h2>
<ol style={{ marginLeft: "20px", marginBottom: "24px", color: "var(--text-secondary)", lineHeight: "1.6" }}>
<li>Click the link below to open Google's secure login page.</li>
<li>Sign in with your Gmail account and allow the requested permissions.</li>
<li>If you are not automatically redirected back, copy the verification code provided by Google and paste it below.</li>
</ol>
<div style={{ background: "rgba(0,0,0,0.3)", padding: "16px", borderRadius: "8px", marginBottom: "24px", wordBreak: "break-all" }}>
<a href={authUrl} style={{ color: "var(--accent-cyan)", textDecoration: "none" }}>{authUrl}</a>
</div>
<form onSubmit={handleVerifyCode} style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<div>
<label style={{ display: "block", marginBottom: "8px", color: "var(--text-secondary)" }}>Verification Code</label>
<input
type="text"
value={authCode}
onChange={(e) => setAuthCode(e.target.value)}
required
style={{ width: "100%", padding: "12px", borderRadius: "8px", border: "1px solid rgba(255,255,255,0.1)", background: "rgba(0,0,0,0.2)", color: "white" }}
placeholder="Paste code here..."
/>
</div>
<div style={{ display: "flex", gap: "10px", marginTop: "10px" }}>
<button type="button" onClick={() => setAuthStep(0)} style={{ flex: 1, padding: "12px", background: "transparent", border: "1px solid rgba(255,255,255,0.2)", color: "white", borderRadius: "8px", cursor: "pointer" }}>Cancel</button>
<button type="submit" style={{ flex: 1, padding: "12px", background: "var(--accent-emerald)", border: "none", color: "black", fontWeight: "bold", borderRadius: "8px", cursor: "pointer" }}>Verify & Connect</button>
</div>
</form>
</div>
)}
</div>
);
}