feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
125
apps/web/src/app/[locale]/(apps)/demo/page.tsx
Normal file
125
apps/web/src/app/[locale]/(apps)/demo/page.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import { cn } from "@turbostarter/ui";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@turbostarter/ui-web/card";
|
||||
import { Icons } from "@turbostarter/ui-web/icons";
|
||||
|
||||
interface DemoItem {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
icon: keyof typeof Icons;
|
||||
status: "stable" | "experimental" | "new";
|
||||
}
|
||||
|
||||
const demos: DemoItem[] = [
|
||||
{
|
||||
title: "Scroll Fade",
|
||||
description: "CSS mask-image based scroll indicators that fade content at edges when scrollable.",
|
||||
href: "/demo/scroll-test",
|
||||
icon: "ScrollText",
|
||||
status: "new",
|
||||
},
|
||||
];
|
||||
|
||||
const statusStyles = {
|
||||
stable: "bg-green-500/10 text-green-500 border-green-500/20",
|
||||
experimental: "bg-amber-500/10 text-amber-500 border-amber-500/20",
|
||||
new: "bg-blue-500/10 text-blue-500 border-blue-500/20",
|
||||
};
|
||||
|
||||
const statusLabels = {
|
||||
stable: "Stable",
|
||||
experimental: "Experimental",
|
||||
new: "New",
|
||||
};
|
||||
|
||||
export default function DemoHubPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Header */}
|
||||
<div className="border-b bg-card/50 backdrop-blur-sm">
|
||||
<div className="mx-auto max-w-6xl px-6 py-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Icons.LayoutDashboard className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Component Demos</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Interactive demonstrations of UI components and patterns
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="mx-auto max-w-6xl px-6 py-8">
|
||||
{demos.length === 0 ? (
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<Icons.Package className="h-12 w-12 text-muted-foreground/50" />
|
||||
<p className="mt-4 text-muted-foreground">No demos available yet</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{demos.map((demo) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
const Icon = Icons[demo.icon] || Icons.Package;
|
||||
return (
|
||||
<Link key={demo.href} href={demo.href}>
|
||||
<Card className="group h-full transition-all hover:border-primary/50 hover:shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-muted transition-colors group-hover:bg-primary/10">
|
||||
<Icon className="h-5 w-5 text-muted-foreground transition-colors group-hover:text-primary" />
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full border px-2 py-0.5 text-xs font-medium",
|
||||
statusStyles[demo.status]
|
||||
)}
|
||||
>
|
||||
{statusLabels[demo.status]}
|
||||
</span>
|
||||
</div>
|
||||
<CardTitle className="mt-3 text-lg">{demo.title}</CardTitle>
|
||||
<CardDescription className="line-clamp-2">
|
||||
{demo.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<div className="flex items-center text-sm text-muted-foreground group-hover:text-primary">
|
||||
<span>View demo</span>
|
||||
<Icons.ArrowRight className="ml-1 h-4 w-4 transition-transform group-hover:translate-x-1" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer info */}
|
||||
<div className="mt-12 rounded-lg border border-dashed bg-muted/30 p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<Icons.Info className="mt-0.5 h-5 w-5 text-muted-foreground" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium">Adding new demos</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Create a new folder in <code className="rounded bg-muted px-1.5 py-0.5 text-xs">app/[locale]/(apps)/demo/</code> and
|
||||
add it to the demos array in this page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
130
apps/web/src/app/[locale]/(apps)/demo/scroll-test/page.tsx
Normal file
130
apps/web/src/app/[locale]/(apps)/demo/scroll-test/page.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@turbostarter/ui-web/card";
|
||||
import { Icons } from "@turbostarter/ui-web/icons";
|
||||
import { ScrollAreaWithShadows } from "@turbostarter/ui-web/scroll-area";
|
||||
|
||||
export default function ScrollTestPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background p-8">
|
||||
<div className="mb-8">
|
||||
<Link
|
||||
href="/demo"
|
||||
className="mb-4 inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<Icons.ArrowLeft className="mr-1 h-4 w-4" />
|
||||
Back to demos
|
||||
</Link>
|
||||
<h1 className="text-2xl font-bold">Scroll Fade Demo</h1>
|
||||
<p className="mt-1 text-muted-foreground text-sm">
|
||||
CSS mask-image based scroll indicators that fade content at edges
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
{/* Card with scrollable content */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Scrollable Card</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<ScrollAreaWithShadows className="px-6 pb-6" maxHeight="300px">
|
||||
<div className="space-y-4">
|
||||
{Array.from({ length: 20 }, (_, i) => (
|
||||
<div key={i} className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">Item {i + 1}</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
This is some sample content for item {i + 1}.
|
||||
Scroll up and down to see the fade effect at the edges.
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollAreaWithShadows>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Another card for comparison */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Another Scrollable Card</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<ScrollAreaWithShadows className="px-6 pb-6" maxHeight="300px">
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: 15 }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-3 rounded-md bg-muted/50 p-3"
|
||||
>
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary font-medium">
|
||||
{i + 1}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">List item {i + 1}</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
Description text here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollAreaWithShadows>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Short content - no scroll needed */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>No Scroll Needed</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<ScrollAreaWithShadows className="px-6 pb-6" maxHeight="300px">
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">Single Item</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
This card has little content, so no fade effect should appear.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollAreaWithShadows>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Taller card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Taller Scroll Area</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<ScrollAreaWithShadows className="px-6 pb-6" maxHeight="400px">
|
||||
<div className="space-y-4">
|
||||
{Array.from({ length: 25 }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="rounded-lg border border-dashed p-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium">Row {i + 1}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{new Date().toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Sed do eiusmod tempor incididunt ut labore.
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollAreaWithShadows>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user