- UI
- Updated side nav to list tags
This commit is contained in:
parent
5d0a7623e3
commit
27cc0ddeb8
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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" }
|
||||
]
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
13
app/root.tsx
13
app/root.tsx
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user