Files

516 lines
21 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}