= {
+ replay: "Replay",
+};
+
+export function Breadcrumbs() {
+ const pathname = usePathname();
+ const segments = pathname.split("/").filter(Boolean);
+
+ if (segments.length === 0) {
+ return null;
+ }
+
+ const crumbs = segments.reduce<{ label: string; href: string }[]>((acc, segment) => {
+ const href = `${acc.at(-1)?.href ?? ""}/${segment}`;
+ const label = /^\d+$/.test(segment) ? `#${segment}` : (LEAF_LABELS[segment] ?? SECTION_LABELS[segment] ?? segment);
+ return [...acc, { label, href }];
+ }, []);
+
+ return (
+
+
+
+
+ {crumbs.map((crumb, i) => {
+ const isLast = i === crumbs.length - 1;
+ return (
+
+
+ {isLast ? (
+ {crumb.label}
+ ) : (
+
+ {crumb.label}
+
+ )}
+
+ );
+ })}
+
+ );
+}
diff --git a/components/layout/NavBar.tsx b/components/layout/NavBar.tsx
index 0fa6f7d..9e09b65 100644
--- a/components/layout/NavBar.tsx
+++ b/components/layout/NavBar.tsx
@@ -1,5 +1,6 @@
"use client";
+import { useState } from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useTheme } from "next-themes";
@@ -45,6 +46,7 @@ function NavLinks({ onNavigate }: { onNavigate?: () => void }) {
export function NavBar() {
const { theme, setTheme } = useTheme();
const router = useRouter();
+ const [menuOpen, setMenuOpen] = useState(false);
async function handleLogout() {
await fetch("/api/auth/logout", { method: "POST" });
@@ -57,7 +59,7 @@ export function NavBar() {
- Sexy
+ Sexy
@@ -79,7 +81,7 @@ export function NavBar() {
-
+
@@ -88,7 +90,7 @@ export function NavBar() {
Menu
-
+ setMenuOpen(false)} />
diff --git a/components/recordings/ReplayPlayer.tsx b/components/recordings/ReplayPlayer.tsx
index 6849712..b738784 100644
--- a/components/recordings/ReplayPlayer.tsx
+++ b/components/recordings/ReplayPlayer.tsx
@@ -1,7 +1,6 @@
"use client";
import { useEffect, useMemo, useState } from "react";
-import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
@@ -32,7 +31,6 @@ function formatTime(ms: number): string {
}
export function ReplayPlayer({ recordingId }: { recordingId: number }) {
- const router = useRouter();
const scanning = useButtplugStore((s) => s.scanning);
const devices = useButtplugStore((s) => s.devices);
const setActuatorValue = useButtplugStore((s) => s.setActuatorValue);
@@ -289,10 +287,6 @@ export function ReplayPlayer({ recordingId }: { recordingId: number }) {
onCancel={() => setShowRemap(false)}
onConfirm={(mapping) => void handleConfirmRemap(mapping)}
/>
-
- router.push("/recordings")}>
- Back to recordings
-
);
}
diff --git a/components/stats/DeviceUsageTable.tsx b/components/stats/DeviceUsageTable.tsx
index 1f4e08b..8ab1a9e 100644
--- a/components/stats/DeviceUsageTable.tsx
+++ b/components/stats/DeviceUsageTable.tsx
@@ -1,3 +1,4 @@
+import { Card, CardContent } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
export interface DeviceUsageRow {
@@ -12,33 +13,41 @@ export interface DeviceUsageRow {
export function DeviceUsageTable({ devices }: { devices: DeviceUsageRow[] }) {
if (devices.length === 0) {
- return No device activity yet.
;
+ return (
+
+ No device activity yet.
+
+ );
}
return (
-
-
-
- Device
- Sessions
- Active time
- Commands
- Last used
-
-
-
- {devices.map((d) => (
-
- {d.displayName ?? d.bleName}
- {d.sessionCount}
- {(d.totalActiveMs / 60_000).toFixed(1)}m
- {d.commandCount}
-
- {d.lastUsedAt ? new Date(d.lastUsedAt).toLocaleString() : "Never"}
-
-
- ))}
-
-
+
+
+
+
+
+ Device
+ Sessions
+ Active time
+ Commands
+ Last used
+
+
+
+ {devices.map((d) => (
+
+ {d.displayName ?? d.bleName}
+ {d.sessionCount}
+ {(d.totalActiveMs / 60_000).toFixed(1)}m
+ {d.commandCount}
+
+ {d.lastUsedAt ? new Date(d.lastUsedAt).toLocaleString() : "Never"}
+
+
+ ))}
+
+
+
+
);
}
diff --git a/components/stats/RecordingLibraryStats.tsx b/components/stats/RecordingLibraryStats.tsx
index 08d4687..6976712 100644
--- a/components/stats/RecordingLibraryStats.tsx
+++ b/components/stats/RecordingLibraryStats.tsx
@@ -35,26 +35,30 @@ export function RecordingLibraryStats({ stats }: { stats: RecordingLibrarySummar
{stats.list.length > 0 && (
-
-
-
- Recording
- Plays
- Last played
-
-
-
- {stats.list.map((r) => (
-
- {r.name}
- {r.playCount}
-
- {r.lastPlayedAt ? new Date(r.lastPlayedAt).toLocaleString() : "Never"}
-
-
- ))}
-
-
+
+
+
+
+
+ Recording
+ Plays
+ Last played
+
+
+
+ {stats.list.map((r) => (
+
+ {r.name}
+ {r.playCount}
+
+ {r.lastPlayedAt ? new Date(r.lastPlayedAt).toLocaleString() : "Never"}
+
+
+ ))}
+
+
+
+
)}
);
diff --git a/components/stats/SessionsSummaryCards.tsx b/components/stats/SessionsSummaryCards.tsx
index 4573254..29f05c1 100644
--- a/components/stats/SessionsSummaryCards.tsx
+++ b/components/stats/SessionsSummaryCards.tsx
@@ -40,9 +40,11 @@ export function SessionsSummaryCards({ summary }: { summary: SessionsSummary })
-
- {liveCount} live · {replayCount} replay
-
+
+
+ {liveCount} live · {replayCount} replay
+
+
{summary.durationPerDevice.length > 0 && (