feat: Mission Control dashboard for project management
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user