diff --git a/.env.example b/.env.example deleted file mode 100644 index f542c9b4..00000000 --- a/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -API_URL=http://localhost:8080 -REACT_APP_AUTH0_DOMAIN=dev-auth0domain-goes-here.us.auth0.com -REACT_APP_AUTH0_CLIENT_ID=some-client-id-goes-here -REACT_APP_AUTH0_AUDIENCE=https://dev-find-this-under-APIs-in-the-dashboard.us.auth0.com/api/v2/ diff --git a/dist/images/3a426ca7e83eb4d81d6e.png b/dist/images/3a426ca7e83eb4d81d6e.png new file mode 100644 index 00000000..665a4490 Binary files /dev/null and b/dist/images/3a426ca7e83eb4d81d6e.png differ diff --git a/dist/index.html b/dist/index.html index e864e00a..9ccd78c6 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,13 +1,19 @@ - - - - - Capstone 1 - - - -
- - + + + + + + + + + Capstone 1 + + + + +
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dc268f09..f1626f5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,13 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@auth0/auth0-react": "^2.3.0", + "@auth0/auth0-react": "^2.4.0", "@babel/core": "^7.27.4", "@babel/preset-react": "^7.27.1", "axios": "^1.10.0", "babel-loader": "^10.0.0", "dotenv": "^17.1.0", + "jwt-decode": "^4.0.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.3", @@ -45,12 +46,12 @@ } }, "node_modules/@auth0/auth0-react": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-2.3.0.tgz", - "integrity": "sha512-YYTc/DWWigKC9fURufR/79h3+3DAnIzbfEzJLZ8Z4Q0BXE0azru3pKUbU+vYzS4lMAJkclwLuAbUnLjK81vCpA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-2.4.0.tgz", + "integrity": "sha512-5bt3sO9FVupNM15IpqyYu/2OPHpLI5El7RgWLQXZOPbnCBbtl+VgdHR+H2NfhNQ4SqQtC/5uKbHWafcVcsxkiw==", "license": "MIT", "dependencies": { - "@auth0/auth0-spa-js": "^2.1.3" + "@auth0/auth0-spa-js": "^2.2.0" }, "peerDependencies": { "react": "^16.11.0 || ^17 || ^18 || ^19", @@ -582,13 +583,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -596,9 +597,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1943,9 +1944,9 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { @@ -1953,7 +1954,7 @@ "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -3212,9 +3213,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -4421,6 +4422,15 @@ "node": ">=4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4868,9 +4878,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index da1486c6..5e0e4be0 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,13 @@ "license": "ISC", "description": "", "dependencies": { - "@auth0/auth0-react": "^2.3.0", + "@auth0/auth0-react": "^2.4.0", "@babel/core": "^7.27.4", "@babel/preset-react": "^7.27.1", "axios": "^1.10.0", "babel-loader": "^10.0.0", "dotenv": "^17.1.0", + "jwt-decode": "^4.0.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.3", diff --git a/src/App.jsx b/src/App.jsx index d793b9af..bbac2b4e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,45 +1,102 @@ import React, { useState, useEffect } from "react"; import { createRoot } from "react-dom/client"; -import axios from "axios"; +// import axios from "axios"; import "./AppStyles.css"; import NavBar from "./components/NavBar"; +import Dashboard from "./components/Dashboard"; +import NowPlaying from "./components/NowPlaying"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import Login from "./components/Login"; import Signup from "./components/Signup"; -import Home from "./components/Home"; +// import Home from "./components/Home"; import NotFound from "./components/NotFound"; -import { API_URL } from "./shared"; +// import CallBack from "./components/CallBack"; +// import { jwtDecode } from "jwt-decode"; +// import { API_URL } from "./shared"; +import axios from './utils/axiosInstance'; +import { Auth0Provider, useAuth0 } from "@auth0/auth0-react"; + +// AUTH0 CONFIGURATION +const AUTH0_DOMAIN = process.env.REACT_APP_AUTH0_DOMAIN; +const AUTH0_CLIENT_ID = process.env.REACT_APP_AUTH0_CLIENT_ID; const App = () => { const [user, setUser] = useState(null); + console.log("this is user--->", user) + //const [token, setToken] = useState(""); - const checkAuth = async () => { - try { - const response = await axios.get(`${API_URL}/auth/me`, { - withCredentials: true, - }); - setUser(response.data.user); - } catch { - console.log("Not authenticated"); - setUser(null); - } - }; + const { + isAuthenticated, + user: auth0User, + getIdTokenClaims, + } = useAuth0(); // swapped getAccessTokenSilently for getIdTokenClaims - // Check authentication status on app load useEffect(() => { + const checkAuth = async () => { + try { + const response = await axios.get("/auth/me", { + withCredentials: true, + }); + setUser(response.data.user); + } catch { + console.log("Not authenticated"); + setUser(null); + } + }; checkAuth(); }, []); - const handleLogout = async () => { - try { - // Logout from our backend - await axios.post( - `${API_URL}/auth/logout`, - {}, - { + useEffect(() => { + const syncSpotifyAndFetchUser = async () => { + if (!isAuthenticated || !auth0User) { + console.warn("User not authenticated or auth0User not ready."); + return; + } + + try { + const claims = await getIdTokenClaims(); + const spotifyAccessToken = claims["https://localbeats.app/spotify_access_token"]; + + if (!spotifyAccessToken) { + console.warn("No Spotify access token found in ID token claims."); + return; + } + + // Sync with backend — this will update DB & create session token + await axios.post("/auth/spotify/sync", {}, { + headers: { + Authorization: `Bearer ${spotifyAccessToken}`, + }, withCredentials: true, } - ); + ); + + + // Fetch user info from DB (using session token) + const res = await axios.get("/auth/me", { withCredentials: true }); + console.log("this is data-->", res.data) + setUser(res.data.user); + } catch (err) { + console.error("Post-login sync failed:", err); + } + }; + + // Update frontend state + // setUser({ + // name: auth0User.name, + // email: auth0User.email, + // picture: auth0User.picture, + // }); + + console.log("Auth0 state ->", { isAuthenticated, auth0User }); + syncSpotifyAndFetchUser(); + }, [isAuthenticated, auth0User, getIdTokenClaims]); + + + + const handleLogout = async () => { + try { + await axios.post('/auth/logout', {}, { withCredentials: true }); setUser(null); } catch (error) { console.error("Logout error:", error); @@ -51,9 +108,15 @@ const App = () => {
- } /> + } /> } /> - } /> + + + + + } /> + } />
@@ -61,11 +124,31 @@ const App = () => { ); }; +// ✅ Root with Spotify audience + scopes const Root = () => { return ( - - - + // + + + + + ); }; diff --git a/src/assets/LocalBeats.png b/src/assets/LocalBeats.png new file mode 100644 index 00000000..27078ef6 Binary files /dev/null and b/src/assets/LocalBeats.png differ diff --git a/src/components/ActiveListener.jsx b/src/components/ActiveListener.jsx new file mode 100644 index 00000000..c8a4231b --- /dev/null +++ b/src/components/ActiveListener.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import ListenerCard from "./LIstenerCard"; + +const Activelistener = ({ user }) => { + // console.log("this is user from actiive listener---> ", user) + return ( +
+

