feat: Mission Control dashboard for project management

This commit is contained in:
root
2026-02-16 11:22:06 +00:00
parent 11252e6520
commit 067be4d5a2
11 changed files with 552 additions and 21 deletions
+4 -3
View File
@@ -93,11 +93,12 @@ export const generateSiteMenteText = async (
): Promise<string> => {
try {
const client = getClient();
const response = await client.models.generateContent({
const params = {
model: TEXT_MODEL,
contents: buildContents(messages),
systemInstruction: buildSystemInstruction(messages),
});
} as any;
params.systemInstruction = buildSystemInstruction(messages);
const response = await client.models.generateContent(params);
return extractText(response);
} catch (error) {
console.error("[SiteMente][Gemini] Text generation failed", error);
+2 -2
View File
@@ -31,8 +31,8 @@ const SYSTEM_PROMPT = [
"Keep answers concise, practical, and business-oriented.",
].join(" ");
const withSystemPrompt = (messages: SiteMenteMessage[]) => {
return [{ role: "system", content: SYSTEM_PROMPT }, ...messages];
const withSystemPrompt = (messages: SiteMenteMessage[]): SiteMenteMessage[] => {
return [{ role: "system" as const, content: SYSTEM_PROMPT }, ...messages];
};
export const runSiteMenteText = async (
+103
View File
@@ -0,0 +1,103 @@
"use client";
import { createContext, useContext, useState, useEffect, ReactNode } from "react";
import { Task, TaskStatus, initialTasks } from "./types";
interface MissionControlStore {
tasks: Task[];
toggleTask: (id: string) => void;
updateTaskStatus: (id: string, status: TaskStatus) => void;
getProjectProgress: (projectId: string) => number;
getTasksByProject: (projectId: string) => Task[];
}
const MissionControlContext = createContext<MissionControlStore | null>(null);
const STORAGE_KEY = "sitemente:mission-control";
export function MissionControlProvider({ children }: { children: ReactNode }) {
const [tasks, setTasks] = useState<Task[]>(initialTasks);
// Load from localStorage on mount
useEffect(() => {
if (typeof window === "undefined") return;
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length > 0) {
setTasks(parsed);
}
} catch {
// Use default
}
}
}, []);
// Save to localStorage on change
useEffect(() => {
if (typeof window === "undefined") return;
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}, [tasks]);
const toggleTask = (id: string) => {
setTasks((prev) =>
prev.map((t) => {
if (t.id !== id) return t;
const newStatus: TaskStatus = t.status === "done" ? "todo" : "done";
return {
...t,
status: newStatus,
completedAt: newStatus === "done" ? new Date().toISOString().split("T")[0] : undefined,
};
})
);
};
const updateTaskStatus = (id: string, status: TaskStatus) => {
setTasks((prev) =>
prev.map((t) =>
t.id === id
? {
...t,
status,
completedAt: status === "done" ? new Date().toISOString().split("T")[0] : undefined,
}
: t
)
);
};
const getProjectProgress = (projectId: string) => {
const projectTasks = tasks.filter((t) => t.project === projectId);
if (projectTasks.length === 0) return 0;
const done = projectTasks.filter((t) => t.status === "done").length;
return Math.round((done / projectTasks.length) * 100);
};
const getTasksByProject = (projectId: string) => {
return tasks.filter((t) => t.project === projectId).sort((a, b) => a.order - b.order);
};
return (
<MissionControlContext.Provider
value={{
tasks,
toggleTask,
updateTaskStatus,
getProjectProgress,
getTasksByProject,
}}
>
{children}
</MissionControlContext.Provider>
);
}
export function useMissionControl() {
const ctx = useContext(MissionControlContext);
if (!ctx) {
throw new Error("useMissionControl must be used within MissionControlProvider");
}
return ctx;
}
+71
View File
@@ -0,0 +1,71 @@
// Mission Control Task Types
export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'blocked' | 'paused';
export type ProjectType = 'sitemente' | 'holacompi';
export interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: 'critical' | 'high' | 'medium' | 'low';
project: ProjectType;
assignee?: string;
dueDate?: string;
completedAt?: string;
order: number;
}
export interface Project {
id: ProjectType;
name: string;
description: string;
status: 'active' | 'paused' | 'completed';
progress: number;
tasks: Task[];
color: string;
}
// V1 SiteMente Checklist
export const initialTasks: Task[] = [
// Step 1: Pricing + Services (already mostly done)
{ id: 't1', title: 'Pricing + Services section', description: '3 tiers + vertical packs + yearly toggle', status: 'done', priority: 'high', project: 'sitemente', order: 1, completedAt: '2026-02-16' },
{ id: 't2', title: 'Vertical pack cards', description: 'Real Estate, Restaurant, Clinic as distinct upsells', status: 'todo', priority: 'high', project: 'sitemente', order: 2 },
// Step 2: Contact/Lead Capture
{ id: 't3', title: 'Contact/onboarding form', description: 'Lead capture: name, business type, phone, needs', status: 'todo', priority: 'high', project: 'sitemente', order: 3 },
// Step 3: Demo Pages
{ id: 't4', title: 'Real Estate demo page', description: 'Polished vertical demo for realtors', status: 'todo', priority: 'high', project: 'sitemente', order: 4 },
{ id: 't5', title: 'Restaurant demo page', description: 'Polished vertical demo for restaurants', status: 'todo', priority: 'medium', project: 'sitemente', order: 5 },
{ id: 't6', title: 'Clinic demo page', description: 'Polished vertical demo for clinics', status: 'todo', priority: 'low', project: 'sitemente', order: 6 },
{ id: 't7', title: 'AI Widget live on landing', description: 'Embed widget on main landing page', status: 'todo', priority: 'high', project: 'sitemente', order: 7 },
// Step 4: How it works + FAQ
{ id: 't8', title: '"How it works" flow', description: '3-step visual flow showing the process', status: 'todo', priority: 'medium', project: 'sitemente', order: 8 },
{ id: 't9', title: 'FAQ accordion', description: '6-8 key questions for objection handling', status: 'todo', priority: 'medium', project: 'sitemente', order: 9 },
// Step 5: Polish
{ id: 't10', title: 'Mobile responsive pass', description: 'Ensure all components work on mobile', status: 'todo', priority: 'high', project: 'sitemente', order: 10 },
{ id: 't11', title: 'Loading states / transitions', description: 'Add skeleton loaders and smooth transitions', status: 'todo', priority: 'low', project: 'sitemente', order: 11 },
{ id: 't12', title: 'Meta tags + SEO basics', description: 'Open Graph, Twitter cards, sitemap', status: 'todo', priority: 'medium', project: 'sitemente', order: 12 },
// Step 6: Go-to-market
{ id: 't13', title: 'Identify 2-3 local businesses', description: 'Target list for first pitches', status: 'todo', priority: 'high', project: 'sitemente', order: 13 },
{ id: 't14', title: '1-pager PDF or demo link', description: 'Leave-behind for prospects', status: 'todo', priority: 'high', project: 'sitemente', order: 14 },
{ id: 't15', title: 'First paying client', description: 'Close the first deal', status: 'todo', priority: 'critical', project: 'sitemente', order: 15 },
// HolaCompi (paused until SiteMente revenue)
{ id: 'h1', title: 'HolaCompi core concept', description: 'AI ally for immigrants/consumers', status: 'paused', priority: 'medium', project: 'holacompi', order: 100 },
{ id: 'h2', title: 'Cross-sell to SiteMente businesses', description: 'Route leads from HolaCompi to SiteMente clients', status: 'paused', priority: 'medium', project: 'holacompi', order: 101 },
];
// Lightweight project summary (used in UI, not stored)
export interface ProjectSummary {
id: ProjectType;
name: string;
description: string;
status: 'active' | 'paused' | 'completed';
progress: number;
color: string;
}