Implement share-link service (task 30)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ginnoir
2026-05-06 14:09:10 -05:00
co-authored by Claude Sonnet 4.6
parent 28475e483d
commit d5deee9a46
7 changed files with 222 additions and 0 deletions
+9
View File
@@ -1,11 +1,13 @@
"use server";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
import { users } from "@/modules/_core/schema";
import { VALID_THEME_IDS, VALID_THEME_MODES } from "@/modules/_core/themes";
import type { ThemeId, ThemeMode } from "@/modules/_core/themes";
import { getCurrentSession } from "@/lib/session";
import { revokeShareLink } from "@/modules/_core/share";
export async function setUserTheme({
theme,
@@ -23,3 +25,10 @@ export async function setUserTheme({
.set({ theme, themeMode: mode })
.where(eq(users.id, user.id));
}
export async function revokeShareLinkAction(formData: FormData): Promise<void> {
const id = formData.get("id");
if (typeof id !== "string") throw new Error("Missing id");
await revokeShareLink(id);
revalidatePath("/settings");
}
+44
View File
@@ -1,10 +1,15 @@
import { getCurrentSession } from "@/lib/session";
import { getActiveShareLinks } from "@/modules/_core/share";
import { getEntityType } from "@/modules/_core/registry";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ThemePicker } from "@/components/theme-picker";
import { revokeShareLinkAction } from "./actions";
import Link from "next/link";
export default async function SettingsPage() {
const { user } = await getCurrentSession();
const shareLinks = await getActiveShareLinks();
return (
<div className="container max-w-2xl py-8 space-y-6">
@@ -23,6 +28,45 @@ export default async function SettingsPage() {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Active Share Links</CardTitle>
</CardHeader>
<CardContent>
{shareLinks.length === 0 ? (
<p className="text-sm text-muted-foreground">No active share links.</p>
) : (
<ul className="space-y-3">
{shareLinks.map((link) => {
const registration = getEntityType(link.entityType);
const label = registration?.label.singular ?? link.entityType;
return (
<li key={link.id} className="flex items-center justify-between gap-4 text-sm">
<div className="min-w-0">
<span className="font-medium">{label}</span>
<span className="text-muted-foreground ml-2">
{link.capabilities.write ? "read + write" : "read-only"}
</span>
{link.expiresAt && (
<span className="text-muted-foreground ml-2">
· expires {link.expiresAt.toLocaleDateString()}
</span>
)}
</div>
<form action={revokeShareLinkAction}>
<input type="hidden" name="id" value={link.id} />
<Button variant="destructive" size="sm" type="submit">
Revoke
</Button>
</form>
</li>
);
})}
</ul>
)}
</CardContent>
</Card>
<Link
href="/settings/household"
className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium shadow-sm hover:bg-accent hover:text-accent-foreground"