- Moved source routes under `/listen` and required user to access
  - Fixed player color
  - Fixed page title
  - Updated favicon
This commit is contained in:
Luke Bunselmeyer 2023-05-07 22:54:00 -04:00
parent 53dc70f8d5
commit e7464e6299
16 changed files with 113 additions and 72 deletions

View File

@ -1,7 +1,6 @@
import { RadioIcon } from "@heroicons/react/24/solid"; import { RadioIcon, UserCircleIcon } from "@heroicons/react/24/solid";
import { Link, NavLink } from "@remix-run/react"; import { Link, NavLink } from "@remix-run/react";
import type { RemixLinkProps } from "@remix-run/react/dist/components"; import type { RemixLinkProps, RemixNavLinkProps } from "@remix-run/react/dist/components";
import { RemixNavLinkProps } from "@remix-run/react/dist/components";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import * as React from "react"; import * as React from "react";
import { createContext, useContext } from "react"; import { createContext, useContext } from "react";
@ -39,6 +38,26 @@ export function ListenNavLink(props: RemixNavLinkProps & React.RefAttributes<HTM
return <NavLink {...props} to={url}>{props.children}</NavLink>; return <NavLink {...props} to={url}>{props.children}</NavLink>;
} }
export type ManageContentNavProps = {
user?: UserWithFavoriteStationsClientSide;
};
export function ManageContentNav({ user }: ManageContentNavProps) {
if (!user) {
return <></>;
}
return (
<>
<li className="menu-title">
<span>Manage Content</span>
</li>
<li>
<NavLink to="/listen/sources">Sources</NavLink>
</li>
</>
);
}
export function PageLayout({ children, tags, user, station }: PageLayoutProps) { export function PageLayout({ children, tags, user, station }: PageLayoutProps) {
return ( return (
@ -64,12 +83,10 @@ export function PageLayout({ children, tags, user, station }: PageLayoutProps) {
{user ? {user ?
<div className="dropdown dropdown-end"> <div className="dropdown dropdown-end">
<label tabIndex={0} className="btn btn-ghost btn-circle avatar placeholder"> <label tabIndex={0} className="btn btn-ghost btn-circle avatar placeholder">
<div className="bg-neutral-focus text-neutral-content rounded-full w-12"> <UserCircleIcon className="w-8 h-8" />
<span>LB</span>
</div>
</label> </label>
<ul tabIndex={0} <ul tabIndex={0}
className="menu menu-compact dropdown-content mt-3 p-2 shadow bg-base-100 rounded-box w-52"> className="menu menu-compact dropdown-content mt-3 p-2 shadow bg-base-200 rounded-box w-30">
<li> <li>
<Link to="/logout">Logout</Link> <Link to="/logout">Logout</Link>
</li> </li>
@ -112,13 +129,7 @@ export function PageLayout({ children, tags, user, station }: PageLayoutProps) {
</li> </li>
); );
})} })}
<li className="menu-title"> <ManageContentNav user={user} />
<span>Manage Content</span>
</li>
<li>
<NavLink to="/sources">Sources</NavLink>
</li>
</ul> </ul>
</div> </div>

View File

