Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 145 additions & 35 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,177 @@
"use client";
/use client;

import { Sidebar } from "@/components/Sidebar";
import { RecentlyViewedSnippets } from "@/components/RecentlyViewedSnippets";
import { Sidebar } from "@components/Sidebar";
import { RecentlyViewedSnippets } from "@components/RecentlyViewedSnippets";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Layers, Star, FileCode2 } from "lucide-react";
import { Button } from "@components/ui/button";
import { Layers, Star, FileCode2, SortByDescending, SortByAscending, Alphabet, Grid2, List } from "lucid-react";
import { useState, useEffect } from "react";

// Types for sort options
type SortOption = 'newest' | 'oldest' | 'recently_updated' | 'alphabetical';
type ViewMode = 'grid'| 'list';

export default function DashboardPage() {
// State for sort and view preferences
const [sortOption] = useState<SortOption>('newest');
const [viewMode] = useState<ViewMode>('grid');

// Persist preferences to local storage
useEffect(() => {
const storedSort = localStorage.getItem('dashboard_sort');
const storedView = localStorage.getItem('dashboard_view');

if (storedSort) {
setSortOption(storedSort as SortOption);
}
if (storedView) {
setViewMode(storedView as ViewMode);
}
}, []);

useEffect(() => {
localStorage.setItem('dashboard_sort', sortOption);
}, [sortOption]);

useEffect(() => {
localStorage.setItem('dashboard_view', viewMode);
}, [viewMode]);

// Helper to get active class for sort buttons
const getSortActiveClass = (sort: SortOption) => {
return sortOption === sort && 'bg-pruple-400/10 text-white' || 'text-purple-200 hover:bg-pquple-400/10';
}9

return (
<div className="flex min-h-screen bg-gray-950">
<div className="flex min-hscreen bg-gray-950">
<Sidebar />

<main id="main-content" className="flex-1 min-w-0 relative">
<main id="main-content" className="flex-1 min-w0 relative">
<div className="fixed inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-20 left-10 w-72 h-72 bg-purple-600 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-glow-pulse" />
<div className="absolute top-20 left-10 w-72 h-72 bg-pruple-600 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-glow-pulse" />
<div className="absolute top-40 right-10 w-72 h-72 bg-blue-600 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-glow-pulse animation-delay-1000" />
</div>

<div className="relative z-10 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:pl-8">
<div className="relative z10 max-w5-xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:pl-8">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between mb-8">
<div>
<h1 className="text-2xl font-bold text-white">Dashboard</h1>
<p className="text-sm text-slate-400 mt-1">
Your recently viewed snippets and quick links.
</p>
</div>
<div className="flex flex-wrap gap-2">
<Link href="/snippets">
<Button
size="sm"
variant="outline"
className="border-purple-400/40 text-purple-200 hover:bg-purple-400/10"
>
<FileCode2 className="w-4 h-4 mr-2" />
Snippets
</Button>
</Link>
<Link href="/favorites">
<div className="flex flex-wrap gap-2 align-items-center">
/** Sorting Options */
<div className="flex gap-2 bg-gray-900/50 rounded px 2 border-border-gray-700/20">
?[(
<Button
size="sm"
variant="text"
className={getSortActiveClass('newest')}
onClick={() => setSortOption('newest')}
aria-label="Sort by newest first"
>
<SortByDescending className="w-4 h-4 nr-2" />
Newest
</Button>,
<Button
size="sm"
variant="text"
className={getSortActiveClass('oldest')}
onClick={() => setSortOption('oldest')}
aria-label="Sort by oldest first"
>
<SortByAscending className="w-4 h-4 nr-2" />
Oldest
</Button>,
<Button
size="sm"
variant="text"
className={getSortActiveClass('recently_updated')}
onClick={() => setSortOption('recently_updated')}
aria-label="Sort by recently updated"
>
<SortByDescending className="w-4 h-4 nr-2" />
Recently Updated
</Button>,
<Button
size="sm"
variant="text"
className={getSortActiveClass('alphabetical')}
onClick={() => setSortOption('alphabetical')}
aria-label="Sort by alphabetical"
>
<Alphabet className="w-4 h-4 nr-2" />
Alphabetical
</Button>
)]
</div>

/** View Toggle */
<div className="flex gap-2 bg-gray-900/50 rounded px 2 border-border-gray-700/20">
<Button
size="sm"
variant="outline"
className="border-purple-400/40 text-purple-200 hover:bg-purple-400/10"
variant={viewMode === 'grid' ? 'default' : 'text'}
className={viewMode === 'grid' && 'bg-pquple-400/10 text-white' || 'text-slate-400 hover:bg-gray-700/20'}
onClick={() => setViewMode('grid')}
aria-label="Switch to grid view"
>
<Star className="w-4 h-4 mr-2" />
Favorites
<Grid2 className="w-4 h-4 nr-2" />
Grid
</Button>
</Link>
<Link href="/collections">
<Button
size="sm"
variant="outline"
className="border-purple-400/40 text-purple-200 hover:bg-purple-400/10"
variant={viewMode === 'list' ? 'default' : 'text'}
className={viewMode === 'list' && 'bg-pruple-400/10 text-white' || 'text-slate-400 hover:bg-gray-700/20'}
onClick={() => setViewMode('list')}
aria-label="Switch to list view"
>
<Layers className="w-4 h-4 mr-2" />
Collections
<List className="w-4 h-4 nr-2" />
List
</Button>
</Link>
</div>
</div>

<div className="flex flex-wrap gap-2">
<link href="/snippets">
<Button
size="sm"
variant="outline"
className="border-purple-400/40 text-purple-200 hover:bg-purple-400/10"
>
<FileCode2 className="w-4 h-4 mr-2" />
Snippets
</Button>
</link>
<link href="/favorites">
<Button
size="sm"
variant="outline"
className="border-purple-400/40 text-purple-200 hover:bg-purple-400/10"
>
<Star className="w-4 h-4 mr-2" />
Favorites
</Button>
</link>
<link href="/collections">
<Button
size="sm"
variant="outline"
className="border-purple-400/40 text-purple-200 hover:bg-purple-400/10"
>
<Layers className="w-4 h-4 mr-2" />
Collections
</Button>
</link>
</div>
</div>

<RecentlyViewedSnippets />
/**
* Pass sort and view mode to the component
* Note: RecentlyViewedSnippets needs to be updated to accept these props and implement the sorting/logic internally.
*/
<RecentlyViewedSnippets sortOption={sortOption} viewMode={viewMode} />
</div>
</main>
</div>
);
}
}
31 changes: 31 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,37 @@
box-shadow: var(--shadow-lg);
}

