update dashbaord UI

This commit is contained in:
Sahaj Jain
2025-04-24 23:57:18 +05:30
parent ff0cbe1715
commit 34277fcbe9
3 changed files with 173 additions and 24 deletions
+105
View File
@@ -0,0 +1,105 @@
"use client";
import { useState, useEffect } from "react";
import type { Theme } from "@/types/theme";
import { ThemeCard } from "./theme-card";
import { Search, ArrowUpDown, Layers } from "lucide-react";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useIsMobile } from "@/hooks/use-mobile";
interface ThemesListProps {
themes: Theme[];
totalCount: number;
}
export function ThemesList({ themes, totalCount }: ThemesListProps) {
const [filteredThemes, setFilteredThemes] = useState<Theme[]>(themes);
const [searchTerm, setSearchTerm] = useState("");
const [sortOption, setSortOption] = useState("newest");
const isMobile = useIsMobile();
useEffect(() => {
const filtered = themes.filter((theme) =>
theme.name?.toLowerCase().includes(searchTerm.toLowerCase())
);
// Sort based on selected option
const sorted = [...filtered].sort((a, b) => {
switch (sortOption) {
case "newest":
return (b.createdAt?.getTime() || 0) - (a.createdAt?.getTime() || 0);
case "oldest":
return (a.createdAt?.getTime() || 0) - (b.createdAt?.getTime() || 0);
case "name":
return (a.name || "").localeCompare(b.name || "");
default:
return 0;
}
});
setFilteredThemes(sorted);
}, [themes, searchTerm, sortOption]);
return (
<div className="space-y-6">
<div className="bg-card rounded-lg border p-4">
<div className="flex flex-col space-y-4 md:space-y-0 md:flex-row md:justify-between md:gap-4 mb-6">
<div className="flex flex-wrap gap-2 items-center">
<div className="flex items-center gap-2 bg-accent px-3 py-1.5 rounded-full">
<Layers className="h-4 w-4 text-accent-foreground" />
<span className="font-medium">{totalCount}</span>
<span className="text-accent-foreground font-normal">
total themes
</span>
</div>
</div>
<div className="flex flex-row gap-2 w-full md:w-auto">
<div className="relative w-full">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search themes..."
className="pl-8 w-full"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<Select value={sortOption} onValueChange={setSortOption}>
<SelectTrigger className="w-full w-[80px] md:w-[180px] gap-2">
<ArrowUpDown className="h-4 w-4 text-muted-foreground" />
{!isMobile && <SelectValue placeholder="Sort by" />}
</SelectTrigger>
<SelectContent>
<SelectItem value="newest">Newest First</SelectItem>
<SelectItem value="oldest">Oldest First</SelectItem>
<SelectItem value="name">Name (A-Z)</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{filteredThemes.length === 0 && searchTerm ? (
<div className="text-center py-12">
<Search className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-medium mb-2">No themes found</h3>
<p className="text-muted-foreground">
No themes match your search term &quot;{searchTerm}&quot;
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredThemes.map((theme: Theme) => (
<ThemeCard key={theme.id} theme={theme} />
))}
</div>
)}
</div>
</div>
);
}
+65 -23
View File
@@ -1,10 +1,9 @@
import { Theme } from "@/types/theme";
import { getThemes } from "@/actions/themes";
import { ThemeCard } from "./components/theme-card";
import { Header } from "@/components/editor/header";
import { Palette } from "lucide-react";
import { Palette, Plus } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { ThemesList } from "@/app/dashboard/components/themes-list";
export default async function ProfilePage() {
const themes = await getThemes();
@@ -12,32 +11,75 @@ export default async function ProfilePage() {
return b.createdAt?.getTime() - a.createdAt?.getTime();
});
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const recentThemeObjects = themes.filter((theme) => {
return theme.createdAt && theme.createdAt > oneWeekAgo;
});
return (
<>
<div className="min-h-screen bg-gradient-to-b from-background to-muted/30">
<Header />
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-8">Your Themes</h1>
{sortedThemes.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="text-6xl mb-4">
<Palette className="size-12" />
</div>
<h2 className="text-2xl font-semibold mb-2">No themes yet</h2>
<p className="text-gray-500 mb-4">
Create your first theme to get started
<div className="container mx-auto py-8 px-4">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
<div>
<h1 className="text-3xl font-bold">Your Themes</h1>
<p className="text-muted-foreground mt-1">
Manage and explore your custom color themes
</p>
<Link href="/editor/theme">
<Button>Create Theme</Button>
</Link>
</div>
<Link href="/editor/theme">
<Button className="gap-1" variant="accent">
New Theme
<Plus className="size-4" />
</Button>
</Link>
</div>
{sortedThemes.length === 0 ? (
<div className="bg-card rounded-xl border shadow-sm p-8 flex flex-col items-center justify-center py-16 text-center">
<div className="bg-primary/10 p-4 rounded-full mb-6">
<Palette className="size-12 text-primary" />
</div>
<h2 className="text-2xl font-semibold mb-2">
No themes created yet
</h2>
<p className="text-muted-foreground max-w-md mb-6">
Create your first custom theme to personalize your projects with
unique color palettes
</p>
<div className="space-y-6 w-full max-w-md">
<Link href="/editor/theme">
<Button size="lg" className="w-full gap-2">
<Plus className="size-4" />
Create Your First Theme
</Button>
</Link>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-card px-2 text-muted-foreground">
or explore examples
</span>
</div>
</div>
<div className="grid grid-cols-3 gap-3">
<div className="bg-gradient-to-br from-pink-500 to-purple-600 h-24 rounded-md shadow-sm cursor-pointer hover:opacity-90 transition-opacity" />
<div className="bg-gradient-to-br from-emerald-500 to-teal-600 h-24 rounded-md shadow-sm cursor-pointer hover:opacity-90 transition-opacity" />
<div className="bg-gradient-to-br from-amber-500 to-orange-600 h-24 rounded-md shadow-sm cursor-pointer hover:opacity-90 transition-opacity" />
</div>
</div>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{sortedThemes.map((theme: Theme) => (
<ThemeCard key={theme.id} theme={theme} />
))}
</div>
<ThemesList
themes={sortedThemes}
recentCount={recentThemeObjects.length}
totalCount={themes.length}
/>
)}
</div>
</>
</div>
);
}
+3 -1
View File
@@ -11,7 +11,8 @@ const buttonVariants = cva(
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
@@ -20,6 +21,7 @@ const buttonVariants = cva(
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
accent: "bg-accent text-accent-foreground shadow-sm hover:bg-accent/80",
},
size: {
default: "h-9 px-4 py-2",