replace icons, improve music
This commit is contained in:
parent
b88c258896
commit
2011d7a83e
@ -3,13 +3,20 @@ import Link from 'next/link';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
const BackButton: React.FC<{ href: string }> = ({ href }) => {
|
interface BackButtonProps {
|
||||||
|
href: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BackButton: React.FC<BackButtonProps> = ({ href, label = 'Back' }) => {
|
||||||
return (
|
return (
|
||||||
<Link href={href} legacyBehavior>
|
<Link
|
||||||
<a className="bg-gray-800 hover:bg-gray-700 text-white font-bold py-2 px-4 mt-4 rounded inline-flex items-center">
|
href={href}
|
||||||
|
className="inline-flex items-center px-4 py-2 mt-4 text-white bg-gray-800 rounded shadow-md transition-all duration-300 ease-in-out hover:bg-gray-700 hover:shadow-lg hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
|
||||||
|
aria-label={`Go back to ${label}`}
|
||||||
|
>
|
||||||
<FontAwesomeIcon icon={faArrowLeft} className="mr-2" />
|
<FontAwesomeIcon icon={faArrowLeft} className="mr-2" />
|
||||||
Back
|
{label}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
12
app/components/LoadingSpinner.tsx
Normal file
12
app/components/LoadingSpinner.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
const LoadingSpinner: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
|
||||||
|
<Loader2 className="w-12 h-12 text-white animate-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoadingSpinner;
|
||||||
|
|
@ -2,47 +2,44 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { Play, SkipBack, SkipForward, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { faPlay, faStepBackward, faStepForward, faArrowLeft, faArrowRight } from '@fortawesome/free-solid-svg-icons';
|
import LoadingSpinner from './LoadingSpinner';
|
||||||
|
|
||||||
export default function Home() {
|
interface Song {
|
||||||
const [timePeriod, setTimePeriod] = useState('2020s');
|
|
||||||
interface Song {
|
|
||||||
albumArt: string;
|
albumArt: string;
|
||||||
name: string;
|
name: string;
|
||||||
artist: string;
|
artist: string;
|
||||||
duration: string;
|
duration: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
}
|
}
|
||||||
const [songs, setSongs] = useState<Song[]>([]);
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
interface Period {
|
||||||
fetch('/data/music.json')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
interface Song {
|
|
||||||
albumArt: string;
|
|
||||||
name: string;
|
|
||||||
artist: string;
|
|
||||||
duration: string;
|
|
||||||
}
|
|
||||||
interface Period {
|
|
||||||
timePeriod: string;
|
timePeriod: string;
|
||||||
songs: Song[];
|
songs: Song[];
|
||||||
}
|
}
|
||||||
const selectedPeriod = data.find((period: Period) => period.timePeriod === timePeriod); const songsList = selectedPeriod ? selectedPeriod.songs : [];
|
|
||||||
setSongs(songsList);
|
export default function Home() {
|
||||||
setCurrentIndex(Math.floor(Math.random() * songsList.length));
|
const [timePeriod, setTimePeriod] = useState('Summer 2024');
|
||||||
});
|
const [songs, setSongs] = useState<Song[]>([]);
|
||||||
}, [timePeriod]);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectElement = document.getElementById('timePeriod');
|
setIsLoading(true);
|
||||||
if (selectElement) {
|
fetch('/data/music.json')
|
||||||
setTimePeriod((selectElement as HTMLSelectElement).value);
|
.then(response => response.json())
|
||||||
}
|
.then((data: Period[]) => {
|
||||||
}, []);
|
const selectedPeriod = data.find((period) => period.timePeriod === timePeriod);
|
||||||
|
const songsList = selectedPeriod ? selectedPeriod.songs : [];
|
||||||
|
setSongs(songsList);
|
||||||
|
setCurrentIndex(Math.floor(Math.random() * songsList.length));
|
||||||
|
setIsLoading(false);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching music data:', error);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, [timePeriod]);
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
setCurrentIndex((currentIndex + 1) % songs.length);
|
setCurrentIndex((currentIndex + 1) % songs.length);
|
||||||
@ -68,10 +65,12 @@ export default function Home() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{songs.length > 0 && (
|
{isLoading && <LoadingSpinner />}
|
||||||
|
|
||||||
|
{!isLoading && songs.length > 0 && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button onClick={handlePrevious} className="absolute left-0 top-1/2 transform -translate-y-1/2 text-gray-300">
|
<button onClick={handlePrevious} className="absolute left-0 top-1/2 transform -translate-y-1/2 text-gray-300">
|
||||||
<FontAwesomeIcon icon={faArrowLeft} size="2x" />
|
<ChevronLeft className="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
@ -87,19 +86,19 @@ export default function Home() {
|
|||||||
<p className="text-gray-300">{songs[currentIndex].duration}</p>
|
<p className="text-gray-300">{songs[currentIndex].duration}</p>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<button className="mr-4 text-gray-300">
|
<button className="mr-4 text-gray-300">
|
||||||
<FontAwesomeIcon icon={faStepBackward} size="2x" />
|
<SkipBack className="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
<button className="mr-4 text-gray-300" onClick={() => window.open(songs[currentIndex]?.link, '_blank')}>
|
<button className="mr-4 text-gray-300" onClick={() => window.open(songs[currentIndex]?.link, '_blank')}>
|
||||||
<FontAwesomeIcon icon={faPlay} size="2x" />
|
<Play className="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
<button className="text-gray-300">
|
<button className="text-gray-300">
|
||||||
<FontAwesomeIcon icon={faStepForward} size="2x" />
|
<SkipForward className="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={handleNext} className="absolute right-0 top-1/2 transform -translate-y-1/2 text-gray-300">
|
<button onClick={handleNext} className="absolute right-0 top-1/2 transform -translate-y-1/2 text-gray-300">
|
||||||
<FontAwesomeIcon icon={faArrowRight} size="2x" />
|
<ChevronRight className="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -107,3 +106,4 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,32 +1,33 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
import MusicInfoButton from './MusicInfoButton';
|
||||||
|
|
||||||
|
interface TimePeriod {
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timePeriods: TimePeriod[] = [
|
||||||
|
{ title: 'Late Summer 2024', slug: 'late-summer-2024' },
|
||||||
|
{ title: 'Early Summer 2024', slug: 'early-summer-2024' },
|
||||||
|
];
|
||||||
|
|
||||||
const MusicInfo: React.FC = () => {
|
const MusicInfo: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto text-center text-gray-200">
|
<div className="max-w-2xl mx-auto text-center text-gray-200">
|
||||||
<section className="mb-12">
|
{timePeriods.map((period) => (
|
||||||
<h2 className="text-2xl font-semibold mb-4">Late Summer 2024</h2>
|
<section key={period.slug} className="mb-12">
|
||||||
<div className="flex justify-center space-x-4">
|
<h2 className="text-2xl font-semibold mb-4">{period.title}</h2>
|
||||||
<Link href="/time-periods/late-summer-2024/what-was-going-on" legacyBehavior>
|
<div className="flex justify-center">
|
||||||
<a className="bg-gray-800 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
|
<MusicInfoButton
|
||||||
WHAT WAS GOING ON
|
href={`/time-periods/${period.slug}/what-was-going-on`}
|
||||||
</a>
|
label="WHAT WAS GOING ON"
|
||||||
</Link>
|
/>
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="mb-12">
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">Early Summer 2024</h2>
|
|
||||||
<div className="flex justify-center space-x-4">
|
|
||||||
<Link href="/time-periods/early-summer-2024/what-was-going-on" legacyBehavior>
|
|
||||||
<a className="bg-gray-800 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
|
|
||||||
WHAT WAS GOING ON
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MusicInfo;
|
export default MusicInfo;
|
||||||
|
|
||||||
|
20
app/components/MusicInfoButton.tsx
Normal file
20
app/components/MusicInfoButton.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
interface MusicInfoButtonProps {
|
||||||
|
href: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MusicInfoButton: React.FC<MusicInfoButtonProps> = ({ href, label }) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
className="inline-block bg-gray-800 text-white font-bold py-2 px-4 rounded shadow-md transition-all duration-300 ease-in-out hover:bg-gray-700 hover:shadow-lg hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MusicInfoButton;
|
@ -2,8 +2,28 @@ import type { NextConfig } from "next";
|
|||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
images: {
|
images: {
|
||||||
domains: ['lastfm.freetls.fastly.net', 'p0ntus.com', 'github-readme-stats.vercel.app'],
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'lastfm.freetls.fastly.net',
|
||||||
|
port: '',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'p0ntus.com',
|
||||||
|
port: '',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'github-readme-stats.vercel.app',
|
||||||
|
port: '',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"@vercel/analytics": "^1.4.1",
|
"@vercel/analytics": "^1.4.1",
|
||||||
"@vercel/speed-insights": "^1.1.0",
|
"@vercel/speed-insights": "^1.1.0",
|
||||||
"geist": "^1.3.1",
|
"geist": "^1.3.1",
|
||||||
|
"lucide-react": "^0.469.0",
|
||||||
"next": "15.1.3",
|
"next": "15.1.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
|
Reference in New Issue
Block a user