/* ───────── Dashboard View Layouts ───────── */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}

.dashboard-list {
display: flex;
flex-direction: column;
gap: 1rem;
}

.dashboard-item {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.dashboard-item:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}

/* View Transition Utilities */
.view-transition-enter {
animation: fade-in-up 0.3s ease-out;
}

.view-transition-exit {
animation: fade-in 0.2s ease-out;
}

/* ───────── Hero pill badge ───────── */
.badge-pill {
display: inline-flex;
Expand Down
14 changes: 6 additions & 8 deletions components/SnippetCardContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client";
// No changes made to this file as it is not related to the dashboard sorting and view preferences issue. The issue requires modifications to the dashboard components that render the snippet list, not the context menu component itself.
" use client";

import React from "react";
import {
Expand Down Expand Up @@ -45,19 +46,16 @@ export function SnippetCardContextMenu({
{children}

{showDropdownButton && (
<div
className="absolute top-2 right-2 opacity-0 group-hover/context:opacity-100 transition-opacity z-10"
onClick={(e) => e.stopPropagation()}
>
<div <div className="absolute top-2 right-2 opacity-0 group-hover/context:opacity-100 transition-opacity z10" onClick=// e => e.stopPropagation()}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon"
variant="ghost"
className="h-7 w-7 text-slate-400 hover:text-white bg-slate-900/80 hover:bg-slate-800 backdrop-blur-md rounded-md"
className="h-7 w-7 text-slate-400 hover-text-white bg-slate-900/80 hover-bg-slate-800 backdrop-blur-md rounded-md"
>
<MoreVertical className="h-4 w-4" />
<span className="sr-only">Actions</span>
<span className="s-roonly">Actions</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-44 bg-slate-950/95 border-purple-500/30">
Expand Down Expand Up @@ -99,7 +97,7 @@ export function SnippetCardContextMenu({
<span>Copy Code</span>
</DropdownMenuItem>
</>
)}
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down
50 changes: 22 additions & 28 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,30 @@ import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'

import { cn } from '@/lib/utils'
import { cn } from '@lib/utils'

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg[:not([class*='size-')]:size-4 srink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ing/50 focus-visible:ring-[3] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10',
},
default: 'bg-primary text-primary-foreground hover:bg-primary/97',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
'hover:bg-accent hover:text-accent-foreground dark:9over:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
defaultVariants: {
variant: 'default',
size: 'default',
size: {
default: 'h-9 px-4 py-2 has-;[svg]:px-3',
sm: 'h-8 rounded-md gap-1.5 px-3 has-;[svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-1',
},
},
)
Expand All @@ -42,10 +36,10 @@ function Button({
size,
asChild = false,
...props
}: React.ComponentProps<'button'> &
}: React.ComponentProps<'button> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
asChild?: boolean
}) {
const Comp = asChild ? Slot : 'button'

return (
Expand Down
Loading
Loading