ui: redesign

This commit is contained in:
Nicolas Meienberger
2025-10-03 23:54:53 +02:00
parent 689f14dff7
commit e134d0e1d1
9 changed files with 137 additions and 121 deletions

View File

@@ -9,9 +9,10 @@ import { StatusDot } from "~/components/status-dot";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
import { VolumeIcon } from "~/components/volume-icon";
import type { Route } from "./+types/home";
import { Card, CardHeader } from "~/components/ui/card";
export function meta(_: Route.MetaArgs) {
return [
@@ -60,85 +61,96 @@ export default function Home({ loaderData }: Route.ComponentProps) {
return (
<>
<h1 className="text-2xl sm:text-3xl font-bold mb-0 uppercase">Ironmount</h1>
<h2 className="text-xs sm:text-sm font-semibold mb-2 text-muted-foreground">
Create, manage, monitor, and automate your volumes with ease.
</h2>
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 mt-4 sm:justify-between">
<span className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
<Input
className="w-full sm:w-[180px]"
placeholder="Search volumes…"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="All status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="mounted">Mounted</SelectItem>
<SelectItem value="unmounted">Unmounted</SelectItem>
<SelectItem value="error">Error</SelectItem>
</SelectContent>
</Select>
<Select value={backendFilter} onValueChange={setBackendFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="All backends" />
</SelectTrigger>
<SelectContent>
<SelectItem value="directory">Directory</SelectItem>
<SelectItem value="nfs">NFS</SelectItem>
<SelectItem value="smb">SMB</SelectItem>
</SelectContent>
</Select>
{(searchQuery || statusFilter || backendFilter) && (
<Button variant="outline" size="sm" onClick={clearFilters} className="w-full sm:w-auto">
<RotateCcw className="h-4 w-4 mr-2" />
Clear filters
</Button>
)}
</span>
<CreateVolumeDialog open={createVolumeOpen} setOpen={setCreateVolumeOpen} />
</div>
<div className="mt-4 overflow-x-auto">
<Table className="border bg-white dark:bg-secondary">
<TableCaption>A list of your managed volumes.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px] uppercase">Name</TableHead>
<TableHead className="uppercase text-left">Backend</TableHead>
<TableHead className="uppercase hidden sm:table-cell">Mountpoint</TableHead>
<TableHead className="uppercase text-center">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredVolumes.map((volume) => (
<TableRow
key={volume.name}
className="hover:bg-accent/50 hover:cursor-pointer"
onClick={() => navigate(`/volumes/${volume.name}`)}
>
<TableCell className="font-medium">{volume.name}</TableCell>
<TableCell>
<VolumeIcon backend={volume.type} />
</TableCell>
<TableCell className="hidden sm:table-cell">
<span className="flex items-center gap-2">
<span className="text-muted-foreground text-xs truncate bg-primary/10 rounded-md px-2 py-1">
{volume.path}
</span>
<Copy size={10} />
</span>
</TableCell>
<TableCell className="text-center">
<StatusDot status={volume.status} />
</TableCell>
{/* <h1 className="text-2xl sm:text-3xl font-bold mb-0 uppercase">Ironmount</h1> */}
{/* <h2 className="text-xs sm:text-sm font-semibold mb-2 text-muted-foreground"> */}
{/* Create, manage, monitor, and automate your volumes with ease. */}
{/* </h2> */}
<Card className="p-0 gap-0">
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:justify-between p-4 bg-card-header py-4">
<span className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
<Input
className="w-full sm:w-[180px]"
placeholder="Search volumes…"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="All status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="mounted">Mounted</SelectItem>
<SelectItem value="unmounted">Unmounted</SelectItem>
<SelectItem value="error">Error</SelectItem>
</SelectContent>
</Select>
<Select value={backendFilter} onValueChange={setBackendFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="All backends" />
</SelectTrigger>
<SelectContent>
<SelectItem value="directory">Directory</SelectItem>
<SelectItem value="nfs">NFS</SelectItem>
<SelectItem value="smb">SMB</SelectItem>
</SelectContent>
</Select>
{(searchQuery || statusFilter || backendFilter) && (
<Button variant="outline" size="sm" onClick={clearFilters} className="w-full sm:w-auto">
<RotateCcw className="h-4 w-4 mr-2" />
Clear filters
</Button>
)}
</span>
<CreateVolumeDialog open={createVolumeOpen} setOpen={setCreateVolumeOpen} />
</div>
<div className="overflow-x-auto">
<Table className="border-t">
<TableHeader className="bg-card-header">
<TableRow>
<TableHead className="w-[100px] uppercase">Name</TableHead>
<TableHead className="uppercase text-left">Backend</TableHead>
<TableHead className="uppercase hidden sm:table-cell">Mountpoint</TableHead>
<TableHead className="uppercase text-center">Status</TableHead>
</TableRow>
))}
</TableBody>
</Table>
</div>
</TableHeader>
<TableBody>
{filteredVolumes.map((volume) => (
<TableRow
key={volume.name}
className="hover:bg-accent/50 hover:cursor-pointer"
onClick={() => navigate(`/volumes/${volume.name}`)}
>
<TableCell className="font-medium text-strong-accent">{volume.name}</TableCell>
<TableCell>
<VolumeIcon backend={volume.type} />
</TableCell>
<TableCell className="hidden sm:table-cell">
<span className="flex items-center gap-2">
<span className="text-muted-foreground text-xs truncate bg-primary/10 rounded-md px-2 py-1">
{volume.path}
</span>
<Copy size={10} />
</span>
</TableCell>
<TableCell className="text-center">
<StatusDot status={volume.status} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="px-4 py-2 text-sm text-muted-foreground bg-card-header flex justify-end border-t">
{filteredVolumes.length === 0 ? (
"No volumes found."
) : (
<span>
<span className="text-strong-accent">{filteredVolumes.length}</span> volume
{filteredVolumes.length > 1 ? "s" : ""}
</span>
)}
</div>
</Card>
</>
);
}

View File

@@ -49,8 +49,9 @@ export default function LoginPage() {
};
return (
<div className="min-h-screen flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<div className="relative min-h-screen flex items-center justify-center p-4 [background-size:20px_20px] sm:[background-size:40px_40px] [background-image:linear-gradient(to_right,#e4e4e7_1px,transparent_1px),linear-gradient(to_bottom,#e4e4e7_1px,transparent_1px)] dark:[background-image:linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]">
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] dark:bg-black" />
<Card className="relative w-full max-w-md">
<CardHeader>
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
<CardDescription>Sign in to your account</CardDescription>

View File

@@ -60,8 +60,9 @@ export default function OnboardingPage() {
};
return (
<div className="min-h-screen flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<div className="relative min-h-screen flex items-center justify-center p-4 [background-size:20px_20px] sm:[background-size:40px_40px] [background-image:linear-gradient(to_right,#e4e4e7_1px,transparent_1px),linear-gradient(to_bottom,#e4e4e7_1px,transparent_1px)] dark:[background-image:linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]">
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] dark:bg-black" />
<Card className="relative w-full max-w-md">
<CardHeader>
<CardTitle className="text-2xl font-bold">Welcome to Ironmount</CardTitle>
<CardDescription>Create the admin user to get started</CardDescription>