diff options
| author | yyamashita <yyamashita@mosquit.one> | 2026-05-08 23:37:51 +0900 |
|---|---|---|
| committer | yyamashita <yyamashita@mosquit.one> | 2026-05-08 23:37:51 +0900 |
| commit | d4d104cd604741ac0c308efe5050a0eec69bb389 (patch) | |
| tree | 4d9becba79e83e02ae93695f71e7fe4c908803e0 /app | |
Initial scaffold: React Router v7 + SQLite + Tailwind CSS v4
Diffstat (limited to 'app')
| -rw-r--r-- | app/app.css | 15 | ||||
| -rw-r--r-- | app/lib/db.server.ts | 45 | ||||
| -rw-r--r-- | app/root.tsx | 76 | ||||
| -rw-r--r-- | app/routes.ts | 5 | ||||
| -rw-r--r-- | app/routes/home.tsx | 8 |
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> + ); +} |
