- Updated side nav to list tags
This commit is contained in:
Luke Bunselmeyer 2023-05-07 16:21:15 -04:00
parent 5d0a7623e3
commit 27cc0ddeb8
7 changed files with 188 additions and 113 deletions

View File

@ -1,12 +1,16 @@
import { RadioIcon } from "@heroicons/react/24/solid";
import { NavLink } from "@remix-run/react";
import type { ReactNode } from "react";
import type { TagWithStationsClientSide } from "~/models/tag.server";
import type { UserWithFavoriteStationsClientSide } from "~/models/user.server";
export type PageLayoutProps = {
children: ReactNode;
tags: TagWithStationsClientSide[];
user?: UserWithFavoriteStationsClientSide
}
export function PageLayout({ children }: PageLayoutProps) {
export function PageLayout({ children, tags, user }: PageLayoutProps) {
return (
<div className="drawer drawer-mobile">
<input id="primary-drawer" type="checkbox" className="drawer-toggle" />
@ -26,10 +30,6 @@ export function PageLayout({ children }: PageLayoutProps) {
<h1 className="text-2xl p-0">Awesome Radio</h1>
</div>
<div className="flex-none hidden lg:block">
<ul className="menu menu-horizontal">
<li><a>Topnav Item 1</a></li>
<li><a>Topnav Item 2</a></li>
</ul>
</div>
</div>
<div className="py-2 px-6">
@ -49,12 +49,21 @@ export function PageLayout({ children }: PageLayoutProps) {
<li>
<NavLink to="/listen/home">Home</NavLink>
</li>
<li>
<NavLink to="/listen/channel/music">Music</NavLink>
<li className="menu-title">
<span>Tags</span>
</li>
<li>
<NavLink to="/listen/channel/news">News & Talk</NavLink>
{tags
.filter(tag => tag.stations.length > 0)
.map((tag) => {
return (
<li key={tag.slug}>
<NavLink to={`/listen/tag/${tag.slug}`} className="capitalize">
{tag.name}
<span className="badge badge-outline">{tag.stations?.length ?? 0}</span>
</NavLink>
</li>
);
})}
<li className="menu-title">
<span>Manage Content</span>
</li>

View File

@ -1,3 +1,4 @@
import { PlayIcon } from "@heroicons/react/24/solid";
import type { Tag } from "@prisma/client";
import { Link } from "@remix-run/react";
import type { StationWithTagsClientSide } from "~/models/station.server";
@ -40,7 +41,10 @@ export function StationsGallery({ stations, tag, channel }: StationsGalleryProps
</h2>
<p>{station.description}</p>
<div className="card-actions justify-end">
<Link to={getStationUrl(station.id)} className="btn btn-primary">Listen Now</Link>
<Link to={getStationUrl(station.id)} className="btn btn-primary gap-2">
<PlayIcon className="h-6 w-6" />
Listen Now
</Link>
</div>
</div>
</div>

View File

@ -28,6 +28,7 @@ export function getStations(reliability: number = 80) {
}
},
orderBy: [
{ reliability: "desc" },
{ popularity: "desc" }
]
});
@ -54,6 +55,7 @@ export function findStationsByTags(tags: string[], reliability: number = 80) {
}
},
orderBy: [
{ reliability: "desc" },
{ popularity: "desc" }
]
});

View File

