Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add search page #73

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
13 changes: 13 additions & 0 deletions src/app/(main)/(secondary)/search/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Search from '@/components/Search';

export default async function Page() {
return (
<div className="mx-auto w-full max-w-screen-md flex-1">
<Search />
nth-chile marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
}

export const metadata = {
title: 'Search',
};
16 changes: 12 additions & 4 deletions src/app/(main)/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import MainNavHeader from './MainNavHeader';
export default async function NavBar() {
const artists = await fetchArtists();

const artistSlugsToName = artists.reduce((memo, next) => {
memo[String(next.slug)] = next.name;
const artistSlugsToName = artists.reduce(
(memo, next) => {
memo[String(next.slug)] = next.name;

return memo;
}, {} as Record<string, string | undefined>);
return memo;
},
{} as Record<string, string | undefined>
);

return (
<div className="relative grid h-[50px] max-h-[50px] min-h-[50px] grid-cols-3 justify-between border-b-[1px] border-b-[#aeaeae] bg-white text-[#333333] max-lg:flex">
Expand All @@ -29,6 +32,11 @@ export default async function NavBar() {
</Flex>
</SimplePopover>
<div className="nav hidden h-full flex-[2] cursor-pointer items-center justify-end text-center font-medium lg:flex">
<div className="h-full px-1">
<Link href="/search" legacyBehavior prefetch={false}>
<a className="nav-btn">SEARCH</a>
</Link>
</div>
Copy link
Author

Choose a reason for hiding this comment

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

Running out of room on the nav. This goes behind the player

<div className="h-full px-1">
<Link href="/today" legacyBehavior prefetch={false}>
<a className="nav-btn">TIH</a>
Expand Down
102 changes: 102 additions & 0 deletions src/components/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use client';

import { useState } from 'react';
import useDebouncedEffect from '../lib/useDebouncedEffect';
import { API_DOMAIN } from '../lib/constants';
import { SearchResuts } from '../types';
import Column from './Column';
import Row from './Row';

async function fetchResults(query) {
const data: SearchResuts = await fetch(`${API_DOMAIN}/api/v2/search?q=${query}`, {
cache: 'no-cache', // seconds
}).then((res) => res.json());

return data;
}

const Search = () => {
const [data, setData] = useState<SearchResuts | null>(null);
const [activeFilter, setActiveFilter] = useState<string>('artists');
const [searchValue, setSearchValue] = useState<string>('');

useDebouncedEffect(
() => {
// TODO: sanitize, trim ...
Copy link
Author

Choose a reason for hiding this comment

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

Prepare the string so it's always a valid param

if (searchValue) {
fetchResults(searchValue).then(setData);
} else {
setData(null);
}
},
200,
[searchValue]
);

return (
<div className="w-screen max-w-screen-md">
<div className="search-bar mb-2 flex items-center p-2">
<i className="fa fa-search px-2" />
<input
className="grow"
type="text"
placeholder="Search..."
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
/>
<button onClick={() => setSearchValue('')} aria-label="clear search" className="flex">
<i className="fa fa-times px-2" />
</button>
</div>
<ul className="search-filters pb-4">
<li>
<button
onClick={() => setActiveFilter('artists')}
className={activeFilter === 'artists' ? 'search-filters-button--active' : ''}
>
Artists
</button>
</li>
<li>
<button
onClick={() => setActiveFilter('songs')}
className={activeFilter === 'songs' ? 'search-filters-button--active' : ''}
>
Songs
</button>
</li>
</ul>
<Column loading={!data}>
{activeFilter === 'artists' &&
data?.Artists?.map(({ name, uuid, slug, show_count, source_count }) => {
return (
<Row key={uuid} href={`/${slug}`}>
<div>
<div>{name}</div>
</div>
<div className="min-w-[20%] text-right text-xs text-[#979797]">
<div>ARTIST</div>
</div>
</Row>
);
})}
{activeFilter === 'songs' &&
data?.Songs?.map(({ name, uuid, slim_artist }) => (
<Row
key={uuid}
// TODO: more data needed for song link
href={slim_artist ? `/${slim_artist.slug}` : undefined}
>
<div>
<div>{name}</div>
<div className="text-xxs text-gray-400">{slim_artist?.name}</div>
</div>
<div className="min-w-[20%] text-right text-xxs text-gray-400">SONG</div>
</Row>
))}
</Column>
</div>
);
};

export default Search;
19 changes: 19 additions & 0 deletions src/lib/useDebouncedEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useRef } from 'react';

export default function useDebouncedEffect(effect, delay, dependencies) {
const effectRef = useRef(effect);

useEffect(() => {
effectRef.current = effect;
}, [effect]);

useEffect(() => {
const handler = setTimeout(() => {
effectRef.current();
}, delay);

return () => {
clearTimeout(handler);
};
}, [...dependencies, delay]);
}
13 changes: 13 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ export const simplePluralize = (str: string, count = 0): string => {
return `${count?.toLocaleString()} ${count === 1 ? str : str + 's'}`;
};

/** example input and output:
*
* [ { id: 1, category: 'A' }, { id: 2, category: 'B' }, { id: 3, category: 'A' } ]
*
* {
* A: [
* { id: 1, category: 'A' },
* { id: 3, category: 'A' }
* ],
* B: [ { id: 2, category: 'B' } ]
* }
*
*/
export const groupBy = function (xs, key) {
return xs.reduce((rv, x) => {
(rv[x[key]] = rv[x[key]] || []).push(x);
Expand Down
24 changes: 24 additions & 0 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@
.button {
@apply rounded bg-green-400 p-4 py-2 border border-green-100 my-4 inline-block font-medium text-black/70 tracking-wide;
}

.search-bar {
@apply rounded-full border border-gray-400;
}

.search-bar:focus-within {
@apply border-gray-600;
}

.search-bar > input:focus {
outline: none;
}

.search-filters > li {
@apply inline-block;
}

.search-filters > li > button {
@apply rounded-full px-2 py-1;
}

.search-filters > li > button.search-filters-button--active {
@apply bg-relisten-100 text-white;
}
}

body { margin: 0; font-family: Roboto, Helvetica, Helvetica Neue, sans-serif; -webkit-font-smoothing: antialiased; color: #333; }
Expand Down
24 changes: 24 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,21 @@ export type Playback = {
activeTrack?: ActiveTrack;
};

export type Song = {
artist_id?: number;
artist_uuid?: string;
created_at?: string;
id: number;
name?: string;
shows_played_at?: number;
slim_artist?: Artist;
slug?: string;
sortName?: string;
updated_at?: string;
upstream_identifier?: string;
uuid?: string;
};

export type ActiveTrack = {
currentTime?: number;
duration?: number;
Expand All @@ -345,3 +360,12 @@ export type ActiveTrack = {
playbackType?: string;
webAudioLoadingState?: string;
};

export type SearchResuts = {
Artists: Artist[];
Shows: Show[];
Songs: Song[];
Sources: Source[];
Tours: Tour[];
Venues: Venue[];
};