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

Create a home page #3

Open
wants to merge 12 commits into
base: base-sha/b3fb5370aaecf29003b08ec24ed854a5b3582159
Choose a base branch
from
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# here put your credentials
REACT_APP_SPOTIFY_CLIENT_ID=""
REACT_APPP_SPOTIFY_CLIENT_SECRET=""
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@material-ui/styles": "^4.11.5",
"@mui/icons-material": "^5.14.14",
"@mui/material": "^5.14.13",
"@reduxjs/toolkit": "^1.9.7",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
Expand Down
19 changes: 3 additions & 16 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import React from 'react';
import logo from './logo.svg';
import Home from './pages/Home';

import './App.css';

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<Home/>
</div>
);
}
Expand Down
42 changes: 42 additions & 0 deletions src/components/atoms/Search/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useState } from 'react';
import SearchIcon from '@mui/icons-material/Search';
import IconButton from '@mui/material/IconButton/IconButton';
import InputBase from '@mui/material/InputBase';

const style = {
background: 'linear-gradient(45deg, #f2f2f2 30%, #f2f2f2 90%)',
borderRadius: 25,
color: 'black'
};

const Search = ({ onSearch }: { onSearch: (term: string) => void }) => {
const [searchTerm, setSearchTerm] = useState('');

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearchTerm(value);
onSearch(value); // Aquí utilizamos onSearch
};

return (
<div>
<div>
<h2>Search Song</h2>
<InputBase
style={style}
sx={{ ml: 1, flex: 1 }}
placeholder="Search Song"
value={searchTerm}
onChange={handleChange}
startAdornment={
<IconButton color="default" aria-label="upload picture" component="span">
<SearchIcon />
</IconButton>
}
/>
</div>
</div>
);
};

export default Search;
4 changes: 4 additions & 0 deletions src/components/cardSong/cardSongProps.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface CardSongProps {
searchTerm: string;
}
export default CardSongProps;
56 changes: 56 additions & 0 deletions src/components/cardSong/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState, useEffect } from "react";
import { getListSong } from "../../services/requetsToEndpoint";
import Track from "../../models/track.inteface";
import Card from '@mui/material/Card';
import CardMedia from '@mui/material/CardMedia';
import CardContent from '@mui/material/CardContent';
import Typography from '@mui/material/Typography';
import CardSongProps from "./cardSongProps.interface";

const CardSong = ({ searchTerm }: CardSongProps) => {
const [songData, setSongData] = useState<Track[]>([]);

useEffect(() => {
const fetchData = async () => {
const data = await getListSong(searchTerm);
setSongData(data);
};
fetchData();
}, [searchTerm]);

const CardSongSearch = () => {
return (
<>
<div style={{display:'inline-block',
marginTop: '20px'}}>
{songData.length > 0 && (
songData.map((track, index) => (
<div key={index}>
<Card sx={{ maxWidth: 345 }}>
<CardMedia
component="img"
height="300"
src={track.imageSong} alt={track.nameSong}
/>
<CardContent>
<Typography variant="h5" color="black">
{track.nameArtist}
</Typography>
<Typography variant="h6" color="black">
{track.nameSong}
</Typography>
</CardContent>
</Card>
<br />
</div>
))
)}
</div>
</>
);
};

return <CardSongSearch />;
};

export default CardSong;
6 changes: 6 additions & 0 deletions src/models/track.inteface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
interface Track {
nameArtist: string,
nameSong: string,
imageSong: string
}
export default Track;
21 changes: 21 additions & 0 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Home.js
import React, { useState } from 'react';
import Search from '../components/atoms/search';
import CardSong from '../components/cardSong';

const Home = () => {
const [searchTerm, setSearchTerm] = useState<string>('');

const handleSearch = (nameSong: string) => {
setSearchTerm(nameSong);
};

return (
<div>
<Search onSearch={handleSearch} />
<CardSong searchTerm={searchTerm} />
</div>
);
}

export default Home;
51 changes: 51 additions & 0 deletions src/services/generateToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const generateToken = async () => {

try {
// POST request using fetch with set headers
const urlencoded = new URLSearchParams();
urlencoded.append("grant_type", "client_credentials");
urlencoded.append("client_id", process.env.REACT_APP_SPOTIFY_CLIENT_ID || '' );
urlencoded.append("client_secret", process.env.REACT_APPP_SPOTIFY_CLIENT_SECRET || '');
const requestOptions = {
method: 'POST',
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: urlencoded
};
const data = await fetch('https://accounts.spotify.com/api/token', requestOptions)
const responseData = await data.json();
return responseData;
} catch (error) {
return;
}
}

const getToken = async () => {
// here we obtain the stored token on localStorage
const localStorageToken = localStorage.getItem('token');

//we verify if the token exists and if it is valid
if (localStorageToken) {
const newObjectToken = JSON.parse(localStorageToken);
if (Date.now() < newObjectToken.tokenExpiryDate) {
return newObjectToken.access_token;
}
}

// here we generate a new token and calculate the expiration date
const token = await generateToken();
const tokenExpiryDate = Date.now() + token.expires_in * 60;


// we created a object new and save of token and also tokenExpiryDate and asigned on localstorage
const newToken = {
access_token: token.access_token,
tokenExpiryDate: tokenExpiryDate
};

localStorage.setItem('token', JSON.stringify(newToken));

return newToken.access_token;
};
export { getToken };
38 changes: 38 additions & 0 deletions src/services/requetsToEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { getToken } from "./generateToken";
import Track from "../models/track.inteface";

const getListSong = async (searchTerm?: string): Promise<Track[]> => {
const songList: Track[] = [];
//we create a variable and to asigned the value of the getToken fuction
const token = await getToken();

if (searchTerm && searchTerm.length > 3) {
try {
//here connets de API and also we does petition of the endpoint
const response = await fetch(
`https://api.spotify.com/v1/search?type=track&q=${searchTerm}&limit=10`,
{
headers: {
Authorization: "Bearer " + `${token}`,
},
}
);
const data = await response.json();
data.tracks.items.map((item: { name: any; album: { images: { url: any; }[]; }; artists: { name: any; }[]; }) => {
songList.push({
imageSong: item.album.images[1].url,
nameSong: item.name,
nameArtist: item.artists[0].name,
});
});
} catch (error) {
console.error("Error fetching data:", error);
}
}
else {
return songList;
}

return songList;
};
export { getListSong };
Loading