@ -10,8 +10,7 @@ export function StationPlayer({ station }: StationPlayerProps) {
} }
return ( return (
<div <div
className="fixed bottom-0 left-0 w-full h-[70px] px-4 py-2 z-50 flex justify-end content-center items-center gap-2" className="fixed bottom-0 left-0 w-full h-[70px] px-4 py-2 z-50 flex justify-end content-center items-center gap-2 bg-accent text-accent-content">
data-theme="aqua">
<h3 className="text-xl">Now Playing: <strong>{station.name}</strong></h3> <h3 className="text-xl">Now Playing: <strong>{station.name}</strong></h3>
<audio controls autoPlay src={station.streamUrl}> <audio controls autoPlay src={station.streamUrl}>

View File

@ -1,5 +1,6 @@
import { cssBundleHref } from "@remix-run/css-bundle"; import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node"; import type { LinksFunction } from "@remix-run/node";
import { V2_MetaFunction } from "@remix-run/node";
import { import {
isRouteErrorResponse, isRouteErrorResponse,
Links, Links,
@ -18,6 +19,8 @@ export const links: LinksFunction = () => [
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []) ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : [])
]; ];
export const meta: V2_MetaFunction = () => [{ title: "Awesome Radio" }];
export type DocumentProps = { export type DocumentProps = {
children: ReactNode; children: ReactNode;
title?: string; title?: string;

View File

@ -1,7 +1,3 @@
import type { V2_MetaFunction } from "@remix-run/node";
export const meta: V2_MetaFunction = () => [{ title: "Awesome Radio" }];
export default function Index() { export default function Index() {
return ( return (
<div className="hero bg-base-200"> <div className="hero bg-base-200">

View File

@ -3,22 +3,27 @@ import type { ActionArgs, LoaderArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node"; import { json, redirect } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react"; import { Link, useLoaderData } from "@remix-run/react";
import { Breadcrumbs } from "~/components/breadcrumbs"; import { Breadcrumbs } from "~/components/breadcrumbs";
import { importSource } from "~/lib/importer.server";
import { getSource, saveSource } from "~/models/source.server"; import { getSource, saveSource } from "~/models/source.server";
import { requireUser } from "~/session.server";
import { notFound } from "~/utils"; import { notFound } from "~/utils";
export async function action({ request, params }: ActionArgs) { export async function action({ request }: ActionArgs) {
await requireUser(request);
const formData = await request.formData(); const formData = await request.formData();
const id = formData.get("id") as string; const id = formData.get("id") as string;
const name = formData.get("name") as string; const name = formData.get("name") as string;
const type = formData.get("type") as string; const type = formData.get("type") as string;
const description = formData.get("description") as string; const description = formData.get("description") as string;
const connectionUrl = formData.get("connectionUrl") as string; const connectionUrl = formData.get("connectionUrl") as string;
await saveSource({ id, name, description, type, connectionUrl }); const source = await saveSource({ id, name, description, type, connectionUrl });
return redirect("/sources"); await importSource(source);
return redirect("/listen/sources");
} }
export async function loader({ params }: LoaderArgs) { export async function loader({ params, request }: LoaderArgs) {
await requireUser(request);
const { id } = params; const { id } = params;
if (!id) { if (!id) {
throw notFound(); throw notFound();
@ -39,8 +44,8 @@ export default function SourcePage() {
return ( return (
<> <>
<Breadcrumbs> <Breadcrumbs>
<Link to="/sources">Sources</Link> <Link to="/listen/sources">Sources</Link>
<Link to={`/sources/${source.id}`}>{source.name ?? "New Source"}</Link> <Link to={`/listen/sources/${source.id}`}>{source.name ?? "New Source"}</Link>
</Breadcrumbs> </Breadcrumbs>
<form method="post"> <form method="post">
<input type="hidden" name="id" value={source.id} /> <input type="hidden" name="id" value={source.id} />
@ -78,7 +83,7 @@ export default function SourcePage() {
defaultValue={source.type} /> defaultValue={source.type} />
</div> </div>
<div className="form-control w-full max-w-lg mt-3"> <div className="form-control w-full max-w-lg mt-3">
<button type="submit" className="btn btn-primary">Save</button> <button type="submit" className="btn btn-primary">Import Stations</button>
</div> </div>
</form> </form>

View File

@ -0,0 +1,54 @@
import { PlusSmallIcon } from "@heroicons/react/24/solid";
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 { getSources } from "~/models/source.server";
import { requireUser } from "~/session.server";
export async function loader({ request }: LoaderArgs) {
await requireUser(request);
const sources = await getSources();
return json({ sources });
}
export default function SourceIndex() {
const { sources } = useLoaderData<typeof loader>();
return (
<>
<Breadcrumbs>
<Link to="/listen/sources">Sources</Link>
<Link to="/listen/sources/new">
<PlusSmallIcon className="w-4 h-4 mr-2 stroke-current" />
Add Source
</Link>
</Breadcrumbs>
<table className="table table-compact w-full">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Type</th>
<th>Connection URL</th>
</tr>
</thead>
<tbody>
{sources.map(source => {
return (
<tr key={source.id} className="hover">
<td><Link className="underline" to={"/listen/sources/" + source.id}>{source.name}</Link>
</td>
<td>{source.description}</td>
<td>{source.type}</td>
<td>{source.connectionUrl}</td>
</tr>
);
})}
</tbody>
</table>
</>
);
}

View File

@ -0,0 +1,14 @@
import type { LoaderArgs } from "@remix-run/node";
import { Outlet } from "@remix-run/react";
import { requireUser } from "~/session.server";
export async function loader({ request }: LoaderArgs) {
await requireUser(request);
return null;
}
export default function SourceLayout() {
return (
<Outlet />
);
}

View File

@ -1,34 +0,0 @@
import { PlusSmallIcon } from "@heroicons/react/24/solid";
import { json } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
import { Breadcrumbs } from "~/components/breadcrumbs";
import { getSources } from "~/models/source.server";
export async function loader() {
const sources = await getSources();
return json({ sources });
}
export default function SourceIndex() {
const { sources } = useLoaderData<typeof loader>();
return (
<>
<Breadcrumbs>
<Link to="/sources">Sources</Link>
<Link to="/sources/new">
<PlusSmallIcon className="w-4 h-4 mr-2 stroke-current" />
Add Source
</Link>
</Breadcrumbs>
<ul className="menu bg-base-100 w-56">
{sources.map(source => {
return (
<li key={source.id}><Link to={"/sources/" + source.id}>{source.name}</Link></li>
);
})}
</ul>
</>
);
}

View File

@ -1,7 +0,0 @@
import { Outlet } from "@remix-run/react";
export default function SourceLayout() {
return (
<Outlet />
);
}

View File

@ -57,12 +57,12 @@ export async function requireUserId(
export async function requireUser(request: Request) { export async function requireUser(request: Request) {
const userId = await requireUserId(request); const userId = await requireUserId(request);
const user = await getUserById(userId); const user = await getUserById(userId);
if (user) return user; if (!user) {
throw await logout(request); throw await logout(request);
} }
return user;
}
export async function createUserSession({ export async function createUserSession({
request, request,

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 15 KiB