diff --git a/frontend/next.config.js b/frontend/next.config.js new file mode 100644 index 0000000..0288026 --- /dev/null +++ b/frontend/next.config.js @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + // 他のカスタム設定をここに追加 +}; + +export default nextConfig; diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000..a8a2f33 --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import "./globals.css"; + +export const metadata = { + title: "ポッドキャスト管理画面", + description: "RSSフィードから自動生成されたポッドキャストを管理", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +
+
+

ポッドキャスト管理画面

+ +
+
{children}
+ +
+ + + ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000..efb41cf --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import FeedList from "../components/FeedList"; +import EpisodePlayer from "../components/EpisodePlayer"; + +export default function Home() { + return ( +
+
+

フィード一覧

+ +
+
+

エピソードプレイヤー

+ +
+
+ ); +} diff --git a/frontend/src/components/EpisodePlayer.tsx b/frontend/src/components/EpisodePlayer.tsx new file mode 100644 index 0000000..7b2cbb8 --- /dev/null +++ b/frontend/src/components/EpisodePlayer.tsx @@ -0,0 +1,87 @@ +import React, { useState } from "react"; + +interface Episode { + id: string; + title: string; + pubDate: string; + audioPath: string; + sourceLink: string; +} + +export default function EpisodePlayer() { + const [episodes, setEpisodes] = useState([]); + const [selectedEpisode, setSelectedEpisode] = useState(null); + const [isPlaying, setIsPlaying] = useState(false); + const [audioUrl, setAudioUrl] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetchEpisodes(); + }, []); + + const fetchEpisodes = async () => { + try { + const response = await fetch("/api/episodes"); + if (!response.ok) { + throw new Error("エピソードの取得に失敗しました"); + } + const data = await response.json(); + setEpisodes(data); + setLoading(false); + } catch (err) { + setError(err instanceof Error ? err.message : "エラーが発生しました"); + setLoading(false); + } + }; + + const handlePlay = (episode: Episode) => { + setSelectedEpisode(episode); + setAudioUrl(`/podcast_audio/${episode.id}.mp3`); + setIsPlaying(true); + }; + + if (loading) return
読み込み中...
; + if (error) return
エラー: {error}
; + + return ( +
+

最近のエピソード

+
+ {episodes.map((episode) => ( +
+ {episode.title} + +
+ ))} +
+ + {selectedEpisode && ( +
+

+ 再生中: {selectedEpisode.title} +

+ {audioUrl ? ( +
+ )} +
+ ); +} diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx new file mode 100644 index 0000000..d751b58 --- /dev/null +++ b/frontend/src/components/FeedList.tsx @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from "react"; + +interface FeedItem { + id: string; + title: string; + link: string; + pubDate: string; + contentSnippet?: string; +} + +export default function FeedList() { + const [feeds, setFeeds] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetchFeeds(); + }, []); + + const fetchFeeds = async () => { + try { + const response = await fetch("/api/feeds"); + if (!response.ok) { + throw new Error("フィードの取得に失敗しました"); + } + const data = await response.json(); + setFeeds(data); + setLoading(false); + } catch (err) { + setError(err instanceof Error ? err.message : "エラーが発生しました"); + setLoading(false); + } + }; + + if (loading) return
読み込み中...
; + if (error) return
エラー: {error}
; + + return ( +
+ {feeds.map((feed) => ( +
+

{feed.title}

+

{feed.pubDate}

+ {feed.contentSnippet && ( +

{feed.contentSnippet}

+ )} + + オリジナル記事へ + +
+ ))} +
+ ); +}