Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
'@tailwindcss/postcss': {
darkMode: 'class',
},
},
};
28 changes: 14 additions & 14 deletions src/app/(content)/recently-played/RecentlyPlayed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ const fetchRecentlyPlayed = async (queryClient: QueryClient) => {
const LoadingSkeleton = () => (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="animate-pulse space-y-3 rounded-xl border border-gray-100 p-4">
<div className="h-4 rounded bg-gray-200"></div>
<div className="h-3 w-3/4 rounded bg-gray-200"></div>
<div key={i} className="animate-pulse space-y-3 rounded-xl border border-border-light p-4">
<div className="h-4 rounded bg-skeleton"></div>
<div className="h-3 w-3/4 rounded bg-skeleton"></div>
<div className="space-y-2">
<div className="h-2 w-1/2 rounded bg-gray-200"></div>
<div className="h-2 w-2/3 rounded bg-gray-200"></div>
<div className="h-2 w-1/2 rounded bg-skeleton"></div>
<div className="h-2 w-2/3 rounded bg-skeleton"></div>
</div>
</div>
))}
Expand All @@ -58,11 +58,11 @@ const EmptyState = () => (
animate={{ opacity: 1, y: 0 }}
className="flex flex-col items-center justify-center py-16 text-center"
>
<div className="mb-4 rounded-full bg-gray-50 p-4">
<Clock className="h-8 w-8 text-gray-400" />
<div className="mb-4 rounded-full bg-background-muted p-4">
<Clock className="h-8 w-8 text-foreground-muted" />
</div>
<h3 className="mb-2 text-lg font-medium text-gray-900">No recent activity</h3>
<p className="max-w-sm text-gray-500">
<h3 className="mb-2 text-lg font-medium text-foreground">No recent activity</h3>
<p className="max-w-sm text-foreground-muted">
Tracks will appear here as people listen to shows across the Relisten community.
</p>
</motion.div>
Expand All @@ -79,7 +79,7 @@ export default function RecentlyPlayed() {
const tracks = query.data ? uniqBy(query.data, keyFn).slice(0, 40) : [];

return (
<div className="min-h-screen bg-gray-50/30">
<div className="min-h-screen bg-background">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Header */}
<motion.div
Expand All @@ -88,12 +88,12 @@ export default function RecentlyPlayed() {
className="text-center"
>
<div className="mb-2 flex items-center justify-center gap-3">
<div className="rounded-full bg-green-100 p-2">
<Activity className="h-6 w-6 text-green-600" />
<div className="rounded-full bg-green-100 p-2 dark:bg-green-900/30">
<Activity className="h-6 w-6 text-green-600 dark:text-green-400" />
</div>
<h1 className="mb-0 text-3xl font-bold text-gray-900 sm:text-4xl">Recently Played</h1>
<h1 className="mb-0 text-3xl font-bold text-foreground sm:text-4xl">Recently Played</h1>
</div>
<p className="mx-auto mb-4 max-w-2xl text-gray-600">
<p className="mx-auto mb-4 max-w-2xl text-foreground-muted">
This is what people are listening to right now - join 'em.
</p>
</motion.div>
Expand Down
10 changes: 5 additions & 5 deletions src/app/(embed)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ export default async function EmbedLayout({ children }: { children: ReactNode })
);

return (
<Flex column className="h-screen bg-white">
<div className="bg-amber-500 text-center text-xs font-semibold tracking-wider">
<Flex column className="h-screen bg-surface">
<div className="bg-amber-500 text-center text-xs font-semibold tracking-wider dark:bg-amber-600">
POWERED BY{' '}
<a
href="https://relisten.net"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-amber-700"
className="underline hover:text-amber-700 dark:hover:text-amber-200"
>
RELISTEN.NET
</a>{' '}
Expand All @@ -31,12 +31,12 @@ export default async function EmbedLayout({ children }: { children: ReactNode })
href="https://phish.in"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-amber-700"
className="underline hover:text-amber-700 dark:hover:text-amber-200"
>
PHISH.IN
</a>
</div>
<div className="flex h-[50px] min-h-[50px] items-center justify-center border-b border-gray-300 bg-white">
<div className="flex h-[50px] min-h-[50px] items-center justify-center border-b border-border bg-surface">
<div className="w-full max-w-2xl">
<Player artistSlugsToName={artistSlugsToName} />
</div>
Expand Down
14 changes: 12 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import NextTopLoader from 'nextjs-toploader';
import dns from 'node:dns';
import React from 'react';
import Providers from './Providers';
import { getTheme } from '@/lib/theme';

// https://github.com/node-fetch/node-fetch/issues/1624#issuecomment-1407717012
dns.setDefaultResultOrder('ipv4first');
Expand All @@ -13,9 +14,18 @@ import '../styles/globals.css';
// TODO: figure out if we don't need any weights
const font = Roboto({ subsets: ['latin'], weight: ['400', '500', '700', '900'] });

export default function RootLayout({ children }: { children: React.ReactNode }) {
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const theme = await getTheme();

// Determine class names: if theme is explicitly set, use it; otherwise let CSS media query decide
const htmlClasses = theme === 'dark'
? 'dark bg-background'
: theme === 'light'
? 'light bg-background'
: 'bg-background';

return (
<html lang="en">
<html lang="en" className={htmlClasses}>
<head>
<link rel="icon" href="/favicon.ico" />
<meta name="apple-itunes-app" content="app-id=715886886" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/ColumnWithToggleControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const ColumnWithToggleControls = ({
)}
<Flex column className="flex-1 overflow-x-hidden overflow-y-auto">
{filteredCount !== undefined && totalCount !== undefined && filteredCount < totalCount && (
<div className="m-2 rounded border border-amber-500/20 bg-amber-500/10 p-2 text-xs text-amber-700">
<div className="m-2 rounded border border-amber-500/20 bg-amber-500/10 p-2 text-xs text-amber-700 dark:border-amber-700/30 dark:bg-amber-900/20 dark:text-amber-400">
{filteredCount === 0 ? (
<>
All {simplePluralize('row', hiddenRows)} are hidden by filters.{' '}
Expand Down
8 changes: 4 additions & 4 deletions src/components/LiveTrack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ export default function LiveTrack({
damping: 20,
stiffness: 300,
}}
className={`relative h-full rounded-xl border border-gray-100 bg-white p-4 shadow-sm transition-all duration-200 hover:border-gray-200 hover:shadow-lg ${
isLastSeen ? 'border-green-200 ring-2 ring-green-100' : ''
className={`relative h-full rounded-xl border border-border-light bg-surface p-4 shadow-sm transition-all duration-200 hover:border-border hover:shadow-lg ${
isLastSeen ? 'border-green-200 ring-2 ring-green-100 dark:border-green-700 dark:ring-green-900' : ''
}`}
data-is-last-seen={isLastSeen}
>
Expand All @@ -99,12 +99,12 @@ export default function LiveTrack({

<div className="space-y-1">
{/* Track title */}
<div className="truncate leading-tight font-semibold text-gray-900 transition-colors group-hover:text-gray-700">
<div className="truncate leading-tight font-semibold text-foreground transition-colors group-hover:text-foreground-muted">
{track.track.title}
</div>

{/* Artist name */}
<div className="text-sm font-medium text-gray-700">{track.source.artist?.name}</div>
<div className="text-sm font-medium text-foreground">{track.source.artist?.name}</div>

{/* Venue and date info */}
<div className="text-foreground-muted space-y-1 text-xs">
Expand Down
6 changes: 5 additions & 1 deletion src/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import Column from './Column';
import Row from './Row';
import ThemeToggle from './ThemeToggle';

// TODO: replace this with shadcn/radix

const Menu = () => (
<div className="mt-2 mr-2 w-[120px] rounded-sm border bg-white shadow-lg">
<div className="mt-2 mr-2 w-[120px] rounded-sm border bg-surface shadow-lg">
<Row href="/">Home</Row>
<Row href="/about">About</Row>
<Row href="/today">Today</Row>
<Row href="/recently-played">Live</Row>
<Row href="/sonos">Sonos</Row>
<Row href="/app">App</Row>
<Row href="/chat">Chat</Row>
<div className="flex items-center justify-center border-t border-border py-2">
<ThemeToggle />
</div>
</div>
);

Expand Down
6 changes: 5 additions & 1 deletion src/components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as Popover from '@/components/Popover';
import RelistenAPI from '@/lib/RelistenAPI';
import MainNavHeader from './MainNavHeader';
import AndroidUpgradeNotification from './AndroidUpgradeNotification';
import ThemeToggle from './ThemeToggle';
import { MenuIcon } from 'lucide-react';
import { headers } from 'next/headers';
import parser from 'ua-parser-js';
Expand Down Expand Up @@ -39,7 +40,7 @@ export default async function NavBar() {

return (
<>
<div className="navigation text-foreground relative flex h-[50px] max-h-[50px] min-h-[50px] grid-cols-3 justify-between border-b-[1px] border-b-[#aeaeae] bg-white px-4 lg:grid">
<div className="navigation text-foreground relative flex h-[50px] max-h-[50px] min-h-[50px] grid-cols-3 justify-between border-b-[1px] border-b-border bg-surface px-4 lg:grid">
<MainNavHeader
artistSlugsToName={artistSlugsToName}
indexOverride={isInIframe ? '/wsp' : undefined}
Expand Down Expand Up @@ -95,6 +96,9 @@ export default async function NavBar() {
ABOUT
</Link>
</div>
<div className="h-full">
<ThemeToggle />
</div>
</div>
</div>
{isAndroid && <AndroidUpgradeNotification />}
Expand Down
12 changes: 6 additions & 6 deletions src/components/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const Player = ({ artistSlugsToName }: Props) => {
<div>{durationToHHMMSS(playback.activeTrack.currentTime)}</div>
</div>
<Flex column className="justify-center pb-1">
<div className="song-title relative top-1 text-center text-[1em] text-gray-900">
<div className="song-title relative top-1 text-center text-[1em] text-foreground">
{activeTrack.title}
{false && (
<Flex className="text-foreground-muted absolute top-[2px] left-full ml-2 w-full items-center text-[0.8em]">
Expand Down Expand Up @@ -149,16 +149,16 @@ const Player = ({ artistSlugsToName }: Props) => {
</div>
</Flex>
<div
className="absolute bottom-0 left-0 z-1 h-1 w-full cursor-pointer bg-[#bcbcbc]"
className="absolute bottom-0 left-0 z-1 h-1 w-full cursor-pointer bg-progress-bg"
onClick={onProgressClick}
style={{ opacity: playback.activeTrack.currentTime < 0.1 ? 0.8 : 1 }}
>
<div
className="absolute bottom-0 left-0 h-1 bg-[#707070]"
className="absolute bottom-0 left-0 h-1 bg-progress-fg"
style={{ width: notchPosition ? notchPosition + 2 : 'auto' }}
/>
<div
className="absolute bottom-0 left-0 z-1 h-2 w-[3px] bg-black"
className="absolute bottom-0 left-0 z-1 h-2 w-[3px] bg-foreground"
style={{ transform: `translate(${notchPosition}px, 0)` }}
/>
</div>
Expand All @@ -167,11 +167,11 @@ const Player = ({ artistSlugsToName }: Props) => {
{activeTrack && (
<div className="volume-control">
<div
className="relative h-full w-[6px] cursor-pointer bg-[#0000001a]"
className="relative h-full w-[6px] cursor-pointer bg-progress-bg/20"
onClick={updateVolume}
>
<div
className="pointer-events-none absolute right-0 bottom-0 left-0 bg-[#707070]"
className="pointer-events-none absolute right-0 bottom-0 left-0 bg-progress-fg"
style={{
height: `${volume * 100}%`,
}}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const Row = ({
return (
<Link href={href ?? '/'} prefetch={false} onClick={onLinkClick} data-is-active={isActive}>
<Flex
className={cn('relisten-row relative min-h-[46px] items-stretch border-b border-gray-100', {
className={cn('relisten-row relative min-h-[46px] items-stretch border-b border-border-light', {
'opacity-70': isPending,
})}
// style={{ minHeight: height }}
Expand Down
2 changes: 1 addition & 1 deletion src/components/RowHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type RowHeaderProps = {

const RowHeader = ({ height, children }: RowHeaderProps) => (
<Flex
className="min-h-[28px] items-center justify-between border-b border-gray-200/50 bg-gray-100/80 px-2 text-xs font-medium text-gray-600"
className="min-h-[28px] items-center justify-between border-b border-border-light bg-background-muted px-2 text-xs font-medium text-foreground-muted"
style={{ minHeight: !children ? 16 : height }}
>
{children}
Expand Down
6 changes: 3 additions & 3 deletions src/components/RowLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type { JSX } from 'react';
const RowLoading = (): JSX.Element => (
<div className="content h-full animate-pulse">
<div className="w-1/2">
<div className="h-[1em] w-full bg-[#dddddd]" />
<div className="h-[1em] w-full bg-skeleton" />
</div>
<div className="w-2/12">
<div className="h-[0.7em] w-full bg-[#dddddd]" />
<div className="h-[0.7em] w-full bg-[#dddddd]" />
<div className="h-[0.7em] w-full bg-skeleton" />
<div className="h-[0.7em] w-full bg-skeleton" />
</div>
</div>
);
Expand Down
46 changes: 46 additions & 0 deletions src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

import { Moon, Sun } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { setTheme as setThemeCookie } from '@/lib/theme';

export default function ThemeToggle() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const router = useRouter();

useEffect(() => {
// Sync with current DOM state (set server-side)
const isDark = document.documentElement.classList.contains('dark');
setTheme(isDark ? 'dark' : 'light');
}, []);

const toggleTheme = async () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);

// Update DOM immediately for instant feedback
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
} else {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
}

// Update cookie and refresh server-side data
await setThemeCookie(newTheme);
router.refresh();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldnt be necessary (will cause some issues too) since you already are manually updating className

};

return (
<button
onClick={toggleTheme}
className="text-foreground-muted flex h-full items-center justify-center px-2 transition-colors hover:text-foreground active:relative active:top-[1px]"
aria-label="Toggle theme"
title={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
>
{theme === 'light' ? <Moon size={18} /> : <Sun size={18} />}
</button>
);
}
4 changes: 2 additions & 2 deletions src/components/TodayTrack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export default ({ day }: { day: Day }) => {

return (
<Link href={createURL(day)} prefetch={false}>
<Flex className="group relative w-full cursor-pointer px-6 py-4 transition-colors duration-200 hover:bg-black/5">
<Flex className="group relative w-full cursor-pointer px-6 py-4 transition-colors duration-200 hover:bg-background-muted">
<div className="group-hover:bg-relisten-400/20 absolute top-0 bottom-0 left-0 w-1 bg-transparent transition-colors duration-200"></div>
<div className="mr-6 w-28 flex-shrink-0">
<div className="text-relisten-700 font-semibold">{day.display_date}</div>
</div>
<div className="min-w-0 flex-1">
<div className="truncate font-medium text-gray-900">
<div className="truncate font-medium text-foreground">
{day.venue?.name || 'Unknown Venue'}
</div>
{day.venue?.location && (
Expand Down
23 changes: 23 additions & 0 deletions src/lib/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use server';

import { cookies } from 'next/headers';

const THEME_COOKIE_NAME = 'relisten_theme';

export type Theme = 'light' | 'dark';

export async function getTheme(): Promise<Theme | null> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will create a server action (since use server) which isn't necessary since its only called from the server.

setTheme can be a server action, but it should include validation (theme can only be light|dark)

const cookieStore = await cookies();
const themeCookie = cookieStore.get(THEME_COOKIE_NAME);
return (themeCookie?.value as Theme) || null;
}

export async function setTheme(theme: Theme) {
const cookieStore = await cookies();
cookieStore.set(THEME_COOKIE_NAME, theme, {
path: '/',
maxAge: 60 * 60 * 24 * 365, // 1 year
sameSite: 'lax',
});
}

Loading