VIBN Frontend for Coolify deployment
This commit is contained in:
@@ -0,0 +1,633 @@
|
||||
"use client";
|
||||
|
||||
import { use, useState } from "react";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Eye, MessageSquare, Copy, Share2, Sparkles, History, Loader2, Send, MousePointer2 } from "lucide-react";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Mock data for page variations
|
||||
const mockPageData: Record<string, any> = {
|
||||
"landing-hero": {
|
||||
name: "Landing Page Hero",
|
||||
emoji: "✨",
|
||||
style: "modern",
|
||||
prompt: "Create a modern landing page hero section with gradient background",
|
||||
v0Url: "https://v0.dev/chat/abc123",
|
||||
variations: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Version 1 - Blue Gradient",
|
||||
thumbnail: "https://placehold.co/800x600/1e40af/ffffff?text=Hero+V1",
|
||||
createdAt: "2025-11-11",
|
||||
views: 45,
|
||||
comments: 3,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Version 2 - Purple Gradient",
|
||||
thumbnail: "https://placehold.co/800x600/7c3aed/ffffff?text=Hero+V2",
|
||||
createdAt: "2025-11-10",
|
||||
views: 32,
|
||||
comments: 2,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Version 3 - Minimal",
|
||||
thumbnail: "https://placehold.co/800x600/6b7280/ffffff?text=Hero+V3",
|
||||
createdAt: "2025-11-09",
|
||||
views: 28,
|
||||
comments: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
"dashboard": {
|
||||
name: "Dashboard Layout",
|
||||
emoji: "📊",
|
||||
style: "minimal",
|
||||
prompt: "Design a clean dashboard with sidebar, metrics cards, and charts",
|
||||
v0Url: "https://v0.dev/chat/def456",
|
||||
variations: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Version 1 - Default",
|
||||
thumbnail: "https://placehold.co/800x600/7c3aed/ffffff?text=Dashboard+V1",
|
||||
createdAt: "2025-11-10",
|
||||
views: 78,
|
||||
comments: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
"pricing": {
|
||||
name: "Pricing Cards",
|
||||
emoji: "💳",
|
||||
style: "colorful",
|
||||
prompt: "Three-tier pricing cards with features, hover effects, and CTA buttons",
|
||||
v0Url: "https://v0.dev/chat/ghi789",
|
||||
variations: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Version 1 - Standard",
|
||||
thumbnail: "https://placehold.co/800x600/059669/ffffff?text=Pricing+V1",
|
||||
createdAt: "2025-11-09",
|
||||
views: 102,
|
||||
comments: 12,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Version 2 - Compact",
|
||||
thumbnail: "https://placehold.co/800x600/0891b2/ffffff?text=Pricing+V2",
|
||||
createdAt: "2025-11-08",
|
||||
views: 67,
|
||||
comments: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
"user-profile": {
|
||||
name: "User Profile",
|
||||
emoji: "👤",
|
||||
style: "modern",
|
||||
prompt: "User profile page with avatar, bio, stats, and activity feed",
|
||||
v0Url: "https://v0.dev/chat/jkl012",
|
||||
variations: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Version 1 - Default",
|
||||
thumbnail: "https://placehold.co/800x600/dc2626/ffffff?text=Profile+V1",
|
||||
createdAt: "2025-11-08",
|
||||
views: 56,
|
||||
comments: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default function DesignPageView({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ projectId: string; pageSlug: string }>;
|
||||
}) {
|
||||
const { projectId, pageSlug } = use(params);
|
||||
const pageData = mockPageData[pageSlug] || mockPageData["landing-hero"];
|
||||
|
||||
const [editPrompt, setEditPrompt] = useState("");
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [currentVersion, setCurrentVersion] = useState(pageData.variations[0]);
|
||||
const [versionsModalOpen, setVersionsModalOpen] = useState(false);
|
||||
const [commentsModalOpen, setCommentsModalOpen] = useState(false);
|
||||
const [chatMessage, setChatMessage] = useState("");
|
||||
const [pageName, setPageName] = useState(pageData.name);
|
||||
const [isEditingName, setIsEditingName] = useState(false);
|
||||
const [designModeActive, setDesignModeActive] = useState(false);
|
||||
const [selectedElement, setSelectedElement] = useState<string | null>(null);
|
||||
|
||||
const handleIterate = async () => {
|
||||
if (!editPrompt.trim()) {
|
||||
toast.error("Please enter a prompt to iterate");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGenerating(true);
|
||||
try {
|
||||
// Call v0 API to generate update
|
||||
const response = await fetch('/api/v0/iterate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
chatId: pageData.v0Url.split('/').pop(),
|
||||
message: editPrompt,
|
||||
projectId,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to iterate');
|
||||
}
|
||||
|
||||
toast.success("Design updated!", {
|
||||
description: "Your changes have been generated",
|
||||
});
|
||||
|
||||
// Refresh or update the current version
|
||||
setEditPrompt("");
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error iterating:', error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to iterate design");
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePushToCursor = () => {
|
||||
toast.success("Code will be pushed to Cursor", {
|
||||
description: "This feature will send the component code to your IDE",
|
||||
});
|
||||
// TODO: Implement actual push to Cursor IDE
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
{/* Toolbar */}
|
||||
<div className="border-b bg-card/50 px-6 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{isEditingName ? (
|
||||
<input
|
||||
type="text"
|
||||
value={pageName}
|
||||
onChange={(e) => setPageName(e.target.value)}
|
||||
onBlur={() => {
|
||||
setIsEditingName(false);
|
||||
toast.success("Page name updated");
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
setIsEditingName(false);
|
||||
toast.success("Page name updated");
|
||||
}
|
||||
}}
|
||||
className="text-lg font-semibold bg-transparent border-b border-primary outline-none px-1 min-w-[200px]"
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<h1
|
||||
className="text-lg font-semibold cursor-pointer hover:text-primary transition-colors"
|
||||
onClick={() => setIsEditingName(true)}
|
||||
>
|
||||
{pageName}
|
||||
</h1>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setVersionsModalOpen(true)}
|
||||
>
|
||||
<History className="h-4 w-4 mr-2" />
|
||||
Versions
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCommentsModalOpen(true)}
|
||||
>
|
||||
<MessageSquare className="h-4 w-4 mr-2" />
|
||||
Comments
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handlePushToCursor}
|
||||
>
|
||||
<Send className="h-4 w-4 mr-2" />
|
||||
Push to Cursor
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Share2 className="h-4 w-4 mr-2" />
|
||||
Share
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Preview */}
|
||||
<div className="flex-1 overflow-auto bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 relative">
|
||||
<div className="w-full h-full p-8">
|
||||
{/* Sample SaaS Dashboard Component */}
|
||||
<div className="mx-auto max-w-7xl space-y-6">
|
||||
{/* Page Header */}
|
||||
<div
|
||||
data-element="page-header"
|
||||
className={cn(
|
||||
"flex items-center justify-between transition-all p-2 rounded-lg",
|
||||
designModeActive && "cursor-pointer hover:ring-2 hover:ring-primary hover:ring-inset",
|
||||
selectedElement === "page-header" && "ring-2 ring-primary ring-inset"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement("page-header");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
data-element="page-title"
|
||||
className={cn(
|
||||
"text-3xl font-bold transition-all rounded px-1",
|
||||
designModeActive && "hover:ring-2 hover:ring-primary/50 hover:ring-inset",
|
||||
selectedElement === "page-title" && "ring-2 ring-primary/50 ring-inset"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement("page-title");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Dashboard Overview
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1">Welcome back! Here's what's happening today.</p>
|
||||
</div>
|
||||
<Button
|
||||
data-element="primary-action-button"
|
||||
className={cn(
|
||||
"transition-all",
|
||||
designModeActive && "hover:ring-2 hover:ring-yellow-400 hover:ring-inset",
|
||||
selectedElement === "primary-action-button" && "ring-2 ring-yellow-400 ring-inset"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement("primary-action-button");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Create New Project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div
|
||||
data-element="stats-grid"
|
||||
className={cn(
|
||||
"grid md:grid-cols-4 gap-4 transition-all rounded-xl",
|
||||
designModeActive && "cursor-pointer hover:ring-2 hover:ring-primary hover:ring-inset",
|
||||
selectedElement === "stats-grid" && "ring-2 ring-primary ring-inset"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement("stats-grid");
|
||||
}
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{ label: "Total Users", value: "2,847", change: "+12.3%", trend: "up" },
|
||||
{ label: "Revenue", value: "$45,231", change: "+8.1%", trend: "up" },
|
||||
{ label: "Active Projects", value: "127", change: "-2.4%", trend: "down" },
|
||||
{ label: "Conversion Rate", value: "3.24%", change: "+0.8%", trend: "up" },
|
||||
].map((stat, i) => (
|
||||
<Card
|
||||
key={i}
|
||||
data-element={`stat-card-${i}`}
|
||||
className={cn(
|
||||
"transition-all",
|
||||
designModeActive && "cursor-pointer hover:ring-2 hover:ring-primary hover:ring-inset",
|
||||
selectedElement === `stat-card-${i}` && "ring-2 ring-primary ring-inset"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement(`stat-card-${i}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription className="text-xs">{stat.label}</CardDescription>
|
||||
<CardTitle className="text-2xl">{stat.value}</CardTitle>
|
||||
<span className={cn(
|
||||
"text-xs font-medium",
|
||||
stat.trend === "up" ? "text-green-600" : "text-red-600"
|
||||
)}>
|
||||
{stat.change}
|
||||
</span>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Data Table */}
|
||||
<Card
|
||||
data-element="data-table"
|
||||
className={cn(
|
||||
"transition-all",
|
||||
designModeActive && "cursor-pointer hover:ring-2 hover:ring-primary hover:ring-inset",
|
||||
selectedElement === "data-table" && "ring-2 ring-primary ring-inset"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement("data-table");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Recent Projects</CardTitle>
|
||||
<CardDescription>Your team's latest work</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-element="table-action-button"
|
||||
className={cn(
|
||||
"transition-all",
|
||||
designModeActive && "hover:ring-2 hover:ring-yellow-400 hover:ring-inset",
|
||||
selectedElement === "table-action-button" && "ring-2 ring-yellow-400 ring-inset"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement("table-action-button");
|
||||
}
|
||||
}}
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ name: "Mobile App Redesign", status: "In Progress", team: "Design Team", updated: "2 hours ago" },
|
||||
{ name: "API Documentation", status: "Review", team: "Engineering", updated: "5 hours ago" },
|
||||
{ name: "Marketing Website", status: "Completed", team: "Marketing", updated: "1 day ago" },
|
||||
{ name: "User Dashboard v2", status: "Planning", team: "Product", updated: "3 days ago" },
|
||||
].map((project, i) => (
|
||||
<div
|
||||
key={i}
|
||||
data-element={`table-row-${i}`}
|
||||
className={cn(
|
||||
"flex items-center justify-between p-3 rounded-lg border transition-all",
|
||||
designModeActive && "cursor-pointer hover:ring-2 hover:ring-primary hover:ring-inset hover:bg-accent",
|
||||
selectedElement === `table-row-${i}` && "ring-2 ring-primary ring-inset bg-accent"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (designModeActive) {
|
||||
e.stopPropagation();
|
||||
setSelectedElement(`table-row-${i}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium">{project.name}</p>
|
||||
<p className="text-sm text-muted-foreground">{project.team}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className={cn(
|
||||
"text-xs font-medium px-2 py-1 rounded-full",
|
||||
project.status === "Completed" && "bg-green-100 text-green-700",
|
||||
project.status === "In Progress" && "bg-blue-100 text-blue-700",
|
||||
project.status === "Review" && "bg-yellow-100 text-yellow-700",
|
||||
project.status === "Planning" && "bg-gray-100 text-gray-700"
|
||||
)}>
|
||||
{project.status}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground w-24 text-right">{project.updated}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating Chat Interface - v0 Style */}
|
||||
<div
|
||||
className="absolute bottom-6 left-1/2 -translate-x-1/2 w-full max-w-3xl px-6"
|
||||
>
|
||||
<div className="bg-background/95 backdrop-blur-lg border border-border rounded-2xl shadow-2xl overflow-hidden">
|
||||
{/* Input Area */}
|
||||
<div className="p-3 relative">
|
||||
<Textarea
|
||||
placeholder="e.g., 'Make the hero section more vibrant', 'Add a call-to-action button', 'Change the color scheme to dark mode'"
|
||||
value={chatMessage}
|
||||
onChange={(e) => setChatMessage(e.target.value)}
|
||||
className="min-h-[60px] resize-none border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 text-sm px-1"
|
||||
disabled={isGenerating}
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Action Bar */}
|
||||
<div className="px-4 pb-3 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant={designModeActive ? "default" : "ghost"}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setDesignModeActive(!designModeActive);
|
||||
setSelectedElement(null);
|
||||
}}
|
||||
>
|
||||
<MousePointer2 className="h-4 w-4 mr-2" />
|
||||
Design Mode
|
||||
</Button>
|
||||
{selectedElement && (
|
||||
<div className="flex items-center gap-2 px-2 py-1 bg-primary/10 text-primary rounded text-xs">
|
||||
<MousePointer2 className="h-3 w-3" />
|
||||
<span className="font-medium">{selectedElement.replace(/-/g, ' ')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={isGenerating}
|
||||
onClick={() => {
|
||||
toast.info("Creating variation...");
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
Variation
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const contextualPrompt = selectedElement
|
||||
? `[Targeting: ${selectedElement.replace(/-/g, ' ')}] ${chatMessage}`
|
||||
: chatMessage;
|
||||
setEditPrompt(contextualPrompt);
|
||||
handleIterate();
|
||||
}}
|
||||
disabled={isGenerating || !chatMessage.trim()}
|
||||
className="gap-2"
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Generating
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
{selectedElement ? 'Modify Selected' : 'Generate'}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Versions Modal */}
|
||||
<Dialog open={versionsModalOpen} onOpenChange={setVersionsModalOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Version History</DialogTitle>
|
||||
<DialogDescription>
|
||||
View and switch between different versions of this design
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[60vh] pr-4">
|
||||
<div className="space-y-3">
|
||||
{pageData.variations.map((variation: any) => (
|
||||
<button
|
||||
key={variation.id}
|
||||
onClick={() => {
|
||||
setCurrentVersion(variation);
|
||||
setVersionsModalOpen(false);
|
||||
toast.success(`Switched to ${variation.name}`);
|
||||
}}
|
||||
className={`w-full text-left rounded-lg border p-4 transition-colors hover:bg-accent ${
|
||||
currentVersion.id === variation.id ? 'border-primary bg-accent' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<img
|
||||
src={variation.thumbnail}
|
||||
alt={variation.name}
|
||||
className="w-32 h-20 rounded object-cover"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-medium text-base">{variation.name}</h4>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{variation.createdAt}
|
||||
</p>
|
||||
<div className="flex items-center gap-4 mt-2 text-sm text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
<Eye className="h-4 w-4" />
|
||||
{variation.views} views
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<MessageSquare className="h-4 w-4" />
|
||||
{variation.comments} comments
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Comments Modal */}
|
||||
<Dialog open={commentsModalOpen} onOpenChange={setCommentsModalOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Comments & Feedback</DialogTitle>
|
||||
<DialogDescription>
|
||||
Discuss this design with your team
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[50vh] pr-4">
|
||||
<div className="space-y-4">
|
||||
{/* Mock comments */}
|
||||
<div className="space-y-3">
|
||||
<div className="rounded-lg border p-4 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center text-sm font-medium">
|
||||
JD
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<span className="text-sm font-medium">Jane Doe</span>
|
||||
<span className="text-xs text-muted-foreground ml-2">2h ago</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Love the gradient! Could we try a darker variant?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 rounded-full bg-green-500/10 flex items-center justify-center text-sm font-medium">
|
||||
MS
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<span className="text-sm font-medium">Mike Smith</span>
|
||||
<span className="text-xs text-muted-foreground ml-2">5h ago</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
The layout looks perfect. Spacing is on point 👍
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{/* Add comment */}
|
||||
<div className="pt-4 border-t space-y-3">
|
||||
<Textarea
|
||||
placeholder="Add a comment..."
|
||||
className="min-h-[100px] resize-none"
|
||||
/>
|
||||
<Button className="w-full">
|
||||
<MessageSquare className="h-4 w-4 mr-2" />
|
||||
Post Comment
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user