Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
12 changes: 10 additions & 2 deletions src/components/ArtistsColumn.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import RelistenAPI from '@/lib/RelistenAPI';
import { getServerFilters } from '@/lib/serverFilterCookies';
import ArtistsColumnWithControls from './ArtistsColumnWithControls';
import { getServerFavorites } from '@/lib/serverFavoriteCookies';

const ArtistsColumn = async () => {
const [artists, initialFilters] = await Promise.all([
const [artists, initialFilters, initialFavorites] = await Promise.all([
RelistenAPI.fetchArtists(),
getServerFilters('root', true),
getServerFavorites(),
]);

return <ArtistsColumnWithControls artists={artists} initialFilters={initialFilters} />;
return (
<ArtistsColumnWithControls
artists={artists}
initialFilters={initialFilters}
initialFavorites={initialFavorites}
/>
);
};

export default ArtistsColumn;
59 changes: 42 additions & 17 deletions src/components/ArtistsColumnWithControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,31 @@ import { FilterState } from '@/lib/filterCookies';
import ColumnWithToggleControls from './ColumnWithToggleControls';
import Row from './Row';
import RowHeader from './RowHeader';
import { useFavoriteState } from '@/hooks/useFavoriteState';

const byObject = {
phish: 'Phish.in',
};

const artistGroups = {
0: 'Bands',
1: 'Featured',
2: 'Favorites',
};

type ArtistsColumnWithControlsProps = {
artists: Artist[];
initialFilters?: FilterState;
initialFavorites: string[];
};

const ArtistsColumnWithControls = ({ artists, initialFilters }: ArtistsColumnWithControlsProps) => {
const ArtistsColumnWithControls = ({
artists,
initialFilters,
initialFavorites,
}: ArtistsColumnWithControlsProps) => {
const { alphaAsc, toggleFilter, clearFilters } = useFilterState(initialFilters, 'root');
const { favorites } = useFavoriteState(initialFavorites);

const toggles = [
{
Expand All @@ -31,24 +44,36 @@ const ArtistsColumnWithControls = ({ artists, initialFilters }: ArtistsColumnWit
];

const processedArtists = useMemo(() => {
const grouped = groupBy(artists, 'featured');
const sortedGroups = Object.entries(grouped).sort(([a], [b]) => b.localeCompare(a));

return sortedGroups.map(([type, groupArtists]) => {
const sorted = [...groupArtists];
try {
const favoritesGroup = artists.filter(
(artist) => artist.uuid && favorites.includes(artist.uuid)
);

// Apply alphabetical sorting (default is desc/A-Z when no filter set)
if (alphaAsc) {
// Z-A (ascending)
sorted.sort((a, b) => (b.name || '').localeCompare(a.name || ''));
} else {
// Default: A-Z (descending)
sorted.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
const grouped = groupBy(artists, 'featured');
if (favoritesGroup.length) {
grouped[2] = favoritesGroup;
}

return [type, sorted] as [string, Artist[]];
});
}, [artists, alphaAsc]);
const sortedGroups = Object.entries(grouped).sort(([a], [b]) => b.localeCompare(a));
return sortedGroups.map(([type, groupArtists]) => {
const sorted = [...groupArtists];

// Apply alphabetical sorting (default is desc/A-Z when no filter set)
if (alphaAsc) {
// Z-A (ascending)
sorted.sort((a, b) => (b.name || '').localeCompare(a.name || ''));
} else {
// Default: A-Z (descending)
sorted.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
}

return [type, sorted] as [string, Artist[]];
});
} catch (error) {
console.error('Error processing artists:', error);
return [];
}
}, [artists, alphaAsc, favorites]);

const totalArtistCount = artists.length;
const filteredArtistCount = processedArtists.reduce(
Expand All @@ -65,7 +90,7 @@ const ArtistsColumnWithControls = ({ artists, initialFilters }: ArtistsColumnWit
onClearFilters={clearFilters}
>
{processedArtists.map(([type, groupArtists]) => [
<RowHeader key={`header-${type}`}>{type === '1' ? 'Featured' : 'Bands'}</RowHeader>,
<RowHeader key={`header-${type}`}>{artistGroups[type]}</RowHeader>,
...groupArtists.map((artist: Artist, idx: number) => (
<Row
key={[idx, artist.id].join(':')}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ColumnWithToggleControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Scroller from './Scroller';
import { simplePluralize } from '@/lib/utils';

type ToggleConfig = {
type: 'sort' | 'filter';
type: 'sort' | 'filter' | 'favorite';
isActive: boolean;
onToggle: () => void;
title: string;
Expand Down
6 changes: 5 additions & 1 deletion src/components/YearsColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { getServerFilters } from '@/lib/serverFilterCookies';
import YearsColumnWithControls from './YearsColumnWithControls';
import TodayInHistoryRow from './TodayInHistoryRow';
import RecentTapesRow from './RecentTapesRow';
import { getServerFavorites } from '@/lib/serverFavoriteCookies';

const YearsColumn = async ({ artistSlug }: Pick<RawParams, 'artistSlug'>) => {
const [artists, artistYears, initialFilters] = await Promise.all([
const [artists, artistYears, initialFilters, initialFavorites] = await Promise.all([
RelistenAPI.fetchArtists(),
RelistenAPI.fetchYears(artistSlug),
getServerFilters(artistSlug || '', true),
getServerFavorites(),
]).catch(() => {
notFound();
});
Expand All @@ -22,7 +24,9 @@ const YearsColumn = async ({ artistSlug }: Pick<RawParams, 'artistSlug'>) => {
artistSlug={artistSlug}
artistName={artist?.name}
artistYears={artistYears}
artistId={artist?.uuid}
initialFilters={initialFilters}
initialFavorites={initialFavorites}
>
<TodayInHistoryRow artistSlug={artistSlug} />
<RecentTapesRow artistSlug={artistSlug} />
Expand Down
16 changes: 16 additions & 0 deletions src/components/YearsColumnWithControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,49 @@ import sortActiveBands from '../lib/sortActiveBands';
import { simplePluralize } from '../lib/utils';
import ColumnWithToggleControls from './ColumnWithToggleControls';
import Row from './Row';
import { Heart } from 'lucide-react';
import { useFavoriteState } from '@/hooks/useFavoriteState';
import cn from '@/lib/cn';

type YearsColumnWithControlsProps = {
artistSlug?: string;
artistName?: string;
artistYears: Year[];
artistId?: string;
initialFilters?: FilterState;
initialFavorites: string[];
} & PropsWithChildren;

const YearsColumnWithControls = ({
artistSlug,
artistName,
artistYears,
artistId,
children,
initialFilters,
initialFavorites,
}: YearsColumnWithControlsProps) => {
const { dateAsc, sbdOnly, toggleFilter, clearFilters } = useFilterState(
initialFilters,
artistSlug
);

const { toggleFavorite, isFavorite } = useFavoriteState(initialFavorites);

const toggles = [
{
type: 'sort' as const,
isActive: dateAsc, // Show as active when oldest first (ascending)
onToggle: () => toggleFilter('date'),
title: !dateAsc ? 'Newest First' : 'Oldest First',
},
{
type: 'favorite' as const,
isActive: isFavorite(artistId!),
onToggle: () => toggleFavorite(artistId!),
title: isFavorite(artistId!) ? 'Unfavorite' : 'Favorite',
icon: <Heart size={20} className={cn(isFavorite(artistId!) ? 'fill-white' : 'text-white')} />,
},
];

const processedYears = useMemo(() => {
Expand Down
59 changes: 59 additions & 0 deletions src/hooks/useFavoriteState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client';

import { useCallback, useMemo } from 'react';
import useCookie from 'react-use-cookie';
import { useRouter } from 'next/navigation';

export function useFavoriteState(initialFavorites: string[]) {
const router = useRouter();
const defaultValue = initialFavorites ? JSON.stringify(initialFavorites) : '[]';
const [cookieValue, setCookieValue] = useCookie('relisten_favorites:artists', defaultValue);
Copy link
Author

Choose a reason for hiding this comment

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

Had a really hard time DRYing the relisten_favorites:artists variable to reuse in src/lib/serverFavoriteCookies.ts. It kept saying the export was a function?

Also open to changing naming convention. Figure it was worth scoping to artist so a different cookie name can be used for tracks.


const favorites = useMemo(() => {
try {
return initialFavorites;
} catch {
return [] as string[];
}
}, [initialFavorites]);

const isFavorite = useCallback(
(artistId: string) => {
return favorites.includes(artistId);
},
[favorites]
);

const setFavorites = useCallback(
(updatedFavorites: string[]) => {
setCookieValue(JSON.stringify(updatedFavorites), {
days: 365,
SameSite: 'Lax',
});

router.refresh();
},
[setCookieValue, router]
);

const toggleFavorite = useCallback(
(artistId: string) => {
let updatedFavorites = [...favorites];

if (favorites.includes(artistId)) {
updatedFavorites = updatedFavorites.filter((favorite) => favorite !== artistId);
} else {
updatedFavorites.push(artistId);
}

setFavorites([...updatedFavorites]);
},
[favorites, setFavorites]
);

return {
favorites,
toggleFavorite,
isFavorite,
};
}
17 changes: 17 additions & 0 deletions src/lib/serverFavoriteCookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { cookies } from 'next/headers';

// Server-side function to read filter cookies
export async function getServerFavorites(): Promise<string[]> {
const cookieStore = await cookies();

try {
const value = cookieStore.get('relisten_favorites:artists')?.value;
Copy link
Member

Choose a reason for hiding this comment

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

can you export the cookie key so its declared once and imported?

Copy link
Author

Choose a reason for hiding this comment

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

Yup. I added to src/lib/constants.ts b/c was getting some weird behavior importing/exporting between client and server components.

if (value) {
return JSON.parse(value);
}
} catch (error) {
console.error('Error parsing favorites cookie on server:', error);
}

return [];
}