202 lines
6.5 KiB
TypeScript
202 lines
6.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Clock, History, Loader2 } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetDescription,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetTrigger,
|
|
} from "@/components/ui/sheet";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { auth, db } from "@/lib/firebase/config";
|
|
import { doc, getDoc, collection, query, where, orderBy, getDocs } from "firebase/firestore";
|
|
import { formatDistanceToNow } from "date-fns";
|
|
|
|
interface MissionRevision {
|
|
id: string;
|
|
content: string;
|
|
updatedAt: Date;
|
|
updatedBy: string;
|
|
source: 'ai' | 'user';
|
|
}
|
|
|
|
interface MissionIdeaSectionProps {
|
|
projectId: string;
|
|
}
|
|
|
|
export function MissionIdeaSection({ projectId }: MissionIdeaSectionProps) {
|
|
const [loading, setLoading] = useState(true);
|
|
const [content, setContent] = useState("");
|
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
|
const [revisions, setRevisions] = useState<MissionRevision[]>([]);
|
|
const [loadingRevisions, setLoadingRevisions] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (projectId) {
|
|
fetchMissionIdea();
|
|
}
|
|
}, [projectId]);
|
|
|
|
const fetchMissionIdea = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const user = auth.currentUser;
|
|
if (!user) return;
|
|
|
|
// Fetch current mission idea from project document
|
|
const projectRef = doc(db, 'projects', projectId);
|
|
const projectSnap = await getDoc(projectRef);
|
|
|
|
if (projectSnap.exists()) {
|
|
const data = projectSnap.data();
|
|
setContent(
|
|
data.missionIdea ||
|
|
"Help solo founders build and launch their products 10x faster by turning conversations into production-ready code, designs, and marketing."
|
|
);
|
|
setLastUpdated(data.missionIdeaUpdatedAt?.toDate() || null);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching mission idea:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const fetchRevisions = async () => {
|
|
setLoadingRevisions(true);
|
|
try {
|
|
const user = auth.currentUser;
|
|
if (!user) return;
|
|
|
|
// Fetch revision history
|
|
const revisionsRef = collection(db, 'missionRevisions');
|
|
const revisionsQuery = query(
|
|
revisionsRef,
|
|
where('projectId', '==', projectId),
|
|
orderBy('updatedAt', 'desc')
|
|
);
|
|
const revisionsSnap = await getDocs(revisionsQuery);
|
|
|
|
const revisionsList: MissionRevision[] = [];
|
|
revisionsSnap.forEach((doc) => {
|
|
const data = doc.data();
|
|
revisionsList.push({
|
|
id: doc.id,
|
|
content: data.content,
|
|
updatedAt: data.updatedAt?.toDate(),
|
|
updatedBy: data.updatedBy || 'AI',
|
|
source: data.source || 'ai',
|
|
});
|
|
});
|
|
|
|
setRevisions(revisionsList);
|
|
} catch (error) {
|
|
console.error('Error fetching revisions:', error);
|
|
} finally {
|
|
setLoadingRevisions(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Content Card */}
|
|
<div className="rounded-lg border bg-card p-6">
|
|
<p className="text-xl font-medium leading-relaxed">
|
|
{content}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Meta Information */}
|
|
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="h-4 w-4" />
|
|
<span>
|
|
{lastUpdated
|
|
? `Last updated ${formatDistanceToNow(lastUpdated, { addSuffix: true })} by AI`
|
|
: 'Not yet updated'}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Revision History */}
|
|
<Sheet>
|
|
<SheetTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={fetchRevisions}
|
|
>
|
|
<History className="h-4 w-4 mr-2" />
|
|
View History
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent className="w-[500px] sm:w-[600px]">
|
|
<SheetHeader>
|
|
<SheetTitle>Revision History</SheetTitle>
|
|
<SheetDescription>
|
|
See how your mission idea has evolved over time
|
|
</SheetDescription>
|
|
</SheetHeader>
|
|
|
|
<ScrollArea className="h-[calc(100vh-120px)] mt-6">
|
|
{loadingRevisions ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
) : revisions.length === 0 ? (
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
<History className="h-12 w-12 mx-auto mb-3 opacity-50" />
|
|
<p className="text-sm">No revision history yet</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{revisions.map((revision, index) => (
|
|
<div
|
|
key={revision.id}
|
|
className="rounded-lg border bg-card p-4 space-y-2"
|
|
>
|
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-medium">
|
|
{revision.source === 'ai' ? 'AI Update' : 'Manual Edit'}
|
|
</span>
|
|
{index === 0 && (
|
|
<span className="px-2 py-0.5 rounded-full bg-primary/10 text-primary text-[10px] font-medium">
|
|
Current
|
|
</span>
|
|
)}
|
|
</div>
|
|
<span>
|
|
{formatDistanceToNow(revision.updatedAt, { addSuffix: true })}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm leading-relaxed">
|
|
{revision.content}
|
|
</p>
|
|
<div className="text-xs text-muted-foreground">
|
|
{revision.updatedAt.toLocaleString()}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</ScrollArea>
|
|
</SheetContent>
|
|
</Sheet>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|