@ -1,9 +1,53 @@
import type { Prisma } from "@prisma/client";
import { prisma } from "~/db.server";
import type { PrismaTxClient } from "~/models/station.server";
import type { ConvertDatesToStrings } from "~/utils";
import { slugify } from "~/utils";
export function findTagBySlug(slug: string) {
return prisma.tag.findUnique({ where: { slug } });
export type TagWithStations = NonNullable<Prisma.PromiseReturnType<typeof findTagBySlug>>;
export type TagWithStationsClientSide = ConvertDatesToStrings<TagWithStations>;
export function findTagBySlug(slug: string, reliability = 80) {
return prisma.tag.findUnique({
where: { slug },
include: {
stations: {
where: {
station: {
reliability: {
gte: reliability
}
}
},
include: {
station: true
}
}
}
});
}
export function getTags(reliability = 80) {
return prisma.tag.findMany({
include: {
stations: {
where: {
station: {
reliability: {
gte: reliability
}
}
},
include: {
station: true
}
}
},
orderBy: {
name: "asc"
}
});
}
export function upsertTagOnName(tag: string, p: PrismaTxClient = prisma) {

View File

@ -1,12 +1,22 @@
import type { Password, User } from "@prisma/client";
import type { Password, Prisma, User } from "@prisma/client";
import bcrypt from "bcryptjs";
import { prisma } from "~/db.server";
import type { ConvertDatesToStrings } from "~/utils";
export type { User } from "@prisma/client";
export type UserWithFavoriteStations = NonNullable<Prisma.PromiseReturnType<typeof getUserById>>;
export type UserWithFavoriteStationsClientSide = ConvertDatesToStrings<UserWithFavoriteStations>;
export async function getUserById(id: User["id"]) {
return prisma.user.findUnique({ where: { id } });
return prisma.user.findUnique({
where: { id },
include: {
favoriteStations: {
include: { station: true }
}
}
});
}
export async function getUserByEmail(email: User["email"]) {
@ -21,10 +31,10 @@ export async function createUser(email: User["email"], password: string) {
email,
password: {
create: {
hash: hashedPassword,
},
},
},
hash: hashedPassword
}
}
}
});
}
@ -39,8 +49,8 @@ export async function verifyLogin(
const userWithPassword = await prisma.user.findUnique({
where: { email },
include: {
password: true,
},
password: true
}
});
if (!userWithPassword || !userWithPassword.password) {

View File

@ -9,10 +9,12 @@ import {
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
useRouteError
} from "@remix-run/react";
import type { ReactNode } from "react";
import { PageLayout } from "~/components/page-layout";
import { getTags, TagWithStations } from "~/models/tag.server";
import { getUser } from "~/session.server";
import stylesheet from "~/tailwind.css";
@ -22,8 +24,10 @@ export const links: LinksFunction = () => [
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : [])
];
export const loader = async ({ request }: LoaderArgs) => {
return json({ user: await getUser(request) });
export async function loader({ request }: LoaderArgs) {
const tags: TagWithStations[] = await getTags();
const user = await getUser(request);
return json({ user, tags });
};
export type DocumentProps = {
@ -32,7 +36,7 @@ export type DocumentProps = {
}
function Document({ title, children }: DocumentProps) {
export function Document({ title, children }: DocumentProps) {
return (
<html lang="en" className="h-full">
<head>
@ -53,9 +57,10 @@ function Document({ title, children }: DocumentProps) {
}
export default function App() {
const { tags, user } = useLoaderData<typeof loader>();
return (
<Document>
<PageLayout>
<PageLayout tags={tags} user={user}>
<Outlet />
</PageLayout>
</Document>

View File

@ -1,7 +1,7 @@
import { createCookieSessionStorage, redirect } from "@remix-run/node";
import invariant from "tiny-invariant";
import type { User } from "~/models/user.server";
import type { User, UserWithFavoriteStations } from "~/models/user.server";
import { getUserById } from "~/models/user.server";
invariant(process.env.SESSION_SECRET, "SESSION_SECRET must be set");
@ -13,8 +13,8 @@ export const sessionStorage = createCookieSessionStorage({
path: "/",
sameSite: "lax",
secrets: [process.env.SESSION_SECRET],
secure: process.env.NODE_ENV === "production",
},
secure: process.env.NODE_ENV === "production"
}
});
const USER_SESSION_KEY = "userId";
@ -28,19 +28,20 @@ export async function getUserId(
request: Request
): Promise<User["id"] | undefined> {
const session = await getSession(request);
const userId = session.get(USER_SESSION_KEY);
return userId;
return session.get(USER_SESSION_KEY);
}
export async function getUser(request: Request) {
const userId = await getUserId(request);
if (userId === undefined) return null;
const user = await getUserById(userId);
if (user) return user;
if (!userId) {
return;
}
const user: UserWithFavoriteStations | null = await getUserById(userId);
if (!user) {
throw await logout(request);
}
return user;
}
export async function requireUserId(
request: Request,
@ -67,7 +68,7 @@ export async function createUserSession({
request,
userId,
remember,
redirectTo,
redirectTo
}: {
request: Request;
userId: string;
@ -81,9 +82,9 @@ export async function createUserSession({
"Set-Cookie": await sessionStorage.commitSession(session, {
maxAge: remember
? 60 * 60 * 24 * 7 // 7 days
: undefined,
}),
},
: undefined
})
}
});
}
@ -91,7 +92,7 @@ export async function logout(request: Request) {
const session = await getSession(request);
return redirect("/", {
headers: {
"Set-Cookie": await sessionStorage.destroySession(session),
},
"Set-Cookie": await sessionStorage.destroySession(session)
}
});
}