+ Active Listeners +

+ {/*
+
    + {user.map((element) => ( +
  • + +
  • + ))} +
+
*/} + +
+ ) +} + +export default Activelistener; \ No newline at end of file diff --git a/src/components/AuthStyles.css b/src/components/AuthStyles.css index 0506f6ce..4837e6dd 100644 --- a/src/components/AuthStyles.css +++ b/src/components/AuthStyles.css @@ -1,5 +1,7 @@ .auth-container { display: flex; + flex-direction: column; + gap: 5rem; justify-content: center; align-items: center; min-height: 60vh; @@ -143,4 +145,4 @@ .auth-link a:hover { text-decoration: underline; -} +} \ No newline at end of file diff --git a/src/components/CallBack.jsx b/src/components/CallBack.jsx new file mode 100644 index 00000000..16cbd121 --- /dev/null +++ b/src/components/CallBack.jsx @@ -0,0 +1,27 @@ +// import React from "react"; +// import { useEffect } from "react"; +// import { useNavigate } from "react-router-dom"; +// import { useAuth0 } from "@auth0/auth0-react"; + +// const CallBack = () => { +// const { isLoading, error } = useAuth0(); +// const navigate = useNavigate(); + +// useEffect(() => { +// if (!isLoading && !error) { +// navigate("/"); +// } +// }, [isLoading, error, navigate]); + +// if (isLoading) { +// return

Loading...

; +// } + +// if (error) { +// return

Error: {error.message}

; +// } + +// return null; +// }; + +// export default CallBack; diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx new file mode 100644 index 00000000..dd35d656 --- /dev/null +++ b/src/components/Dashboard.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Activelistener from './ActiveListener'; + +const Dashboard = ({ user }) => { + // console.log("this is user from dashboard---> ", user) + return ( +
+

