Initial commit
This commit is contained in:
		
							
								
								
									
										19
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "podcast-frontend",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "bun dev",
 | 
			
		||||
    "build": "bun build",
 | 
			
		||||
    "start": "bun start"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "react": "^18.0.0",
 | 
			
		||||
    "react-dom": "^18.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "typescript": "^4.0.0",
 | 
			
		||||
    "@types/react": "^18.0.0",
 | 
			
		||||
    "@types/react-dom": "^18.0.0",
 | 
			
		||||
    "bun-types": "^0.1.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								frontend/public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/public/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="ja">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="UTF-8" />
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
  <title>ポッドキャスト管理画面</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div id="root"></div>
 | 
			
		||||
  <script type="module" src="../src/index.tsx"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										14
									
								
								frontend/src/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/src/App.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import FeedList from "./components/FeedList";
 | 
			
		||||
import EpisodeList from "./components/EpisodeList";
 | 
			
		||||
 | 
			
		||||
export default function App() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={{ padding: "20px", fontFamily: "sans-serif" }}>
 | 
			
		||||
      <h1>ポッドキャスト自動生成サービス 管理画面</h1>
 | 
			
		||||
      <FeedList />
 | 
			
		||||
      <hr style={{ margin: "20px 0" }} />
 | 
			
		||||
      <EpisodeList />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								frontend/src/components/EpisodeList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								frontend/src/components/EpisodeList.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
import React, { useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
interface Episode {
 | 
			
		||||
  id: string;
 | 
			
		||||
  title: string;
 | 
			
		||||
  pubDate: string;
 | 
			
		||||
  audioPath: string;
 | 
			
		||||
  sourceLink: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function EpisodeList() {
 | 
			
		||||
  const [episodes, setEpisodes] = useState<Episode[]>([]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetch("/api/episodes")
 | 
			
		||||
      .then((res) => res.json())
 | 
			
		||||
      .then((data) => setEpisodes(data));
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <h2>エピソード一覧</h2>
 | 
			
		||||
      <table style={{ width: "100%", borderCollapse: "collapse" }}>
 | 
			
		||||
        <thead>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <th style={{ border: "1px solid #ccc", padding: "8px" }}>
 | 
			
		||||
              タイトル
 | 
			
		||||
            </th>
 | 
			
		||||
            <th style={{ border: "1px solid #ccc", padding: "8px" }}>
 | 
			
		||||
              公開日時
 | 
			
		||||
            </th>
 | 
			
		||||
            <th style={{ border: "1px solid #ccc", padding: "8px" }}>
 | 
			
		||||
              プレビュー
 | 
			
		||||
            </th>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
          {episodes.map((ep) => (
 | 
			
		||||
            <tr key={ep.id}>
 | 
			
		||||
              <td style={{ border: "1px solid #ccc", padding: "8px" }}>
 | 
			
		||||
                {ep.title}
 | 
			
		||||
              </td>
 | 
			
		||||
              <td style={{ border: "1px solid #ccc", padding: "8px" }}>
 | 
			
		||||
                {new Date(ep.pubDate).toLocaleString("ja-JP")}
 | 
			
		||||
              </td>
 | 
			
		||||
              <td style={{ border: "1px solid #ccc", padding: "8px" }}>
 | 
			
		||||
                <audio controls preload="none">
 | 
			
		||||
                  <source
 | 
			
		||||
                    src={`/podcast_audio/${ep.id}.mp3`}
 | 
			
		||||
                    type="audio/mpeg"
 | 
			
		||||
                  />
 | 
			
		||||
                  お使いのブラウザは audio タグに対応していません。
 | 
			
		||||
                </audio>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
          ))}
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								frontend/src/components/FeedList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								frontend/src/components/FeedList.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import React, { useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
export default function FeedList() {
 | 
			
		||||
  const [feeds, setFeeds] = useState<string[]>([]);
 | 
			
		||||
  const [newUrl, setNewUrl] = useState("");
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetch("/api/feeds")
 | 
			
		||||
      .then((res) => res.json())
 | 
			
		||||
      .then((data) => setFeeds(data));
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const addFeed = async () => {
 | 
			
		||||
    if (!newUrl) return;
 | 
			
		||||
    await fetch("/api/feeds", {
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      headers: { "Content-Type": "application/json" },
 | 
			
		||||
      body: JSON.stringify({ feedUrl: newUrl }),
 | 
			
		||||
    });
 | 
			
		||||
    setNewUrl("");
 | 
			
		||||
    const updated = await fetch("/api/feeds").then((res) => res.json());
 | 
			
		||||
    setFeeds(updated);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <h2>RSSフィード管理</h2>
 | 
			
		||||
      <ul>
 | 
			
		||||
        {feeds.map((url) => (
 | 
			
		||||
          <li key={url}>{url}</li>
 | 
			
		||||
        ))}
 | 
			
		||||
      </ul>
 | 
			
		||||
      <input
 | 
			
		||||
        type="text"
 | 
			
		||||
        placeholder="RSSフィードURLを入力"
 | 
			
		||||
        value={newUrl}
 | 
			
		||||
        onChange={(e) => setNewUrl(e.target.value)}
 | 
			
		||||
        style={{ width: "300px" }}
 | 
			
		||||
      />
 | 
			
		||||
      <button onClick={addFeed} style={{ marginLeft: "8px" }}>
 | 
			
		||||
        追加
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								frontend/src/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/src/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import ReactDOM from "react-dom/client";
 | 
			
		||||
import App from "./App";
 | 
			
		||||
 | 
			
		||||
const root = ReactDOM.createRoot(document.getElementById("root")!);
 | 
			
		||||
root.render(<App />);
 | 
			
		||||
							
								
								
									
										14
									
								
								frontend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "ESNext",
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "jsx": "react-jsx",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "outDir": "build"
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["src"]
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user