From 7150142bfefc10194aae7d2a3a644791df77e2ef Mon Sep 17 00:00:00 2001 From: n4mlz Date: Mon, 31 Aug 2026 17:05:32 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:sparkles:=20=E7=AE=A1=E7=90=86=E8=80=85?= =?UTF-8?q?=E5=90=91=E3=81=91=E6=A5=BD=E6=9B=B2=E4=B8=80=E8=A6=A7=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/musics/page.tsx | 14 ++++++ components/dashboard-home.tsx | 1 + components/music-list.tsx | 83 +++++++++++++++++++++++++++++++++++ lib/api.ts | 53 ++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 app/musics/page.tsx create mode 100644 components/music-list.tsx create mode 100644 lib/api.ts diff --git a/app/musics/page.tsx b/app/musics/page.tsx new file mode 100644 index 0000000..a5a20b3 --- /dev/null +++ b/app/musics/page.tsx @@ -0,0 +1,14 @@ +import MusicList from "@/components/music-list"; +import { fetchMusics } from "@/lib/api"; + +type MusicPageProps = { + searchParams: Promise<{ cursor?: string; limit?: string }>; +}; + +export default async function MusicPage({ searchParams }: MusicPageProps) { + const params = await searchParams; + const limit = params.limit ? Number(params.limit) : undefined; + const data = await fetchMusics({ cursor: params.cursor, limit }); + + return ; +} diff --git a/components/dashboard-home.tsx b/components/dashboard-home.tsx index 3a07830..ed8d0cc 100644 --- a/components/dashboard-home.tsx +++ b/components/dashboard-home.tsx @@ -24,6 +24,7 @@ export default function DashboardHome({ userName }: { userName: string }) {
管理者用ダッシュボードへようこそ、{userName} さん。
+
diff --git a/components/music-list.tsx b/components/music-list.tsx new file mode 100644 index 0000000..b3f707c --- /dev/null +++ b/components/music-list.tsx @@ -0,0 +1,83 @@ +"use client"; + +import AppLayout from "@cloudscape-design/components/app-layout"; +import Button from "@cloudscape-design/components/button"; +import Container from "@cloudscape-design/components/container"; +import ContentLayout from "@cloudscape-design/components/content-layout"; +import Header from "@cloudscape-design/components/header"; +import SpaceBetween from "@cloudscape-design/components/space-between"; +import Table from "@cloudscape-design/components/table"; +import TopNavigation from "@cloudscape-design/components/top-navigation"; + +import type { MusicListResponse } from "@/lib/api"; + +export default function MusicList({ + data, + limit, +}: { + data: MusicListResponse; + limit?: number; +}) { + const nextPageHref = data.nextCursor + ? `/musics?${new URLSearchParams({ + cursor: data.nextCursor, + ...(limit ? { limit: String(limit) } : {}), + }).toString()}` + : undefined; + + return ( +
+ + ホーム} variant="h1"> + 楽曲管理 + + } + > + + + item.music.title }, + { + header: "アーティスト", + cell: (item) => item.music.artist, + }, + { header: "BPM", cell: (item) => item.music.bpm }, + { header: "譜面", cell: (item) => item.sheets.length }, + { + header: "登録日時", + cell: (item) => + new Date(item.music.registrationDate).toLocaleString( + "ja-JP", + ), + }, + ]} + items={data.items} + header={ +
楽曲一覧
+ } + empty={楽曲がありません。} + /> +
+ +
+ + + + } + /> + + ); +} diff --git a/lib/api.ts b/lib/api.ts new file mode 100644 index 0000000..7a0e30a --- /dev/null +++ b/lib/api.ts @@ -0,0 +1,53 @@ +import { auth0 } from "@/lib/auth0"; + +export type Sheet = { + id: string; + musicId: string; + difficulty: "easy" | "normal" | "hard"; + level: number; + notesDesigner: string; +}; + +export type Music = { + id: string; + title: string; + artist: string; + bpm: number; + genre: string; + jacket: string; + registrationDate: string; + isTest: boolean; +}; + +export type MusicWithSheets = { + music: Music; + sheets: Sheet[]; +}; + +export type MusicListResponse = { + items: MusicWithSheets[]; + nextCursor: string | null; +}; + +export async function fetchMusics( + searchParams: { cursor?: string; limit?: number } = {}, +): Promise { + const accessToken = await auth0.getAccessToken(); + const params = new URLSearchParams(); + if (searchParams.cursor) params.set("cursor", searchParams.cursor); + if (searchParams.limit) params.set("limit", String(searchParams.limit)); + + const response = await fetch( + `${process.env.API_BASE_URL}/admin/musics?${params.toString()}`, + { + headers: { Authorization: `Bearer ${accessToken.token}` }, + cache: "no-store", + }, + ); + + if (!response.ok) { + throw new Error(`Failed to fetch musics: ${response.status}`); + } + + return response.json() as Promise; +} From 3ead4bdb684c95388a348406bd6d4b1f1575c1a7 Mon Sep 17 00:00:00 2001 From: n4mlz Date: Mon, 31 Aug 2026 17:47:31 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:bug:=20API=20=E3=82=A2=E3=82=AF=E3=82=BB?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3=E3=81=AE=20audien?= =?UTF-8?q?ce=20=E3=82=92=E6=98=8E=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api.ts b/lib/api.ts index 7a0e30a..71f4348 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -32,7 +32,8 @@ export type MusicListResponse = { export async function fetchMusics( searchParams: { cursor?: string; limit?: number } = {}, ): Promise { - const accessToken = await auth0.getAccessToken(); + const audience = process.env.AUTH0_AUDIENCE ?? "https://api.xlair.dev"; + const accessToken = await auth0.getAccessToken({ audience }); const params = new URLSearchParams(); if (searchParams.cursor) params.set("cursor", searchParams.cursor); if (searchParams.limit) params.set("limit", String(searchParams.limit));