diff --git a/app/components/page-layout.tsx b/app/components/page-layout.tsx
index 50d3048..6eaa191 100644
--- a/app/components/page-layout.tsx
+++ b/app/components/page-layout.tsx
@@ -47,13 +47,10 @@ export function PageLayout({ children }: PageLayoutProps) {
Listen
- Music
+ Music
- Sports
-
-
- News & Talk
+ News & Talk
Manage Content
diff --git a/app/components/stations-gallery.tsx b/app/components/stations-gallery.tsx
new file mode 100644
index 0000000..29103ea
--- /dev/null
+++ b/app/components/stations-gallery.tsx
@@ -0,0 +1,37 @@
+import { Link } from "@remix-run/react";
+import type { ConvertDatesToStrings } from "@remix-run/router/utils";
+import type { StationWithTags } from "~/models/station.server";
+
+export type StationsGalleryProps = {
+ stations: ConvertDatesToStrings>[]
+};
+
+export function StationsGallery({ stations }: StationsGalleryProps) {
+ return (
+
+ {stations.map((station) => {
+ return (
+
+

+
+
+ {station.name}
+
+
+ {station.tags.map((t, id) => {
+ return {t.tag.name};
+ })}
+
+
{station.description}
+
+
+
+
+
+ );
+
+ })}
+
+ );
+}
diff --git a/app/models/station.server.ts b/app/models/station.server.ts
index a699950..219bdd5 100644
--- a/app/models/station.server.ts
+++ b/app/models/station.server.ts
@@ -1,4 +1,4 @@
-import type { PrismaClient, Station, Tag } from "@prisma/client";
+import type { Prisma, PrismaClient, Station, Tag } from "@prisma/client";
import { prisma } from "~/db.server";
import { upsertTagOnName } from "~/models/tag.server";
import { slugify } from "~/utils";
@@ -16,6 +16,43 @@ export type StationInput = {
export type PrismaTxClient = Omit;
+export function findStationsByTags(tags: string[]) {
+ return prisma.station.findMany({
+ where: {
+ tags: {
+ some: {
+ tag: {
+ name: { in: tags }
+ }
+ }
+ }
+ },
+ include: {
+ tags: {
+ include: {
+ tag: true
+ }
+ }
+ }
+ });
+}
+
+export type StationWithTags = Prisma.PromiseReturnType;
+
+export function getStationById(id: string) {
+ return prisma.station.findUnique({
+ where: { id },
+ include: {
+ tags: {
+ include: {
+ tag: true
+ }
+ }
+ }
+ });
+
+}
+
export function upsertStationOnStreamUrl(input: StationInput, p: PrismaTxClient = prisma) {
return p.station.upsert({
where: { streamUrl: input.streamUrl },
diff --git a/app/models/tag.server.ts b/app/models/tag.server.ts
index b95142a..cb58cd6 100644
--- a/app/models/tag.server.ts
+++ b/app/models/tag.server.ts
@@ -2,6 +2,10 @@ import { prisma } from "~/db.server";
import type { PrismaTxClient } from "~/models/station.server";
import { slugify } from "~/utils";
+export function findTagBySlug(slug: string) {
+ return prisma.tag.findUnique({ where: { slug } });
+}
+
export function upsertTagOnName(tag: string, p: PrismaTxClient = prisma) {
const slug = slugify(tag);
return p.tag.upsert({
diff --git a/app/routes/listen._index.tsx b/app/routes/listen._index.tsx
new file mode 100644
index 0000000..63509f6
--- /dev/null
+++ b/app/routes/listen._index.tsx
@@ -0,0 +1,13 @@
+import { Link } from "@remix-run/react";
+import { Breadcrumbs } from "~/components/breadcrumbs";
+
+export default function ListenHome() {
+ return (
+ <>
+
+ Home
+
+ >
+ );
+
+}
diff --git a/app/routes/listen.channel.$channel.tsx b/app/routes/listen.channel.$channel.tsx
new file mode 100644
index 0000000..fb35c8b
--- /dev/null
+++ b/app/routes/listen.channel.$channel.tsx
@@ -0,0 +1,58 @@
+import type { LoaderArgs } from "@remix-run/node";
+import { json } from "@remix-run/node";
+import { Link, useLoaderData } from "@remix-run/react";
+import { Breadcrumbs } from "~/components/breadcrumbs";
+import { StationsGallery } from "~/components/stations-gallery";
+import type { StationWithTags } from "~/models/station.server";
+import { findStationsByTags } from "~/models/station.server";
+import { notFound } from "~/utils";
+
+
+export type Channel = {
+ tags: string[];
+ slug: string;
+ name: string;
+}
+
+const channels: { [channel: string]: Channel } = {
+ "music": {
+ tags: ["music"],
+ slug: "music",
+ name: "Music"
+ },
+ "news": {
+ tags: ["news"],
+ slug: "news",
+ name: "News"
+ }
+};
+
+
+export async function loader({ params }: LoaderArgs) {
+ if (!params.channel) {
+ throw notFound();
+ }
+
+ const channel = channels[params.channel];
+ if (!channel) {
+ throw notFound();
+ }
+
+ const stations: NonNullable[] = await findStationsByTags(channel.tags);
+ return json({ channel, stations });
+}
+
+export default function ListenChannel() {
+ const { channel, stations } = useLoaderData();
+
+ return (
+ <>
+
+ Home
+ {channel.name}
+
+
+ >
+ );
+
+}
diff --git a/app/routes/listen.tag.$tag.tsx b/app/routes/listen.tag.$tag.tsx
new file mode 100644
index 0000000..13ae53b
--- /dev/null
+++ b/app/routes/listen.tag.$tag.tsx
@@ -0,0 +1,40 @@
+import type { LoaderArgs } from "@remix-run/node";
+import { json } from "@remix-run/node";
+import { Link, useLoaderData } from "@remix-run/react";
+import { Breadcrumbs } from "~/components/breadcrumbs";
+import { StationsGallery } from "~/components/stations-gallery";
+import type { StationWithTags } from "~/models/station.server";
+import { findStationsByTags } from "~/models/station.server";
+import { findTagBySlug } from "~/models/tag.server";
+import { notFound } from "~/utils";
+
+
+export async function loader({ params }: LoaderArgs) {
+ if (!params.tag) {
+ throw notFound();
+ }
+
+ const tag = await findTagBySlug(params.tag);
+ if (!tag) {
+ throw notFound();
+ }
+
+ const stations: NonNullable[] = await findStationsByTags([tag.name]);
+
+ return json({ tag, stations });
+
+
+}
+
+export default function ListenTag() {
+ const { tag, stations } = useLoaderData();
+ return (
+ <>
+
+ Home
+ {tag.name}
+
+
+ >
+ );
+}
diff --git a/app/routes/listen.tsx b/app/routes/listen.tsx
new file mode 100644
index 0000000..bce6b09
--- /dev/null
+++ b/app/routes/listen.tsx
@@ -0,0 +1,8 @@
+import { Outlet } from "@remix-run/react";
+
+
+export default function ListenLayout() {
+ return (
+
+ );
+}