Merge pull request 'Reimplement Last.fm, minor design improvements' (#2) from aidan/my-website:main into main

Reviewed-on: #2
This commit is contained in:
GivFNZ 2025-04-23 18:55:49 +00:00
commit 663bc1c6bd
9 changed files with 123 additions and 68 deletions

View File

@ -29,28 +29,13 @@ Any contribution is welcome. If you want to test the website in your own machine
- `bun install` - Install dependencies
- `bun dev` - Run dev server
The development server will be available at http://localhost:5173.
### Environment Variables
**The LastFM feature is disabled, and this environment variable is not required yet.**
To run the project, you need to set up the following environment variables:
| Variable | Description |
|-----------------------------|--------------------------|
| `VITE_LASTFM_API_KEY` | Your [Last.fm API Key]() |
Create a `.env` file in the root of the project and add the required variables like so:
```env
VITE_LASTFM_API_KEY=your-lastfm-api-key-here
```
The development server will be available at <http://localhost:5173>.
## Special Thanks
- [Aidan](https://github.com/ihatenodejs)
- [lucmsilva](https://github.com/lucmsilva651/)
- NineTailedFox
- [biancarosa](https://github.com/biancarosa) - Last.fm API
**Enjoy!**

View File

@ -1,45 +1,61 @@
import { api } from "../lib/axios";
const api_key = null;
export interface TrackResponse {
track: {
album: {
mbid: string,
"#text": string
},
artist: {
mbid: string,
"#text": string
},
date: {
uts: string,
"#text": string
},
image: [{
"#text": string,
size: string
}],
mbid: string,
name: string,
streamable: string,
url: string
}
}
export interface getRecentTracksResponse {
recenttracks: {
track: [
{
artist: {
mbid: string,
"#text": string
},
streamable: string,
image: [],
track: [{
artist: {
mbid: string,
name: string,
url: string,
date: {
uts: string,
"#text": string
}
"#text": string
},
streamable: string,
image: [{
"#text": string,
size: string
}],
mbid: string,
name: string,
url: string,
date: {
uts: string,
"#text": string
}
],
"@attr": {
user: string,
totalPages: string,
page: string,
perPage: string,
total: string
}
}]
}
}
export async function getRecentTracks() {
const response = await api.get<getRecentTracksResponse>('/', {
params: {
api_key,
method: 'user.getrecenttracks',
user: 'givfnz',
format: 'json',
limit: 1
}
})
return response.data
try {
const response = await api.get<TrackResponse>('');
console.log('Last.fm data:', response.data);
return response.data;
} catch (error) {
console.error('Err while fetching Last.fm data:', error);
throw error;
}
}

View File

@ -1,11 +1,13 @@
import styled from "styled-components";
{/* this is misaligned */}
export const Drawer = styled.button`
all: unset;
color: white;
cursor: pointer;
transition: color 0.3s;
display: none;
font-size: 1.5rem;
&:hover {
color: ${props => props.theme.cream}
@ -63,4 +65,23 @@ animation-duration: 0.5s;
&:hover {
color: ${props => props.theme.cream}
}
`
export const DrawerTitle = styled.h4`
text-align: center;
margin-top: 1rem;
color: white;
animation-name: slideIn;
animation-duration: 0.5s;
@keyframes slideIn {
0% {
transform: translateX(-100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
`

View File

@ -2,7 +2,7 @@ import { HashRouter } from "react-router-dom";
import { HeaderContainer, NavLink, HomeLink, LanguageSelector } from "./styles";
import { AvatarHeader } from "../Avatar/avatar";
import i18n from "../../utils/i18n";
import { Drawer, DrawerContainer, DrawerItem } from "../Drawer/styles";
import { Drawer, DrawerContainer, DrawerItem, DrawerTitle } from "../Drawer/styles";
import useDrawerVisible from "../../hooks/useDrawerVisible";
import { useTranslation } from "react-i18next";
@ -13,6 +13,7 @@ export default function Header() {
localStorage.setItem("language", event.target.value);
};
const { ref, openDrawer, setOpenDrawer } = useDrawerVisible();
return (
<>
<HashRouter>
@ -38,7 +39,7 @@ export default function Header() {
</HeaderContainer>
{openDrawer &&
<DrawerContainer onClick={() => setOpenDrawer(false)}>
<DrawerItem>Giv's Website</DrawerItem>
<DrawerTitle>Giv's Website</DrawerTitle>
<DrawerItem href="#about">{t("about")}</DrawerItem>
<DrawerItem href="#skills">{t("skills")}</DrawerItem>
<DrawerItem href="#contact">{t("contact")}</DrawerItem>

View File

@ -6,6 +6,6 @@ export const MainContainer = styled.div`
background-color: ${props => props.theme["gray-700"]};
border-radius: 8px;
@media(max-width: 640px){
margin: 0 0 3rem 0;
margin: 0.75rem 0 3rem 0;
}
`

View File

@ -1,5 +1,7 @@
import axios from 'axios'
const defaultApiUrl = 'https://lastfm-last-played.biancarosa.com.br/givfnz/latest-song';
export const api = axios.create({
baseURL: import.meta.env.VITE_LASTFM_API_URL,
baseURL: import.meta.env.VITE_LASTFM_API_URL || defaultApiUrl,
})

View File

@ -10,8 +10,10 @@
"send": "Send",
"myLast": "My Last.FM Status",
"myLastDescription": "I'm listening to:",
"lastPlayed": "Last played song:",
"lastLink": "View on Last.FM",
"music": "Music",
"currentlyListening": "I'm currently listening to it!",
"lastUpdate": "Last update: "
"lastUpdate": "Last update: ",
"noRecentTracks": "No recent tracks found"
}

View File

@ -10,8 +10,10 @@
"send": "Enviar",
"myLast": "Meu status do Last.FM",
"myLastDescription": "Dê uma olhada na música que eu estava ouvindo:",
"lastPlayed": "Última música ouvida:",
"lastLink": "Clique aqui para ver a música no Last.FM",
"music": "Música",
"currentlyListening": "Eu estou ouvindo neste momento!",
"lastUpdate": "Última atualização: "
"lastUpdate": "Última atualização: ",
"noRecentTracks": "Nenhuma música recente encontrada"
}

View File

@ -1,4 +1,4 @@
import { getRecentTracks } from "../../api/lastfm";
import { getRecentTracks, TrackResponse } from "../../api/lastfm";
import { MainContainer } from "../../components/MainContent/styles";
import { useQuery } from '@tanstack/react-query'
import { Paragraph } from "../../components/Paragraph/styles";
@ -8,22 +8,48 @@ import { MusicDescription, MusicTitle } from "./styles";
export default function Music() {
const { t } = useTranslation()
const { data: lastResponse } = useQuery({
const { data: lastResponse, isLoading, isError, error } = useQuery<TrackResponse>({
queryKey: ['song'],
queryFn: getRecentTracks
queryFn: getRecentTracks,
retry: 1,
refetchOnWindowFocus: false
})
const isAvailable = false;
if(isAvailable){
const isAvailable = true;
if (isAvailable){
const track = lastResponse?.track;
const isCurrentlyPlaying = !track?.date?.["#text"];
return (
<MainContainer>
<MusicTitle>{t("myLast")}</MusicTitle>
<MusicDescription>{t("myLastDescription")}</MusicDescription>
<Paragraph>{lastResponse?.recenttracks?.track[0].name} - {lastResponse?.recenttracks?.track[0].artist["#text"]}</Paragraph>
<IconLink target="blank" href={lastResponse?.recenttracks?.track[0].url}>{t("lastLink")}</IconLink>
{lastResponse?.recenttracks?.track[0]?.date?.["#text"] ?
<Paragraph>{t("lastUpdate")}{lastResponse?.recenttracks?.track[0]?.date["#text"]} UTC</Paragraph>
: <Paragraph>{t("currentlyListening")}</Paragraph>
}
{isLoading ? (
<Paragraph>Loading music data...</Paragraph>
) : isError ? (
<>
<Paragraph>Error loading music data</Paragraph>
<Paragraph>Error details: {error instanceof Error ? error.message : String(error)}</Paragraph>
</>
) : (
<>
<MusicDescription>
{isCurrentlyPlaying ? t("myLastDescription") : t("lastPlayed")}
</MusicDescription>
{track ? (
<>
<Paragraph>{track.name} - {track.artist["#text"]}</Paragraph>
<IconLink target="blank" href={track.url}>{t("lastLink")}</IconLink>
{isCurrentlyPlaying ?
<Paragraph>{t("currentlyListening")}</Paragraph>
: <Paragraph>{t("lastUpdate")}{track.date?.["#text"]} UTC</Paragraph>
}
</>
) : (
<Paragraph>{t("noRecentTracks")}</Paragraph>
)}
</>
)}
</MainContainer>
)
}