Add YouTube transcripts tab to Mission Control

This commit is contained in:
Horus
2026-02-27 15:00:38 +01:00
parent 0a4853bcda
commit d7cd81b293
18 changed files with 3517 additions and 12 deletions
+515
View File
@@ -0,0 +1,515 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import Card from "@/components/ui/Card";
const today = () => new Date().toISOString().split("T")[0];
interface MarketData {
btc: string;
eth: string;
sol: string;
btcChange: string;
ethChange: string;
solChange: string;
}
interface SitementeStatus {
serverUptime: string;
demoPages: string;
leadStatus: string;
}
interface WarmLead {
name: string;
business: string;
note: string;
}
interface FormState {
date: string;
location: string;
weather: string;
marketData: MarketData;
aiNewsHeadlines: string[];
skillsToLearn: string[];
euAiActStatus: "OK" | "Risk" | "Opportunity";
sitementeStatus: SitementeStatus;
warmLeads: WarmLead[];
dayPriorities: string[];
skippedTasks: string[];
autoExecutionSummary: string;
}
const defaultForm: FormState = {
date: today(),
location: "Benalmádena",
weather: "",
marketData: { btc: "", eth: "", sol: "", btcChange: "", ethChange: "", solChange: "" },
aiNewsHeadlines: ["", "", "", "", ""],
skillsToLearn: ["", "", ""],
euAiActStatus: "OK",
sitementeStatus: { serverUptime: "", demoPages: "", leadStatus: "" },
warmLeads: [
{ name: "", business: "", note: "" },
{ name: "", business: "", note: "" },
{ name: "", business: "", note: "" },
],
dayPriorities: ["", "", "", "", ""],
skippedTasks: [""],
autoExecutionSummary: "",
};
export default function MorningBriefPage() {
const [form, setForm] = useState<FormState>(defaultForm);
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const [loadingDate, setLoadingDate] = useState(false);
const loadBriefForDate = useCallback(async (date: string) => {
setLoadingDate(true);
try {
const res = await fetch(`/api/morning?date=${date}`);
const data = await res.json();
if (data) {
setForm({
date: data.date,
location: data.location || "Benalmádena",
weather: data.weather || "",
marketData: typeof data.marketData === "string" ? JSON.parse(data.marketData) : (data.marketData || defaultForm.marketData),
aiNewsHeadlines: typeof data.aiNewsHeadlines === "string" ? JSON.parse(data.aiNewsHeadlines) : (data.aiNewsHeadlines || defaultForm.aiNewsHeadlines),
skillsToLearn: typeof data.skillsToLearn === "string" ? JSON.parse(data.skillsToLearn) : (data.skillsToLearn || defaultForm.skillsToLearn),
euAiActStatus: data.euAiActStatus || "OK",
sitementeStatus: typeof data.sitementeStatus === "string" ? JSON.parse(data.sitementeStatus) : (data.sitementeStatus || defaultForm.sitementeStatus),
warmLeads: typeof data.warmLeads === "string" ? JSON.parse(data.warmLeads) : (data.warmLeads || defaultForm.warmLeads),
dayPriorities: typeof data.dayPriorities === "string" ? JSON.parse(data.dayPriorities) : (data.dayPriorities || defaultForm.dayPriorities),
skippedTasks: typeof data.skippedTasks === "string" ? JSON.parse(data.skippedTasks) : (data.skippedTasks || defaultForm.skippedTasks),
autoExecutionSummary: data.autoExecutionSummary || "",
});
} else {
setForm({ ...defaultForm, date });
}
} catch {
setForm({ ...defaultForm, date });
} finally {
setLoadingDate(false);
}
}, []);
useEffect(() => {
loadBriefForDate(today());
}, [loadBriefForDate]);
const validate = (): boolean => {
const errs: Record<string, string> = {};
if (!form.location.trim()) errs.location = "Location is required";
if (!form.weather.trim()) errs.weather = "Weather is required";
if (!form.dayPriorities.some((p) => p.trim())) errs.dayPriorities = "At least one priority is required";
setErrors(errs);
return Object.keys(errs).length === 0;
};
const handleSave = async () => {
if (!validate()) return;
setSaving(true);
try {
const res = await fetch("/api/morning", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
if (!res.ok) {
const err = await res.json();
setErrors({ submit: err.error || "Failed to save" });
} else {
setSaved(true);
setTimeout(() => setSaved(false), 3000);
}
} catch {
setErrors({ submit: "Network error. Please try again." });
} finally {
setSaving(false);
}
};
const updateArrayItem = (field: keyof FormState, index: number, value: string) => {
setForm((prev) => {
const arr = [...(prev[field] as string[])];
arr[index] = value;
return { ...prev, [field]: arr };
});
};
const addArrayItem = (field: keyof FormState, defaultVal: string = "") => {
setForm((prev) => ({ ...prev, [field]: [...(prev[field] as string[]), defaultVal] }));
};
const removeArrayItem = (field: keyof FormState, index: number) => {
setForm((prev) => {
const arr = (prev[field] as string[]).filter((_, i) => i !== index);
return { ...prev, [field]: arr };
});
};
const updateLead = (index: number, field: keyof WarmLead, value: string) => {
setForm((prev) => {
const leads = [...prev.warmLeads];
leads[index] = { ...leads[index], [field]: value };
return { ...prev, warmLeads: leads };
});
};
const changeColor = (val: string) => {
const n = parseFloat(val);
if (isNaN(n)) return "text-slate-400";
return n >= 0 ? "text-emerald-400" : "text-red-400";
};
return (
<div className="p-6 max-w-4xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-xl font-bold text-white flex items-center gap-2">
<span className="text-amber-400"></span> Morning Brief
</h1>
<p className="text-sm text-slate-500 mt-0.5">Horus daily intelligence log</p>
</div>
<div className="flex items-center gap-3">
<input
type="date"
value={form.date}
onChange={(e) => {
setForm((prev) => ({ ...prev, date: e.target.value }));
loadBriefForDate(e.target.value);
}}
className="bg-[#1a1d27] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-300 focus:outline-none focus:border-amber-500/50"
/>
<button
onClick={handleSave}
disabled={saving || loadingDate}
className="px-4 py-2 bg-amber-500 hover:bg-amber-400 disabled:opacity-50 text-black font-semibold text-sm rounded-lg transition-colors"
>
{saving ? "Saving…" : saved ? "✓ Saved" : "Save Brief"}
</button>
</div>
</div>
{errors.submit && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-sm">
{errors.submit}
</div>
)}
{loadingDate && (
<div className="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg text-blue-400 text-sm">
Loading brief for selected date
</div>
)}
<div className="space-y-5">
{/* Location & Weather */}
<Card title="📍 Location & Weather" accent="amber">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs text-slate-500 mb-1.5">Location</label>
<input
type="text"
value={form.location}
onChange={(e) => setForm((p) => ({ ...p, location: e.target.value }))}
placeholder="Benalmádena"
className={`w-full bg-[#0f1117] border ${errors.location ? "border-red-500/50" : "border-[#2a2d3a]"} rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-amber-500/50`}
/>
{errors.location && <p className="text-xs text-red-400 mt-1">{errors.location}</p>}
</div>
<div>
<label className="block text-xs text-slate-500 mb-1.5">Weather</label>
<input
type="text"
value={form.weather}
onChange={(e) => setForm((p) => ({ ...p, weather: e.target.value }))}
placeholder="e.g. 22°C, Sunny"
className={`w-full bg-[#0f1117] border ${errors.weather ? "border-red-500/50" : "border-[#2a2d3a]"} rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-amber-500/50`}
/>
{errors.weather && <p className="text-xs text-red-400 mt-1">{errors.weather}</p>}
</div>
</div>
</Card>
{/* Market Prices */}
<Card title="📈 Market Prices" accent="blue">
<div className="grid grid-cols-3 gap-4">
{(["btc", "eth", "sol"] as const).map((coin) => (
<div key={coin}>
<label className="block text-xs text-slate-500 mb-1.5 uppercase font-mono">{coin}</label>
<input
type="text"
value={form.marketData[coin]}
onChange={(e) =>
setForm((p) => ({ ...p, marketData: { ...p.marketData, [coin]: e.target.value } }))
}
placeholder="$0.00"
className="w-full bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500/50 mb-2"
/>
<input
type="text"
value={form.marketData[`${coin}Change` as keyof MarketData]}
onChange={(e) =>
setForm((p) => ({
...p,
marketData: { ...p.marketData, [`${coin}Change`]: e.target.value },
}))
}
placeholder="24h % (e.g. +2.5)"
className={`w-full bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-blue-500/50 ${changeColor(form.marketData[`${coin}Change` as keyof MarketData])}`}
/>
</div>
))}
</div>
</Card>
{/* AI News Headlines */}
<Card title="🤖 AI News Headlines" subtitle="35 headlines" accent="purple">
<div className="space-y-2">
{form.aiNewsHeadlines.map((h, i) => (
<div key={i} className="flex gap-2">
<span className="text-xs text-slate-600 font-mono mt-2.5 w-4">{i + 1}.</span>
<input
type="text"
value={h}
onChange={(e) => updateArrayItem("aiNewsHeadlines", i, e.target.value)}
placeholder={`Headline ${i + 1}`}
className="flex-1 bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-purple-500/50"
/>
{form.aiNewsHeadlines.length > 3 && (
<button
onClick={() => removeArrayItem("aiNewsHeadlines", i)}
className="text-slate-600 hover:text-red-400 transition-colors text-lg leading-none mt-1"
>
×
</button>
)}
</div>
))}
{form.aiNewsHeadlines.length < 7 && (
<button
onClick={() => addArrayItem("aiNewsHeadlines")}
className="text-xs text-purple-400 hover:text-purple-300 transition-colors mt-1"
>
+ Add headline
</button>
)}
</div>
</Card>
{/* Skills to Learn */}
<Card title="🎯 Top Skills to Learn" subtitle="Top 3" accent="green">
<div className="space-y-2">
{form.skillsToLearn.map((s, i) => (
<div key={i} className="flex gap-2">
<span className="text-xs text-slate-600 font-mono mt-2.5 w-4">{i + 1}.</span>
<input
type="text"
value={s}
onChange={(e) => updateArrayItem("skillsToLearn", i, e.target.value)}
placeholder={`Skill ${i + 1}`}
className="flex-1 bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-emerald-500/50"
/>
</div>
))}
</div>
</Card>
{/* EU AI Act Status */}
<Card title="🇪🇺 EU AI Act Status" accent="amber">
<div className="flex gap-3">
{(["OK", "Risk", "Opportunity"] as const).map((status) => (
<button
key={status}
onClick={() => setForm((p) => ({ ...p, euAiActStatus: status }))}
className={`flex-1 py-2.5 rounded-lg text-sm font-medium border transition-all ${
form.euAiActStatus === status
? status === "OK"
? "bg-emerald-500/20 border-emerald-500/50 text-emerald-400"
: status === "Risk"
? "bg-red-500/20 border-red-500/50 text-red-400"
: "bg-blue-500/20 border-blue-500/50 text-blue-400"
: "bg-transparent border-[#2a2d3a] text-slate-500 hover:border-slate-500"
}`}
>
{status === "OK" ? "✓ OK" : status === "Risk" ? "⚠ Risk" : "💡 Opportunity"}
</button>
))}
</div>
</Card>
{/* SiteMente Status */}
<Card title="🌐 SiteMente Status" accent="blue">
<div className="space-y-3">
<div>
<label className="block text-xs text-slate-500 mb-1.5">Server Uptime</label>
<input
type="text"
value={form.sitementeStatus.serverUptime}
onChange={(e) =>
setForm((p) => ({ ...p, sitementeStatus: { ...p.sitementeStatus, serverUptime: e.target.value } }))
}
placeholder="e.g. 99.9% — All systems operational"
className="w-full bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500/50"
/>
</div>
<div>
<label className="block text-xs text-slate-500 mb-1.5">Demo Pages</label>
<input
type="text"
value={form.sitementeStatus.demoPages}
onChange={(e) =>
setForm((p) => ({ ...p, sitementeStatus: { ...p.sitementeStatus, demoPages: e.target.value } }))
}
placeholder="e.g. 3 live, 1 in review"
className="w-full bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500/50"
/>
</div>
<div>
<label className="block text-xs text-slate-500 mb-1.5">Lead Status (Supabase)</label>
<textarea
value={form.sitementeStatus.leadStatus}
onChange={(e) =>
setForm((p) => ({ ...p, sitementeStatus: { ...p.sitementeStatus, leadStatus: e.target.value } }))
}
placeholder="e.g. 12 leads in pipeline, 3 hot prospects, 1 demo scheduled"
rows={2}
className="w-full bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500/50 resize-none"
/>
</div>
</div>
</Card>
{/* Warm Leads */}
<Card title="🔥 Warm Leads to Contact" subtitle="Top 3" accent="red">
<div className="space-y-4">
{form.warmLeads.map((lead, i) => (
<div key={i} className="p-3 bg-[#0f1117] rounded-lg border border-[#2a2d3a]">
<p className="text-xs text-slate-600 font-mono mb-2">Lead #{i + 1}</p>
<div className="grid grid-cols-2 gap-2 mb-2">
<input
type="text"
value={lead.name}
onChange={(e) => updateLead(i, "name", e.target.value)}
placeholder="Name"
className="bg-[#13151f] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-red-500/50"
/>
<input
type="text"
value={lead.business}
onChange={(e) => updateLead(i, "business", e.target.value)}
placeholder="Business"
className="bg-[#13151f] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-red-500/50"
/>
</div>
<input
type="text"
value={lead.note}
onChange={(e) => updateLead(i, "note", e.target.value)}
placeholder="Note / context"
className="w-full bg-[#13151f] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-red-500/50"
/>
</div>
))}
</div>
</Card>
{/* Day Priorities */}
<Card title="⚡ Day Priorities" subtitle="57 numbered priorities" accent="amber">
{errors.dayPriorities && (
<p className="text-xs text-red-400 mb-2">{errors.dayPriorities}</p>
)}
<div className="space-y-2">
{form.dayPriorities.map((p, i) => (
<div key={i} className="flex gap-2">
<span className="text-xs text-amber-500/70 font-mono mt-2.5 w-5">{i + 1}.</span>
<input
type="text"
value={p}
onChange={(e) => updateArrayItem("dayPriorities", i, e.target.value)}
placeholder={`Priority ${i + 1}`}
className="flex-1 bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-amber-500/50"
/>
{form.dayPriorities.length > 3 && (
<button
onClick={() => removeArrayItem("dayPriorities", i)}
className="text-slate-600 hover:text-red-400 transition-colors text-lg leading-none mt-1"
>
×
</button>
)}
</div>
))}
{form.dayPriorities.length < 7 && (
<button
onClick={() => addArrayItem("dayPriorities")}
className="text-xs text-amber-400 hover:text-amber-300 transition-colors mt-1"
>
+ Add priority
</button>
)}
</div>
</Card>
{/* Skipped Tasks */}
<Card title="⏭ Skipped Tasks" subtitle="Tasks carried over from yesterday">
<div className="space-y-2">
{form.skippedTasks.map((t, i) => (
<div key={i} className="flex gap-2">
<input
type="text"
value={t}
onChange={(e) => updateArrayItem("skippedTasks", i, e.target.value)}
placeholder="Skipped task"
className="flex-1 bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-slate-500/50"
/>
{form.skippedTasks.length > 1 && (
<button
onClick={() => removeArrayItem("skippedTasks", i)}
className="text-slate-600 hover:text-red-400 transition-colors text-lg leading-none mt-1"
>
×
</button>
)}
</div>
))}
<button
onClick={() => addArrayItem("skippedTasks")}
className="text-xs text-slate-400 hover:text-slate-300 transition-colors mt-1"
>
+ Add skipped task
</button>
</div>
</Card>
{/* Auto-Execution Summary */}
<Card title="🦅 Horus Auto-Execution" subtitle="What Horus will do without asking" accent="amber">
<textarea
value={form.autoExecutionSummary}
onChange={(e) => setForm((p) => ({ ...p, autoExecutionSummary: e.target.value }))}
placeholder="e.g. Monitor BTC for breakout above $70k, send SiteMente weekly report to leads, update Notion task board…"
rows={4}
className="w-full bg-[#0f1117] border border-[#2a2d3a] rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-amber-500/50 resize-none"
/>
</Card>
{/* Save Button */}
<div className="flex justify-end pb-6">
<button
onClick={handleSave}
disabled={saving}
className="px-6 py-2.5 bg-amber-500 hover:bg-amber-400 disabled:opacity-50 text-black font-semibold text-sm rounded-lg transition-colors"
>
{saving ? "Saving…" : saved ? "✓ Brief Saved!" : "Save Morning Brief"}
</button>
</div>
</div>
</div>
);
}