feat: Enhance dashboard responsiveness across various pages and update project visibility icons for improved clarity.

This commit is contained in:
Yusuf İpek
2026-02-20 14:28:43 +03:00
parent fe496b7fa5
commit 1bb1c8e574
12 changed files with 525 additions and 512 deletions
+3 -3
View File
@@ -82,8 +82,8 @@ export function ProjectFilter({ projects, workspaces }: ProjectFilterProps) {
return ( return (
<> <>
{/* Header */} {/* Header */}
<div className="flex items-center justify-between mb-8"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
<div className="flex items-center gap-4"> <div className="flex flex-col sm:flex-row sm:items-center gap-4">
<h1 className="text-3xl font-bold tracking-tight">Projects</h1> <h1 className="text-3xl font-bold tracking-tight">Projects</h1>
{workspaces.length > 0 && ( {workspaces.length > 0 && (
<Select value={selectedWorkspace} onValueChange={setSelectedWorkspace}> <Select value={selectedWorkspace} onValueChange={setSelectedWorkspace}>
@@ -101,7 +101,7 @@ export function ProjectFilter({ projects, workspaces }: ProjectFilterProps) {
</Select> </Select>
)} )}
</div> </div>
<div className="flex items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@@ -195,8 +195,8 @@ export default function ProjectMembersPage() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form onSubmit={handleInvite} className="flex gap-3 items-end"> <form onSubmit={handleInvite} className="flex flex-col sm:flex-row gap-3 sm:items-end">
<div className="flex-1"> <div className="w-full sm:flex-1">
<Label htmlFor="email" className="mb-2 block">Email Address</Label> <Label htmlFor="email" className="mb-2 block">Email Address</Label>
<Input <Input
id="email" id="email"
@@ -208,7 +208,7 @@ export default function ProjectMembersPage() {
disabled={isInviting} disabled={isInviting}
/> />
</div> </div>
<div className="w-40"> <div className="w-full sm:w-40">
<Label className="mb-2 block">Role</Label> <Label className="mb-2 block">Role</Label>
<Select value={inviteRole} onValueChange={(v) => setInviteRole(v as 'ADMIN' | 'COMMENTATOR')}> <Select value={inviteRole} onValueChange={(v) => setInviteRole(v as 'ADMIN' | 'COMMENTATOR')}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
@@ -220,7 +220,7 @@ export default function ProjectMembersPage() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<Button type="submit" disabled={isInviting}> <Button type="submit" disabled={isInviting} className="w-full sm:w-auto">
{isInviting ? ( {isInviting ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
) : ( ) : (
@@ -257,7 +257,7 @@ export default function ProjectMembersPage() {
<CardContent className="space-y-3"> <CardContent className="space-y-3">
{/* Owner */} {/* Owner */}
{owner && ( {owner && (
<div className="flex items-center justify-between p-3 rounded-lg bg-accent/30"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-0 p-3 rounded-lg bg-accent/30">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Avatar className="h-9 w-9"> <Avatar className="h-9 w-9">
<AvatarImage src={owner.image ?? undefined} /> <AvatarImage src={owner.image ?? undefined} />
@@ -277,7 +277,7 @@ export default function ProjectMembersPage() {
{/* Members */} {/* Members */}
{members.map((member) => ( {members.map((member) => (
<div key={member.id} className="flex items-center justify-between p-3 rounded-lg border"> <div key={member.id} className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-0 p-3 rounded-lg border">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Avatar className="h-9 w-9"> <Avatar className="h-9 w-9">
<AvatarImage src={member.user.image ?? undefined} /> <AvatarImage src={member.user.image ?? undefined} />
@@ -288,12 +288,12 @@ export default function ProjectMembersPage() {
<p className="text-xs text-muted-foreground">{member.user.email}</p> <p className="text-xs text-muted-foreground">{member.user.email}</p>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 w-full sm:w-auto">
<Select <Select
value={member.role} value={member.role}
onValueChange={(v) => handleRoleChange(member.id, v)} onValueChange={(v) => handleRoleChange(member.id, v)}
> >
<SelectTrigger className="w-36 h-8"> <SelectTrigger className="w-full sm:w-36 h-8">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -11,6 +11,9 @@ import {
Building2, Building2,
ArrowUp, ArrowUp,
ArrowDown, ArrowDown,
Globe,
UserPlus,
Lock,
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
@@ -71,9 +74,9 @@ export function ProjectContentClient({
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<h1 className="text-3xl font-bold tracking-tight">{project.name}</h1> <h1 className="text-3xl font-bold tracking-tight">{project.name}</h1>
<Badge variant="outline" className="flex items-center gap-1"> <Badge variant="outline" className="flex items-center gap-1">
{project.visibility === 'PUBLIC' && <span className="text-xs">🌐</span>} {project.visibility === 'PUBLIC' && <Globe className="h-3 w-3" />}
{project.visibility === 'INVITE' && <span className="text-xs">📧</span>} {project.visibility === 'INVITE' && <UserPlus className="h-3 w-3" />}
{project.visibility === 'PRIVATE' && <span className="text-xs">🔒</span>} {project.visibility === 'PRIVATE' && <Lock className="h-3 w-3" />}
{project.visibility.toLowerCase()} {project.visibility.toLowerCase()}
</Badge> </Badge>
</div> </div>
@@ -92,7 +95,7 @@ export function ProjectContentClient({
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-2 mt-4 sm:mt-0">
{/* Sort Button - Left of Share */} {/* Sort Button - Left of Share */}
<Button <Button
variant="outline" variant="outline"
@@ -362,7 +362,7 @@ export default function ProjectSettingsPage({ params }: ProjectSettingsPageProps
{/* Existing tags */} {/* Existing tags */}
<div className="space-y-2"> <div className="space-y-2">
{tags.map((tag) => ( {tags.map((tag) => (
<div key={tag.id} className="flex items-center gap-2 p-2 rounded-lg border bg-card"> <div key={tag.id} className="flex flex-wrap items-center gap-2 p-2 rounded-lg border bg-card">
{editingTagId === tag.id ? ( {editingTagId === tag.id ? (
<> <>
<input <input
@@ -417,7 +417,7 @@ export default function ProjectSettingsPage({ params }: ProjectSettingsPageProps
</div> </div>
{/* Add new tag */} {/* Add new tag */}
<div className="flex items-center gap-2 pt-2 border-t"> <div className="flex flex-wrap items-center gap-2 pt-2 border-t">
<input <input
type="color" type="color"
value={newTagColor} value={newTagColor}
@@ -224,7 +224,7 @@ export default function NewVideoPage() {
</p> </p>
)} )}
<div className="flex gap-3"> <div className="flex flex-wrap gap-3">
<Button type="submit" disabled={isLoading || !videoSource}> <Button type="submit" disabled={isLoading || !videoSource}>
{isLoading && <Loader2 className="h-4 w-4 mr-2 animate-spin" />} {isLoading && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
Add Video Add Video
+2 -2
View File
@@ -54,7 +54,7 @@ function ToggleButton({
: 'border-border hover:bg-accent/50' : 'border-border hover:bg-accent/50'
)} )}
> >
<div> <div className="flex-1 min-w-0 pr-4">
<span className="text-sm font-medium">{label}</span> <span className="text-sm font-medium">{label}</span>
{description && ( {description && (
<p className="text-xs text-muted-foreground mt-0.5">{description}</p> <p className="text-xs text-muted-foreground mt-0.5">{description}</p>
@@ -62,7 +62,7 @@ function ToggleButton({
</div> </div>
<div <div
className={cn( className={cn(
'w-10 h-6 rounded-full relative transition-colors', 'w-10 h-6 shrink-0 rounded-full relative transition-colors',
enabled ? 'bg-primary' : 'bg-muted' enabled ? 'bg-primary' : 'bg-muted'
)} )}
> >
@@ -194,8 +194,8 @@ export default function WorkspaceMembersPage() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form onSubmit={handleInvite} className="flex gap-3 items-end"> <form onSubmit={handleInvite} className="flex flex-col sm:flex-row gap-3 sm:items-end">
<div className="flex-1"> <div className="w-full sm:flex-1">
<Label htmlFor="email" className="mb-2 block">Email Address</Label> <Label htmlFor="email" className="mb-2 block">Email Address</Label>
<Input <Input
id="email" id="email"
@@ -207,7 +207,7 @@ export default function WorkspaceMembersPage() {
disabled={isInviting} disabled={isInviting}
/> />
</div> </div>
<div className="w-40"> <div className="w-full sm:w-40">
<Label className="mb-2 block">Role</Label> <Label className="mb-2 block">Role</Label>
<Select value={inviteRole} onValueChange={(v) => setInviteRole(v as 'ADMIN' | 'COMMENTATOR')}> <Select value={inviteRole} onValueChange={(v) => setInviteRole(v as 'ADMIN' | 'COMMENTATOR')}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
@@ -219,7 +219,7 @@ export default function WorkspaceMembersPage() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<Button type="submit" disabled={isInviting}> <Button type="submit" disabled={isInviting} className="w-full sm:w-auto">
{isInviting ? ( {isInviting ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
) : ( ) : (
@@ -255,7 +255,7 @@ export default function WorkspaceMembersPage() {
<CardContent className="space-y-3"> <CardContent className="space-y-3">
{/* Owner */} {/* Owner */}
{owner && ( {owner && (
<div className="flex items-center justify-between p-3 rounded-lg bg-accent/30"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-0 p-3 rounded-lg bg-accent/30">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Avatar className="h-9 w-9"> <Avatar className="h-9 w-9">
<AvatarImage src={owner.image ?? undefined} /> <AvatarImage src={owner.image ?? undefined} />
@@ -275,7 +275,7 @@ export default function WorkspaceMembersPage() {
{/* Members */} {/* Members */}
{members.map((member) => ( {members.map((member) => (
<div key={member.id} className="flex items-center justify-between p-3 rounded-lg border"> <div key={member.id} className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-0 p-3 rounded-lg border">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Avatar className="h-9 w-9"> <Avatar className="h-9 w-9">
<AvatarImage src={member.user.image ?? undefined} /> <AvatarImage src={member.user.image ?? undefined} />
@@ -286,12 +286,12 @@ export default function WorkspaceMembersPage() {
<p className="text-xs text-muted-foreground">{member.user.email}</p> <p className="text-xs text-muted-foreground">{member.user.email}</p>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 w-full sm:w-auto">
<Select <Select
value={member.role} value={member.role}
onValueChange={(v) => handleRoleChange(member.id, v)} onValueChange={(v) => handleRoleChange(member.id, v)}
> >
<SelectTrigger className="w-36 h-8"> <SelectTrigger className="w-full sm:w-36 h-8">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -98,13 +98,13 @@ export default async function WorkspacePage({ params }: WorkspacePageProps) {
</Link> </Link>
</div> </div>
<div className="flex items-center justify-between mb-8"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">{workspace.name}</h1> <h1 className="text-3xl font-bold tracking-tight">{workspace.name}</h1>
{workspace.description && ( {workspace.description && (
<p className="text-muted-foreground mt-1">{workspace.description}</p> <p className="text-muted-foreground mt-1">{workspace.description}</p>
)} )}
<div className="flex items-center gap-4 mt-2 text-sm text-muted-foreground"> <div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 mt-2 text-sm text-muted-foreground">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<FolderOpen className="h-3.5 w-3.5" /> <FolderOpen className="h-3.5 w-3.5" />
{workspace._count.projects} projects {workspace._count.projects} projects
@@ -115,22 +115,22 @@ export default async function WorkspacePage({ params }: WorkspacePageProps) {
</span> </span>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-2 w-full sm:w-auto mt-2 sm:mt-0">
{isAdmin && ( {isAdmin && (
<> <>
<Button asChild variant="outline" size="sm"> <Button asChild variant="outline" size="sm" className="flex-1 sm:flex-none">
<Link href={`/workspaces/${workspaceId}/members`}> <Link href={`/workspaces/${workspaceId}/members`}>
<Users className="h-4 w-4 mr-2" /> <Users className="h-4 w-4 mr-2" />
Members Members
</Link> </Link>
</Button> </Button>
<Button asChild variant="outline" size="sm"> <Button asChild variant="outline" size="sm" className="flex-1 sm:flex-none">
<Link href={`/workspaces/${workspaceId}/settings`}> <Link href={`/workspaces/${workspaceId}/settings`}>
<Settings className="h-4 w-4 mr-2" /> <Settings className="h-4 w-4 mr-2" />
Settings Settings
</Link> </Link>
</Button> </Button>
<Button asChild size="sm"> <Button asChild size="sm" className="w-full sm:w-auto">
<Link href={`/workspaces/${workspaceId}/projects/new`}> <Link href={`/workspaces/${workspaceId}/projects/new`}>
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
New Project New Project
+2 -2
View File
@@ -48,14 +48,14 @@ export default async function WorkspacesPage() {
return ( return (
<div className="px-6 lg:px-8 py-8 w-full"> <div className="px-6 lg:px-8 py-8 w-full">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between mb-8"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Workspaces</h1> <h1 className="text-3xl font-bold tracking-tight">Workspaces</h1>
<p className="text-muted-foreground mt-1"> <p className="text-muted-foreground mt-1">
Manage your workspaces and their projects Manage your workspaces and their projects
</p> </p>
</div> </div>
<Button asChild> <Button asChild className="w-full sm:w-auto">
<Link href="/workspaces/new"> <Link href="/workspaces/new">
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
New Workspace New Workspace
-3
View File
@@ -22,9 +22,6 @@ export default async function HomePage() {
<Button asChild variant="ghost"> <Button asChild variant="ghost">
<Link href="/dashboard">Dashboard</Link> <Link href="/dashboard">Dashboard</Link>
</Button> </Button>
<Button asChild>
<Link href="/projects/new">New Project</Link>
</Button>
</> </>
) : ( ) : (
<> <>
+6 -4
View File
@@ -24,7 +24,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { Sheet, SheetContent, SheetTrigger, SheetTitle, SheetDescription } from '@/components/ui/sheet';
import { ThemeToggle } from '@/components/theme-toggle'; import { ThemeToggle } from '@/components/theme-toggle';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@@ -62,7 +62,7 @@ export function Header({ user }: HeaderProps) {
return ( return (
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="px-6 lg:px-8 flex h-14 items-center w-full"> <div className="px-4 md:px-6 lg:px-8 flex h-14 items-center w-full">
{/* Mobile menu */} {/* Mobile menu */}
<Sheet> <Sheet>
<SheetTrigger asChild> <SheetTrigger asChild>
@@ -72,7 +72,9 @@ export function Header({ user }: HeaderProps) {
</Button> </Button>
</SheetTrigger> </SheetTrigger>
<SheetContent side="left" className="w-64"> <SheetContent side="left" className="w-64">
<nav className="flex flex-col gap-2 mt-4"> <SheetTitle className="sr-only">Navigation Menu</SheetTitle>
<SheetDescription className="sr-only">Access your projects and workspaces</SheetDescription>
<nav className="flex flex-col gap-2 mt-10">
{navItems.map((item) => ( {navItems.map((item) => (
<Link <Link
key={item.href} key={item.href}
@@ -124,7 +126,7 @@ export function Header({ user }: HeaderProps) {
<ThemeToggle /> <ThemeToggle />
{user ? ( {user ? (
<DropdownMenu> <DropdownMenu modal={false}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full"> <Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8"> <Avatar className="h-8 w-8">
File diff suppressed because it is too large Load Diff