summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authoryyamashita <yyamashita@mosquit.one>2026-05-08 23:37:51 +0900
committeryyamashita <yyamashita@mosquit.one>2026-05-08 23:37:51 +0900
commitd4d104cd604741ac0c308efe5050a0eec69bb389 (patch)
tree4d9becba79e83e02ae93695f71e7fe4c908803e0 /app
Initial scaffold: React Router v7 + SQLite + Tailwind CSS v4
Diffstat (limited to 'app')
-rw-r--r--app/app.css15
-rw-r--r--app/lib/db.server.ts45
-rw-r--r--app/root.tsx76
-rw-r--r--app/routes.ts5
-rw-r--r--app/routes/home.tsx8
5 files changed, 149 insertions, 0 deletions
diff --git a/app/app.css b/app/app.css
new file mode 100644
index 0000000..99345d8
--- /dev/null
+++ b/app/app.css
@@ -0,0 +1,15 @@
+@import "tailwindcss";
+
+@theme {
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+html,
+body {
+ @apply bg-white dark:bg-gray-950;
+
+ @media (prefers-color-scheme: dark) {
+ color-scheme: dark;
+ }
+}
diff --git a/app/lib/db.server.ts b/app/lib/db.server.ts
new file mode 100644
index 0000000..6aa9313
--- /dev/null
+++ b/app/lib/db.server.ts
@@ -0,0 +1,45 @@
+import Database from "better-sqlite3";
+import path from "path";
+
+let db: Database.Database | null = null;
+
+export function getDb(): Database.Database {
+ if (!db) {
+ const dbPath = path.resolve("whois.db");
+ db = new Database(dbPath);
+ db.pragma("journal_mode = WAL");
+ db.pragma("foreign_keys = ON");
+ initSchema(db);
+ }
+ return db;
+}
+
+function initSchema(db: Database.Database) {
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS bands (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ name_kana TEXT,
+ formed_at TEXT,
+ area TEXT,
+ genre TEXT,
+ url TEXT,
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+
+ CREATE TABLE IF NOT EXISTS members (
+ id TEXT PRIMARY KEY,
+ band_id TEXT NOT NULL REFERENCES bands(id),
+ name TEXT NOT NULL,
+ name_kana TEXT,
+ role TEXT,
+ joined_at TEXT,
+ left_at TEXT,
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_members_band_id ON members(band_id);
+ `);
+}
diff --git a/app/root.tsx b/app/root.tsx
new file mode 100644
index 0000000..2c88ff1
--- /dev/null
+++ b/app/root.tsx
@@ -0,0 +1,76 @@
+import {
+ isRouteErrorResponse,
+ Links,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "react-router";
+
+import type { Route } from "./+types/root";
+import "./app.css";
+
+export const links: Route.LinksFunction = () => [
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
+ {
+ rel: "preconnect",
+ href: "https://fonts.gstatic.com",
+ crossOrigin: "anonymous",
+ },
+ {
+ rel: "stylesheet",
+ href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
+ },
+];
+
+export function Layout({ children }: { children: React.ReactNode }) {
+ return (
+ <html lang="en" className="dark">
+ <head>
+ <meta charSet="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>whois.band</title>
+ <Meta />
+ <Links />
+ </head>
+ <body className="bg-gray-950 text-gray-100 antialiased">
+ {children}
+ <ScrollRestoration />
+ <Scripts />
+ </body>
+ </html>
+ );
+}
+
+export default function App() {
+ return <Outlet />;
+}
+
+export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
+ let message = "Oops!";
+ let details = "An unexpected error occurred.";
+ let stack: string | undefined;
+
+ if (isRouteErrorResponse(error)) {
+ message = error.status === 404 ? "404" : "Error";
+ details =
+ error.status === 404
+ ? "The requested page could not be found."
+ : error.statusText || details;
+ } else if (import.meta.env.DEV && error && error instanceof Error) {
+ details = error.message;
+ stack = error.stack;
+ }
+
+ return (
+ <main className="pt-16 p-4 container mx-auto">
+ <h1>{message}</h1>
+ <p>{details}</p>
+ {stack && (
+ <pre className="w-full p-4 overflow-x-auto">
+ <code>{stack}</code>
+ </pre>
+ )}
+ </main>
+ );
+}
diff --git a/app/routes.ts b/app/routes.ts
new file mode 100644
index 0000000..935792d
--- /dev/null
+++ b/app/routes.ts
@@ -0,0 +1,5 @@
+import { type RouteConfig, index } from "@react-router/dev/routes";
+
+export default [
+ index("routes/home.tsx"),
+] satisfies RouteConfig;
diff --git a/app/routes/home.tsx b/app/routes/home.tsx
new file mode 100644
index 0000000..03ae39a
--- /dev/null
+++ b/app/routes/home.tsx
@@ -0,0 +1,8 @@
+export default function Home() {
+ return (
+ <main className="container mx-auto px-4 py-16 text-center">
+ <h1 className="text-4xl font-bold tracking-tight">whois.band</h1>
+ <p className="mt-4 text-gray-400">Band identification service. Coming soon.</p>
+ </main>
+ );
+}