Refactor code structure for improved readability and maintainability
Some checks failed
GBCI / build (push) Has been cancelled
Some checks failed
GBCI / build (push) Has been cancelled
This commit is contained in:
parent
1954b928dc
commit
c95d1d9deb
18 changed files with 2179 additions and 787 deletions
|
@ -9,12 +9,13 @@ const examples = [
|
|||
{ name: "Chat", href: "/chat", color: "#25D366" }, // WhatsApp green
|
||||
{ name: "Dashboard", href: "/dashboard", color: "#6366F1" }, // Indigo
|
||||
{ name: "Mail", href: "/mail", color: "#FFD700" }, // Outlook yellow
|
||||
{ name: "Calendar", href: "/calendar", color: "#1DB954" }, // Spotify green
|
||||
{ name: "Meet", href: "/meet", color: "#059669" }, // Google Meet green
|
||||
{ name: "Drive", href: "/drive", color: "#10B981" }, // Emerald green
|
||||
{ name: "Editor", href: "/editor", color: "#2563EB" }, // Word blue
|
||||
{ name: "Tables", href: "/tables", color: "#8B5CF6" }, // Purple
|
||||
{ name: "Meet", href: "/meet", color: "#059669" }, // Google Meet green
|
||||
{ name: "Videos", href: "/videos", color: "#DC2626" }, // YouTube red
|
||||
{ name: "Music", href: "/music", color: "#1DB954" }, // Spotify green
|
||||
{ name: "Media", href: "/media", color: "Yellow" },
|
||||
{ name: "News", href: "/news", color: "blue" },
|
||||
{ name: "Templates", href: "/templates", color: "#F59E0B" }, // Amber
|
||||
{ name: "Settings", href: "/settings", color: "#6B7280" }, // Gray
|
||||
];
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
"use client";
|
||||
import React, { useState } from 'react';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export function CalendarDateRangePicker() {
|
||||
const [dateRange, setDateRange] = useState({
|
||||
startDate: new Date(),
|
||||
endDate: new Date()
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="px-3 py-1 border rounded"
|
||||
onClick={() => {
|
||||
const date = new Date(prompt("Enter start date (YYYY-MM-DD)") || dateRange.startDate);
|
||||
setDateRange(prev => ({ ...prev, startDate: date }));
|
||||
}}
|
||||
>
|
||||
Start: {format(dateRange.startDate, 'MMM dd, yyyy')}
|
||||
</button>
|
||||
<span>to</span>
|
||||
<button
|
||||
className="px-3 py-1 border rounded"
|
||||
onClick={() => {
|
||||
const date = new Date(prompt("Enter end date (YYYY-MM-DD)") || dateRange.endDate);
|
||||
setDateRange(prev => ({ ...prev, endDate: date }));
|
||||
}}
|
||||
>
|
||||
End: {format(dateRange.endDate, 'MMM dd, yyyy')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export function MainNav() {
|
||||
return (
|
||||
<nav className="flex space-x-4">
|
||||
{['Overview', 'Customers', 'Products', 'Settings'].map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
className="px-3 py-2 text-sm font-medium hover:text-primary"
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export function Overview() {
|
||||
return (
|
||||
<div className="p-4 border rounded-lg">
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const salesData = [
|
||||
{ name: "Olivia Martin", email: "olivia.martin@email.com", amount: "+$1,999.00" },
|
||||
{ name: "Jackson Lee", email: "jackson.lee@email.com", amount: "+$39.00" },
|
||||
{ name: "Isabella Nguyen", email: "isabella.nguyen@email.com", amount: "+$299.00" },
|
||||
{ name: "William Kim", email: "will@email.com", amount: "+$99.00" },
|
||||
{ name: "Sofia Davis", email: "sofia.davis@email.com", amount: "+$39.00" },
|
||||
];
|
||||
|
||||
export function RecentSales() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{salesData.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-2 border-b">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{item.name}</p>
|
||||
<p className="text-sm text-gray-500">{item.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="font-medium">{item.amount}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<div className="relative max-w-md">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
className="w-full pl-8 pr-4 py-2 rounded-full border focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<svg
|
||||
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const groups = [
|
||||
{
|
||||
label: "Personal Account",
|
||||
teams: [
|
||||
{ label: "Alicia Koch", value: "personal" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Teams",
|
||||
teams: [
|
||||
{ label: "Acme Inc.", value: "acme-inc" },
|
||||
{ label: "Monsters Inc.", value: "monsters" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function TeamSwitcher() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [showNewTeamDialog, setShowNewTeamDialog] = useState(false);
|
||||
const [selectedTeam, setSelectedTeam] = useState(groups[0].teams[0]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setOpen(true)}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
|
||||
{selectedTeam.label[0]}
|
||||
</div>
|
||||
<span>{selectedTeam.label}</span>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="absolute z-10 mt-2 w-56 bg-white rounded-md shadow-lg">
|
||||
<div className="p-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search team..."
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
{groups.map((group) => (
|
||||
<div key={group.label} className="py-1">
|
||||
<p className="px-3 py-1 text-sm font-medium text-gray-500">{group.label}</p>
|
||||
{group.teams.map((team) => (
|
||||
<button
|
||||
key={team.value}
|
||||
onClick={() => {
|
||||
setSelectedTeam(team);
|
||||
setOpen(false);
|
||||
}}
|
||||
className="w-full text-left px-3 py-2 hover:bg-gray-100"
|
||||
>
|
||||
{team.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div className="border-t p-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setShowNewTeamDialog(true);
|
||||
}}
|
||||
className="w-full p-2 text-left hover:bg-gray-100"
|
||||
>
|
||||
Create Team
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showNewTeamDialog && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<div className="bg-white p-4 rounded-md">
|
||||
<h3 className="text-lg font-medium mb-4">Create team</h3>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Team name"
|
||||
className="w-full p-2 border rounded mb-4"
|
||||
/>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<button
|
||||
onClick={() => setShowNewTeamDialog(false)}
|
||||
className="px-4 py-2 border rounded"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowNewTeamDialog(false)}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export function UserNav() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center"
|
||||
>
|
||||
U
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10">
|
||||
<div className="p-2 border-b">
|
||||
<p className="font-medium">shadcn</p>
|
||||
<p className="text-sm text-gray-500">m@example.com</p>
|
||||
</div>
|
||||
{['Profile', 'Billing', 'Settings', 'New Team', 'Log out'].map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
className="w-full text-left px-3 py-2 hover:bg-gray-100"
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,183 +1,202 @@
|
|||
"use client";
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { Search, Plus, Upload, Download, Trash2, Copy, Move, Share, Star, Grid, List, Filter, SortAsc, Eye, Edit3, Archive, Clock, Users, Lock, Folder, File, Image, Video, Music, FileText, Code, Database, Package } from 'lucide-react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import {
|
||||
Search, Plus, Upload, Download, Trash2, Share, Star,
|
||||
Grid, List, MoreVertical, Home, ChevronRight,
|
||||
Folder, File, Image, Video, Music, FileText, Code, Database,
|
||||
Package, Archive, Clock, Users, Eye, Edit3
|
||||
} from 'lucide-react';
|
||||
import { cn } from "@/lib/utils";
|
||||
// Simple date formatting functions
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
// Enhanced file system with realistic data structure
|
||||
const formatDateTime = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
};
|
||||
|
||||
const formatDistanceToNow = (date) => {
|
||||
const now = new Date();
|
||||
const diffMs = now - new Date(date);
|
||||
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffMinutes < 1) return 'now';
|
||||
if (diffMinutes < 60) return `${diffMinutes}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
return formatDate(date);
|
||||
};
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
// File system data
|
||||
const fileSystemData = {
|
||||
"": {
|
||||
name: "Root",
|
||||
id: "root",
|
||||
name: "My Drive",
|
||||
path: "",
|
||||
is_dir: true,
|
||||
children: ["projects", "documents", "media", "shared", "archives", "templates"]
|
||||
children: ["projects", "documents", "media", "shared"]
|
||||
},
|
||||
"projects": {
|
||||
id: "projects",
|
||||
name: "Projects",
|
||||
path: "projects",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-15T10:30:00Z",
|
||||
created: "2024-12-01T09:00:00Z",
|
||||
starred: true,
|
||||
shared: false,
|
||||
tags: ["work", "development"],
|
||||
children: ["web-apps", "mobile-apps", "data-science", "ai-research", "blockchain"]
|
||||
children: ["web-apps", "mobile-apps", "ai-research"]
|
||||
},
|
||||
"projects/web-apps": {
|
||||
id: "web-apps",
|
||||
name: "Web Applications",
|
||||
path: "projects/web-apps",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-14T16:45:00Z",
|
||||
created: "2024-12-01T09:15:00Z",
|
||||
starred: false,
|
||||
shared: true,
|
||||
tags: ["frontend", "backend", "fullstack"],
|
||||
children: ["dashboard-pro", "e-commerce-platform", "social-media-app", "file-manager"]
|
||||
children: ["dashboard-pro", "package.json", "README.md"]
|
||||
},
|
||||
"projects/web-apps/dashboard-pro": {
|
||||
name: "Dashboard Pro",
|
||||
path: "projects/web-apps/dashboard-pro",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-13T14:20:00Z",
|
||||
created: "2024-12-10T11:00:00Z",
|
||||
starred: true,
|
||||
shared: true,
|
||||
tags: ["react", "typescript", "tailwind"],
|
||||
children: ["src", "components", "styles", "tests", "package.json", "README.md", "deployment.yaml"]
|
||||
},
|
||||
"projects/web-apps/dashboard-pro/package.json": {
|
||||
"projects/web-apps/package.json": {
|
||||
id: "package-json",
|
||||
name: "package.json",
|
||||
path: "projects/web-apps/dashboard-pro/package.json",
|
||||
path: "projects/web-apps/package.json",
|
||||
is_dir: false,
|
||||
size: 2048,
|
||||
type: "json",
|
||||
modified: "2025-01-13T14:20:00Z",
|
||||
created: "2024-12-10T11:00:00Z",
|
||||
starred: false,
|
||||
shared: false,
|
||||
tags: ["config", "dependencies"]
|
||||
shared: false
|
||||
},
|
||||
"projects/web-apps/dashboard-pro/README.md": {
|
||||
"projects/web-apps/README.md": {
|
||||
id: "readme-md",
|
||||
name: "README.md",
|
||||
path: "projects/web-apps/dashboard-pro/README.md",
|
||||
path: "projects/web-apps/README.md",
|
||||
is_dir: false,
|
||||
size: 5120,
|
||||
type: "markdown",
|
||||
modified: "2025-01-12T09:30:00Z",
|
||||
created: "2024-12-10T11:05:00Z",
|
||||
starred: false,
|
||||
shared: true,
|
||||
tags: ["documentation"]
|
||||
shared: true
|
||||
},
|
||||
"documents": {
|
||||
id: "documents",
|
||||
name: "Documents",
|
||||
path: "documents",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-14T12:00:00Z",
|
||||
created: "2024-11-01T08:00:00Z",
|
||||
starred: false,
|
||||
shared: false,
|
||||
tags: ["personal", "work"],
|
||||
children: ["proposals", "contracts", "presentations", "reports", "spreadsheets"]
|
||||
children: ["proposals", "Q1-Strategy.pdf", "Budget-2025.xlsx"]
|
||||
},
|
||||
"documents/proposals": {
|
||||
name: "Proposals",
|
||||
path: "documents/proposals",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-10T15:30:00Z",
|
||||
created: "2024-11-15T10:00:00Z",
|
||||
starred: true,
|
||||
shared: true,
|
||||
tags: ["business", "client"],
|
||||
children: ["Q1-2025-Strategy.pdf", "AI-Integration-Proposal.docx", "Budget-Proposal-2025.xlsx"]
|
||||
},
|
||||
"documents/proposals/Q1-2025-Strategy.pdf": {
|
||||
name: "Q1 2025 Strategy.pdf",
|
||||
path: "documents/proposals/Q1-2025-Strategy.pdf",
|
||||
"documents/Q1-Strategy.pdf": {
|
||||
id: "q1-strategy",
|
||||
name: "Q1 Strategy.pdf",
|
||||
path: "documents/Q1-Strategy.pdf",
|
||||
is_dir: false,
|
||||
size: 1048576,
|
||||
type: "pdf",
|
||||
modified: "2025-01-10T15:30:00Z",
|
||||
created: "2025-01-08T14:00:00Z",
|
||||
starred: true,
|
||||
shared: true,
|
||||
tags: ["strategy", "quarterly", "important"]
|
||||
shared: true
|
||||
},
|
||||
"media": {
|
||||
id: "media",
|
||||
name: "Media",
|
||||
path: "media",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-13T18:45:00Z",
|
||||
created: "2024-10-01T12:00:00Z",
|
||||
starred: false,
|
||||
shared: false,
|
||||
tags: ["photos", "videos", "audio"],
|
||||
children: ["photos", "videos", "audio", "graphics"]
|
||||
children: ["photos", "videos", "vacation-2024.jpg"]
|
||||
},
|
||||
"media/photos": {
|
||||
name: "Photos",
|
||||
path: "media/photos",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-13T18:45:00Z",
|
||||
created: "2024-10-01T12:15:00Z",
|
||||
starred: false,
|
||||
shared: false,
|
||||
tags: ["photography"],
|
||||
children: ["vacation-2024.jpg", "team-photo.jpg", "product-shots.jpg", "nature-landscape.jpg"]
|
||||
},
|
||||
"media/photos/vacation-2024.jpg": {
|
||||
"media/vacation-2024.jpg": {
|
||||
id: "vacation-photo",
|
||||
name: "vacation-2024.jpg",
|
||||
path: "media/photos/vacation-2024.jpg",
|
||||
path: "media/vacation-2024.jpg",
|
||||
is_dir: false,
|
||||
size: 3145728,
|
||||
type: "image",
|
||||
modified: "2024-12-25T20:00:00Z",
|
||||
created: "2024-12-25T20:00:00Z",
|
||||
starred: true,
|
||||
shared: false,
|
||||
tags: ["vacation", "memories"]
|
||||
shared: false
|
||||
},
|
||||
"shared": {
|
||||
id: "shared",
|
||||
name: "Shared",
|
||||
path: "shared",
|
||||
is_dir: true,
|
||||
size: null,
|
||||
modified: "2025-01-12T11:20:00Z",
|
||||
created: "2024-11-01T08:30:00Z",
|
||||
starred: false,
|
||||
shared: true,
|
||||
tags: ["collaboration", "team"],
|
||||
children: ["team-resources", "client-deliverables", "public-assets"]
|
||||
children: ["team-resources", "client-files"]
|
||||
}
|
||||
};
|
||||
|
||||
const getFileIcon = (item) => {
|
||||
if (item.is_dir) {
|
||||
return item.starred ? <Star className="w-4 h-4 text-primary" /> : <Folder className="w-4 h-4 text-accent" />;
|
||||
return <Folder className="w-4 h-4 text-blue-600" />;
|
||||
}
|
||||
|
||||
const iconMap = {
|
||||
pdf: <FileText className="w-4 h-4 text-red-500" />,
|
||||
docx: <FileText className="w-4 h-4 text-blue-500" />,
|
||||
xlsx: <Database className="w-4 h-4 text-green-500" />,
|
||||
json: <Code className="w-4 h-4 text-yellow-500" />,
|
||||
xlsx: <Database className="w-4 h-4 text-green-600" />,
|
||||
json: <Code className="w-4 h-4 text-yellow-600" />,
|
||||
markdown: <Edit3 className="w-4 h-4 text-purple-500" />,
|
||||
md: <Edit3 className="w-4 h-4 text-purple-500" />,
|
||||
jpg: <Image className="w-4 h-4 text-pink-500" />,
|
||||
jpeg: <Image className="w-4 h-4 text-pink-500" />,
|
||||
png: <Image className="w-4 h-4 text-pink-500" />,
|
||||
mp4: <Video className="w-4 h-4 text-red-600" />,
|
||||
mp3: <Music className="w-4 h-4 text-green-600" />,
|
||||
zip: <Archive className="w-4 h-4 text-orange-500" />,
|
||||
yaml: <Code className="w-4 h-4 text-blue-400" />
|
||||
mp3: <Music className="w-4 h-4 text-green-600" />
|
||||
};
|
||||
|
||||
return iconMap[item.type] || <File className="w-4 h-4 text-muted-foreground" />;
|
||||
return iconMap[item.type] || <File className="w-4 h-4 text-gray-500" />;
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
|
@ -187,18 +206,7 @@ const formatFileSize = (bytes) => {
|
|||
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return '';
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
const FileTree = ({ onSelect, selectedPath }) => {
|
||||
const FolderTree = ({ onSelect, selectedPath, isCollapsed }) => {
|
||||
const [expanded, setExpanded] = useState({ "": true, "projects": true });
|
||||
|
||||
const toggleExpand = (path) => {
|
||||
|
@ -206,6 +214,14 @@ const FileTree = ({ onSelect, selectedPath }) => {
|
|||
onSelect(path);
|
||||
};
|
||||
|
||||
const navLinks = [
|
||||
{ title: "My Drive", path: "", icon: Home },
|
||||
{ title: "Shared", path: "shared", icon: Users },
|
||||
{ title: "Starred", path: "starred", icon: Star },
|
||||
{ title: "Recent", path: "recent", icon: Clock },
|
||||
{ title: "Trash", path: "trash", icon: Trash2 },
|
||||
];
|
||||
|
||||
const renderTreeItem = (path, level = 0) => {
|
||||
const item = fileSystemData[path];
|
||||
if (!item || !item.is_dir) return null;
|
||||
|
@ -214,25 +230,27 @@ const FileTree = ({ onSelect, selectedPath }) => {
|
|||
const isSelected = selectedPath === path;
|
||||
|
||||
return (
|
||||
<div key={path}>
|
||||
<div
|
||||
<div key={item.id}>
|
||||
<button
|
||||
onClick={() => toggleExpand(path)}
|
||||
className={`flex items-center cursor-pointer hover:bg-accent hover:text-accent-foreground p-2 rounded transition-colors
|
||||
${isSelected ? 'bg-primary text-primary-foreground' : 'text-foreground'}`}
|
||||
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
||||
className={cn(
|
||||
"flex items-center w-full px-2 py-1.5 text-sm rounded-md hover:bg-accent transition-colors",
|
||||
isSelected && "bg-muted",
|
||||
isCollapsed ? "justify-center" : "justify-start"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center space-x-2 flex-1">
|
||||
{getFileIcon(item)}
|
||||
<span className="text-sm font-medium truncate">{item.name}</span>
|
||||
{item.starred && <Star className="w-3 h-3 text-yellow-500 fill-current" />}
|
||||
{item.shared && <Users className="w-3 h-3 text-blue-500" />}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{isExpanded ? '▼' : '▶'}
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded && item.children && (
|
||||
<div>
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
<ChevronRight className={cn("w-4 h-4 mr-1 transition-transform", isExpanded && "rotate-90")} />
|
||||
{getFileIcon(item)}
|
||||
<span className="ml-2 truncate">{item.name}</span>
|
||||
{item.starred && <Star className="w-3 h-3 ml-auto text-yellow-500 fill-current" />}
|
||||
</>
|
||||
)}
|
||||
{isCollapsed && getFileIcon(item)}
|
||||
</button>
|
||||
{!isCollapsed && isExpanded && item.children && (
|
||||
<div className="ml-4">
|
||||
{item.children.map(childPath => {
|
||||
const fullPath = path ? `${path}/${childPath}` : childPath;
|
||||
return renderTreeItem(fullPath, level + 1);
|
||||
|
@ -244,24 +262,55 @@ const FileTree = ({ onSelect, selectedPath }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="w-80 border-r border-border bg-card overflow-y-auto">
|
||||
<div className="p-4 border-b border-border">
|
||||
<h2 className="text-lg font-semibold text-card-foreground flex items-center">
|
||||
<Folder className="w-5 h-5 mr-2 text-primary" />
|
||||
File Explorer
|
||||
</h2>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
{renderTreeItem("")}
|
||||
<div className="flex flex-col h-full">
|
||||
<div className={cn("p-2", isCollapsed ? "px-1" : "")}>
|
||||
<nav className="space-y-1">
|
||||
{navLinks.map((link) =>
|
||||
isCollapsed ? (
|
||||
<Tooltip key={link.path} delayDuration={0}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={selectedPath === link.path ? "default" : "ghost"}
|
||||
size="icon"
|
||||
className="w-9 h-9"
|
||||
onClick={() => onSelect(link.path)}
|
||||
>
|
||||
<link.icon className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{link.title}</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
key={link.path}
|
||||
variant={selectedPath === link.path ? "default" : "ghost"}
|
||||
className="w-full justify-start"
|
||||
onClick={() => onSelect(link.path)}
|
||||
>
|
||||
<link.icon className="mr-2 h-4 w-4" />
|
||||
{link.title}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
<Separator />
|
||||
<ScrollArea className="flex-1 p-2">
|
||||
{renderTreeItem("")}
|
||||
</ScrollArea>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FileBrowser = ({ path, viewMode, searchTerm, sortBy, filterType }) => {
|
||||
const currentItem = fileSystemData[path];
|
||||
const FileList = ({ path, searchTerm, filterType }) => {
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
|
||||
const files = useMemo(() => {
|
||||
const currentItem = fileSystemData[path];
|
||||
if (!currentItem || !currentItem.is_dir || !currentItem.children) return [];
|
||||
|
||||
let items = currentItem.children.map(childName => {
|
||||
|
@ -269,317 +318,251 @@ const FileBrowser = ({ path, viewMode, searchTerm, sortBy, filterType }) => {
|
|||
return fileSystemData[childPath];
|
||||
}).filter(Boolean);
|
||||
|
||||
// Apply search filter
|
||||
if (searchTerm) {
|
||||
items = items.filter(item =>
|
||||
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(item.tags && item.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())))
|
||||
item.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// Apply type filter
|
||||
if (filterType && filterType !== 'all') {
|
||||
items = items.filter(item => {
|
||||
if (filterType === 'folders') return item.is_dir;
|
||||
if (filterType === 'files') return !item.is_dir;
|
||||
if (filterType === 'starred') return item.starred;
|
||||
if (filterType === 'shared') return item.shared;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
items.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
return a.name.localeCompare(b.name);
|
||||
case 'modified':
|
||||
return new Date(b.modified || 0) - new Date(a.modified || 0);
|
||||
case 'size':
|
||||
return (b.size || 0) - (a.size || 0);
|
||||
case 'type':
|
||||
return a.type?.localeCompare(b.type || '') || 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return items.sort((a, b) => {
|
||||
if (a.is_dir && !b.is_dir) return -1;
|
||||
if (!a.is_dir && b.is_dir) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}, [path, searchTerm, filterType]);
|
||||
|
||||
return items;
|
||||
}, [currentItem, searchTerm, sortBy, filterType, path]);
|
||||
|
||||
if (viewMode === 'grid') {
|
||||
return (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 p-4">
|
||||
return (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="flex flex-col">
|
||||
{files.map((item) => (
|
||||
<div key={item.path} className="group relative">
|
||||
<div className="bg-card border border-border rounded-lg p-4 hover:shadow-md transition-shadow cursor-pointer hover:bg-accent hover:text-accent-foreground">
|
||||
<div className="flex flex-col items-center space-y-2">
|
||||
<div className="text-3xl">
|
||||
{getFileIcon(item)}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-center truncate w-full text-card-foreground group-hover:text-accent-foreground">
|
||||
{item.name}
|
||||
</div>
|
||||
{!item.is_dir && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatFileSize(item.size)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex space-x-1">
|
||||
<button
|
||||
key={item.id}
|
||||
className={cn(
|
||||
"flex items-center gap-3 p-3 text-left text-sm transition-all hover:bg-accent border-b",
|
||||
selectedFile?.id === item.id && "bg-muted"
|
||||
)}
|
||||
onClick={() => setSelectedFile(item)}
|
||||
>
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
{getFileIcon(item)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="font-medium truncate">{item.name}</div>
|
||||
{item.starred && <Star className="w-3 h-3 text-yellow-500 fill-current" />}
|
||||
{item.shared && <Users className="w-3 h-3 text-blue-500" />}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{item.is_dir ? 'Folder' : formatFileSize(item.size)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatDistanceToNow(new Date(item.modified), { addSuffix: true })}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
||||
const FileDisplay = ({ file }) => {
|
||||
if (!file) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center text-muted-foreground">
|
||||
<File className="w-12 h-12 mb-4" />
|
||||
<div className="text-lg font-medium">No file selected</div>
|
||||
<div className="text-sm">Select a file to view details</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden">
|
||||
<div className="bg-muted border-b border-border p-2">
|
||||
<div className="grid grid-cols-12 gap-4 text-sm font-medium text-muted-foreground">
|
||||
<div className="col-span-5">Name</div>
|
||||
<div className="col-span-2">Modified</div>
|
||||
<div className="col-span-1">Size</div>
|
||||
<div className="col-span-2">Type</div>
|
||||
<div className="col-span-2">Tags</div>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex items-center p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Download</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Share className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Share</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Star className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Star</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>Rename</DropdownMenuItem>
|
||||
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||
<DropdownMenuItem>Move to trash</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div className="divide-y divide-border">
|
||||
{files.map((item) => (
|
||||
<div key={item.path} className="grid grid-cols-12 gap-4 p-3 hover:bg-accent hover:text-accent-foreground cursor-pointer group">
|
||||
<div className="col-span-5 flex items-center space-x-3">
|
||||
{getFileIcon(item)}
|
||||
<span className="font-medium truncate">{item.name}</span>
|
||||
<div className="flex space-x-1">
|
||||
{item.starred && <Star className="w-3 h-3 text-yellow-500 fill-current" />}
|
||||
{item.shared && <Users className="w-3 h-3 text-blue-500" />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 text-sm text-muted-foreground flex items-center">
|
||||
{formatDate(item.modified)}
|
||||
</div>
|
||||
<div className="col-span-1 text-sm text-muted-foreground flex items-center">
|
||||
{formatFileSize(item.size)}
|
||||
</div>
|
||||
<div className="col-span-2 text-sm text-muted-foreground flex items-center">
|
||||
{item.is_dir ? 'Folder' : item.type?.toUpperCase() || '—'}
|
||||
</div>
|
||||
<div className="col-span-2 flex items-center">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{item.tags?.slice(0, 2).map(tag => (
|
||||
<span key={tag} className="text-xs bg-secondary text-secondary-foreground px-2 py-1 rounded-full">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{item.tags?.length > 2 && (
|
||||
<span className="text-xs text-muted-foreground">+{item.tags.length - 2}</span>
|
||||
)}
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="flex-1 flex-col">
|
||||
<div className="flex items-start gap-4 p-4">
|
||||
<div className="p-2 rounded-lg bg-accent">
|
||||
{getFileIcon(file)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-semibold">{file.name}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{file.is_dir ? 'Folder' : `${file.type?.toUpperCase() || 'File'} • ${formatFileSize(file.size)}`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{file.modified && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatDate(file.modified)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="p-4 space-y-4">
|
||||
<div>
|
||||
<div className="text-sm font-medium">Location</div>
|
||||
<div className="text-sm text-muted-foreground">/{file.path || ''}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium">Modified</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{formatDateTime(file.modified)}
|
||||
</div>
|
||||
</div>
|
||||
{!file.is_dir && (
|
||||
<div>
|
||||
<div className="text-sm font-medium">Size</div>
|
||||
<div className="text-sm text-muted-foreground">{formatFileSize(file.size)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FileOperations = ({ currentPath, onRefresh }) => {
|
||||
const [showUploadProgress, setShowUploadProgress] = useState(false);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
|
||||
const handleUpload = async () => {
|
||||
setShowUploadProgress(true);
|
||||
// Simulate upload progress
|
||||
for (let i = 0; i <= 100; i += 10) {
|
||||
setUploadProgress(i);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
setTimeout(() => {
|
||||
setShowUploadProgress(false);
|
||||
setUploadProgress(0);
|
||||
onRefresh();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const createFolder = () => {
|
||||
const folderName = prompt('Enter folder name:');
|
||||
if (folderName) {
|
||||
onRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-t border-border bg-card p-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
className="flex items-center space-x-2 bg-primary text-primary-foreground px-4 py-2 rounded-md hover:opacity-90 transition-opacity"
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
<span>Upload Files</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={createFolder}
|
||||
className="flex items-center space-x-2 bg-secondary text-secondary-foreground px-4 py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>New Folder</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-2 bg-secondary text-secondary-foreground px-4 py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors">
|
||||
<Download className="w-4 h-4" />
|
||||
<span>Download</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-2 bg-secondary text-secondary-foreground px-4 py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors">
|
||||
<Share className="w-4 h-4" />
|
||||
<span>Share</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-2 bg-destructive text-destructive-foreground px-4 py-2 rounded-md hover:opacity-90 transition-opacity">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showUploadProgress && (
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-between text-sm text-muted-foreground mb-1">
|
||||
<span>Uploading files...</span>
|
||||
<span>{uploadProgress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-secondary rounded-full h-2">
|
||||
<div
|
||||
className="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${uploadProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function DriveScreen() {
|
||||
export default function FileManager() {
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [currentPath, setCurrentPath] = useState('');
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const [viewMode, setViewMode] = useState('list');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterType, setFilterType] = useState('all');
|
||||
|
||||
const handleRefresh = () => {
|
||||
setRefreshKey(prev => prev + 1);
|
||||
};
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
|
||||
const currentItem = fileSystemData[currentPath];
|
||||
const breadcrumbs = currentPath ? currentPath.split('/') : [];
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background text-foreground">
|
||||
<FileTree onSelect={setCurrentPath} selectedPath={currentPath} />
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="border-b border-border bg-card p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h1 className="text-2xl font-bold text-card-foreground">
|
||||
{currentItem?.name || 'Root'}
|
||||
</h1>
|
||||
{currentItem?.starred && <Star className="w-5 h-5 text-yellow-500 fill-current" />}
|
||||
{currentItem?.shared && <Users className="w-5 h-5 text-blue-500" />}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => setViewMode(viewMode === 'list' ? 'grid' : 'list')}
|
||||
className="p-2 hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
>
|
||||
{viewMode === 'list' ? <Grid className="w-4 h-4" /> : <List className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Breadcrumbs */}
|
||||
<div className="flex items-center space-x-2 text-sm text-muted-foreground mb-4">
|
||||
<button
|
||||
onClick={() => setCurrentPath('')}
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
Home
|
||||
</button>
|
||||
{breadcrumbs.map((crumb, index) => {
|
||||
const path = breadcrumbs.slice(0, index + 1).join('/');
|
||||
return (
|
||||
<React.Fragment key={path}>
|
||||
<span>/</span>
|
||||
<button
|
||||
onClick={() => setCurrentPath(path)}
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
{crumb}
|
||||
</button>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Search and Filters */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search files and folders..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 pr-4 py-2 w-64 bg-input border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-ring text-foreground placeholder:text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<select
|
||||
value={filterType}
|
||||
onChange={(e) => setFilterType(e.target.value)}
|
||||
className="px-3 py-2 bg-input border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-ring text-foreground"
|
||||
>
|
||||
<option value="all">All Items</option>
|
||||
<option value="folders">Folders</option>
|
||||
<option value="files">Files</option>
|
||||
<option value="starred">Starred</option>
|
||||
<option value="shared">Shared</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
className="px-3 py-2 bg-input border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-ring text-foreground"
|
||||
>
|
||||
<option value="name">Sort by Name</option>
|
||||
<option value="modified">Sort by Modified</option>
|
||||
<option value="size">Sort by Size</option>
|
||||
<option value="type">Sort by Type</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Browser */}
|
||||
<div className="flex-1 overflow-y-auto bg-background">
|
||||
<FileBrowser
|
||||
key={`${currentPath}-${refreshKey}`}
|
||||
path={currentPath}
|
||||
viewMode={viewMode}
|
||||
searchTerm={searchTerm}
|
||||
sortBy={sortBy}
|
||||
filterType={filterType}
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<ResizablePanelGroup direction="horizontal" className="h-full max-h-[800px]">
|
||||
{/* Left Sidebar */}
|
||||
<ResizablePanel
|
||||
defaultSize={20}
|
||||
collapsedSize={4}
|
||||
collapsible={true}
|
||||
minSize={15}
|
||||
maxSize={30}
|
||||
onCollapse={() => setIsCollapsed(true)}
|
||||
onResize={() => setIsCollapsed(false)}
|
||||
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300")}
|
||||
>
|
||||
<FolderTree
|
||||
onSelect={setCurrentPath}
|
||||
selectedPath={currentPath}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* File Operations */}
|
||||
<FileOperations currentPath={currentPath} onRefresh={handleRefresh} />
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle withHandle />
|
||||
|
||||
{/* Middle File List */}
|
||||
<ResizablePanel defaultSize={50} minSize={30}>
|
||||
<Tabs defaultValue="all" className="flex flex-col h-full">
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<h1 className="text-xl font-bold">{currentItem?.name || 'My Drive'}</h1>
|
||||
<TabsList className="ml-auto">
|
||||
<TabsTrigger value="all">All</TabsTrigger>
|
||||
<TabsTrigger value="starred">Starred</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="bg-background/95 p-4 backdrop-blur">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search files"
|
||||
className="pl-8"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Select value={filterType} onValueChange={setFilterType}>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All items</SelectItem>
|
||||
<SelectItem value="folders">Folders</SelectItem>
|
||||
<SelectItem value="files">Files</SelectItem>
|
||||
<SelectItem value="starred">Starred</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="all" className="m-0 flex-1">
|
||||
<FileList
|
||||
path={currentPath}
|
||||
searchTerm={searchTerm}
|
||||
filterType={filterType}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="starred" className="m-0 flex-1">
|
||||
<FileList
|
||||
path={currentPath}
|
||||
searchTerm={searchTerm}
|
||||
filterType="starred"
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle withHandle />
|
||||
|
||||
{/* Right File Details */}
|
||||
<ResizablePanel defaultSize={30} minSize={25}>
|
||||
<FileDisplay file={selectedFile} />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
8
app/news/page.tsx
Normal file
8
app/news/page.tsx
Normal file
|
@ -0,0 +1,8 @@
|
|||
'use client'
|
||||
|
||||
export default function MainPage() {
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-gray-50">
|
||||
</div>
|
||||
)
|
||||
}
|
8
app/paper/page.tsx
Normal file
8
app/paper/page.tsx
Normal file
|
@ -0,0 +1,8 @@
|
|||
'use client'
|
||||
|
||||
export default function MainPage() {
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-gray-50">
|
||||
</div>
|
||||
)
|
||||
}
|
1
app/templates/README.md
Normal file
1
app/templates/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
Prompts come from: https://github.com/0xeb/TheBigPromptLibrary
|
|
@ -1,71 +0,0 @@
|
|||
export interface Album {
|
||||
name: string
|
||||
artist: string
|
||||
cover: string
|
||||
}
|
||||
|
||||
export const listenNowAlbums: Album[] = [
|
||||
{
|
||||
name: "React Rendezvous",
|
||||
artist: "Ethan Byte",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1611348586804-61bf6c080437?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Async Awakenings",
|
||||
artist: "Nina Netcode",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "The Art of Reusability",
|
||||
artist: "Lena Logic",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1528143358888-6d3c7f67bd5d?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Stateful Symphony",
|
||||
artist: "Beth Binary",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80",
|
||||
},
|
||||
]
|
||||
|
||||
export const madeForYouAlbums: Album[] = [
|
||||
{
|
||||
name: "Thinking Components",
|
||||
artist: "Lena Logic",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1615247001958-f4bc92fa6a4a?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Functional Fury",
|
||||
artist: "Beth Binary",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1513745405825-efaf9a49315f?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "React Rendezvous",
|
||||
artist: "Ethan Byte",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1614113489855-66422ad300a4?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Stateful Symphony",
|
||||
artist: "Beth Binary",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1446185250204-f94591f7d702?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Async Awakenings",
|
||||
artist: "Nina Netcode",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "The Art of Reusability",
|
||||
artist: "Lena Logic",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80",
|
||||
},
|
||||
]
|
|
@ -1,16 +0,0 @@
|
|||
export type Playlist = (typeof playlists)[number]
|
||||
|
||||
export const playlists = [
|
||||
"Recently Added",
|
||||
"Recently Played",
|
||||
"Top Songs",
|
||||
"Top Albums",
|
||||
"Top Artists",
|
||||
"Logic Discography",
|
||||
"Bedtime Beats",
|
||||
"Feeling Happy",
|
||||
"I miss Y2K Pop",
|
||||
"Runtober",
|
||||
"Mellow Days",
|
||||
"Eminem Essentials",
|
||||
]
|
|
@ -1,52 +1,213 @@
|
|||
import React from 'react';
|
||||
import { Menu } from './components/menu';
|
||||
import { Sidebar } from './components/sidebar';
|
||||
import { PodcastEmptyPlaceholder } from './components/podcast-empty-placeholder';
|
||||
import { AlbumArtwork } from './components/album-artwork';
|
||||
import { listenNowAlbums, madeForYouAlbums } from './data/albums';
|
||||
import { playlists } from './data/playlists';
|
||||
import { ChevronDown, ChevronRight, Search, Star, Grid, List } from 'lucide-react'
|
||||
|
||||
export default function TemplatesPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<Menu />
|
||||
<div className="flex">
|
||||
<div className="w-64 border-r border-gray-200">
|
||||
<Sidebar playlists={playlists} />
|
||||
</div>
|
||||
<div className="flex-1 p-4 overflow-auto">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold">Listen Now</h2>
|
||||
<p className="text-gray-500">Top picks for you. Updated daily.</p>
|
||||
<div className="flex overflow-x-auto py-4 space-x-4">
|
||||
{listenNowAlbums.map((album) => (
|
||||
<AlbumArtwork
|
||||
key={album.name}
|
||||
album={album}
|
||||
width={250}
|
||||
height={330}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold">Made for You</h2>
|
||||
<p className="text-gray-500">Your personal playlists. Updated daily.</p>
|
||||
<div className="flex overflow-x-auto py-4 space-x-4">
|
||||
{madeForYouAlbums.map((album) => (
|
||||
<AlbumArtwork
|
||||
key={album.name}
|
||||
album={album}
|
||||
width={150}
|
||||
height={150}
|
||||
aspectRatio="square"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<PodcastEmptyPlaceholder />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
interface Prompt {
|
||||
id: string
|
||||
name: string
|
||||
category: string
|
||||
description: string
|
||||
icon: string
|
||||
featured?: boolean
|
||||
}
|
||||
|
||||
const prompts: Prompt[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "SOP Analyzer",
|
||||
category: "Education",
|
||||
description: "Analyzes statements of purpose for academic applications",
|
||||
icon: "📝",
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Break This GPT",
|
||||
category: "Security",
|
||||
description: "Tests GPT vulnerabilities and security",
|
||||
icon: "🔓",
|
||||
featured: true
|
||||
},
|
||||
// Add all your prompts here...
|
||||
{
|
||||
id: "100",
|
||||
name: "Coinflipper Game",
|
||||
category: "Games",
|
||||
description: "Simple coin flipping game",
|
||||
icon: "🪙"
|
||||
}
|
||||
]
|
||||
|
||||
const categories = Array.from(new Set(prompts.map(p => p.category)))
|
||||
const featuredPrompts = prompts.filter(p => p.featured)
|
||||
|
||||
export default function PromptsPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||
<div className="container mx-auto px-4 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-6">
|
||||
<h1 className="text-xl font-bold">Prompt Store</h1>
|
||||
<nav className="hidden md:flex space-x-6">
|
||||
<button className="text-sm font-medium">Discover</button>
|
||||
<button className="text-sm font-medium">Categories</button>
|
||||
<button className="text-sm font-medium">Top Charts</button>
|
||||
<button className="text-sm font-medium">Collections</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<button className="p-2 rounded-full hover:bg-gray-100">
|
||||
<Search className="h-4 w-4" />
|
||||
</button>
|
||||
<button className="px-4 py-2 text-sm font-medium rounded-full bg-black text-white">
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="container mx-auto px-4 py-6">
|
||||
{/* Hero Section */}
|
||||
<section className="mb-12">
|
||||
<div className="bg-gradient-to-r from-blue-500 to-purple-600 rounded-2xl p-8 text-white">
|
||||
<h2 className="text-3xl font-bold mb-2">Discover Amazing GPTs</h2>
|
||||
<p className="text-lg mb-6">Browse hundreds of specialized AI assistants</p>
|
||||
<div className="relative max-w-md">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search prompts..."
|
||||
className="w-full pl-10 pr-4 py-2 rounded-full text-gray-900 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Section */}
|
||||
<section className="mb-12">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-2xl font-bold">Featured Prompts</h2>
|
||||
<button className="text-blue-600 text-sm font-medium flex items-center">
|
||||
See All <ChevronRight className="h-4 w-4 ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{featuredPrompts.map(prompt => (
|
||||
<div key={prompt.id} className="bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="p-5">
|
||||
<div className="flex items-center mb-4">
|
||||
<span className="text-3xl mr-3">{prompt.icon}</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">{prompt.name}</h3>
|
||||
<p className="text-xs text-gray-500">{prompt.category}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-4 line-clamp-2">{prompt.description}</p>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<Star className="h-4 w-4 text-yellow-500 fill-yellow-500" />
|
||||
<span className="text-xs ml-1">Featured</span>
|
||||
</div>
|
||||
<button className="text-xs font-medium text-blue-600 hover:text-blue-800">
|
||||
Get Prompt
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Categories Section */}
|
||||
<section>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-2xl font-bold">Browse by Category</h2>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button className="p-2 rounded-md hover:bg-gray-100">
|
||||
<Grid className="h-5 w-5" />
|
||||
</button>
|
||||
<button className="p-2 rounded-md hover:bg-gray-100">
|
||||
<List className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{categories.map(category => {
|
||||
const categoryPrompts = prompts.filter(p => p.category === category)
|
||||
return (
|
||||
<div key={category}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-xl font-semibold">{category}</h3>
|
||||
<button className="text-sm text-blue-600 font-medium flex items-center">
|
||||
See All <ChevronRight className="h-4 w-4 ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{categoryPrompts.slice(0, 4).map(prompt => (
|
||||
<div key={prompt.id} className="bg-white rounded-lg border overflow-hidden hover:shadow-sm transition-shadow">
|
||||
<div className="p-4">
|
||||
<div className="flex items-center mb-3">
|
||||
<span className="text-2xl mr-2">{prompt.icon}</span>
|
||||
<h4 className="font-medium">{prompt.name}</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-4 line-clamp-2">{prompt.description}</p>
|
||||
<button className="w-full py-2 text-sm font-medium rounded-md bg-gray-100 hover:bg-gray-200">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-gray-100 border-t mt-12">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">The Prompt Store</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="hover:text-blue-600">About Us</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">Careers</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">News</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">For Developers</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="hover:text-blue-600">Submit a Prompt</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">Documentation</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">API</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">Support</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="hover:text-blue-600">Help Center</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">Community</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">Legal</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="hover:text-blue-600">Terms of Service</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">Privacy Policy</a></li>
|
||||
<li><a href="#" className="hover:text-blue-600">Cookie Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t mt-8 pt-8 text-sm text-gray-500">
|
||||
© {new Date().getFullYear()} Prompt Store. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
1567
app/templates/prompts.csv
Normal file
1567
app/templates/prompts.csv
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue