mirror of
https://github.com/Chewbaccalakis/rfid-database.git
synced 2026-09-10 00:11:56 -07:00
Implement RFID tag dump PWA with REST API, auth, and CI (#1)
Add Next.js app with SQLite/Drizzle storage for sites and LF/HF tag dumps, multi-user Auth.js (credentials + optional OIDC), personal API tokens in Settings, versioned /api/v1 for UI and future CLI use, MCT/Proxmark/JSON import-export, PWA offline shell, Vitest tests, and GitHub Actions CI. Co-authored-by: Cursor Agent <[email protected]>
This commit is contained in:
co-authored by
Cursor Agent
parent
5029945cc2
commit
8500d2f673
@@ -0,0 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
|
||||
export function AppHeader() {
|
||||
const { data: session } = useSession();
|
||||
const pathname = usePathname();
|
||||
if (pathname === "/login") return null;
|
||||
|
||||
return (
|
||||
<header
|
||||
style={{
|
||||
borderBottom: "1px solid var(--border)",
|
||||
background: "rgba(15, 20, 25, 0.85)",
|
||||
backdropFilter: "blur(8px)",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 20,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="container"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "0.75rem 0",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
<Link href="/" style={{ display: "flex", alignItems: "baseline", gap: "0.5rem" }}>
|
||||
<span style={{ fontWeight: 800, letterSpacing: "-0.03em", fontSize: "1.1rem" }}>
|
||||
RFID DB
|
||||
</span>
|
||||
<span className="muted" style={{ fontSize: "0.75rem" }}>
|
||||
tag dumps
|
||||
</span>
|
||||
</Link>
|
||||
<nav className="row" style={{ gap: "0.25rem" }}>
|
||||
<Link className="btn btn-ghost" href="/">
|
||||
Sites
|
||||
</Link>
|
||||
<Link className="btn btn-ghost" href="/tags/new">
|
||||
New tag
|
||||
</Link>
|
||||
<Link className="btn btn-ghost" href="/settings">
|
||||
Settings
|
||||
</Link>
|
||||
{session?.user && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost"
|
||||
onClick={() => signOut({ callbackUrl: "/login" })}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export function CopyButton({ value, label = "Copy" }: { value: string; label?: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
async function onCopy() {
|
||||
await navigator.clipboard.writeText(value);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
}
|
||||
|
||||
return (
|
||||
<button type="button" className="btn btn-secondary" onClick={onCopy} style={{ padding: "0.3rem 0.6rem", fontSize: "0.8rem" }}>
|
||||
{copied ? "Copied" : label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function ServiceWorkerRegister() {
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !("serviceWorker" in navigator)) return;
|
||||
navigator.serviceWorker.register("/sw.js").catch(() => {
|
||||
// ignore registration failures in dev
|
||||
});
|
||||
}, []);
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user