From 6a2df78e7f2a9f886b09b40710d4944a38efd803 Mon Sep 17 00:00:00 2001
From: Jake Walker <hi@jakew.me>
Date: Thu, 6 Feb 2025 18:02:48 +0000
Subject: [PATCH] add podcast landing page

---
 client/src/index.tsx                |  4 ++
 client/src/routes/admin/podcast.tsx |  2 +-
 client/src/routes/podcast.tsx       | 85 +++++++++++++++++++++++++++++
 client/tsconfig.app.json            |  3 +-
 4 files changed, 91 insertions(+), 3 deletions(-)
 create mode 100644 client/src/routes/podcast.tsx

diff --git a/client/src/index.tsx b/client/src/index.tsx
index cf90e6e..4f06dcb 100644
--- a/client/src/index.tsx
+++ b/client/src/index.tsx
@@ -83,6 +83,10 @@ const routes = [
     component: () => (
       <Protected>{lazy(() => import("./routes/admin/episode"))()}</Protected>
     ),
+  },
+  {
+    path: "/:podcastId",
+    component: lazy(() => import("./routes/podcast")),
   }
 ];
 
diff --git a/client/src/routes/admin/podcast.tsx b/client/src/routes/admin/podcast.tsx
index d2291d6..b397898 100644
--- a/client/src/routes/admin/podcast.tsx
+++ b/client/src/routes/admin/podcast.tsx
@@ -89,7 +89,7 @@ export default function AdminPodcast() {
                 <Show when={podcastQuery.data?.image_filename}>
                   <div class="column">
                     <figure class="image is-128x128">
-                      <img src={new URL(params.podcastId + "/" + podcastQuery.data!.image_filename, SERVER_URL).href} />
+                      <img src={new URL(`/files/${params.podcastId}/${podcastQuery.data!.image_filename}`, SERVER_URL).href} />
                     </figure>
                   </div>
                 </Show>
diff --git a/client/src/routes/podcast.tsx b/client/src/routes/podcast.tsx
new file mode 100644
index 0000000..5afc28e
--- /dev/null
+++ b/client/src/routes/podcast.tsx
@@ -0,0 +1,85 @@
+import { useParams } from "@solidjs/router";
+import { createQuery } from "@tanstack/solid-query";
+import { readPodcastOptions, readEpisodesOptions } from "../client/@tanstack/solid-query.gen";
+import { ErrorBoundary, For, Show, Suspense } from "solid-js";
+import Error from "../components/error";
+import Loading from "../components/loading";
+import { DownloadIcon, MusicIcon } from "lucide-solid";
+import { SERVER_URL } from "../constants";
+
+export default function Podcast() {
+  const params = useParams();
+
+  const podcastQuery = createQuery(() => ({
+    ...readPodcastOptions({
+      path: {
+        podcast_id: params.podcastId,
+      }
+    })
+  }));
+  const episodeQuery = createQuery(() => ({
+    ...readEpisodesOptions({
+      path: {
+        podcast_id: params.podcastId,
+      }
+    })
+  }))
+
+  return (
+    <main>
+      <ErrorBoundary fallback={(err, reset) => <Error message={err} reset={reset} />}>
+        <Suspense fallback={<Loading />}>
+          <section class="hero is-medium is-primary">
+            <div class="hero-body">
+              <div class="columns">
+                <Show when={podcastQuery.data?.image_filename}>
+                  <div class="column">
+                    <figure class="image is-128x128">
+                      <img src={new URL(`/files/${params.podcastId}/${podcastQuery.data!.image_filename}`, SERVER_URL).href} />
+                    </figure>
+                  </div>
+                </Show>
+                <div class="column is-full">
+                  <h1 class="title">{podcastQuery.data?.name}</h1>
+                  <p class="subtitle">{podcastQuery.data?.description}</p>
+                </div>
+              </div>
+            </div>
+          </section>
+          <section class="section">
+            <div class="container is-max-desktop">
+              <For each={episodeQuery.data}>
+                {(episode) => (
+                  <article class="media">
+                    <figure class="media-left">
+                      <p class="image is-64x64 has-background-primary is-flex is-align-content-center">
+                        <MusicIcon class="p-4 has-text-dark" />
+                      </p>
+                    </figure>
+                    <div class="media-content">
+                      <div class="content">
+                        <p class="title is-5">{episode.name}</p>
+                        <Show when={episode.publish_date}>
+                          <p class="subtitle is-6">{new Date(episode.publish_date!).toLocaleDateString()}</p>
+                        </Show>
+                        <figure class="m-0 mb-4">
+                          <audio controls src={new URL(`/files/${episode.podcast_id}/${episode.id}.m4a`, SERVER_URL).toString()}></audio>
+                        </figure>
+                        <Show when={episode.description_html}>
+                          <p innerHTML={episode.description_html!}></p>
+                        </Show>
+                      </div>
+                    </div>
+                    <div class="media-right">
+                      <a href={new URL(`/files/${episode.podcast_id}/${episode.id}.m4a`, SERVER_URL).toString()}><DownloadIcon /></a>
+                    </div>
+                  </article>
+                )}
+              </For>
+            </div>
+          </section>
+        </Suspense>
+      </ErrorBoundary>
+    </main>
+  )
+}
diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json
index 12f076e..d50c655 100644
--- a/client/tsconfig.app.json
+++ b/client/tsconfig.app.json
@@ -23,9 +23,8 @@
     "noFallthroughCasesInSwitch": true,
     "noUncheckedSideEffectImports": true,
 
-    "baseUrl": "./",
     "paths": {
-      "*": ["src/types/*"]
+      "*": ["./src/types/*"]
     }
   },
   "include": ["src", "src/types"]