This commit is contained in:
Unknown 2025-01-17 13:37:24 -07:00
parent aa8f3e1003
commit 3fb3e9e4c6
14 changed files with 477 additions and 47 deletions

50
cloudflare-worker.js Normal file
View file

@ -0,0 +1,50 @@
import { Router } from 'itty-router';
const router = Router();
const CLIENT_ID = 'YOUR_SPOTIFY_CLIENT_ID';
const CLIENT_SECRET = 'YOUR_SPOTIFY_CLIENT_SECRET';
const REDIRECT_URI = 'YOUR_REDIRECT_URI';
let accessToken = null;
// Function to refresh Spotify Access Token
async function refreshAccessToken() {
const authResponse = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)
},
body: 'grant_type=client_credentials'
});
const data = await authResponse.json();
accessToken = data.access_token;
}
// Spotify Data Fetch Endpoint
router.get('/spotify-data', async (request) => {
if (!accessToken) {
await refreshAccessToken();
}
const spotifyResponse = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=10', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const spotifyData = await spotifyResponse.json();
return new Response(JSON.stringify(spotifyData), {
headers: { 'Content-Type': 'application/json' }
});
});
// Default Route
router.all('*', () => new Response('Not Found', { status: 404 }));
// Event Listener for Worker Requests
addEventListener('fetch', (event) => {
event.respondWith(router.handle(event.request));
});

View file

@ -8,7 +8,7 @@ npm run build
# Step 3: Deploy Frontend to Cloudflare Pages
Write-Output "Deploying frontend to Cloudflare Pages..."
wrangler pages deploy ./build --project-name personal-site
wrangler pages deploy ./build --project-name personal-site-test
# Step 4: Deploy Backend to Cloudflare Workers
Write-Output "Deploying backend worker..."

View file

@ -3,6 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/plugin-transform-class-properties": "^7.25.9",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9",
"@babel/plugin-transform-numeric-separator": "^7.25.9",
"@babel/plugin-transform-optional-chaining": "^7.25.9",
"@babel/plugin-transform-private-methods": "^7.25.9",
"@eslint/eslintrc": "^1.4.1",
"@eslint/js": "^8.57.1",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@babel/plugin-transform-class-properties": "^7.25.9",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9",
"@babel/plugin-transform-numeric-separator": "^7.25.9",
"@babel/plugin-transform-optional-chaining": "^7.25.9",
"@babel/plugin-transform-private-methods": "^7.25.9",
"@eslint/eslintrc": "^1.4.1",
"@eslint/js": "^8.57.1",
"@jridgewell/sourcemap-codec": "^1.5.0",
"cra-template": "1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View file

@ -1,6 +1,6 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "Personal Website",
"name": "My Personal Website",
"icons": [
{
"src": "favicon.ico",
@ -8,12 +8,12 @@
"type": "image/x-icon"
},
{
"src": "logo192.png",
"src": "logo.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"src": "logo.png",
"type": "image/png",
"sizes": "512x512"
}

73
spotify-worker.js Normal file
View file

@ -0,0 +1,73 @@
import { Router } from 'itty-router';
const router = Router();
const CLIENT_ID = 'YOUR_SPOTIFY_CLIENT_ID';
const CLIENT_SECRET = 'YOUR_SPOTIFY_CLIENT_SECRET';
let accessToken = null;
let tokenExpiry = null;
// Helper function to fetch a new access token
async function fetchAccessToken() {
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)
},
body: 'grant_type=client_credentials'
});
if (!response.ok) {
throw new Error(`Failed to fetch Spotify token: ${response.statusText}`);
}
const data = await response.json();
accessToken = data.access_token;
tokenExpiry = Date.now() + (data.expires_in * 1000);
console.log('New access token fetched');
}
// Middleware to ensure a valid access token
async function ensureAccessToken() {
if (!accessToken || Date.now() >= tokenExpiry) {
await fetchAccessToken();
}
}
// Route to fetch Spotify data (Top Tracks)
router.get('/spotify-data', async () => {
try {
await ensureAccessToken();
const response = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=10', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
if (!response.ok) {
return new Response(JSON.stringify({ error: 'Failed to fetch Spotify data' }), { status: response.status });
}
const data = await response.json();
return new Response(JSON.stringify({
topTracks: data.items.map(track => ({
name: track.name,
artist: track.artists.map(artist => artist.name).join(', ')
}))
}), { headers: { 'Content-Type': 'application/json' } });
} catch (error) {
console.error('Error fetching Spotify data:', error);
return new Response(JSON.stringify({ error: 'Internal server error' }), { status: 500 });
}
});
// Default route for unmatched paths
router.all('*', () => new Response('Not Found', { status: 404 }));
// Event listener for handling requests
addEventListener('fetch', (event) => {
event.respondWith(router.handle(event.request));
});

