diff options
| author | yyamashita <yyamashita@mosquit.one> | 2026-05-09 00:51:08 +0900 |
|---|---|---|
| committer | yyamashita <yyamashita@mosquit.one> | 2026-05-09 00:51:08 +0900 |
| commit | b8548d029760ecfa59cafedd23899a91e6120b5f (patch) | |
| tree | 681543d0acc26d6adc90bbe19493e50740505de3 /app/routes | |
| parent | d944f11581553c5e038b33fa4558566713f6d1f4 (diff) | |
Add predefined link types and artist roles
- app/lib/constants.ts: LINK_TYPES (12種) and ARTIST_ROLES (13種)
- Band link forms: label input → type select (公式サイト, X, Instagram, ...)
- Band member forms: role text → role select (Vocal, Guitar, Bass, ...)
- Band detail: resolve link type key to display label via LINK_TYPE_LABEL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'app/routes')
| -rw-r--r-- | app/routes/band-by-uuid.tsx | 3 | ||||
| -rw-r--r-- | app/routes/band-edit.tsx | 24 | ||||
| -rw-r--r-- | app/routes/band-new.tsx | 25 |
3 files changed, 35 insertions, 17 deletions
diff --git a/app/routes/band-by-uuid.tsx b/app/routes/band-by-uuid.tsx index 1945a57..94a7d3c 100644 --- a/app/routes/band-by-uuid.tsx +++ b/app/routes/band-by-uuid.tsx @@ -6,6 +6,7 @@ import { getBandArtists, getBandRevisions, } from "~/lib/db.server"; +import { LINK_TYPE_LABEL } from "~/lib/constants"; export async function loader({ params }: LoaderFunctionArgs) { const band = getBandById(params.uuid!); @@ -90,7 +91,7 @@ export default function BandDetail() { rel="noopener noreferrer" className="text-blue-400 hover:text-blue-300 transition-colors text-sm" > - {l.label} + {LINK_TYPE_LABEL[l.label] ?? l.label} </a> </li> ))} diff --git a/app/routes/band-edit.tsx b/app/routes/band-edit.tsx index 99d4ee7..50b8bd4 100644 --- a/app/routes/band-edit.tsx +++ b/app/routes/band-edit.tsx @@ -9,6 +9,7 @@ import { listArtists, updateBand, } from "~/lib/db.server"; +import { ARTIST_ROLES, LINK_TYPES } from "~/lib/constants"; export async function loader({ params }: LoaderFunctionArgs) { const band = getBandById(params.uuid!); @@ -159,12 +160,15 @@ export default function BandEdit() { <div className="space-y-2"> {links.map((link, i) => ( <div key={i} className="flex gap-2"> - <input + <select value={link.label} onChange={(e) => setLinks(links.map((l, idx) => idx === i ? { ...l, label: e.target.value } : l))} - placeholder="ラベル (例: X)" - className="w-28 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-gray-100 focus:outline-none focus:border-blue-500 text-sm" - /> + className="w-36 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-gray-100 focus:outline-none focus:border-blue-500 text-sm" + > + {LINK_TYPES.map((t) => ( + <option key={t.value} value={t.value}>{t.label}</option> + ))} + </select> <input value={link.url} onChange={(e) => setLinks(links.map((l, idx) => idx === i ? { ...l, url: e.target.value } : l))} @@ -183,7 +187,7 @@ export default function BandEdit() { </div> <button type="button" - onClick={() => setLinks([...links, { label: "", url: "" }])} + onClick={() => setLinks([...links, { label: LINK_TYPES[0].value, url: "" }])} className="mt-2 text-blue-400 hover:text-blue-300 text-sm transition-colors" > + リンクを追加 @@ -196,12 +200,16 @@ export default function BandEdit() { {picked.map((p, i) => ( <div key={p.id} className="flex gap-2 items-center"> <span className="text-gray-200 text-sm w-32 truncate">{p.name}</span> - <input + <select value={p.role} onChange={(e) => setPicked(picked.map((a, idx) => idx === i ? { ...a, role: e.target.value } : a))} - placeholder="パート (例: Guitar)" className="flex-1 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-gray-100 focus:outline-none focus:border-blue-500 text-sm" - /> + > + <option value="">— パートを選択 —</option> + {ARTIST_ROLES.map((r) => ( + <option key={r} value={r}>{r}</option> + ))} + </select> <button type="button" onClick={() => setPicked(picked.filter((_, idx) => idx !== i))} diff --git a/app/routes/band-new.tsx b/app/routes/band-new.tsx index b4d49e2..86222a3 100644 --- a/app/routes/band-new.tsx +++ b/app/routes/band-new.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { Form, Link, redirect, useActionData, useLoaderData } from "react-router"; import type { ActionFunctionArgs } from "react-router"; import { createBand, getIpAddress, listArtists } from "~/lib/db.server"; +import { ARTIST_ROLES, LINK_TYPES } from "~/lib/constants"; export function loader() { return { artists: listArtists() }; @@ -53,6 +54,7 @@ export default function BandNew() { const [slug, setSlug] = useState(""); const [slugManual, setSlugManual] = useState(false); const [links, setLinks] = useState<{ label: string; url: string }[]>([]); + const DEFAULT_LINK_TYPE = LINK_TYPES[0].value; const [picked, setPicked] = useState<{ id: string; name: string; role: string }[]>([]); const available = artists.filter((a) => !picked.some((p) => p.id === a.id)); @@ -136,12 +138,15 @@ export default function BandNew() { <div className="space-y-2"> {links.map((link, i) => ( <div key={i} className="flex gap-2"> - <input + <select value={link.label} onChange={(e) => setLinks(links.map((l, idx) => idx === i ? { ...l, label: e.target.value } : l))} - placeholder="ラベル (例: X)" - className="w-28 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-gray-100 focus:outline-none focus:border-blue-500 text-sm" - /> + className="w-36 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-gray-100 focus:outline-none focus:border-blue-500 text-sm" + > + {LINK_TYPES.map((t) => ( + <option key={t.value} value={t.value}>{t.label}</option> + ))} + </select> <input value={link.url} onChange={(e) => setLinks(links.map((l, idx) => idx === i ? { ...l, url: e.target.value } : l))} @@ -160,7 +165,7 @@ export default function BandNew() { </div> <button type="button" - onClick={() => setLinks([...links, { label: "", url: "" }])} + onClick={() => setLinks([...links, { label: DEFAULT_LINK_TYPE, url: "" }])} className="mt-2 text-blue-400 hover:text-blue-300 text-sm transition-colors" > + リンクを追加 @@ -173,12 +178,16 @@ export default function BandNew() { {picked.map((p, i) => ( <div key={p.id} className="flex gap-2 items-center"> <span className="text-gray-200 text-sm w-32 truncate">{p.name}</span> - <input + <select value={p.role} onChange={(e) => setPicked(picked.map((a, idx) => idx === i ? { ...a, role: e.target.value } : a))} - placeholder="パート (例: Guitar)" className="flex-1 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-gray-100 focus:outline-none focus:border-blue-500 text-sm" - /> + > + <option value="">— パートを選択 —</option> + {ARTIST_ROLES.map((r) => ( + <option key={r} value={r}>{r}</option> + ))} + </select> <button type="button" onClick={() => setPicked(picked.filter((_, idx) => idx !== i))} |
