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}
+ )}
+
+ オリジナル記事へ
+
+
+ ))}
+
+ );
+}