Dashboard

+ +
+ ) +}; + +export default Dashboard; \ No newline at end of file diff --git a/src/components/LIstenerCard.jsx b/src/components/LIstenerCard.jsx new file mode 100644 index 00000000..54e5181b --- /dev/null +++ b/src/components/LIstenerCard.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import logo from "../assets/LocalBeats.png" +import "./ListenerCard.css" + +const ListenerCard = () => { + return ( +
+
+ Profile picture +
+ +
+

Flo

+

Like a Love Song

+

New York, NY

+
+
Playing
+
+ ) +} + +export default ListenerCard; \ No newline at end of file diff --git a/src/components/ListenerCard.css b/src/components/ListenerCard.css new file mode 100644 index 00000000..9cc2c1c8 --- /dev/null +++ b/src/components/ListenerCard.css @@ -0,0 +1,17 @@ +.listener-card-container { + display: flex; + border-style: solid; + border-color: black; + border-radius: 1rem; + gap: 3rem; + align-items: center; + background-color: rgb(204, 203, 203); + +} + +.listener-card-status { + border-style: solid; + border-radius: .5rem; + box-sizing: border-box; + padding: .3rem; +} \ No newline at end of file diff --git a/src/components/Login.jsx b/src/components/Login.jsx index 849e495a..affef3dd 100644 --- a/src/components/Login.jsx +++ b/src/components/Login.jsx @@ -1,127 +1,62 @@ -import React, { useState } from "react"; -import { useNavigate, Link } from "react-router-dom"; -import axios from "axios"; -import { API_URL } from "../shared"; +import React, { useEffect } from "react"; +import axios from "../utils/axiosInstance"; +import { useNavigate } from "react-router-dom"; +import { useAuth0 } from "@auth0/auth0-react"; import "./AuthStyles.css"; const Login = ({ setUser }) => { - const [formData, setFormData] = useState({ - username: "", - password: "", - }); - const [errors, setErrors] = useState({}); - const [isLoading, setIsLoading] = useState(false); + const { loginWithRedirect, isAuthenticated, user: auth0User } = useAuth0(); const navigate = useNavigate(); - const validateForm = () => { - const newErrors = {}; - - if (!formData.username) { - newErrors.username = "Username is required"; - } else if (formData.username.length < 3 || formData.username.length > 20) { - newErrors.username = "Username must be between 3 and 20 characters"; - } - - if (!formData.password) { - newErrors.password = "Password is required"; - } else if (formData.password.length < 6) { - newErrors.password = "Password must be at least 6 characters"; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validateForm()) { - return; - } - - setIsLoading(true); - try { - const response = await axios.post(`${API_URL}/auth/login`, formData, { - withCredentials: true, - }); - - setUser(response.data.user); - navigate("/"); - } catch (error) { - if (error.response?.data?.error) { - setErrors({ general: error.response.data.error }); - } else { - setErrors({ general: "An error occurred during login" }); + useEffect(() => { + const verifySessionAndSetUser = async () => { + if (isAuthenticated && auth0User) { + try { + const res = await axios.get("/auth/me"); + if (res.data.user) { + setUser(res.data.user); + navigate("/"); + } else { + console.warn("No user returned from /auth/me"); + } + } catch (error) { + console.error("Error verifying session:", error.response?.data || error.message); + } } - } finally { - setIsLoading(false); - } - }; - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prev) => ({ - ...prev, - [name]: value, - })); - - // Clear error when user starts typing - if (errors[name]) { - setErrors((prev) => ({ - ...prev, - [name]: "", - })); - } - }; + }; + verifySessionAndSetUser(); + }, [isAuthenticated, auth0User, setUser, navigate]); return (
-
-

Login

- - {errors.general && ( -
{errors.general}
- )} - -
-
- - - {errors.username && ( - {errors.username} - )} -
- -
- - - {errors.password && ( - {errors.password} - )} -
- - -
- -

- Don't have an account? Sign up -

+

Discover What NYC is Vibing to!

+
+

Login with Spotify

+ +
); diff --git a/src/components/NavBar.jsx b/src/components/NavBar.jsx index 648f3808..991c61f2 100644 --- a/src/components/NavBar.jsx +++ b/src/components/NavBar.jsx @@ -1,24 +1,46 @@ import React from "react"; import { Link } from "react-router-dom"; import "./NavBarStyles.css"; +import logo from '../assets/LocalBeats.png' const NavBar = ({ user, onLogout }) => { return (