feat(mission-control): add BackToMC button to all pages
- Created BackToMC component - Added to all 27 mission control pages - Each page now has '← Back to Mission Control' button at top - Also pushed beta to develop to sync branches
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
import AutoRunPanel from "@/components/mission-control/AutoRunPanel";
|
||||
|
||||
export default function AutoRunPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<AutoRunPanel />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<AutoRunPanel />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
import BrainownPanel from "@/components/mission-control/BrainownPanel";
|
||||
|
||||
export default function BrainownPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<BrainownPanel />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<BrainownPanel />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
import ChangeLogPanel from "@/components/mission-control/ChangeLogPanel";
|
||||
|
||||
export default function ChangeLogPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<ChangeLogPanel />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<ChangeLogPanel />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function CommandPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">⌨️ Command Center</h1>
|
||||
<p className="text-slate-400">Execute commands</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">⌨️ Command Center</h1>
|
||||
<p className="text-slate-400">Execute commands</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Command center loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Command center loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
import HorusChat from "@/components/mission-control/HorusChat";
|
||||
|
||||
export default function CouncilPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">🏛️ Council Chat</h1>
|
||||
<p className="text-slate-400">Chat with your agent council</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">🏛️ Council Chat</h1>
|
||||
<p className="text-slate-400">Chat with your agent council</p>
|
||||
</div>
|
||||
<HorusChat />
|
||||
</div>
|
||||
<HorusChat />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
import ExecutionLogsPanel from "@/components/mission-control/ExecutionLogsPanel";
|
||||
|
||||
export default function ExecutionLogsPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<ExecutionLogsPanel />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<ExecutionLogsPanel />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,151 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
const SKILLS = [
|
||||
{ id: "github", name: "GitHub", description: "Manage GitHub repositories", enabled: true },
|
||||
{ id: "healthcheck", name: "Health Check", description: "System security hardening", enabled: true },
|
||||
{ id: "tmux", name: "Tmux", description: "Remote-control tmux sessions", enabled: true },
|
||||
{ id: "weather", name: "Weather", description: "Weather forecasts via wttr.in", enabled: true },
|
||||
{ id: "tavily", name: "Tavily", description: "Web search and extraction", enabled: true },
|
||||
{ id: "coingecko", name: "CoinGecko", description: "Crypto prices and market data", enabled: true },
|
||||
{ id: "discord", name: "Discord", description: "Discord operations", enabled: false },
|
||||
{ id: "clawhub", name: "ClawHub", description: "Skill management", enabled: true },
|
||||
];
|
||||
|
||||
const APIS = [
|
||||
{ id: "perplexity", name: "Perplexity", status: "active", color: "green" },
|
||||
{ id: "openweather", name: "OpenWeather", status: "active", color: "green" },
|
||||
{ id: "newsapi", name: "NewsAPI", status: "active", color: "green" },
|
||||
{ id: "coingecko", name: "CoinGecko", status: "active", color: "green" },
|
||||
{ id: "tavily", name: "Tavily", status: "active", color: "green" },
|
||||
{ id: "elevenlabs", name: "ElevenLabs", status: "inactive", color: "yellow" },
|
||||
];
|
||||
|
||||
const AUTOMATIONS = [
|
||||
{ id: "morning-brief", name: "Morning Brief", schedule: "06:00 CET", enabled: true },
|
||||
{ id: "backups", name: "Backups", schedule: "02:00 CET", enabled: true },
|
||||
{ id: "health-checks", name: "Health Checks", schedule: "30min", enabled: true },
|
||||
{ id: "trading-scan", name: "Trading Scan", schedule: "30min", enabled: false },
|
||||
];
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function HorusAIPage() {
|
||||
const [skills, setSkills] = useState(SKILLS);
|
||||
const [automations, setAutomations] = useState(AUTOMATIONS);
|
||||
|
||||
const toggleSkill = (id: string) => {
|
||||
setSkills(skills.map(s => s.id === id ? { ...s, enabled: !s.enabled } : s));
|
||||
};
|
||||
|
||||
const toggleAutomation = (id: string) => {
|
||||
setAutomations(automations.map(a => a.id === id ? { ...a, enabled: !a.enabled } : a));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-6xl">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">🤖 Horus AI</h1>
|
||||
<p className="text-slate-400">Manage skills, APIs, and automation toggles</p>
|
||||
</div>
|
||||
|
||||
{/* Skills */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<span>🎯</span> Skills
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{skills.map(skill => (
|
||||
<div key={skill.id} className="bg-slate-800 rounded-lg p-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium text-white">{skill.name}</h3>
|
||||
<p className="text-xs text-slate-400">{skill.description}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => toggleSkill(skill.id)}
|
||||
className={`w-12 h-6 rounded-full transition-colors ${
|
||||
skill.enabled ? "bg-green-500" : "bg-slate-600"
|
||||
}`}
|
||||
>
|
||||
<div className={`w-5 h-5 rounded-full bg-white transition-transform ${
|
||||
skill.enabled ? "translate-x-6" : "translate-x-0.5"
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">🤖 Horus AI</h1>
|
||||
<p className="text-slate-400">Manage skills, APIs, and automation toggles</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Horus AI management panel loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* APIs */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<span>🔌</span> APIs
|
||||
</h2>
|
||||
<div className="bg-slate-800 rounded-lg overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-700">
|
||||
<th className="text-left p-3 text-slate-400 font-medium">API</th>
|
||||
<th className="text-left p-3 text-slate-400 font-medium">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{APIS.map(api => (
|
||||
<tr key={api.id} className="border-b border-slate-700/50">
|
||||
<td className="p-3 text-white">{api.name}</td>
|
||||
<td className="p-3">
|
||||
<span className={`inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium ${
|
||||
api.color === "green" ? "bg-green-500/20 text-green-400" : "bg-yellow-500/20 text-yellow-400"
|
||||
}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${
|
||||
api.color === "green" ? "bg-green-400" : "bg-yellow-400"
|
||||
}`} />
|
||||
{api.status}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Automations */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<span>⚡</span> Automation
|
||||
</h2>
|
||||
<div className="bg-slate-800 rounded-lg overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-700">
|
||||
<th className="text-left p-3 text-slate-400 font-medium">Cron Job</th>
|
||||
<th className="text-left p-3 text-slate-400 font-medium">Schedule</th>
|
||||
<th className="text-left p-3 text-slate-400 font-medium">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{automations.map(automation => (
|
||||
<tr key={automation.id} className="border-b border-slate-700/50">
|
||||
<td className="p-3 text-white">{automation.name}</td>
|
||||
<td className="p-3 text-slate-400">{automation.schedule}</td>
|
||||
<td className="p-3">
|
||||
<button
|
||||
onClick={() => toggleAutomation(automation.id)}
|
||||
className={`w-12 h-6 rounded-full transition-colors ${
|
||||
automation.enabled ? "bg-green-500" : "bg-slate-600"
|
||||
}`}
|
||||
>
|
||||
<div className={`w-5 h-5 rounded-full bg-white transition-transform ${
|
||||
automation.enabled ? "translate-x-6" : "translate-x-0.5"
|
||||
}`} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,72 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface Submission {
|
||||
name: string;
|
||||
email: string;
|
||||
whatsapp: string;
|
||||
message: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export default function HPSubmissionsPage() {
|
||||
const [submissions, setSubmissions] = useState<Submission[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/hp-submissions')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
setSubmissions(data.submissions || []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ padding: '2rem', color: '#fff' }}>
|
||||
<h1>HP Submissions</h1>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function Hp-submissionsPage() {
|
||||
return (
|
||||
<div style={{ padding: '2rem', color: '#fff' }}>
|
||||
<h1 style={{ marginBottom: '2rem' }}>📬 HP Contact Submissions</h1>
|
||||
|
||||
{submissions.length === 0 ? (
|
||||
<p style={{ color: '#888' }}>No submissions yet</p>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gap: '1rem' }}>
|
||||
{submissions.map((sub, i) => (
|
||||
<div key={i} style={{
|
||||
background: '#1e293b',
|
||||
padding: '1.5rem',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid #334155'
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '0.5rem' }}>
|
||||
<strong style={{ color: '#4169e1', fontSize: '1.1rem' }}>{sub.name}</strong>
|
||||
<span style={{ color: '#64748b', fontSize: '0.85rem' }}>{sub.date}</span>
|
||||
</div>
|
||||
<div style={{ color: '#94a3b8', marginBottom: '0.5rem' }}>
|
||||
📧 {sub.email}
|
||||
</div>
|
||||
{sub.whatsapp && (
|
||||
<div style={{ color: '#94a3b8', marginBottom: '0.5rem' }}>
|
||||
📱 {sub.whatsapp}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ color: '#cbd5e1', marginTop: '0.75rem', padding: '0.75rem', background: '#0f172a', borderRadius: '8px' }}>
|
||||
{sub.message}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Hp-submissions</h1>
|
||||
<p className="text-slate-400">hp-submissions panel</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">hp-submissions loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
import { useState } from "react";
|
||||
|
||||
const LEAD_STATUSES = ["all", "new", "contacted", "qualified", "proposal", "won", "lost"];
|
||||
|
||||
const SAMPLE_LEADS = [
|
||||
{ id: 1, name: "Restaurante El Galeón", email: "info@galeon.es", phone: "+34 952 123 456", status: "qualified", source: "Website" },
|
||||
{ id: 2, name: "Clínica Dental Mar", email: "contacto@clinica-dental-mar.es", phone: "+34 951 234 567", status: "contacted", source: "Referral" },
|
||||
@@ -12,77 +11,49 @@ const SAMPLE_LEADS = [
|
||||
|
||||
export default function LeadsPage() {
|
||||
const [filter, setFilter] = useState("all");
|
||||
|
||||
const filteredLeads = filter === "all"
|
||||
? SAMPLE_LEADS
|
||||
: SAMPLE_LEADS.filter(l => l.status === filter);
|
||||
|
||||
const filteredLeads = filter === "all" ? SAMPLE_LEADS : SAMPLE_LEADS.filter(l => l.status === filter);
|
||||
const statusColors: Record<string, string> = {
|
||||
new: "bg-blue-500/20 text-blue-400",
|
||||
contacted: "bg-yellow-500/20 text-yellow-400",
|
||||
qualified: "bg-green-500/20 text-green-400",
|
||||
proposal: "bg-purple-500/20 text-purple-400",
|
||||
won: "bg-emerald-500/20 text-emerald-400",
|
||||
lost: "bg-red-500/20 text-red-400",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📋 Lead Manager</h1>
|
||||
<p className="text-slate-400">Manage your SiteMente leads</p>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex gap-2 mb-6 flex-wrap">
|
||||
{LEAD_STATUSES.map(status => (
|
||||
<button
|
||||
key={status}
|
||||
onClick={() => setFilter(status)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
filter === status
|
||||
? "bg-brand-pink text-white"
|
||||
: "bg-slate-800 text-slate-400 hover:bg-slate-700"
|
||||
}`}
|
||||
>
|
||||
{status.charAt(0).toUpperCase() + status.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="bg-slate-800 rounded-lg overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-700">
|
||||
<th className="text-left p-4 text-slate-400 font-medium">Name</th>
|
||||
<th className="text-left p-4 text-slate-400 font-medium">Email</th>
|
||||
<th className="text-left p-4 text-slate-400 font-medium">Phone</th>
|
||||
<th className="text-left p-4 text-slate-400 font-medium">Status</th>
|
||||
<th className="text-left p-4 text-slate-400 font-medium">Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredLeads.map(lead => (
|
||||
<tr key={lead.id} className="border-b border-slate-700/50 hover:bg-slate-700/30">
|
||||
<td className="p-4 text-white font-medium">{lead.name}</td>
|
||||
<td className="p-4 text-slate-400">{lead.email}</td>
|
||||
<td className="p-4 text-slate-400">{lead.phone}</td>
|
||||
<td className="p-4">
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${statusColors[lead.status]}`}>
|
||||
{lead.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-4 text-slate-400">{lead.source}</td>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📋 Lead Manager</h1>
|
||||
<p className="text-slate-400">Manage your SiteMente leads</p>
|
||||
</div>
|
||||
<div className="flex gap-2 mb-6">
|
||||
{["all", "new", "contacted", "qualified"].map(s => (
|
||||
<button key={s} onClick={() => setFilter(s)} className={`px-4 py-2 rounded-lg text-sm ${filter === s ? "bg-brand-pink text-white" : "bg-slate-800 text-slate-400"}`}>
|
||||
{s.charAt(0).toUpperCase() + s.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-700">
|
||||
<th className="text-left p-4 text-slate-400">Name</th>
|
||||
<th className="text-left p-4 text-slate-400">Email</th>
|
||||
<th className="text-left p-4 text-slate-400">Status</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredLeads.map(lead => (
|
||||
<tr key={lead.id} className="border-b border-slate-700/50">
|
||||
<td className="p-4 text-white">{lead.name}</td>
|
||||
<td className="p-4 text-slate-400">{lead.email}</td>
|
||||
<td className="p-4"><span className={`px-2 py-1 rounded text-xs ${statusColors[lead.status]}`}>{lead.status}</span></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-slate-500 text-sm mt-4">
|
||||
Showing {filteredLeads.length} of {SAMPLE_LEADS.length} leads (database connection needed for full functionality)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import MondayBoard from "@/components/mission-control/MondayBoard";
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function MondayPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<MondayBoard />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Monday</h1>
|
||||
<p className="text-slate-400">monday panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">monday loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function OfficePage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-950 flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="bg-slate-900 border-b border-slate-800 px-6 py-4 flex-shrink-0">
|
||||
<h1 className="text-2xl font-bold text-white">🏢 Claw3D Office</h1>
|
||||
<p className="text-slate-400 text-sm">3D workspace for AI agents</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Office</h1>
|
||||
<p className="text-slate-400">office panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">office loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Claw3D Embed - via Apache proxy over HTTPS */}
|
||||
<div className="flex-1 relative">
|
||||
<iframe
|
||||
src="https://sitemente.com/claw3d/office"
|
||||
className="absolute inset-0 w-full h-full border-0"
|
||||
title="Claw3D Office"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,154 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export default function PDFViewerPage() {
|
||||
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pdfBase64, setPdfBase64] = useState<string | null>(null);
|
||||
|
||||
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (file.type !== "application/pdf") {
|
||||
setError("Please select a PDF file");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Read file as base64
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const base64 = event.target?.result as string;
|
||||
setPdfBase64(base64);
|
||||
setPdfUrl(URL.createObjectURL(file));
|
||||
setLoading(false);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
setError("Failed to read file");
|
||||
setLoading(false);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const handleUrlSubmit = async () => {
|
||||
const input = prompt("Enter PDF URL:");
|
||||
if (!input) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
new URL(input);
|
||||
setPdfUrl(input);
|
||||
setPdfBase64(null);
|
||||
} catch {
|
||||
setError("Invalid URL");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function Pdf-viewerPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-950 flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="bg-slate-900 border-b border-slate-800 px-6 py-4 flex-shrink-0">
|
||||
<h1 className="text-2xl font-bold text-white">📄 PDF Viewer</h1>
|
||||
<p className="text-slate-400 text-sm">View and analyze PDF documents</p>
|
||||
</div>
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="bg-slate-900/50 border-b border-slate-800 px-6 py-3 flex-shrink-0">
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="cursor-pointer bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
|
||||
📁 Upload PDF
|
||||
<input
|
||||
type="file"
|
||||
accept="application/pdf"
|
||||
onChange={handleFileUpload}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
onClick={handleUrlSubmit}
|
||||
className="bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
|
||||
>
|
||||
🔗 Load from URL
|
||||
</button>
|
||||
|
||||
{pdfUrl && (
|
||||
<a
|
||||
href={pdfUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
|
||||
>
|
||||
↗ Open Original
|
||||
</a>
|
||||
)}
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Pdf-viewer</h1>
|
||||
<p className="text-slate-400">pdf-viewer panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">pdf-viewer loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-hidden flex flex-col">
|
||||
{error && (
|
||||
<div className="bg-red-900/50 border border-red-700 rounded-lg p-4 m-4 max-w-md">
|
||||
<h3 className="text-red-400 font-bold mb-2">Error</h3>
|
||||
<p className="text-red-300">{error}</p>
|
||||
<button
|
||||
onClick={() => setError(null)}
|
||||
className="mt-2 bg-red-700 hover:bg-red-600 text-white px-4 py-1 rounded text-sm"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!pdfUrl && !loading && (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-8xl mb-6 opacity-50">📄</div>
|
||||
<h2 className="text-xl font-bold text-white mb-2">No PDF Loaded</h2>
|
||||
<p className="text-slate-400 mb-6">Upload a PDF or enter a URL to view it</p>
|
||||
<div className="text-slate-500 text-sm space-y-1">
|
||||
<p>• Upload your resume to let Horus analyze it</p>
|
||||
<p>• Supports PDF files up to 50MB</p>
|
||||
<p>• Or load from any public PDF URL</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin text-6xl mb-4">⏳</div>
|
||||
<p className="text-slate-400">Loading PDF...</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pdfUrl && !loading && (
|
||||
<div className="flex-1 overflow-auto bg-slate-800 p-4 flex justify-center">
|
||||
<iframe
|
||||
src={pdfUrl}
|
||||
className="w-full h-full min-h-[600px] bg-white shadow-2xl rounded"
|
||||
title="PDF Viewer"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="bg-slate-900/50 border-t border-slate-800 px-6 py-2 flex-shrink-0">
|
||||
<p className="text-slate-500 text-xs">
|
||||
💡 Tip: Upload your resume PDF and Horus can analyze the design to recreate it
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
export default function ProjectsPanelPage() {
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function Projects-panelPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📁 Projects Panel</h1>
|
||||
<p className="text-slate-400">All projects overview</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Projects-panel</h1>
|
||||
<p className="text-slate-400">projects-panel panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">projects-panel loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Projects panel loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,226 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface ResearchResult {
|
||||
title: string;
|
||||
url: string;
|
||||
score: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface Research {
|
||||
id: string;
|
||||
name: string;
|
||||
query: string;
|
||||
results_count: number;
|
||||
created_at: string;
|
||||
saved: boolean;
|
||||
summary: string;
|
||||
results?: ResearchResult[];
|
||||
}
|
||||
|
||||
export default function MissionResearchPage() {
|
||||
const [research, setResearch] = useState<Research[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [newResearch, setNewResearch] = useState({ name: "", query: "" });
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [expanded, setExpanded] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchResearch();
|
||||
}, []);
|
||||
|
||||
const fetchResearch = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch("/api/research");
|
||||
const data = await res.json();
|
||||
setResearch(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!newResearch.name || !newResearch.query) return;
|
||||
|
||||
setSearching(true);
|
||||
try {
|
||||
const tavilyRes = await fetch('https://api.tavily.com/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
api_key: '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',
|
||||
query: newResearch.query,
|
||||
max_results: 20,
|
||||
search_depth: 'advanced'
|
||||
})
|
||||
});
|
||||
const tavilyData = await tavilyRes.json();
|
||||
|
||||
const results = (tavilyData.results || []).map((r: any) => ({
|
||||
title: r.title || 'No title',
|
||||
url: r.url || '',
|
||||
score: r.score || 0,
|
||||
content: (r.content || '').substring(0, 400)
|
||||
}));
|
||||
|
||||
const summary = results.slice(0, 5).map((r: any) => r.title).join('; ') || 'No results';
|
||||
|
||||
const res = await fetch("/api/research", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name: newResearch.name,
|
||||
query: newResearch.query,
|
||||
results_count: results.length,
|
||||
summary: summary.substring(0, 150),
|
||||
results: results
|
||||
})
|
||||
});
|
||||
await res.json();
|
||||
setNewResearch({ name: "", query: "" });
|
||||
fetchResearch();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const deleteResearch = async (id: string) => {
|
||||
try {
|
||||
await fetch(`/api/research?id=${id}`, { method: "DELETE" });
|
||||
fetchResearch();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function ResearchPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="bg-red-900/30 border border-red-600 rounded-lg p-3 mb-4">
|
||||
<p className="text-red-400 text-sm">
|
||||
🔒 <strong>Security Rule:</strong> Never install skills from the web. Read the content, understand what it does, then BUILD IT yourself.
|
||||
We build, we don't install. One compromised skill can wipe everything.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1 className="text-2xl font-bold mb-6">🔍 Deep Research</h1>
|
||||
|
||||
{/* Search Form */}
|
||||
<div className="bg-gray-800 p-4 rounded-lg mb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Research Name (e.g., OpenClaw Skills)"
|
||||
value={newResearch.name}
|
||||
onChange={(e) => setNewResearch({ ...newResearch, name: e.target.value })}
|
||||
className="bg-gray-700 text-white px-4 py-2 rounded"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search Query"
|
||||
value={newResearch.query}
|
||||
onChange={(e) => setNewResearch({ ...newResearch, query: e.target.value })}
|
||||
className="bg-gray-700 text-white px-4 py-2 rounded"
|
||||
/>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Research</h1>
|
||||
<p className="text-slate-400">research panel</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={searching || !newResearch.name || !newResearch.query}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded disabled:opacity-50"
|
||||
>
|
||||
{searching ? "Researching..." : "Run Research"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Research List */}
|
||||
{loading ? (
|
||||
<p className="text-gray-400">Loading...</p>
|
||||
) : research.length === 0 ? (
|
||||
<p className="text-gray-400">No research yet. Run your first search!</p>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{research.map((r) => (
|
||||
<div key={r.id} className="bg-gray-800 rounded-lg overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="p-4 border-b border-gray-700 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="font-bold text-lg">{r.name}</h3>
|
||||
<p className="text-gray-400 text-sm">{r.query}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setExpanded(expanded === r.id ? null : r.id)}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded"
|
||||
>
|
||||
{expanded === r.id ? "Hide Table" : "View Table"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteResearch(r.id)}
|
||||
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table View */}
|
||||
{expanded === r.id && r.results && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-gray-900">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-gray-400 font-medium">✓</th>
|
||||
<th className="px-4 py-3 text-left text-gray-400 font-medium">Skill / Tool</th>
|
||||
<th className="px-4 py-3 text-left text-gray-400 font-medium">What It Does</th>
|
||||
<th className="px-4 py-3 text-center text-gray-400 font-medium">Score</th>
|
||||
<th className="px-4 py-3 text-center text-gray-400 font-medium">Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700">
|
||||
{r.results.map((result, i) => (
|
||||
<tr key={i} className="hover:bg-gray-750">
|
||||
<td className="px-4 py-3 text-center">
|
||||
<input type="checkbox" className="w-5 h-5 accent-green-500" />
|
||||
</td>
|
||||
<td className="px-4 py-3 font-medium text-white max-w-xs truncate">
|
||||
{result.title}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-gray-300 max-w-md">
|
||||
{result.content.substring(0, 150)}...
|
||||
</td>
|
||||
<td className="px-4 py-3 text-center">
|
||||
<span className={`px-2 py-1 rounded text-xs ${
|
||||
result.score > 0.9 ? 'bg-green-900 text-green-400' :
|
||||
result.score > 0.7 ? 'bg-yellow-900 text-yellow-400' :
|
||||
'bg-gray-700 text-gray-400'
|
||||
}`}>
|
||||
{(result.score * 100).toFixed(1)}%
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-center">
|
||||
<a
|
||||
href={result.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 underline"
|
||||
>
|
||||
Open →
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">research loading...</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,81 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ResumeUploadPage() {
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [uploadedImages, setUploadedImages] = useState<string[]>([]);
|
||||
|
||||
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
if (!files) return;
|
||||
|
||||
setUploading(true);
|
||||
const newImages: string[] = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/upload", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.url) {
|
||||
newImages.push(data.url);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Upload failed:", err);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedImages([...uploadedImages, ...newImages]);
|
||||
setUploading(false);
|
||||
};
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function Resume-uploadPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-950 p-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h1 className="text-2xl font-bold text-white mb-6">📤 Upload Resume Images</h1>
|
||||
<p className="text-slate-400 mb-6">Upload images of your resume design to let Horus analyze and recreate it.</p>
|
||||
|
||||
<div className="border-2 border-dashed border-slate-600 rounded-xl p-12 text-center hover:border-slate-500 transition-colors">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
multiple
|
||||
onChange={handleUpload}
|
||||
className="hidden"
|
||||
id="file-upload"
|
||||
/>
|
||||
<label htmlFor="file-upload" className="cursor-pointer">
|
||||
<div className="text-6xl mb-4">📁</div>
|
||||
<div className="text-white text-lg mb-2">Click to upload images</div>
|
||||
<div className="text-slate-500 text-sm">PNG, JPG, GIF up to 10MB</div>
|
||||
</label>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Resume-upload</h1>
|
||||
<p className="text-slate-400">resume-upload panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">resume-upload loading...</p>
|
||||
</div>
|
||||
|
||||
{uploading && (
|
||||
<div className="mt-6 text-center text-slate-400">Uploading...</div>
|
||||
)}
|
||||
|
||||
{uploadedImages.length > 0 && (
|
||||
<div className="mt-8">
|
||||
<h2 className="text-white font-bold mb-4">Uploaded Images:</h2>
|
||||
<div className="space-y-4">
|
||||
{uploadedImages.map((url, i) => (
|
||||
<div key={i} className="bg-slate-800 rounded-lg p-4">
|
||||
<div className="text-slate-400 text-sm mb-2">Image {i + 1}</div>
|
||||
<div className="bg-slate-700 rounded p-2 text-slate-300 text-sm font-mono break-all">{url}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,519 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface PersonalInfo {
|
||||
name: string;
|
||||
title: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
location: string;
|
||||
linkedin: string;
|
||||
website: string;
|
||||
}
|
||||
|
||||
interface Experience {
|
||||
id: string;
|
||||
title: string;
|
||||
company: string;
|
||||
location: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
bullets: string[];
|
||||
}
|
||||
|
||||
interface Education {
|
||||
id: string;
|
||||
degree: string;
|
||||
school: string;
|
||||
location: string;
|
||||
graduationDate: string;
|
||||
gpa: string;
|
||||
honors: string;
|
||||
}
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function ResumePage() {
|
||||
const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit");
|
||||
const [personal, setPersonal] = useState<PersonalInfo>({
|
||||
name: "Your Name",
|
||||
title: "Your Professional Title",
|
||||
email: "your.email@email.com",
|
||||
phone: "+1 (555) 000-0000",
|
||||
location: "City, State",
|
||||
linkedin: "linkedin.com/in/yourprofile",
|
||||
website: "yourwebsite.com"
|
||||
});
|
||||
|
||||
const [experiences, setExperiences] = useState<Experience[]>([
|
||||
{
|
||||
id: "1",
|
||||
title: "Job Title",
|
||||
company: "Company Name",
|
||||
location: "City, State",
|
||||
startDate: "Jan 2020",
|
||||
endDate: "Present",
|
||||
bullets: [
|
||||
"Key achievement or responsibility with measurable impact",
|
||||
"Another accomplishment that demonstrates your skills",
|
||||
"Additional relevant contribution"
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const [education, setEducation] = useState<Education[]>([
|
||||
{
|
||||
id: "1",
|
||||
degree: "Bachelor of Science in Computer Science",
|
||||
school: "University Name",
|
||||
location: "City, State",
|
||||
graduationDate: "May 2016",
|
||||
gpa: "3.8/4.0",
|
||||
honors: "Magna Cum Laude"
|
||||
}
|
||||
]);
|
||||
|
||||
const [skills, setSkills] = useState<string[]>([
|
||||
"JavaScript/TypeScript", "React/Next.js", "Node.js", "Python",
|
||||
"AWS/Cloud Services", "Database Management", "Agile/Scrum", "Git"
|
||||
]);
|
||||
|
||||
const [languages, setLanguages] = useState<{name: string, level: string}[]>([
|
||||
{ name: "English", level: "Native" },
|
||||
{ name: "Spanish", level: "Professional" },
|
||||
{ name: "Arabic", level: "Native" }
|
||||
]);
|
||||
|
||||
const [certifications, setCertifications] = useState<string[]>([
|
||||
"AWS Solutions Architect",
|
||||
"Google Cloud Professional"
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem("haitham_resume_v2");
|
||||
if (saved) {
|
||||
try {
|
||||
const data = JSON.parse(saved);
|
||||
if (data.personal) setPersonal(data.personal);
|
||||
if (data.experiences) setExperiences(data.experiences);
|
||||
if (data.education) setEducation(data.education);
|
||||
if (data.skills) setSkills(data.skills);
|
||||
if (data.languages) setLanguages(data.languages);
|
||||
if (data.certifications) setCertifications(data.certifications);
|
||||
} catch (e) {}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("haitham_resume_v2", JSON.stringify({
|
||||
personal, experiences, education, skills, languages, certifications
|
||||
}));
|
||||
}, [personal, experiences, education, skills, languages, certifications]);
|
||||
|
||||
const addExperience = () => {
|
||||
setExperiences([...experiences, {
|
||||
id: Date.now().toString(),
|
||||
title: "", company: "", location: "", startDate: "", endDate: "", bullets: [""]
|
||||
}]);
|
||||
};
|
||||
|
||||
const updateExperience = (id: string, field: keyof Experience, value: any) => {
|
||||
setExperiences(experiences.map(e => e.id === id ? { ...e, [field]: value } : e));
|
||||
};
|
||||
|
||||
const addBullet = (id: string) => {
|
||||
setExperiences(experiences.map(e =>
|
||||
e.id === id ? { ...e, bullets: [...e.bullets, ""] } : e
|
||||
));
|
||||
};
|
||||
|
||||
const updateBullet = (expId: string, index: number, value: string) => {
|
||||
setExperiences(experiences.map(e =>
|
||||
e.id === expId ? { ...e, bullets: e.bullets.map((b, i) => i === index ? value : b) } : e
|
||||
));
|
||||
};
|
||||
|
||||
const removeBullet = (expId: string, index: number) => {
|
||||
setExperiences(experiences.map(e =>
|
||||
e.id === expId ? { ...e, bullets: e.bullets.filter((_, i) => i !== index) } : e
|
||||
));
|
||||
};
|
||||
|
||||
const addEducation = () => {
|
||||
setEducation([...education, {
|
||||
id: Date.now().toString(), degree: "", school: "", location: "",
|
||||
graduationDate: "", gpa: "", honors: ""
|
||||
}]);
|
||||
};
|
||||
|
||||
const updateEducation = (id: string, field: keyof Education, value: string) => {
|
||||
setEducation(education.map(e => e.id === id ? { ...e, [field]: value } : e));
|
||||
};
|
||||
|
||||
const handlePrint = () => {
|
||||
setActiveTab("preview");
|
||||
setTimeout(() => window.print(), 100);
|
||||
};
|
||||
|
||||
const inputClass = "w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500";
|
||||
const labelClass = "block text-slate-400 text-xs mb-1 font-medium";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-950 flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="bg-slate-900 border-b border-slate-800 px-6 py-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">📝 Professional Resume</h1>
|
||||
<p className="text-slate-400 text-sm">Edit • Preview • Download PDF</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Resume</h1>
|
||||
<p className="text-slate-400">resume panel</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setActiveTab(activeTab === "edit" ? "preview" : "edit")}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
activeTab === "edit" ? "bg-slate-700 text-white" : "bg-blue-600 text-white"
|
||||
}`}
|
||||
>
|
||||
{activeTab === "edit" ? "👁️ Preview" : "✏️ Edit"}
|
||||
</button>
|
||||
<button
|
||||
onClick={handlePrint}
|
||||
className="bg-emerald-600 hover:bg-emerald-500 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
|
||||
>
|
||||
📄 Download PDF
|
||||
</button>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">resume loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeTab === "edit" ? (
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
|
||||
{/* Personal Info */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
|
||||
<h2 className="text-white font-bold mb-4">👤 Personal Information</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className={labelClass}>Full Name</label>
|
||||
<input type="text" value={personal.name} onChange={e => setPersonal({...personal, name: e.target.value})} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Professional Title</label>
|
||||
<input type="text" value={personal.title} onChange={e => setPersonal({...personal, title: e.target.value})} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Email</label>
|
||||
<input type="email" value={personal.email} onChange={e => setPersonal({...personal, email: e.target.value})} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Phone</label>
|
||||
<input type="tel" value={personal.phone} onChange={e => setPersonal({...personal, phone: e.target.value})} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Location</label>
|
||||
<input type="text" value={personal.location} onChange={e => setPersonal({...personal, location: e.target.value})} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>LinkedIn</label>
|
||||
<input type="text" value={personal.linkedin} onChange={e => setPersonal({...personal, linkedin: e.target.value})} className={inputClass} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Experience */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-white font-bold">💼 Work Experience</h2>
|
||||
<button onClick={addExperience} className="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded text-sm">+ Add Position</button>
|
||||
</div>
|
||||
{experiences.map((exp) => (
|
||||
<div key={exp.id} className="bg-slate-900 rounded-lg p-4 mb-4 border border-slate-700">
|
||||
<div className="grid grid-cols-2 gap-4 mb-3">
|
||||
<div>
|
||||
<label className={labelClass}>Job Title</label>
|
||||
<input type="text" value={exp.title} onChange={e => updateExperience(exp.id, "title", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Company</label>
|
||||
<input type="text" value={exp.company} onChange={e => updateExperience(exp.id, "company", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Location</label>
|
||||
<input type="text" value={exp.location} onChange={e => updateExperience(exp.id, "location", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<label className={labelClass}>Start</label>
|
||||
<input type="text" value={exp.startDate} onChange={e => updateExperience(exp.id, "startDate", e.target.value)} className={inputClass} placeholder="Jan 2020" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className={labelClass}>End</label>
|
||||
<input type="text" value={exp.endDate} onChange={e => updateExperience(exp.id, "endDate", e.target.value)} className={inputClass} placeholder="Present" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className={labelClass}>Key Achievements</label>
|
||||
{exp.bullets.map((bullet, i) => (
|
||||
<div key={i} className="flex gap-2 mb-2">
|
||||
<input type="text" value={bullet} onChange={e => updateBullet(exp.id, i, e.target.value)} className={inputClass} placeholder={`Achievement ${i + 1}`} />
|
||||
{exp.bullets.length > 1 && (
|
||||
<button onClick={() => removeBullet(exp.id, i)} className="text-red-400 hover:text-red-300 text-sm px-2">✕</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => addBullet(exp.id)} className="text-blue-400 hover:text-blue-300 text-xs">+ Add achievement</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Education */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-white font-bold">🎓 Education</h2>
|
||||
<button onClick={addEducation} className="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded text-sm">+ Add</button>
|
||||
</div>
|
||||
{education.map((edu) => (
|
||||
<div key={edu.id} className="bg-slate-900 rounded-lg p-4 mb-4 border border-slate-700">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className={labelClass}>Degree</label>
|
||||
<input type="text" value={edu.degree} onChange={e => updateEducation(edu.id, "degree", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>School</label>
|
||||
<input type="text" value={edu.school} onChange={e => updateEducation(edu.id, "school", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Location</label>
|
||||
<input type="text" value={edu.location} onChange={e => updateEducation(edu.id, "location", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Graduation Date</label>
|
||||
<input type="text" value={edu.graduationDate} onChange={e => updateEducation(edu.id, "graduationDate", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>GPA (optional)</label>
|
||||
<input type="text" value={edu.gpa} onChange={e => updateEducation(edu.id, "gpa", e.target.value)} className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Honors (optional)</label>
|
||||
<input type="text" value={edu.honors} onChange={e => updateEducation(edu.id, "honors", e.target.value)} className={inputClass} placeholder="Magna Cum Laude" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Skills */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
|
||||
<h2 className="text-white font-bold mb-4">🛠️ Technical Skills</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{skills.map((skill, i) => (
|
||||
<div key={i} className="flex items-center gap-2 bg-slate-900 rounded-lg px-3 py-1">
|
||||
<input
|
||||
type="text"
|
||||
value={skill}
|
||||
onChange={e => {
|
||||
const newSkills = [...skills];
|
||||
newSkills[i] = e.target.value;
|
||||
setSkills(newSkills);
|
||||
}}
|
||||
className="bg-transparent text-white text-sm w-24 focus:outline-none"
|
||||
/>
|
||||
<button onClick={() => setSkills(skills.filter((_, j) => j !== i))} className="text-slate-500 hover:text-red-400 text-xs">✕</button>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => setSkills([...skills, ""])} className="bg-slate-700 hover:bg-slate-600 text-white text-sm px-3 py-1 rounded-lg">+ Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Languages */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
|
||||
<h2 className="text-white font-bold mb-4">🌍 Languages</h2>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{languages.map((lang, i) => (
|
||||
<div key={i} className="flex items-center gap-2 bg-slate-900 rounded-lg px-3 py-2">
|
||||
<input
|
||||
type="text"
|
||||
value={lang.name}
|
||||
onChange={e => {
|
||||
const newLangs = [...languages];
|
||||
newLangs[i].name = e.target.value;
|
||||
setLanguages(newLangs);
|
||||
}}
|
||||
className="bg-transparent text-white text-sm w-24 focus:outline-none"
|
||||
/>
|
||||
<span className="text-slate-500 text-xs">•</span>
|
||||
<input
|
||||
type="text"
|
||||
value={lang.level}
|
||||
onChange={e => {
|
||||
const newLangs = [...languages];
|
||||
newLangs[i].level = e.target.value;
|
||||
setLanguages(newLangs);
|
||||
}}
|
||||
className="bg-transparent text-slate-400 text-sm w-20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => setLanguages([...languages, {name: "", level: ""}])} className="bg-slate-700 hover:bg-slate-600 text-white text-sm px-3 py-2 rounded-lg">+ Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Certifications */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
|
||||
<h2 className="text-white font-bold mb-4">📜 Certifications</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{certifications.map((cert, i) => (
|
||||
<div key={i} className="flex items-center gap-2 bg-slate-900 rounded-lg px-3 py-1">
|
||||
<input
|
||||
type="text"
|
||||
value={cert}
|
||||
onChange={e => {
|
||||
const newCerts = [...certifications];
|
||||
newCerts[i] = e.target.value;
|
||||
setCertifications(newCerts);
|
||||
}}
|
||||
className="bg-transparent text-white text-sm w-40 focus:outline-none"
|
||||
/>
|
||||
<button onClick={() => setCertifications(certifications.filter((_, j) => j !== i))} className="text-slate-500 hover:text-red-400 text-xs">✕</button>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => setCertifications([...certifications, ""])} className="bg-slate-700 hover:bg-slate-600 text-white text-sm px-3 py-1 rounded-lg">+ Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* PREVIEW - Professional Resume Template */
|
||||
<div className="flex-1 overflow-auto bg-slate-100 p-8">
|
||||
<div className="max-w-850px mx-auto bg-white shadow-xl rounded-lg overflow-hidden" style={{maxWidth: "850px"}}>
|
||||
|
||||
{/* Header - Dark Blue Professional */}
|
||||
<div className="bg-gradient-to-r from-slate-800 to-slate-700 text-white px-8 py-10">
|
||||
<div className="text-center mb-4">
|
||||
<h1 className="text-3xl font-bold tracking-wide mb-2">{personal.name || "Your Name"}</h1>
|
||||
<div className="text-slate-300 text-lg font-light">{personal.title || "Professional Title"}</div>
|
||||
</div>
|
||||
<div className="flex justify-center flex-wrap gap-4 text-sm text-slate-300">
|
||||
{personal.email && <span>✉️ {personal.email}</span>}
|
||||
{personal.phone && <span>📱 {personal.phone}</span>}
|
||||
{personal.location && <span>📍 {personal.location}</span>}
|
||||
{personal.linkedin && <span>💼 {personal.linkedin}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="px-8 py-6 space-y-6">
|
||||
|
||||
{/* Summary */}
|
||||
<div>
|
||||
<div className="border-b-2 border-slate-800 pb-1 mb-3">
|
||||
<span className="text-sm font-bold text-slate-800 uppercase tracking-wider">Professional Summary</span>
|
||||
</div>
|
||||
<p className="text-slate-600 text-sm leading-relaxed">
|
||||
Results-driven professional with proven expertise in delivering high-impact solutions. Skilled in modern technologies and best practices. Demonstrated ability to lead projects, optimize processes, and achieve organizational goals.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Experience */}
|
||||
<div>
|
||||
<div className="border-b-2 border-slate-800 pb-1 mb-3">
|
||||
<span className="text-sm font-bold text-slate-800 uppercase tracking-wider">Work Experience</span>
|
||||
</div>
|
||||
{experiences.filter(e => e.title || e.company).map((exp) => (
|
||||
<div key={exp.id} className="mb-4">
|
||||
<div className="flex justify-between items-baseline mb-1">
|
||||
<div>
|
||||
<span className="font-bold text-slate-800">{exp.title || "Job Title"}</span>
|
||||
<span className="text-slate-600"> | {exp.company || "Company"}</span>
|
||||
</div>
|
||||
<span className="text-slate-500 text-sm">{exp.startDate} - {exp.endDate}</span>
|
||||
</div>
|
||||
{exp.location && <div className="text-slate-500 text-sm mb-2">{exp.location}</div>}
|
||||
<ul className="space-y-1">
|
||||
{exp.bullets.filter(b => b).map((bullet, i) => (
|
||||
<li key={i} className="text-slate-600 text-sm flex items-start gap-2">
|
||||
<span className="text-slate-400">•</span>
|
||||
<span>{bullet}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Education */}
|
||||
<div>
|
||||
<div className="border-b-2 border-slate-800 pb-1 mb-3">
|
||||
<span className="text-sm font-bold text-slate-800 uppercase tracking-wider">Education</span>
|
||||
</div>
|
||||
{education.filter(e => e.degree || e.school).map((edu) => (
|
||||
<div key={edu.id} className="mb-3">
|
||||
<div className="flex justify-between items-baseline">
|
||||
<div>
|
||||
<span className="font-bold text-slate-800">{edu.degree || "Degree"}</span>
|
||||
{edu.honors && <span className="text-slate-500"> ({edu.honors})</span>}
|
||||
</div>
|
||||
<span className="text-slate-500 text-sm">{edu.graduationDate}</span>
|
||||
</div>
|
||||
<div className="text-slate-600 text-sm">{edu.school}{edu.location && `, ${edu.location}`}</div>
|
||||
{edu.gpa && <div className="text-slate-500 text-sm">GPA: {edu.gpa}</div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Skills */}
|
||||
<div>
|
||||
<div className="border-b-2 border-slate-800 pb-1 mb-3">
|
||||
<span className="text-sm font-bold text-slate-800 uppercase tracking-wider">Technical Skills</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{skills.filter(s => s).map((skill, i) => (
|
||||
<span key={i} className="bg-slate-100 text-slate-700 px-3 py-1 rounded text-sm">{skill}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Languages & Certs */}
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<div className="border-b-2 border-slate-800 pb-1 mb-3">
|
||||
<span className="text-sm font-bold text-slate-800 uppercase tracking-wider">Languages</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{languages.filter(l => l.name).map((lang, i) => (
|
||||
<div key={i} className="text-sm">
|
||||
<span className="text-slate-800 font-medium">{lang.name}</span>
|
||||
<span className="text-slate-500"> - {lang.level}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="border-b-2 border-slate-800 pb-1 mb-3">
|
||||
<span className="text-sm font-bold text-slate-800 uppercase tracking-wider">Certifications</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{certifications.filter(c => c).map((cert, i) => (
|
||||
<div key={i} className="text-slate-700 text-sm">{cert}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style jsx global>{`
|
||||
@media print {
|
||||
body { background: white !important; }
|
||||
.bg-slate-100 { background: white !important; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import VoiceChat from "@/components/mission-control/VoiceChat";
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function VoiceChatPage() {
|
||||
export default function SessionsPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<VoiceChat />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Sessions</h1>
|
||||
<p className="text-slate-400">sessions panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">sessions loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
export default function SkillsPanelPage() {
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function SkillsPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">🎯 Skills Panel</h1>
|
||||
<p className="text-slate-400">Manage OpenClaw skills</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Skills</h1>
|
||||
<p className="text-slate-400">skills panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">skills loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Skills panel loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
export default function SystemStatusPage() {
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function System-statusPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📡 System Status</h1>
|
||||
<p className="text-slate-400">Server health and monitoring</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">System-status</h1>
|
||||
<p className="text-slate-400">system-status panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">system-status loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">System status loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import TaskHistoryPanel from "@/components/mission-control/TaskHistoryPanel";
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function TaskHistoryPage() {
|
||||
export default function Task-historyPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<TaskHistoryPanel />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Task-history</h1>
|
||||
<p className="text-slate-400">task-history panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">task-history loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
import { TaskCardsPanel } from "@/components/mission-control/TaskCardsPanel";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { Task, TaskStatus } from "@/lib/mission-control/types";
|
||||
|
||||
const SAMPLE_TASKS: Task[] = [
|
||||
@@ -22,12 +23,15 @@ export default function TasksPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📋 Task Board</h1>
|
||||
<p className="text-slate-400">Manage tasks across all projects</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📋 Task Board</h1>
|
||||
<p className="text-slate-400">Manage tasks across all projects</p>
|
||||
</div>
|
||||
<TaskCardsPanel tasks={tasks} toggleTask={toggleTask} />
|
||||
</div>
|
||||
<TaskCardsPanel tasks={tasks} toggleTask={toggleTask} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import TempleOfAIDashboard from "@/components/temple-of-ai/TempleOfAIDashboard";
|
||||
"use client";
|
||||
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function TemplePage() {
|
||||
return <TempleOfAIDashboard />;
|
||||
return (
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Temple</h1>
|
||||
<p className="text-slate-400">temple panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">temple loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
export default function TradingChartPage() {
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function Trading-chartPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📊 Trading Chart</h1>
|
||||
<p className="text-slate-400">BTC, ETH, SOL price charts</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Trading-chart</h1>
|
||||
<p className="text-slate-400">trading-chart panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">trading-chart loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Trading charts loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
export default function TradingReportsPage() {
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function Trading-reportsPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">📔 Trading Reports</h1>
|
||||
<p className="text-slate-400">P&L stats, win rate, trade history</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Trading-reports</h1>
|
||||
<p className="text-slate-400">trading-reports panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">trading-reports loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Trading reports loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
export default function TradingToolsPage() {
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function Trading-toolsPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">🛠️ Trading Tools</h1>
|
||||
<p className="text-slate-400">Risk management, position sizing, etc.</p>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Trading-tools</h1>
|
||||
<p className="text-slate-400">trading-tools panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">trading-tools loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">Trading tools loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,228 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const AVAILABLE_CATEGORIES = [
|
||||
"SiteMente",
|
||||
"OpenClaw",
|
||||
"Trading",
|
||||
"Marketing",
|
||||
"Technical",
|
||||
"Voice AI",
|
||||
"Business",
|
||||
"Inspiration",
|
||||
"Personal Growth"
|
||||
];
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function TranscriptsPage() {
|
||||
const [transcripts, setTranscripts] = useState<any[]>([]);
|
||||
const [videoUrl, setVideoUrl] = useState("");
|
||||
const [transcriptText, setTranscriptText] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||
const [filterCategory, setFilterCategory] = useState<string>("all");
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTranscripts();
|
||||
}, []);
|
||||
|
||||
const fetchTranscripts = async () => {
|
||||
const res = await fetch("/api/transcripts");
|
||||
const data = await res.json();
|
||||
setTranscripts(data);
|
||||
};
|
||||
|
||||
const toggleCategory = (cat: string) => {
|
||||
setSelectedCategories(prev =>
|
||||
prev.includes(cat)
|
||||
? prev.filter(c => c !== cat)
|
||||
: [...prev, cat]
|
||||
);
|
||||
};
|
||||
|
||||
const saveTranscript = async () => {
|
||||
if (!videoUrl || !transcriptText) return;
|
||||
setSaving(true);
|
||||
|
||||
const res = await fetch("/api/transcripts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
videoUrl,
|
||||
title: title || videoUrl,
|
||||
transcript: transcriptText,
|
||||
categories: selectedCategories
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
setSaving(false);
|
||||
|
||||
if (data.transcripts) {
|
||||
setTranscripts(data.transcripts);
|
||||
setVideoUrl("");
|
||||
setTranscriptText("");
|
||||
setTitle("");
|
||||
setSelectedCategories([]);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTranscript = async (videoId: string) => {
|
||||
if (!confirm("Delete this transcript?")) return;
|
||||
|
||||
const res = await fetch("/api/transcripts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ action: "delete", videoId })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.transcripts) {
|
||||
setTranscripts(data.transcripts);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredTranscripts = filterCategory === "all"
|
||||
? transcripts
|
||||
: transcripts.filter(t => t.categories?.includes(filterCategory));
|
||||
|
||||
const allCategories = [...new Set(transcripts.flatMap(t => t.categories || []))];
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<h1 className="text-2xl font-bold mb-6">🎬 YouTube Transcripts</h1>
|
||||
|
||||
{/* Add New Transcript */}
|
||||
<div className="bg-white/10 rounded-lg p-4 mb-6">
|
||||
<h2 className="text-lg font-semibold mb-3">Add Transcript</h2>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={videoUrl}
|
||||
onChange={(e) => setVideoUrl(e.target.value)}
|
||||
placeholder="YouTube URL..."
|
||||
className="w-full px-4 py-2 bg-black/30 border border-white/20 rounded-lg text-white placeholder-white/50 mb-3"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Title..."
|
||||
className="w-full px-4 py-2 bg-black/30 border border-white/20 rounded-lg text-white placeholder-white/50 mb-3"
|
||||
/>
|
||||
|
||||
<div className="mb-3">
|
||||
<p className="text-white/70 mb-2">Categories:</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{AVAILABLE_CATEGORIES.map(cat => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => toggleCategory(cat)}
|
||||
className={`px-3 py-1 rounded-full text-sm ${
|
||||
selectedCategories.includes(cat)
|
||||
? "bg-brand-pink text-white"
|
||||
: "bg-white/10 text-white/70 hover:bg-white/20"
|
||||
}`}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Transcripts</h1>
|
||||
<p className="text-slate-400">transcripts panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">transcripts loading...</p>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
value={transcriptText}
|
||||
onChange={(e) => setTranscriptText(e.target.value)}
|
||||
placeholder="Paste transcript here..."
|
||||
rows={6}
|
||||
className="w-full px-4 py-2 bg-black/30 border border-white/20 rounded-lg text-white placeholder-white/50 mb-3"
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={saveTranscript}
|
||||
disabled={saving || !videoUrl || !transcriptText}
|
||||
className="px-6 py-2 bg-brand-pink rounded-lg font-medium hover:bg-[#ff7bc0] disabled:opacity-50"
|
||||
>
|
||||
{saving ? "Saving..." : "Save Transcript"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Filter */}
|
||||
<div className="flex gap-2 mb-4 flex-wrap">
|
||||
<button
|
||||
onClick={() => setFilterCategory("all")}
|
||||
className={`px-3 py-1 rounded-full text-sm ${
|
||||
filterCategory === "all" ? "bg-brand-pink" : "bg-white/10"
|
||||
}`}
|
||||
>
|
||||
All ({transcripts.length})
|
||||
</button>
|
||||
{allCategories.map(cat => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setFilterCategory(cat)}
|
||||
className={`px-3 py-1 rounded-full text-sm ${
|
||||
filterCategory === cat ? "bg-brand-pink" : "bg-white/10"
|
||||
}`}
|
||||
>
|
||||
{cat} ({transcripts.filter(t => t.categories?.includes(cat)).length})
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Saved Transcripts */}
|
||||
<div className="space-y-4">
|
||||
{filteredTranscripts.map((t: any, i: number) => (
|
||||
<div key={i} className="bg-white/10 rounded-lg p-4">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div>
|
||||
<h3 className="font-semibold">{t.title}</h3>
|
||||
<a
|
||||
href={t.videoUrl}
|
||||
target="_blank"
|
||||
className="text-brand-pink text-sm hover:underline"
|
||||
>
|
||||
YouTube ↗
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="text-white/40 text-xs">
|
||||
{new Date(t.savedAt).toLocaleDateString()}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => deleteTranscript(t.videoId)}
|
||||
className="text-red-400 hover:text-red-300"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1 mb-2">
|
||||
{t.categories?.map((c: string) => (
|
||||
<span key={c} className="px-2 py-0.5 bg-brand-pink/30 rounded text-xs">
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary className="text-white/50 cursor-pointer text-sm">
|
||||
View Transcript ({t.transcript?.length || 0} chars)
|
||||
</summary>
|
||||
<pre className="mt-2 text-xs text-white/70 whitespace-pre-wrap max-h-60 overflow-y-auto bg-black/20 p-2 rounded">
|
||||
{t.transcript}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{filteredTranscripts.length === 0 && (
|
||||
<div className="text-white/50">No transcripts saved yet</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import VoiceChat from "@/components/mission-control/VoiceChat";
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
export default function VoicePage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<VoiceChat />
|
||||
</div>
|
||||
<>
|
||||
<BackToMC />
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Voice</h1>
|
||||
<p className="text-slate-400">voice panel</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-8 text-center">
|
||||
<p className="text-slate-400">voice loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
export default function BackToMC() {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<Link
|
||||
href="/mission-control"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white text-sm rounded-lg transition-colors"
|
||||
>
|
||||
<span>←</span>
|
||||
<span>Back to Mission Control</span>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user