View file

@ -5,6 +5,9 @@
.App-logo {
height: 40vmin;
pointer-events: none;
background-image: url('/logo.png');
background-size: contain;
background-repeat: no-repeat;
}
@media (prefers-reduced-motion: no-preference) {

View file

@ -1,21 +1,42 @@
import React from 'react';
import Navbar from './components/Navbar';
import SpotifyList from './components/SpotifyList';
import GitHubRepos from './components/GitHubRepos';
import './index.css';
import React, { useEffect } from 'react';
import './rolling-code.css';
import logo from './logo.png';
function App() {
return (
<div className="App">
<Navbar />
<main>
<h1>Welcome to my resume website</h1>
<p>This is the homepage. It is currently a WIP.</p>
</main>
<SpotifyList />
<GitHubRepos />
</div>
);
}
const App = () => {
useEffect(() => {
// Generate rolling code lines
const container = document.querySelector('.rolling-code-container');
for (let i = 0; i < 30; i++) {
const line = document.createElement('div');
line.className = 'code-line';
line.style.animationDelay = `${Math.random() * 5}s`;
line.textContent = Math.random().toString(36).substr(2, 80);
container.appendChild(line);
}
// Generate particles
const particleContainer = document.querySelector('.particle-container');
for (let i = 0; i < 50; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = `${Math.random() * 100}vw`;
particle.style.animationDelay = `${Math.random() * 10}s`;
particleContainer.appendChild(particle);
}
}, []);
return (
<div>
<img src={logo} alt="Logo" style="width: 100px; height: auto;"></img>
<div className="animated-lighting"></div>
<div className="rolling-code-container"></div>
<div className="particle-container"></div>
<div className="content">
<h1>Welcome to My Website</h1>
<p>Enhanced Background with Lighting Effects</p>
</div>
</div>
);
};
export default App;

View file

@ -0,0 +1,54 @@
.github-repos-container {
padding: 2rem;
background-color: #f9f9f9;
font-family: Arial, sans-serif;
text-align: center;
}
.repos-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.repo-card {
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 1rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.repo-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
}
.repo-name {
font-size: 1.2rem;
font-weight: bold;
color: #0077cc;
text-decoration: none;
}
.repo-name:hover {
text-decoration: underline;
}
.repo-description {
color: #555;
margin-top: 0.5rem;
}
.repo-language {
display: inline-block;
margin-top: 1rem;
padding: 0.3rem 0.6rem;
background-color: #eee;
border-radius: 4px;
font-size: 0.9rem;
color: #333;
}

View file

@ -1,29 +1,44 @@
import React, { useEffect, useState } from 'react';
import './GithubRepos.css';
function GitHubRepos() {
const [repos, setRepos] = useState([]);
const GithubRepos = () => {
const [repos, setRepos] = useState([]);
useEffect(() => {
fetch('/github-repos')
.then(response => response.json())
.then(data => {
setRepos(data);
});
}, []);
useEffect(() => {
const fetchRepos = async () => {
try {
const response = await fetch('https://api.github.com/users/EndofTimee/repos');
const data = await response.json();
setRepos(data);
} catch (error) {
console.error('Error fetching GitHub repos:', error);
}
};
return (
<div id="github-repos">
<h2>My GitHub Repositories</h2>
<ul>
{repos.map(repo => (
<li key={repo.id}>
<h3>{repo.name}</h3>
<p>{repo.description}</p>
</li>
))}
</ul>
</div>
);
}
fetchRepos();
}, []);
export default GitHubRepos;
return (
<div className="github-repos-container">
<h1>My GitHub Repositories</h1>
<div className="repos-grid">
{repos.map((repo) => (
<div key={repo.id} className="repo-card">
<a href={repo.html_url} target="_blank" rel="noopener noreferrer" className="repo-name">
{repo.name}
</a>
<p className="repo-description">
{repo.description || 'No description provided.'}
</p>
{repo.language && (
<span className="repo-language">{repo.language}</span>
)}
</div>
))}
</div>
</div>
);
};
export default GithubRepos;

View file

@ -0,0 +1,14 @@
import React from 'react';
import './LoadingAnimation.css';
const LoadingAnimation = () => {
return (
<div className="loading-container">
<div className="loading-spinner"></div>
<p>Loading...</p>
</div>
);
};
export default LoadingAnimation;

View file

@ -0,0 +1,20 @@
import React from 'react';
import './parallax.css';
const ParallaxEffect = () => {
return (
<div className="parallax-container">
<div className="parallax-layer layer-1"></div>
<div className="parallax-layer layer-2"></div>
<div className="content-layer">
<div className="parallax-text">
<h1>Welcome to the Parallax Effect</h1>
<p>Scroll down to see the magic happen!</p>
</div>
</div>
</div>
);
};
export default ParallaxEffect;

View file

@ -0,0 +1,24 @@
.loading-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background: black;
color: white;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(255, 255, 255, 0.3);
border-top: 5px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

52
src/parallax-effect.css Normal file
View file

@ -0,0 +1,52 @@
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow-x: hidden;
font-family: Arial, sans-serif;
}
.parallax-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
perspective: 2px; /* Creates the parallax effect */
}
.parallax-layer {
position: absolute;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
transform: translateZ(0);
z-index: -1;
}
.layer-1 {
background-image: url('https://source.unsplash.com/1600x900/?nature');
transform: translateZ(-1px) scale(1.5);
}
.layer-2 {
background-image: url('https://source.unsplash.com/1600x900/?forest');
transform: translateZ(-0.5px) scale(1.2);
}
.content-layer {
position: relative;
z-index: 1;
text-align: center;
color: white;
font-size: 2rem;
padding-top: 40vh;
}
.parallax-text {
background: rgba(0, 0, 0, 0.5);
padding: 1rem 2rem;
display: inline-block;
border-radius: 8px;
}

