Skip to content
Merged
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
66 changes: 6 additions & 60 deletions webdev-prosjekt1/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,63 +17,6 @@ body {
overflow: hidden;
}

.main-header {
max-width: 1240px;
margin: 0 auto;
padding: 18px 48px 14px;
border-bottom: 2px solid rgba(212, 175, 55, 0.22);
}

.app-title {
color: var(--primary_color);
text-decoration: none;
display: inline-flex;
align-items: stretch;
position: relative;
padding-left: 70px;
}

.app-title-text-wrapper {
display: flex;
flex-direction: column;
}

.app-logo-wrapper {
position: absolute;
left: 0;
top: 0;

height: 100%;
margin: var(--margin);
}

.app-logo {
display: block;

height: 100%;
width: auto;

object-fit: contain;
}

.app-title-text {
font-size: 48px;
line-height: 0.95;
margin: var(--margin);
}

.header-note {
font-size: 13px;
margin: 4px 0 0;

font-weight: 400;
letter-spacing: 0.08em;

text-transform: uppercase;
white-space: nowrap;
text-align: center;
}

.search-section {
display: flex;
gap: 10px;
Expand Down Expand Up @@ -190,9 +133,12 @@ body {
align-items: start;
}

@media (max-width: 900px) {
.results-section {
grid-template-columns: 1fr;
@media (max-width: 700px) {
.search-controls {
width: calc(100% - 48px);
}

.hero-section {
width: calc(100% - 48px);
}
}
Expand Down
169 changes: 93 additions & 76 deletions webdev-prosjekt1/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,119 @@
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import './App.css';
import './variables.css';
import { Button } from './components/Button';
import { Searchbar } from './components/Searchbar';
import { SongDetailPage } from './components/SongDetailPage';
import { AlbumDetailPage } from './components/AlbumDetailPage';
import { ArtistDetailPage } from './components/ArtistDetailPage';
import { Header } from './components/Header';
import { TrackCard } from './components/TrackCard';
import { AlbumCard } from './components/AlbumCard';
import { ArtistCard } from './components/ArtistCard';
import type { Album, Artist, Track } from './api/types';
import { search } from './api/spotify';
import filterIcon from './assets/filter.png';
import musicWizImage from './assets/musicwiz.png';
import logoImage from './assets/music-wiz-logo.png';
import { useQuery } from '@tanstack/react-query';
import { search } from './api/spotify';

function App() {
const [query, setQuery] = useState('');
const hasSearched = query.trim().length > 0;

const handleSearch = (nextQuery: string) => {
setQuery(nextQuery);
const isSongDetailRoute = window.location.pathname === '/song-detail' || window.location.pathname === '/song-detail/';
const isAlbumDetailRoute = window.location.pathname === '/album-detail' || window.location.pathname === '/album-detail/';
const isArtistDetailRoute = window.location.pathname === '/artist-detail' || window.location.pathname === '/artist-detail/';
const dummyAlbumImage = {
url: musicWizImage,
height: 640,
width: 640,
};

const {
data: results,
isLoading,
isFetching,
isError,
error,
} = useQuery({
const dummyArtist: Artist = {
id: 1,
name: 'Artist Name',
type: 'artist',
href: 'https://api.spotify.com/v1/artists/artist-id',
uri: 'spotify:artist:artist-id',
images: [dummyAlbumImage],
};
const dummySong: Track = {
id: 'song-id',
name: 'Song Name',
type: 'track',
href: 'https://api.spotify.com/v1/tracks/song-id',
artists: [dummyArtist],
album: {
id: 'album-id',
name: 'Album Name',
album_type: 'album',
artists: [dummyArtist],
images: [dummyAlbumImage],
release_date: '2026',
release_date_precision: 'year',
total_tracks: 10,
uri: 'spotify:album:album-id',
},
duration_ms: 214000,
explicit: false,
track_number: 1,
external_ids: { isrc: 'DUMMY-ISRC' },
is_playable: true,
uri: 'spotify:track:song-id',
};
const dummyAlbum: Album = {
id: 'album-id',
name: 'Album Name',
album_type: 'album',
artists: [dummyArtist],
images: [dummyAlbumImage],
release_date: '2026',
release_date_precision: 'year',
total_tracks: 10,
uri: 'spotify:album:album-id',
};
const handleSearch = (nextQuery: string) => setQuery(nextQuery);
const { data: results, isLoading, isFetching, isError, error } = useQuery({
queryKey: ['search', query],
queryFn: () => search(query),
enabled: query.trim().length > 0,
});

console.log(results);
if (isAlbumDetailRoute) return <AlbumDetailPage album={dummyAlbum} />;
if (isArtistDetailRoute) return <ArtistDetailPage artist={dummyArtist} />;
if (isSongDetailRoute) return <SongDetailPage song={dummySong} />;

return (
<>
<main className='app-shell'>
<header className='main-header'>
<a className='app-title' href='/'>
<figure className='app-logo-wrapper'>
<img
className='app-logo'
src={logoImage}
alt=''
/>
</figure>
<span className='app-title-text-wrapper'>
<h1 className='app-title-text'>MUSIC WIZ</h1>
<h3 className='header-note'>Your personal music magician</h3>
</span>
</a>
</header>

<section className='search-section'>
<Searchbar onSearch={handleSearch} />
<Button content={<><img src={filterIcon} alt='' /> <span>Filter</span></>} />
<main className='app-shell'>
<Header />
<section className='search-section'>
<Searchbar onSearch={handleSearch} />
<Button content={<><img src={filterIcon} alt='' /> <span>Filter</span></>} />
</section>
{hasSearched ? (
<section className='results-section'>
<div className='results-group'>
<h2 className='results-heading'>Tracks</h2>
<ul className='card-list'>{results?.tracks.map((track) => <TrackCard key={track.id} track={track} />)}</ul>
</div>
<div className='results-group'>
<h2 className='results-heading'>Albums</h2>
<ul className='card-list'>{results?.albums.map((album) => <AlbumCard key={album.id} album={album} />)}</ul>
</div>
<div className='results-group'>
<h2 className='results-heading'>Artists</h2>
<ul className='card-list'>{results?.artists.map((artist) => <ArtistCard key={artist.id} artist={artist} />)}</ul>
</div>
</section>

{hasSearched ? (
<section className='results-section'>
<div className='results-group'>
<h2 className='results-heading'>Tracks</h2>
<ul className='card-list'>
{results?.tracks.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</ul>
</div>

<div className='results-group'>
<h2 className='results-heading'>Albums</h2>
<ul className='card-list'>
{results?.albums.map((album) => (
<AlbumCard key={album.id} album={album} />
))}
</ul>
</div>

<div className='results-group'>
<h2 className='results-heading'>Artists</h2>
<ul className='card-list'>
{results?.artists.map((artist) => (
<ArtistCard key={artist.id} artist={artist} />
))}
</ul>
</div>
</section>
) : (
<section className='hero-section'>
<figure className='hero-art'>
<div className='art-glow' />
<img src={musicWizImage} alt='A musical wizard' />
</figure>
</section>
)}
</main>
</>
) : (
<section className='hero-section'>
<figure className='hero-art'>
<div className='art-glow' />
<img src={musicWizImage} alt='A musical wizard' />
</figure>
</section>
)}
{(isLoading || isFetching) && <p>Loading...</p>}
{isError && <p>Search failed: {error instanceof Error ? error.message : 'Unknown error'}</p>}
</main>
);
}

Expand Down
Loading