Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
60 changes: 60 additions & 0 deletions src/hooks/useFavoriteState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

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

export function useFavoriteState(initialFavorites: string[]) {
const router = useRouter();
const defaultValue = initialFavorites ? JSON.stringify(initialFavorites) : '[]';
const [_cookieValue, setCookieValue] = useCookie(FAVORITE_ARTIST_COOKIE_NAME, defaultValue);

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: 3650, // 10 years
SameSite: 'Lax',
});

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

const toggleFavorite = useCallback(
(artistId: string) => {
const updatedFavorites = new Set([...initialFavorites]);

if (updatedFavorites.has(artistId)) {
updatedFavorites.delete(artistId);
} else {
updatedFavorites.add(artistId);
}

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

return {
favorites,
toggleFavorite,
isFavorite,
};
}
2 changes: 2 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
// http://localhost:3823
export const API_DOMAIN = 'https://api.relisten.net';

export const FAVORITE_ARTIST_COOKIE_NAME = 'relisten_favorites:artists';
20 changes: 20 additions & 0 deletions src/lib/serverFavoriteCookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { cookies } from 'next/headers';
import { FAVORITE_ARTIST_COOKIE_NAME } from './constants';



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

try {
const value = cookieStore.get(FAVORITE_ARTIST_COOKIE_NAME)?.value;
if (value) {
return JSON.parse(value);
}
} catch (error) {
console.error('Error parsing favorites cookie on server:', error);
}

return [];
}