88
src/rolling-effects.css Normal file
View file

@ -0,0 +1,88 @@
@keyframes rollingCode {
0% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(100vh); opacity: 0; }
}
@keyframes particleMove {
0% { transform: translateY(0) translateX(0); opacity: 0.5; }
50% { opacity: 1; }
100% { transform: translateY(100vh) translateX(10px); opacity: 0; }
}
@keyframes lightAnimation {
0% {
background-position: 0% 0%;
}
50% {
background-position: 100% 100%;
}
100% {
background-position: 0% 0%;
}
}
body {
margin: 0;
overflow: hidden;
background: black;
font-family: monospace;
font-size: 1rem;
color: limegreen;
position: relative;
height: 100vh;
}
.animated-lighting {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(0, 255, 0, 0.3), transparent 60%);
background-size: 300% 300%;
animation: lightAnimation 12s infinite linear;
z-index: 0;
}
.code-line {
position: absolute;
top: -10%;
left: 0;
right: 0;
width: 100%;
white-space: nowrap;
overflow: hidden;
animation: rollingCode 10s linear infinite;
opacity: 0.7;
z-index: 1;
color: #00ff00;
}
.particle {
position: absolute;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
animation: particleMove 8s linear infinite;
z-index: 2;
}
.particle:nth-child(odd) {
animation-duration: 12s;
}
.particle:nth-child(even) {
animation-duration: 10s;
width: 4px;
height: 4px;
}
.content {
position: relative;
z-index: 3;
color: white;
text-align: center;
margin-top: 20vh;
}