diff --git a/backend/server.js b/backend/server.js index 82273d2..66fd620 100755 --- a/backend/server.js +++ b/backend/server.js @@ -219,10 +219,20 @@ app.post('/api/signin', async (req, res) => { return res.status(400).json({ error: 'Both username and password are required' }); } - const query = `SELECT user_auth.user_id, user_auth.hashed_password, user_profile.zipcode - FROM user_auth - LEFT JOIN user_profile ON user_auth.user_id = user_profile.user_id - WHERE user_auth.username = ?`; + // JOIN user_profile to fetch is_premium, email, or whatever columns you need + const query = ` + SELECT + user_auth.user_id, + user_auth.hashed_password, + user_profile.zipcode, + user_profile.is_premium, + user_profile.email, + user_profile.firstname, + user_profile.lastname + FROM user_auth + LEFT JOIN user_profile ON user_auth.user_id = user_profile.user_id + WHERE user_auth.username = ? + `; db.get(query, [username], async (err, row) => { if (err) { @@ -230,33 +240,43 @@ app.post('/api/signin', async (req, res) => { return res.status(500).json({ error: 'Failed to query user authentication data' }); } - console.log('Row data:', row); // Log the result of the query - + // If no matching username if (!row) { return res.status(401).json({ error: 'Invalid username or password' }); } - // Verify password + // Verify the password using bcrypt const isMatch = await bcrypt.compare(password, row.hashed_password); if (!isMatch) { return res.status(401).json({ error: 'Invalid username or password' }); } - // Ensure that you're using the correct user_id - const { user_id, zipcode } = row; + // user_id from the row + const { user_id } = row; - console.log('UserID:', user_id); - console.log('ZIP Code:', zipcode); // Log the ZIP code to ensure it's correct - - // Send correct token with user_id + // Generate JWT const token = jwt.sign({ userId: user_id }, SECRET_KEY, { expiresIn: '2h' }); - // You can optionally return the ZIP code or any other data as part of the response - res.status(200).json({ message: 'Login successful', token, userId: user_id, zipcode }); + // Return user object including is_premium and other columns + // The front end can store this in state (e.g. setUser). + res.status(200).json({ + message: 'Login successful', + token, + userId: user_id, + user: { + user_id, + firstname: row.firstname, + lastname: row.lastname, + email: row.email, + zipcode: row.zipcode, + is_premium: row.is_premium, + } + }); }); }); + // Route to fetch user profile app.get('/api/user-profile', (req, res) => { const token = req.headers.authorization?.split(' ')[1]; diff --git a/src/App.js b/src/App.js index 124febe..cee6881 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom'; +import PremiumRoute from './components/PremiumRoute.js'; import SessionExpiredHandler from './components/SessionExpiredHandler.js'; import GettingStarted from './components/GettingStarted.js'; import SignIn from './components/SignIn.js'; @@ -8,8 +9,8 @@ import InterestInventory from './components/InterestInventory.js'; import Dashboard from './components/Dashboard.js'; import UserProfile from './components/UserProfile.js'; import FinancialProfileForm from './components/FinancialProfileForm.js'; -import MilestoneTracker from "./components/MilestoneTracker.js"; -import Paywall from "./components/Paywall.js"; +import MilestoneTracker from './components/MilestoneTracker.js'; +import Paywall from './components/Paywall.js'; import OnboardingContainer from './components/PremiumOnboarding/OnboardingContainer.js'; import MultiScenarioView from './components/MultiScenarioView.js'; @@ -17,18 +18,22 @@ function App() { const navigate = useNavigate(); const location = useLocation(); + // Track whether user is authenticated const [isAuthenticated, setIsAuthenticated] = useState(() => { return !!localStorage.getItem('token'); }); - // Hide the Upgrade CTA on these paths: + // Track the user object, including is_premium + const [user, setUser] = useState(null); + + // We hide the "Upgrade to Premium" CTA if we're already on certain premium routes const premiumPaths = [ '/milestone-tracker', '/paywall', '/financial-profile', '/multi-scenario', + '/premium-onboarding' ]; - const showPremiumCTA = !premiumPaths.includes(location.pathname); return ( @@ -51,28 +56,69 @@ function App() { {/* Main Content */}
+ {/* Default to /signin if no path */} } /> + + {/* Public routes */} } + element={ + + } /> } /> + + {/* Paywall - open to everyone for subscription */} } /> + {/* Authenticated routes */} {isAuthenticated && ( <> } /> } /> } /> } /> - } /> - } /> - } /> - } /> + + {/* Premium-only routes use */} + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> )} - {/* Fallback */} + {/* Fallback if route not found */} } />
diff --git a/src/components/CareerSearch.js b/src/components/CareerSearch.js index c7c3492..ad4d8eb 100644 --- a/src/components/CareerSearch.js +++ b/src/components/CareerSearch.js @@ -84,7 +84,7 @@ const CareerSearch = ({ onCareerSelected }) => { ); diff --git a/src/components/Paywall.js b/src/components/Paywall.js index 2513c0c..05bf806 100644 --- a/src/components/Paywall.js +++ b/src/components/Paywall.js @@ -1,12 +1,19 @@ -// Paywall.js import React from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; const Paywall = () => { const navigate = useNavigate(); + const { state } = useLocation(); + // Extract the selectedCareer from location state + const { selectedCareer } = state || {}; const handleSubscribe = () => { - navigate('/milestone-tracker'); + // Once the user subscribes, navigate to MilestoneTracker + navigate('/PremiumOnboarding', { + state: { + selectedCareer, + }, + }); }; return ( diff --git a/src/components/PopoutPanel.js b/src/components/PopoutPanel.js index 9cf6d33..5db9c75 100644 --- a/src/components/PopoutPanel.js +++ b/src/components/PopoutPanel.js @@ -111,15 +111,16 @@ function PopoutPanel({ closePanel(); } - // Original PlanMyPath logic async function handlePlanMyPath() { if (!token) { alert("You need to be logged in to create a career path."); return; } + try { + // 1) Fetch existing career profiles (a.k.a. "careerPaths") const allPathsResponse = await fetch( - `${process.env.REACT_APP_API_URL}/premium/planned-path/all`, + `${process.env.REACT_APP_API_URL}/premium/career-profile/all`, { method: "GET", headers: { @@ -128,47 +129,67 @@ function PopoutPanel({ }, } ); - if (!allPathsResponse.ok) throw new Error(`HTTP error ${allPathsResponse.status}`); - - const { careerPath } = await allPathsResponse.json(); - const match = careerPath.find((path) => path.career_name === data.title); - + + if (!allPathsResponse.ok) { + throw new Error(`HTTP error ${allPathsResponse.status}`); + } + + // The server returns { careerPaths: [...] } + const { careerPaths } = await allPathsResponse.json(); + + // 2) Check if there's already a career path with the same name + const match = careerPaths.find((cp) => cp.career_name === data.title); + if (match) { + // If a path already exists for this career, confirm with the user const decision = window.confirm( - `A career path for "${data.title}" already exists.\n\n` + + `A career path (scenario) for "${data.title}" already exists.\n\n` + `Click OK to RELOAD the existing path.\nClick Cancel to CREATE a new one.` ); if (decision) { + // Reload existing path → go to Paywall navigate("/paywall", { state: { - selectedCareer: { career_path_id: match.id, career_name: data.title }, + selectedCareer: { + career_path_id: match.id, // 'id' is the primary key from the DB + career_name: data.title, + }, }, }); return; } } - - const newCareerPath = { - career_path_id: uuidv4(), - career_name: data.title, - }; + + // 3) Otherwise, create a new career profile using POST /premium/career-profile const newResponse = await fetch( - `${process.env.REACT_APP_API_URL}/premium/planned-path`, + `${process.env.REACT_APP_API_URL}/premium/career-profile`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - body: JSON.stringify(newCareerPath), + body: JSON.stringify({ + // The server expects at least career_name + career_name: data.title, + // Optionally pass scenario_title, start_date, etc. + }), } ); - if (!newResponse.ok) throw new Error("Failed to create new career path."); - - navigate("/milestone-tracker", { + + if (!newResponse.ok) { + throw new Error("Failed to create new career path."); + } + + // The server returns something like { message: 'Career profile upserted.', career_path_id: 'xxx-xxx' } + const result = await newResponse.json(); + const newlyCreatedId = result?.career_path_id; + + // 4) Navigate to /paywall, passing the newly created career_path_id + navigate("/paywall", { state: { selectedCareer: { - career_path_id: newCareerPath.career_path_id, + career_path_id: newlyCreatedId, career_name: data.title, }, }, @@ -177,6 +198,7 @@ function PopoutPanel({ console.error("Error in Plan My Path:", error); } } + // Filter & sort schools const filteredAndSortedSchools = [...schools] diff --git a/src/components/PremiumRoute.js b/src/components/PremiumRoute.js new file mode 100644 index 0000000..5257388 --- /dev/null +++ b/src/components/PremiumRoute.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; + +function PremiumRoute({ user, children }) { + if (!user) { + // Not even logged in; go to sign in + return ; + } + + if (!user.is_premium) { + // Logged in but not premium; go to paywall + return ; + } + + // User is logged in and has premium + return children; +} + +export default PremiumRoute; diff --git a/src/components/SignIn.js b/src/components/SignIn.js index e672e4a..f784ae0 100644 --- a/src/components/SignIn.js +++ b/src/components/SignIn.js @@ -1,7 +1,7 @@ import React, { useRef, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -function SignIn({ setIsAuthenticated }) { +function SignIn({ setIsAuthenticated, setUser }) { const navigate = useNavigate(); const usernameRef = useRef(''); const passwordRef = useRef(''); @@ -32,12 +32,22 @@ function SignIn({ setIsAuthenticated }) { } const data = await response.json(); - const { token, userId } = data; + // Destructure user, which includes is_premium, etc. + const { token, userId, user } = data; + // Store token & userId in localStorage localStorage.setItem('token', token); localStorage.setItem('userId', userId); + // Mark user as authenticated setIsAuthenticated(true); + + // Store the full user object in state, so we can check user.is_premium, etc. + if (setUser && user) { + setUser(user); + } + + // Navigate to next screen navigate('/getting-started'); } catch (error) { setError(error.message); @@ -48,13 +58,13 @@ function SignIn({ setIsAuthenticated }) {

Sign In

- + {error && (

{error}

)} - +
- +

Don’t have an account?{' '}