import { useState } from "react";
const ACCOUNTS = [
{ id: "personal", label: "[email protected]", email: "[email protected]", icon: "□" },
{ id: "dmr", label: "[email protected]", email: "[email protected]", icon: "□" },
{ id: "duane", label: "[email protected]", email: "[email protected]", icon: "□" },
];
const CATEGORY_RULES = [
{ label: "□ Tournament", keywords: ["tournament", "blackjack", "slot", "poker", "free roll", "freeroll"] },
{ label: "□ Giveaway", keywords: ["giveaway", "gift", "drawing", "sweepstakes", "prize", "win"] },
{ label: "□️ Promotion", keywords: ["promotion", "promo", "bonus", "offer", "reward", "point", "comp"] },
{ label: "□ Schedule", keywords: ["schedule", "calendar", "upcoming", "reminder", "rsvp", "reserve", "registration"] },
{ label: "□ VIP / Event", keywords: ["vip", "dinner", "gala", "show", "concert", "bbq", "brunch", "reception"] },
];
function categorize(subject = "", snippet = "") {
const text = (subject + " " + snippet).toLowerCase();
for (const rule of CATEGORY_RULES) {
if (rule.keywords.some((kw) => text.includes(kw))) return rule.label;
}
return "□ General";
}
function formatDate(internalDate) {
if (!internalDate) return "";
const d = new Date(parseInt(internalDate));
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
}
async function scanAccount(account) {
const query = 'casino OR tournament OR giveaway OR promotion OR "Red Hawk" OR "Hard Rock" newer_than:7d';
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 1000,
messages: [{
role: "user",
content: `Search Gmail account ${account.email} for emails matching: "${query}". Use search_threads or list_threads. Return up to 20 results with full subject, sender, snippet, and internalDate.`
}],
mcp_servers: [{ type: "url", url: "https://gmailmcp.googleapis.com/mcp/v1", name: "gmail-mcp" }],
}),
});
return response.json();
}
function parseEmails(data, account) {
const emails = [];
if (!data?.content) return emails;
for (const block of data.content) {
if (block.type === "mcp_tool_result") {
const raw = block.content?.[0]?.text || "";
try {
const parsed = JSON.parse(raw);
const threads = parsed.threads || parsed.messages || [];
for (const t of threads) {
const headers = t.payload?.headers || [];
const subject = headers.find((h) => h.name === "Subject")?.value || "(no subject)";
const from = headers.find((h) => h.name === "From")?.value || "";
const snippet = t.snippet || "";
const date = formatDate(t.internalDate);
const id = `${account.id}-${t.id || Math.random().toString(36).slice(2)}`;
emails.push({ id, subject, from, snippet, date, category: categorize(subject, snippet), account });
}
} catch {
const lines = raw.split("\n");
const subjectLine = lines.find((l) => l.includes("Subject:"));
if (subjectLine) {
emails.push({
id: `${account.id}-${Math.random().toString(36).slice(2)}`,
subject: subjectLine.replace("Subject:", "").trim(),
from: lines.find((l) => l.includes("From:"))?.replace("From:", "").trim() || "",
snippet: raw.slice(0, 250),
date: new Date().toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }),
category: categorize(raw, ""),
account,
});
}
}
}
}
return emails;
}
async function extractEvents(emails) {
if (!emails.length) return [];
const list = emails
.map((e, i) => `[${i}] Account: ${e.account.email}\nSubject: ${e.subject}\nFrom: ${e.from}\nSnippet: ${e.snippet}\nDate found: ${e.date}`)
.join("\n\n");
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 1000,
messages: [{
role: "user",
content: `You are a calendar assistant. From these casino/entertainment emails, extract SPECIFIC upcoming events with a clear date only. Skip vague promotions with no date.
Return ONLY a JSON array (no markdown) where each item has:
- emailIndex: number
- title: string (short event name)
- date: string (ISO "2026-07-04" or null)
- startTime: string (ISO datetime like "2026-07-04T18:00:00" or null)
- endTime: string (ISO datetime or null)
- location: string or null
- description: string (1-2 sentences)
If none found, return [].
Emails:
${list}`
}],
}),
});
const data = await response.json();
const text = data.content?.find((b) => b.type === "text")?.text || "[]";
try {
return JSON.parse(text.replace(/```json|```/g, "").trim());
} catch { return []; }
}
async function addEventToGoogleCalendar(event) {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 1000,
messages: [{
role: "user",
content: `You are a Google Calendar assistant. Using the Gmail MCP (which also has access to Google Calendar APIs):
1. First search Google Calendar for existing events around "${event.date || "upcoming"}" with a title similar to "${event.title}" to check for duplicates.
2. If a very similar event already exists on the same date, respond with JSON only: {"added": false, "reason": "already exists"}
3. If no duplicate, create a new Google Calendar event with:
- Summary/Title: "${event.title}"
- Start: ${event.startTime || (event.date ? event.date + "T10:00:00" : "TBD")}
- End: ${event.endTime || (event.date ? event.date + "T12:00:00" : "TBD")}
- Location: "${event.location || ""}"
- Description: "${event.description || ""}"
- Calendar: primary
4. Respond ONLY with JSON: {"added": true} if created successfully, or {"added": false, "reason": "..."} if not.
Do not include any explanation -- JSON only.`
}],
mcp_servers: [{ type: "url", url: "https://gmailmcp.googleapis.com/mcp/v1", name: "gmail-mcp" }],
}),
});
const data = await response.json();
const text = data.content?.find((b) => b.type === "text")?.text || "{}";
try {
return JSON.parse(text.replace(/```json|```/g, "").trim());
} catch { return { added: false, reason: "parse error" }; }
}
async function buildSummary(emails, calendarResults) {
if (!emails.length) return "No relevant emails found this week across any account.";
const addedCount = calendarResults.filter((r) => r?.added).length;
const byAccount = ACCOUNTS.map(a => {
const count = emails.filter(e => e.account.id === a.id).length;
return `${a.icon} ${a.email}: ${count} email(s)`;
}).join(", ");
const list = emails.map((e) => `${e.account.icon} [${e.account.email}] ${e.category} | ${e.subject} | ${e.date}`).join("\n");
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 1000,
messages: [{
role: "user",
content: `You are a personal assistant for Mykha. Scanned 3 Gmail accounts: ${byAccount}. Write a Monday briefing -- friendly, concise, highlight time-sensitive events. Mention ${addedCount} new event(s) added to Google Calendar. Group by category.\n\nEmails:\n${list}`
}],
}),
});
const data = await response.json();
return data.content?.find((b) => b.type === "text")?.text || "Summary unavailable.";
}
export default function MondayEmailScanner() {
const [phase, setPhase] = useState("idle");
const [phaseLabel, setPhaseLabel] = useState("");
const [accountStatus, setAccountStatus] = useState({});
const [emails, setEmails] = useState([]);
const [extractedEvents, setExtractedEvents] = useState([]);
const [calendarResults, setCalendarResults] = useState([]);
const [summary, setSummary] = useState("");
const [errorMsg, setErrorMsg] = useState("");
const [activeFilter, setActiveFilter] = useState("All");
const [activeAccount, setActiveAccount] = useState("All");
const [lastRun, setLastRun] = useState(null);
const today = new Date();
const dayName = today.toLocaleDateString("en-US", { weekday: "long" });
const dateStr = today.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
const categories = ["All", ...Array.from(new Set(emails.map((e) => e.category)))];
const accountFiltered = activeAccount === "All" ? emails : emails.filter((e) => e.account.id === activeAccount);
const filtered = activeFilter === "All" ? accountFiltered : accountFiltered.filter((e) => e.category === activeFilter);
const addedCount = calendarResults.filter((r) => r?.added).length;
const skippedCount = calendarResults.filter((r) => r && !r.added).length;
async function runScan() {
setPhase("scanning");
setEmails([]);
setExtractedEvents([]);
setCalendarResults([]);
setSummary("");
setErrorMsg("");
setAccountStatus({});
try {
const allEmails = [];
for (const account of ACCOUNTS) {
setPhaseLabel(`${account.icon} Scanning ${account.email}...`);
setAccountStatus(prev => ({ ...prev, [account.id]: "scanning" }));
const data = await scanAccount(account);
const found = parseEmails(data, account);
allEmails.push(...found);
setAccountStatus(prev => ({ ...prev, [account.id]: `${found.length} found` }));
}
// Deduplicate by subject+from
const seen = new Set();
const unique = allEmails.filter((e) => {
const key = `${e.subject.toLowerCase()}-${e.from.toLowerCase()}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
setEmails(unique);
if (!unique.length) {
setSummary("No relevant casino or event emails found this week across all accounts.");
setPhase("done");
setLastRun(new Date().toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }));
return;
}
setPhaseLabel("□ Extracting event details...");
const events = await extractEvents(unique);
setExtractedEvents(events);
const results = [];
for (let i = 0; i < events.length; i++) {
setPhaseLabel(`□ Syncing to Google Calendar: "${events[i].title}" (${i + 1}/${events.length})...`);
const result = await addEventToGoogleCalendar(events[i]);
results.push(result);
}
setCalendarResults(results);
setPhaseLabel("✨ Writing your weekly briefing...");
const sum = await buildSummary(unique, results);
setSummary(sum);
setLastRun(new Date().toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }));
setPhase("done");
} catch (err) {
setErrorMsg("Scan failed: " + err.message);
setPhase("error");
}
}
const pill = (label, active, onClick) => (
);
return (
{/* Header */}
{/* Account status row */}
);
}
Monday Briefing · 3 Gmail Accounts · Google Calendar
□ Casino Email Scanner
{dayName}, {dateStr}
{lastRun && · Last scanned {lastRun}}
{ACCOUNTS.map(a => (
{a.icon}{a.email}
{accountStatus[a.id] && (
· {accountStatus[a.id] === "scanning" ? "⏳" : accountStatus[a.id]}
)}
))}
{/* Idle */}
{phase === "idle" && (
)}
{/* Scanning */}
{phase === "scanning" && (
)}
{/* Error */}
{phase === "error" && (
{ACCOUNTS.map(a => (
))}
{/* Extracted events */}
{extractedEvents.length > 0 && (
)}
{/* Summary */}
{/* Account tabs */}
□
Ready for your Monday briefing
Scans all 3 Gmail accounts for casino events, tournaments & giveaways — then syncs new events directly to Google Calendar.
{[["□","Scan Gmail","3 accounts"], ["□","Extract Events","AI-powered"], ["□","Google Calendar","Auto-sync"], ["✨","Briefing","Weekly summary"]].map(([icon, title, sub], i) => (
))}
{icon}
{title}
{sub}
⚙️
{phaseLabel}
Scanning all accounts & syncing to Google Calendar
{ACCOUNTS.map(a => (
))}
{a.icon} {a.email.split("@")[0]}
{accountStatus[a.id] || "Waiting..."}
⚠️ {errorMsg}
Make sure Gmail is connected in your Claude settings — it handles both Gmail and Google Calendar access.
)}
{/* Results */}
{phase === "done" && (
<>
{/* Stat cards */}
Make sure Gmail is connected in your Claude settings — it handles both Gmail and Google Calendar access.
{addedCount}
Added to Calendar
{skippedCount}
Already Exists
{emails.length}
Emails Scanned
{a.icon}
{emails.filter(e => e.account.id === a.id).length}
{a.email.split("@")[0]}
□ Events Detected & Synced
{extractedEvents.map((ev, i) => {
const result = calendarResults[i];
return (
);
})}
{ev.title}
{ev.date || "Date TBD"}{ev.location ? ` · ${ev.location}` : ""}
{ev.description && {ev.description}
}
{result?.added ? "✅ Added" : result ? "⏭️ Already exists" : "–"}
✨ Weekly Summary
{summary}
{pill("All Accounts", activeAccount === "All", () => setActiveAccount("All"))}
{ACCOUNTS.map(a => pill(`${a.icon} ${a.email.split("@")[0]}`, activeAccount === a.id, () => setActiveAccount(a.id)))}
{/* Category tabs */}
{categories.map((cat) => pill(
`${cat} (${cat === "All" ? accountFiltered.length : accountFiltered.filter(e => e.category === cat).length})`,
activeFilter === cat, () => setActiveFilter(cat)
))}
{/* Email cards */}
{filtered.length === 0
?
))
}
>
)}
No emails match this filter.
: filtered.map((email) => (
{email.category}
{email.account.icon} {email.account.email.split("@")[0]}
{email.subject}
From: {email.from}
{email.snippet}
{email.date}