diff --git a/.gitignore b/.gitignore index 8692cf6..e405c8d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +.dev.vars +.prod.env +.prod.vars diff --git a/README.md b/README.md index 58beeac..92c86db 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,169 @@ -# Getting Started with Create React App +# Personal Website with Cloudflare Integration -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +## 🏗️ Architecture Overview -## Available Scripts +This project implements a modern web application architecture leveraging Cloudflare's edge computing capabilities. The architecture consists of three primary components: -In the project directory, you can run: +1. **React Frontend**: A Single Page Application (SPA) built with Create React App +2. **Cloudflare Workers**: Serverless functions handling API integrations +3. **Cloudflare Pages**: Static site hosting with global CDN distribution -### `npm start` +## 🚀 Getting Started -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. +### Prerequisites -The page will reload when you make changes.\ -You may also see any lint errors in the console. +- Node.js (v16.0.0 or higher) +- npm (v7.0.0 or higher) +- Cloudflare account +- Spotify Developer account +- Git -### `npm test` +### Environment Setup -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +1. Clone the repository: +```bash +git clone https://github.com/EndofTimee/My-website +cd personal-website +``` -### `npm run build` +2. Create a `.env` file in the root directory: +```env +SPOTIFY_CLIENT_ID=your_spotify_client_id +SPOTIFY_CLIENT_SECRET=your_spotify_client_secret +SPOTIFY_REDIRECT_URI=your_redirect_uri +``` -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. +3. Install dependencies: +```bash +npm install +``` -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! +## 💻 Local Development -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +### Starting the Development Server -### `npm run eject` +```bash +# Start React development server +npm start -**Note: this is a one-way operation. Once you `eject`, you can't go back!** +# In a separate terminal, start the Cloudflare Worker +npx wrangler dev spotify-worker.js +``` -If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +The application will be available at: +- Frontend: http://localhost:3000 +- Worker: http://localhost:8787 -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. +### Component Structure -You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. +The project follows a modular component structure: -## Learn More +``` +src/ +├── components/ +│ ├── SpotifyList/ # Spotify integration +│ ├── GithubRepos/ # GitHub repository display +│ ├── LoadingAnimation/ # Loading states +│ └── ParallaxEffect/ # Visual effects +├── App.js # Main application component +└── index.js # Application entry point +``` -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +## 🌐 Deployment -To learn React, check out the [React documentation](https://reactjs.org/). +### Automated Deployment -### Code Splitting +The project includes a PowerShell deployment script that handles both frontend and worker deployment: -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) +```bash +npm run deploy +``` -### Analyzing the Bundle Size +This script: +1. Loads environment variables +2. Installs dependencies +3. Builds the React application +4. Deploys to Cloudflare Pages +5. Deploys the Spotify Worker +6. Sets up environment secrets -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) +### Manual Deployment Steps -### Making a Progressive Web App +If you need to deploy components individually: -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) +1. Frontend Deployment: +```bash +npm run build +npx wrangler pages deploy ./build +``` -### Advanced Configuration +2. Worker Deployment: +```bash +npx wrangler deploy spotify-worker.js +``` -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) +### Environment Configuration -### Deployment +#### Cloudflare Pages Configuration: -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) +1. Build settings: + - Build command: `npm run build` + - Build output directory: `build` + - Node.js version: 16 (or higher) -### `npm run build` fails to minify +2. Environment variables: + - Production branch: `main` + - Preview branches: `dev/*` -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) +#### Worker Configuration: + +Required environment secrets: +- `SPOTIFY_CLIENT_ID` +- `SPOTIFY_CLIENT_SECRET` +- `SPOTIFY_REDIRECT_URI` + +## 🐛 Troubleshooting + +### Common Issues + +1. Worker Deployment Failures: + ```bash + # Verify wrangler.toml configuration + npx wrangler config + + # Check worker status + npx wrangler tail + ``` + +2. Build Issues: + ```bash + # Clear dependency cache + rm -rf node_modules + npm clean-cache --force + npm install + ``` + +3. Environment Variables: + ```bash + # Verify environment variables + npx wrangler secret list + ``` + +## 📚 Additional Resources + +- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/) +- [Cloudflare Pages Documentation](https://developers.cloudflare.com/pages/) +- [Spotify Web API Documentation](https://developer.spotify.com/documentation/web-api/) +- [React Documentation](https://reactjs.org/docs/getting-started.html) + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch +3. Commit changes +4. Push to the branch +5. Open a Pull Request + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/cloudflare-worker.js b/cloudflare-worker.js index 35500d4..a054adf 100644 --- a/cloudflare-worker.js +++ b/cloudflare-worker.js @@ -3,9 +3,9 @@ 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'; +const CLIENT_ID = process.env.CLIENT_ID; +const CLIENT_SECRET = proccess.env.CLIENT_SECRET; +const REDIRECT_URI = proces.env.REDIRECT_URI let accessToken = null; // Function to refresh Spotify Access Token diff --git a/deploy-master.ps1 b/deploy-master.ps1 new file mode 100644 index 0000000..a64dbca --- /dev/null +++ b/deploy-master.ps1 @@ -0,0 +1,432 @@ +# deploy-master.ps1 +#Requires -Version 5.1 +Set-StrictMode -Version Latest + +# Configure warning and error preferences +$WarningPreference = 'Continue' +$ErrorActionPreference = 'Continue' + +# Configure output encoding +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::InputEncoding = [System.Text.Encoding]::UTF8 + +# Script Configuration +$CONFIG = @{ + ProjectName = "personal-site" + RequiredFiles = @{ + Root = @( + "package.json", + "wrangler.toml", + "spotify-worker.js", + ".env", + ".dev.vars", + ".prod.vars" + ) + Src = @( + "App.js", + "App.css", + "index.js", + "App.test.js", + "reportWebVitals.js", + "setupTests.js" + ) + Public = @( + "index.html", + "manifest.json", + "robots.txt" + ) + } + RequiredDirs = @( + "src", + "public", + "build", + "logs" + ) + RequiredEnvVars = @{ + ".env" = @( + "REACT_APP_SPOTIFY_CLIENT_ID", + "REACT_APP_SPOTIFY_CLIENT_SECRET", + "REACT_APP_SPOTIFY_REDIRECT_URI", + "REACT_APP_WORKER_URL" + ) + ".prod.vars" = @( + "SPOTIFY_CLIENT_ID", + "SPOTIFY_CLIENT_SECRET", + "SPOTIFY_REDIRECT_URI" + ) + } +} + +# Helper Functions +function Write-Header { + param([string]$Title) + $border = "=" * 80 + Write-Host "`n$border" -ForegroundColor Magenta + Write-Host $Title -ForegroundColor Magenta + Write-Host "$border" -ForegroundColor Magenta +} + +function Write-Status { + param( + [string]$Message, + [string]$Type = "Info", + [switch]$NoNewline + ) + + $colors = @{ + Info = "Cyan" + Success = "Green" + Warning = "Yellow" + Error = "Red" + } + + $prefix = switch ($Type) { + "Success" { "[+]" } + "Error" { "[-]" } + "Warning" { "[!]" } + "Info" { "[*]" } + } + + if ($NoNewline) { + Write-Host "$prefix $Message" -ForegroundColor $colors[$Type] -NoNewline + } + else { + Write-Host "$prefix $Message" -ForegroundColor $colors[$Type] + } +} + +function Test-NodeEnvironment { + Write-Header "Checking Node.js Environment" + $errors = @() + + try { + $nodeVersion = node --version 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Status "Node.js version: $nodeVersion" "Success" + } + else { + $errors += "Node.js not found" + } + + $npmVersion = npm --version 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Status "npm version: $npmVersion" "Success" + } + else { + $errors += "npm not found" + } + } + catch { + Write-Status "Error checking Node.js environment: $_" "Warning" + } + + return $errors +} + +function Test-Dependencies { + Write-Header "Checking Dependencies" + $errors = @() + + try { + # Clean existing installations + if (Test-Path "node_modules") { + Write-Status "Removing existing node_modules..." "Info" + Remove-Item "node_modules" -Recurse -Force -ErrorAction Continue + } + + if (Test-Path "package-lock.json") { + Write-Status "Removing package-lock.json..." "Info" + Remove-Item "package-lock.json" -Force -ErrorAction Continue + } + + # Fresh install with warning handling + Write-Status "Installing dependencies..." "Info" + + $npmOutput = npm install --legacy-peer-deps --no-audit 2>&1 + $npmExitCode = $LASTEXITCODE + + # Process npm output + $npmOutput | ForEach-Object { + if ($_ -match "ERR!") { + Write-Status $_ "Error" + $errors += $_ + } + elseif ($_ -match "WARN") { + Write-Status $_ "Warning" + } + else { + Write-Status $_ "Info" + } + } + + if ($npmExitCode -ne 0) { + $errors += "npm install failed with exit code: $npmExitCode" + } + else { + Write-Status "Dependencies installed successfully" "Success" + } + } + catch { + Write-Status "Caught exception during dependency installation: $_" "Warning" + } + + return $errors +} + +function Test-CloudflareSetup { + Write-Header "Checking Cloudflare Setup" + $errors = @() + + try { + $wranglerVersion = npx wrangler --version 2>&1 + Write-Status "Wrangler version: $wranglerVersion" "Success" + + $whoami = npx wrangler whoami 2>&1 + if ($whoami -match "You are logged in") { + Write-Status "Authenticated with Cloudflare" "Success" + } + else { + Write-Status "Not authenticated with Cloudflare. Please log in..." "Warning" + npx wrangler login + if ($LASTEXITCODE -ne 0) { + $errors += "Failed to authenticate with Cloudflare" + } + } + } + catch { + Write-Status "Error checking Cloudflare setup: $_" "Warning" + } + + return $errors +} + +function Test-ProjectFiles { + Write-Header "Checking Project Files" + $errors = @() + + foreach ($category in $CONFIG.RequiredFiles.Keys) { + foreach ($file in $CONFIG.RequiredFiles[$category]) { + $path = switch ($category) { + "Root" { $file } + "Src" { "src/$file" } + "Public" { "public/$file" } + } + + if (-not (Test-Path $path)) { + $errors += "Missing required file: $path" + } + else { + Write-Status "Found $path" "Success" + } + } + } + + foreach ($dir in $CONFIG.RequiredDirs) { + if (-not (Test-Path $dir)) { + Write-Status "Creating directory: $dir" "Info" + New-Item -Path $dir -ItemType Directory -Force | Out-Null + } + Write-Status "Directory exists: $dir" "Success" + } + + return $errors +} + +function Test-EnvVars { + Write-Header "Checking Environment Variables" + $errors = @() + + foreach ($file in $CONFIG.RequiredEnvVars.Keys) { + if (Test-Path $file) { + $content = Get-Content $file -Raw + foreach ($var in $CONFIG.RequiredEnvVars[$file]) { + if (-not ($content -match $var)) { + $errors += "Missing $var in $file" + } + } + } + else { + $errors += "Missing file: $file" + } + } + + return $errors +} + +function Build-Project { + Write-Header "Building Project" + $errors = @() + + try { + if (Test-Path "build") { + Remove-Item "build" -Recurse -Force -ErrorAction Continue + Write-Status "Cleaned previous build" "Success" + } + + Write-Status "Building project..." "Info" + $buildOutput = npm run build 2>&1 + + # Process build output + $buildOutput | ForEach-Object { + if ($_ -match "ERR!") { + Write-Status $_ "Error" + $errors += $_ + } + elseif ($_ -match "WARN") { + Write-Status $_ "Warning" + } + else { + Write-Status $_ "Info" + } + } + + if ($LASTEXITCODE -ne 0) { + $errors += "Build failed with exit code: $LASTEXITCODE" + } + else { + Write-Status "Build completed successfully" "Success" + } + } + catch { + Write-Status "Error during build: $_" "Warning" + } + + return $errors +} + +function Deploy-Project { + Write-Header "Deploying Project" + $errors = @() + + try { + # Deploy worker + Write-Status "Deploying Cloudflare Worker..." "Info" + $workerOutput = npx wrangler deploy spotify-worker.js --minify 2>&1 + + $workerOutput | ForEach-Object { + if ($_ -match "ERR!") { + Write-Status $_ "Error" + $errors += $_ + } + elseif ($_ -match "WARN") { + Write-Status $_ "Warning" + } + else { + Write-Status $_ "Info" + } + } + + if ($LASTEXITCODE -ne 0) { + $errors += "Worker deployment failed with exit code: $LASTEXITCODE" + } + else { + Write-Status "Worker deployed successfully" "Success" + } + + # Deploy pages + Write-Status "Deploying to Cloudflare Pages..." "Info" + $pagesOutput = npx wrangler pages deploy build/ 2>&1 + + $pagesOutput | ForEach-Object { + if ($_ -match "ERR!") { + Write-Status $_ "Error" + $errors += $_ + } + elseif ($_ -match "WARN") { + Write-Status $_ "Warning" + } + else { + Write-Status $_ "Info" + } + } + + if ($LASTEXITCODE -ne 0) { + $errors += "Pages deployment failed with exit code: $LASTEXITCODE" + } + else { + Write-Status "Pages deployed successfully" "Success" + } + } + catch { + Write-Status "Error during deployment: $_" "Warning" + } + + return $errors +} + +# Main execution +$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" +$logFile = "logs/deploy_$timestamp.log" + +if (-not (Test-Path "logs")) { + New-Item -ItemType Directory -Path "logs" | Out-Null +} + +Start-Transcript -Path $logFile + +try { + Write-Header "Starting Deployment Process" + $allErrors = @() + $continueDeployment = $true + + $allErrors += Test-NodeEnvironment + $allErrors += Test-Dependencies + $allErrors += Test-CloudflareSetup + $allErrors += Test-ProjectFiles + $allErrors += Test-EnvVars + + if ($allErrors.Count -gt 0) { + Write-Header "Validation Warnings/Errors" + foreach ($error in $allErrors) { + Write-Status $error "Warning" + } + Write-Status "Found $($allErrors.Count) issues during validation" "Warning" + $userResponse = Read-Host "Do you want to continue with deployment? (y/n)" + $continueDeployment = $userResponse -eq 'y' + } + + if ($continueDeployment) { + $buildErrors = Build-Project + if ($buildErrors) { + Write-Status "Build completed with warnings:" "Warning" + foreach ($error in $buildErrors) { + Write-Status $error "Warning" + } + $userResponse = Read-Host "Do you want to continue with deployment? (y/n)" + $continueDeployment = $userResponse -eq 'y' + } + + if ($continueDeployment) { + $deployErrors = Deploy-Project + if ($deployErrors) { + Write-Status "Deployment completed with warnings:" "Warning" + foreach ($error in $deployErrors) { + Write-Status $error "Warning" + } + } + else { + Write-Header "Deployment Successful" + Write-Status "All components deployed successfully!" "Success" + } + } + } + + Write-Status "Log file: $logFile" "Info" +} +catch { + Write-Header "Deployment Error" + Write-Status $_.Exception.Message "Error" + Write-Status "Check the log file for details: $logFile" "Info" + + Write-Header "Troubleshooting Steps" + Write-Status "1. Check the log file: $logFile" "Info" + Write-Status "2. Verify Node.js and npm installations" "Info" + Write-Status "3. Check Cloudflare authentication: npx wrangler login" "Info" + Write-Status "4. Verify all required files exist" "Info" + Write-Status "5. Check build output" "Info" + + exit 1 +} +finally { + Stop-Transcript +} \ No newline at end of file diff --git a/deploy.ps1 b/deploy.ps1 deleted file mode 100644 index 21c2611..0000000 --- a/deploy.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -# Step 1: Install Dependencies -Write-Output "Installing dependencies..." -npm install - -# Step 2: Build Frontend -Write-Output "Building the React project..." -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-test - -# Step 4: Deploy Backend to Cloudflare Workers -Write-Output "Deploying backend worker..." -wrangler deploy spotify-worker.js --name spotify-worker - -Write-Output "✅ Deployment complete! Frontend and backend are live." diff --git a/logs/deploy_20250131_121116.log b/logs/deploy_20250131_121116.log new file mode 100644 index 0000000..42cc255 --- /dev/null +++ b/logs/deploy_20250131_121116.log @@ -0,0 +1,83 @@ +********************** +Windows PowerShell transcript start +Start time: 20250131121116 +Username: ENDOFTIMEE\mason +RunAs User: ENDOFTIMEE\mason +Configuration Name: +Machine: ENDOFTIMEE (Microsoft Windows NT 10.0.26100.0) +Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -Command Import-Module 'c:\Users\mason\.vscode\extensions\ms-vscode.powershell-2025.0.0\modules\PowerShellEditorServices\PowerShellEditorServices.psd1'; Start-EditorServices -HostName 'Visual Studio Code Host' -HostProfileId 'Microsoft.VSCode' -HostVersion '2025.0.0' -BundledModulesPath 'c:\Users\mason\.vscode\extensions\ms-vscode.powershell-2025.0.0\modules' -EnableConsoleRepl -StartupBanner "PowerShell Extension v2025.0.0 +Copyright (c) Microsoft Corporation. + +https://aka.ms/vscode-powershell +Type 'help' to get help. +" -LogLevel 'Warning' -LogPath 'c:\Users\mason\AppData\Roaming\Code\logs\20250131T111852\window1\exthost\ms-vscode.powershell' -SessionDetailsPath 'c:\Users\mason\AppData\Roaming\Code\User\globalStorage\ms-vscode.powershell\sessions\PSES-VSCode-31640-527219.json' -FeatureFlags @() +Process ID: 36136 +PSVersion: 5.1.26100.2161 +PSEdition: Desktop +PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.26100.2161 +BuildVersion: 10.0.26100.2161 +CLRVersion: 4.0.30319.42000 +WSManStackVersion: 3.0 +PSRemotingProtocolVersion: 2.3 +SerializationVersion: 1.1.0.1 +********************** +Transcript started, output file is logs/deploy_20250131_121116.log + +================================================================================ +Starting Deployment Process +================================================================================ + +================================================================================ +Checking Node.js Environment +================================================================================ +[+] Node.js version: v22.12.0 +[+] npm version: 10.9.0 + +================================================================================ +Checking Dependencies +================================================================================ +[*] Installing dependencies... +node.exe : npm warn deprecated @babel/plugin-proposal-numeric-separator@7.18.6: This proposal has been merged to the +ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator +instead. +At C:\Program Files\nodejs\npm.ps1:29 char:3 ++ & $NODE_EXE $NPM_CLI_JS $args ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (npm warn deprec...arator instead.:String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError +npm warn deprecated @babel/plugin-proposal-private-methods@7.18.6: This proposal has been merged to the ECMAScript +standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. +npm warn deprecated @babel/plugin-proposal-class-properties@7.18.6: This proposal has been merged to the ECMAScript +standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. +npm warn deprecated @babel/plugin-proposal-nullish-coalescing-operator@7.18.6: This proposal has been merged to the +ECMAScript standard and thus this plugin is no longer maintained. Please use +@babel/plugin-transform-nullish-coalescing-operator instead. +npm warn deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead +npm warn deprecated stable@0.1.8: Modern JS already guarantees Array#sort() is a stable sort, so this library is +deprecated. See the compatibility table on MDN: +https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility +npm warn deprecated @babel/plugin-proposal-private-property-in-object@7.21.11: This proposal has been merged to the +ECMAScript standard and thus this plugin is no longer maintained. Please use +@babel/plugin-transform-private-property-in-object instead. +npm warn deprecated @babel/plugin-proposal-optional-chaining@7.21.0: This proposal has been merged to the ECMAScript +standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. +npm warn deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported +npm warn deprecated rollup-plugin-inject@3.0.2: This package has been deprecated and is no longer maintained. Please +use @rollup/plugin-inject. +npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported +npm warn deprecated rollup-plugin-terser@7.0.2: This package has been deprecated and is no longer maintained. Please +use @rollup/plugin-terser +npm warn deprecated abab@2.0.6: Use your platform's native atob() and btoa() methods instead +npm warn deprecated q@1.5.1: You or someone you depend on is using Q, the JavaScript Promise library that gave +JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript +promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. +npm warn deprecated +npm warn deprecated (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) +npm warn deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead +npm warn deprecated domexception@2.0.1: Use your platform's native DOMException instead +npm warn deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead +npm warn deprecated w3c-hr-time@1.0.2: Use your platform's native performance.now() and performance.timeOrigin. +npm warn deprecated workbox-cacheable-response@6.6.0: workbox-background-sync@6.6.0 +npm warn deprecated workbox-google-analytics@6.6.0: It is not compatible with newer versions of GA starting with v4, as +long as you are using GAv3 it should be ok, but the package is not longer being maintained +npm warn deprecated svgo@1.3.2: This SVGO version is no longer supported. Upgrade to v2.x.x. diff --git a/package.json b/package.json index 29eca1e..6400b7b 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,33 @@ -{ - "name": "my-website", +{ + "name": "personal-site", "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", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@eslint/eslintrc": "^1.4.1", - "@eslint/js": "^8.57.1", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@rollup/plugin-terser": "7.0.2", - "eslint": "^8.57.1", - "rimraf": "^4.4.1", - "glob": "^9.3.5", - "svgo": "^2.8.0" + "web-vitals": "^2.1.4", + "itty-router": "^4.0.27", + "lru-cache": "^10.1.0" + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@cloudflare/workers-types": "^4.20240208.0", + "wrangler": "^3.28.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "deploy": "powershell ./deploy-master.ps1" }, "eslintConfig": { "extends": [ - "react-app", - "react-app/jest" + "react-app" ] }, "browserslist": { @@ -60,5 +41,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "overrides": { + "inflight": "npm:lru-cache@^10.1.0" } -} +} \ No newline at end of file diff --git a/public/pages/about.html b/public/pages/about-us.html similarity index 100% rename from public/pages/about.html rename to public/pages/about-us.html diff --git a/public/pages/system.html b/public/pages/system.html new file mode 100644 index 0000000..e69de29 diff --git a/servers/server.js b/servers/server.js deleted file mode 100644 index 761f419..0000000 --- a/servers/server.js +++ /dev/null @@ -1,124 +0,0 @@ -require('dotenv').config(); -const express = require('express'); -const querystring = require('querystring'); -const app = express(); -const port = process.env.PORT || 3000; - -const client_id = process.env.SPOTIFY_CLIENT_ID; -const client_secret = process.env.SPOTIFY_CLIENT_SECRET; -const redirect_uri = process.env.SPOTIFY_REDIRECT_URI; - -let access_token = ''; -let refresh_token = ''; - -app.use(express.static('public')); - -app.get('/login', (req, res) => { - const scope = 'user-top-read'; - const authUrl = 'https://accounts.spotify.com/authorize?' + - querystring.stringify({ - response_type: 'code', - client_id: client_id, - scope: scope, - redirect_uri: redirect_uri - }); - res.redirect(authUrl); -}); - -app.get('/callback', async (req, res) => { - const code = req.query.code || null; - const fetch = (await import('node-fetch')).default; - const tokenResponse = await fetch('https://accounts.spotify.com/api/token', { - method: 'POST', - headers: { - 'Authorization': 'Basic ' + Buffer.from(`${client_id}:${client_secret}`).toString('base64'), - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: querystring.stringify({ - code: code, - redirect_uri: redirect_uri, - grant_type: 'authorization_code' - }) - }); - const tokenData = await tokenResponse.json(); - access_token = tokenData.access_token; - refresh_token = tokenData.refresh_token; - - res.redirect('/'); -}); - -async function refreshAccessToken() { - const fetch = (await import('node-fetch')).default; - const tokenResponse = await fetch('https://accounts.spotify.com/api/token', { - method: 'POST', - headers: { - 'Authorization': 'Basic ' + Buffer.from(`${client_id}:${client_secret}`).toString('base64'), - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: querystring.stringify({ - grant_type: 'refresh_token', - refresh_token: refresh_token - }) - }); - const tokenData = await tokenResponse.json(); - access_token = tokenData.access_token; -} - -app.get('/spotify-data', async (req, res) => { - if (!access_token) { - await refreshAccessToken(); - } - const fetch = (await import('node-fetch')).default; - const response = await fetch('https://api.spotify.com/v1/me/top/tracks', { - headers: { - 'Authorization': `Bearer ${access_token}` - } - }); - const data = await response.json(); - res.json(data); -}); - -app.get('/github-repos', async (req, res) => { - const fetch = (await import('node-fetch')).default; - const response = await fetch(`https://api.github.com/users/${process.env.GITHUB_USERNAME}/repos`); - const data = await response.json(); - res.json(data); -}); - -app.listen(port, () => { - console.log(`Server running at http://localhost:${port}`); -}); -app.get('/spotify-data', async (req, res) => { - if (!access_token) { - return res.status(401).json({ error: 'User not authenticated. Please login first.' }); - } - try { - const fetch = (await import('node-fetch')).default; - - // Fetch user's top tracks - const tracksResponse = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=10', { - headers: { Authorization: `Bearer ${access_token}` } - }); - const topTracks = await tracksResponse.json(); - - // Fetch user's playlists - const playlistsResponse = await fetch('https://api.spotify.com/v1/me/playlists?limit=5', { - headers: { Authorization: `Bearer ${access_token}` } - }); - const playlists = await playlistsResponse.json(); - - res.json({ - topTracks: topTracks.items.map(track => ({ - name: track.name, - artist: track.artists.map(artist => artist.name).join(', ') - })), - playlists: playlists.items.map(playlist => ({ - name: playlist.name, - url: playlist.external_urls.spotify - })) - }); - } catch (error) { - console.error('Error fetching Spotify data:', error); - res.status(500).json({ error: 'Failed to fetch Spotify data' }); - } -}); diff --git a/spotify-worker.js b/spotify-worker.js index 24b7020..0a7417e 100644 --- a/spotify-worker.js +++ b/spotify-worker.js @@ -1,73 +1,83 @@ - import { Router } from 'itty-router'; const router = Router(); -const CLIENT_ID = 'YOUR_SPOTIFY_CLIENT_ID'; -const CLIENT_SECRET = 'YOUR_SPOTIFY_CLIENT_SECRET'; +// CORS headers +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', +}; -let accessToken = null; -let tokenExpiry = null; +// Response helper +const jsonResponse = (data, status = 200) => { + return new Response(JSON.stringify(data), { + status, + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + }, + }); +}; -// 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' +// Error response helper +const errorResponse = (message, status = 500) => { + return jsonResponse({ error: message }, status); +}; + +// Spotify credentials +const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID; +const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET; +const SPOTIFY_REDIRECT_URI = process.env.SPOTIFY_REDIRECT_URI; + +// 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(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`) + }, + body: 'grant_type=client_credentials' + }); + + const data = await authResponse.json(); + return data.access_token; +} + +// CORS preflight handler +router.options('*', () => new Response(null, { headers: corsHeaders })); + +// Health check endpoint +router.get('/health', () => { + return jsonResponse({ + status: 'healthy', + timestamp: new Date().toISOString(), + }); +}); + +// Spotify top tracks endpoint +router.get('/top-tracks', async () => { + try { + const accessToken = await refreshAccessToken(); + + const spotifyResponse = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=10', { + headers: { + 'Authorization': `Bearer ${accessToken}` + } }); - 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 }); - } + const spotifyData = await spotifyResponse.json(); + return jsonResponse(spotifyData); + } catch (error) { + return errorResponse('Failed to fetch Spotify data: ' + error.message); + } }); -// Default route for unmatched paths -router.all('*', () => new Response('Not Found', { status: 404 })); +// 404 handler +router.all('*', () => errorResponse('Not Found', 404)); -// Event listener for handling requests -addEventListener('fetch', (event) => { - event.respondWith(router.handle(event.request)); -}); +// Export handler +export default { + fetch: (request, env, ctx) => router.handle(request, env, ctx) +}; \ No newline at end of file diff --git a/src/App.js b/src/App.js index c90e6ed..d2f5ad9 100644 --- a/src/App.js +++ b/src/App.js @@ -1,33 +1,36 @@ import React, { useEffect } from 'react'; -import './rolling-code.css'; -import logo from './logo.png'; +import './styles/rolling-effects.css'; // Fixed import path +import './App.css'; 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); + if (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); + if (particleContainer) { + 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 ( -
- Logo +
@@ -39,4 +42,4 @@ const App = () => { ); }; -export default App; +export default App; \ No newline at end of file diff --git a/src/components/SpotifyList.js b/src/components/SpotifyList.js index addee21..78821e8 100644 --- a/src/components/SpotifyList.js +++ b/src/components/SpotifyList.js @@ -1,25 +1,80 @@ import React, { useEffect, useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +const WORKER_URL = 'https://spotify-worker.your-worker-subdomain.workers.dev'; function SpotifyList() { const [tracks, setTracks] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); useEffect(() => { - fetch('/spotify-data') - .then(response => response.json()) - .then(data => { - setTracks(data.items); - }); + const fetchTracks = async () => { + try { + setLoading(true); + const response = await fetch(`${WORKER_URL}/top-tracks`); + + if (!response.ok) { + throw new Error('Failed to fetch tracks'); + } + + const data = await response.json(); + setTracks(data.items || []); + } catch (err) { + setError(err.message); + console.error('Error fetching tracks:', err); + } finally { + setLoading(false); + } + }; + + fetchTracks(); }, []); + if (loading) { + return ( + + +
+
+
+
+
+ ); + } + + if (error) { + return ( + + +

Error: {error}

+
+
+ ); + } + return ( -
-

My Most Listened to Songs

-
    - {tracks.map(track => ( -
  • {track.name} by {track.artists.map(artist => artist.name).join(', ')}
  • - ))} -
-
+ + + My Top Tracks + + +
    + {tracks.map((track, index) => ( +
  • + {index + 1}.{' '} + {track.name} by{' '} + + {track.artists.map(artist => artist.name).join(', ')} + +
  • + ))} +
+
+
); } diff --git a/src/index.js b/src/index.js index 8d5cddf..b8f2f84 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; +import './styles/index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; @@ -11,4 +11,4 @@ root.render( ); -reportWebVitals(); +reportWebVitals(); \ No newline at end of file diff --git a/src/rolling-effects.css b/src/rolling-effects.css deleted file mode 100644 index b04e553..0000000 --- a/src/rolling-effects.css +++ /dev/null @@ -1,88 +0,0 @@ - -@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; -} diff --git a/src/components/GithubRepos.css b/src/styles/GithubRepos.css similarity index 100% rename from src/components/GithubRepos.css rename to src/styles/GithubRepos.css diff --git a/src/components/LoadingAnimation.css b/src/styles/LoadingAnimation.css similarity index 100% rename from src/components/LoadingAnimation.css rename to src/styles/LoadingAnimation.css diff --git a/src/index.css b/src/styles/index.css similarity index 100% rename from src/index.css rename to src/styles/index.css diff --git a/src/components/loading-screen.css b/src/styles/loading-screen.css similarity index 100% rename from src/components/loading-screen.css rename to src/styles/loading-screen.css diff --git a/src/parallax-effect.css b/src/styles/parallax-effect.css similarity index 100% rename from src/parallax-effect.css rename to src/styles/parallax-effect.css diff --git a/src/styles/rolling-effects.css b/src/styles/rolling-effects.css new file mode 100644 index 0000000..287224a --- /dev/null +++ b/src/styles/rolling-effects.css @@ -0,0 +1,23 @@ + +@keyframes rollingCode { + 0% { transform: translateY(0); opacity: 1; } + 100% { transform: translateY(100vh); opacity: 0; } +} + +body { + margin: 0; + overflow: hidden; + background: black; + color: limegreen; + font-family: monospace; + font-size: 1rem; +} + +.code-line { + position: absolute; + top: -10%; + width: 100%; + white-space: nowrap; + overflow: hidden; + animation: rollingCode 5s linear infinite; +} diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..1032210 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,18 @@ +name = "personal-site" +main = "spotify-worker.js" +compatibility_date = "2024-01-30" + +[build] +command = "npm run build" +watch_dir = "build" + +[site] +bucket = "./build" + +[env.production] +name = "personal-site" +vars = { ENVIRONMENT = "production" } + +[env.development] +name = "personal-site-dev" +vars = { ENVIRONMENT = "development" } \ No newline at end of file