summaryrefslogtreecommitdiff
path: root/app/components
diff options
context:
space:
mode:
authoryyamashita <yyamashita@hetzner.yyamashita.com>2026-09-26 00:19:53 +0900
committeryyamashita <yyamashita@hetzner.yyamashita.com>2026-09-26 00:19:53 +0900
commit874749df46cc291ca61f2f98092ccabfdeffbf16 (patch)
treee4cb53b5bfa23974e7935eb2616e0e005954f782 /app/components
parent999b7a3133fccc3f120ac5ae24c02bb38a4ecce9 (diff)
Add passkey login and favorite search termsHEADmaster
Single-user auth: any registered passkey logs in as the owner. First registration needs a one-time token that the app writes to data/registration-token when no passkey exists yet; once logged in, more passkeys can be added without a token. Logged in, /favorites manages a list of terms (e.g. band names). Events whose title or artist contains a term (NFKC + case-insensitive) get a ★, and the event list gains an "お気に入りのみ" filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/components')
-rw-r--r--app/components/EventCard.tsx1
-rw-r--r--app/components/EventListRow.tsx1
-rw-r--r--app/components/FilterBar.tsx19
3 files changed, 19 insertions, 2 deletions
diff --git a/app/components/EventCard.tsx b/app/components/EventCard.tsx
index b23742d..ad55a57 100644
--- a/app/components/EventCard.tsx
+++ b/app/components/EventCard.tsx
@@ -30,6 +30,7 @@ export default function EventCard({ event }: Props) {
<div className="flex-1 p-4 flex flex-col gap-2">
{/* Title */}
<h2 className="font-semibold text-sm leading-snug group-hover:text-indigo-300 transition-colors line-clamp-2">
+ {event.is_favorite ? <span className="text-amber-400" title="お気に入り">★ </span> : null}
{event.title}
</h2>
diff --git a/app/components/EventListRow.tsx b/app/components/EventListRow.tsx
index 30800fb..42f3e8b 100644
--- a/app/components/EventListRow.tsx
+++ b/app/components/EventListRow.tsx
@@ -31,6 +31,7 @@ export default function EventListRow({ event }: Props) {
{/* Title + artist */}
<span className="flex-1 min-w-0">
<span className="block truncate font-medium text-gray-100 group-hover:text-indigo-300 transition-colors">
+ {event.is_favorite ? <span className="text-amber-400" title="お気に入り">★ </span> : null}
{event.title}
</span>
{event.artist && (
diff --git a/app/components/FilterBar.tsx b/app/components/FilterBar.tsx
index 7a95d2c..f897e67 100644
--- a/app/components/FilterBar.tsx
+++ b/app/components/FilterBar.tsx
@@ -6,9 +6,11 @@ interface Props {
areas?: string[];
defaultDateFrom?: string;
defaultDateTo?: string;
+ /** Show the "favorites only" toggle (logged in only) */
+ showFavorites?: boolean;
}
-export default function FilterBar({ venues, areas, defaultDateFrom, defaultDateTo }: Props) {
+export default function FilterBar({ venues, areas, defaultDateFrom, defaultDateTo, showFavorites }: Props) {
const [searchParams] = useSearchParams();
return (
@@ -98,6 +100,19 @@ export default function FilterBar({ venues, areas, defaultDateFrom, defaultDateT
</select>
</div>
+ {showFavorites && (
+ <label className="flex items-center gap-1.5 py-1.5 text-sm text-gray-300 cursor-pointer">
+ <input
+ name="fav"
+ type="checkbox"
+ value="1"
+ defaultChecked={searchParams.get("fav") === "1"}
+ className="accent-amber-400"
+ />
+ <span className="text-amber-400">★</span> お気に入りのみ
+ </label>
+ )}
+
<button
type="submit"
className="rounded-md bg-gray-700 px-4 py-1.5 text-sm font-medium hover:bg-gray-600 transition-colors"
@@ -118,5 +133,5 @@ export default function FilterBar({ venues, areas, defaultDateFrom, defaultDateT
}
function hasFilters(params: URLSearchParams): boolean {
- return ["keyword", "venue_id", "date_from", "date_to", "capacity_range", "area"].some((k) => params.get(k));
+ return ["keyword", "venue_id", "date_from", "date_to", "capacity_range", "area", "fav"].some((k) => params.get(k));
}