From ae3bfaadd1c8bd2f3daaceaf7e74244defb85813 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 4 Jun 2025 16:49:17 +0000 Subject: [PATCH] Signin/registration fixes, added SignInLanding.js --- backend/server.js | 63 ++++++++----- src/App.js | 154 ++++++++++++++++++++------------ src/components/SignIn.js | 17 +--- src/components/SignInLanding.js | 43 +++++++++ src/components/SignUp.js | 87 ++++++++++-------- 5 files changed, 227 insertions(+), 137 deletions(-) create mode 100644 src/components/SignInLanding.js diff --git a/backend/server.js b/backend/server.js index 758d4c2..d0f5e35 100755 --- a/backend/server.js +++ b/backend/server.js @@ -164,7 +164,6 @@ app.post('/api/register', async (req, res) => { (firstname, lastname, email, zipcode, state, area, career_situation) VALUES (?, ?, ?, ?, ?, ?, ?) `; - pool.query( profileQuery, [firstname, lastname, email, zipcode, state, area, career_situation], @@ -176,33 +175,51 @@ app.post('/api/register', async (req, res) => { .json({ error: 'Failed to create user profile' }); } - const newProfileId = resultProfile.insertId; // auto-increment PK + const newProfileId = resultProfile.insertId; // auto-increment PK from user_profile - // 2) Insert into user_auth, referencing user_profile.id + // 2) Insert into user_auth const authQuery = ` INSERT INTO user_auth (user_id, username, hashed_password) - VALUES (?, ?, ?) + VALUES (?, ?, ?) `; - pool.query( - authQuery, - [newProfileId, username, hashedPassword], - (errAuth) => { - if (errAuth) { - console.error('Error inserting user_auth:', errAuth.message); - if (errAuth.code === 'ER_DUP_ENTRY') { - return res - .status(400) - .json({ error: 'Username already exists' }); - } - return res.status(500).json({ error: 'Failed to register user' }); + pool.query(authQuery, [newProfileId, username, hashedPassword], async (errAuth) => { + if (errAuth) { + console.error('Error inserting user_auth:', errAuth.message); + if (errAuth.code === 'ER_DUP_ENTRY') { + return res + .status(400) + .json({ error: 'Username already exists' }); } - - return res.status(201).json({ - message: 'User registered successfully', - profileId: newProfileId, // the user_profile.id - }); + return res.status(500).json({ error: 'Failed to register user' }); } - ); + + // NEW: Now that we have the new user_profile.id (newProfileId), + // generate a JWT to auto-sign them in. + // We'll mimic what /api/signin does: + const token = jwt.sign({ id: newProfileId }, SECRET_KEY, { + expiresIn: '2h', + }); + + // Optionally fetch or build a user object. We already know: + // firstname, lastname, email, etc. from the request body. + const userPayload = { + firstname, + lastname, + email, + zipcode, + state, + area, + career_situation, + // any other fields you want + }; + + return res.status(201).json({ + message: 'User registered successfully', + profileId: newProfileId, // the user_profile.id + token, // NEW: the signed JWT + user: userPayload // optional: user info + }); + }); } ); } catch (err) { @@ -295,8 +312,6 @@ app.post('/api/signin', async (req, res) => { }); }); - - /* ------------------------------------------------------------------ CHECK USERNAME (MySQL) ------------------------------------------------------------------ */ diff --git a/src/App.js b/src/App.js index 1717726..371752c 100644 --- a/src/App.js +++ b/src/App.js @@ -9,11 +9,10 @@ import { } from 'react-router-dom'; import { Button } from './components/ui/button.js'; import { cn } from './utils/cn.js'; - -// Import all components +import PromptModal from './components/ui/PromptModal.js'; import PremiumRoute from './components/PremiumRoute.js'; import SessionExpiredHandler from './components/SessionExpiredHandler.js'; -import GettingStarted from './components/GettingStarted.js'; +import SignInLanding from './components/SignInLanding.js'; import SignIn from './components/SignIn.js'; import SignUp from './components/SignUp.js'; import PlanningLanding from './components/PlanningLanding.js'; @@ -30,19 +29,26 @@ import CareerRoadmap from './components/CareerRoadmap.js'; import Paywall from './components/Paywall.js'; import OnboardingContainer from './components/PremiumOnboarding/OnboardingContainer.js'; import MultiScenarioView from './components/MultiScenarioView.js'; - -// Import your ResumeRewrite component -import ResumeRewrite from './components/ResumeRewrite.js'; // adjust path if needed +import ResumeRewrite from './components/ResumeRewrite.js'; function App() { const navigate = useNavigate(); const location = useLocation(); + // Auth states const [isAuthenticated, setIsAuthenticated] = useState(false); const [user, setUser] = useState(null); + + // Loading state while verifying token const [isLoading, setIsLoading] = useState(true); - // Premium paths (including /enhancing, /retirement) + // Logout warning modal + const [showLogoutWarning, setShowLogoutWarning] = useState(false); + + // Check if user can access premium + const canAccessPremium = user?.is_premium || user?.is_pro_premium; + + // List of premium paths for your CTA logic const premiumPaths = [ '/career-roadmap', '/paywall', @@ -55,60 +61,92 @@ function App() { ]; const showPremiumCTA = !premiumPaths.includes(location.pathname); - // We'll define "canAccessPremium" for user - const canAccessPremium = user?.is_premium || user?.is_pro_premium; + // Helper to see if user is mid–premium-onboarding + function isOnboardingInProgress() { + try { + const stored = JSON.parse(localStorage.getItem('premiumOnboardingState') || '{}'); + // If step < 4 (example), user is in progress + return stored.step && stored.step < 4; + } catch (e) { + return false; + } + } - // ==================== - // Single Rehydrate UseEffect - // ==================== + // ============================== + // 1) Single Rehydrate UseEffect + // ============================== useEffect(() => { const token = localStorage.getItem('token'); - // No token? -> not authenticated if (!token) { + // No token => not authenticated setIsLoading(false); return; } - // Token exists, let's check with the server + // If we have a token, validate it by fetching user fetch('https://dev1.aptivaai.com/api/user-profile', { headers: { Authorization: `Bearer ${token}` }, }) .then((res) => { - if (!res.ok) { - // For example, 401 => invalid or expired token - throw new Error('Token invalid on server side'); - } + if (!res.ok) throw new Error('Token invalid on server side'); return res.json(); }) .then((profile) => { + // Successfully got user profile => user is authenticated setUser(profile); setIsAuthenticated(true); }) .catch((err) => { console.error(err); - // Server says token invalid -> remove from localStorage + // Invalid token => remove it, force sign in localStorage.removeItem('token'); - // Force user to sign in again navigate('/signin?session=expired'); }) .finally(() => { + // Either success or fail, we're done loading setIsLoading(false); }); }, [navigate]); - // ==================== - // Logout Handler - // ==================== - const handleLogout = () => { + // ========================== + // 2) Logout Handler + Modal + // ========================== + const handleLogoutClick = () => { + if (isOnboardingInProgress()) { + // Show a modal to confirm losing onboarding data + setShowLogoutWarning(true); + } else { + // No onboarding => just logout + confirmLogout(); + } + }; + + const confirmLogout = () => { + // Clear relevant localStorage keys localStorage.removeItem('token'); localStorage.removeItem('careerSuggestionsCache'); + localStorage.removeItem('lastSelectedCareerProfileId'); + localStorage.removeItem('aiClickCount'); + localStorage.removeItem('aiClickDate'); + localStorage.removeItem('aiRecommendations'); + localStorage.removeItem('premiumOnboardingState'); + + // Reset auth setIsAuthenticated(false); setUser(null); + setShowLogoutWarning(false); + navigate('/signin'); }; - // If we're still verifying the token, show a loading indicator + const cancelLogout = () => { + setShowLogoutWarning(false); + }; + + // ==================================== + // 3) If still verifying the token, show loading + // ==================================== if (isLoading) { return (
@@ -117,9 +155,9 @@ function App() { ); } - // ==================== - // Main App Render - // ==================== + // ===================== + // Main Render / Layout + // ===================== return (
{/* Header */} @@ -132,7 +170,7 @@ function App() { <> {/* NAV MENU */}
- {/* 4) Retirement Planning (Premium) */} + {/* 4) Retirement (Premium) */}
@@ -331,18 +352,33 @@ function App() { Upgrade to Premium )} + + {/* LOGOUT BUTTON */} + + {/* SHOW WARNING MODAL IF needed */} + {showLogoutWarning && ( + + )}
)} - {/* Main Content */} + {/* MAIN CONTENT */}
{/* Default to /signin */} @@ -358,22 +394,22 @@ function App() { /> } /> - } /> + } + /> } /> {/* Authenticated routes */} {isAuthenticated && ( <> - } /> + }/> } /> } /> } /> } /> } /> - } - /> + } /> } /> {/* Premium-only routes */} diff --git a/src/components/SignIn.js b/src/components/SignIn.js index f68aa4e..ebde410 100644 --- a/src/components/SignIn.js +++ b/src/components/SignIn.js @@ -55,21 +55,8 @@ function SignIn({ setIsAuthenticated, setUser }) { // Store the full user object in state, so we can check user.is_premium, etc. if (setUser && user) { setUser(user); - } - - const userCareerSituation = user.career_situation; - - const careerSituationRouteMap = { - planning: '/planning', - preparing: '/preparing', - enhancing: '/enhancing', - retirement: '/retirement', - }; - - if (careerSituationRouteMap[userCareerSituation]) { - navigate(careerSituationRouteMap[userCareerSituation]); - } else { - navigate('/getting-started'); // fallback if undefined + + navigate('/signin-landing'); // fallback if undefined } } catch (error) { diff --git a/src/components/SignInLanding.js b/src/components/SignInLanding.js new file mode 100644 index 0000000..7e38370 --- /dev/null +++ b/src/components/SignInLanding.js @@ -0,0 +1,43 @@ +// SignInLanding.jsx +import React from 'react'; +import { Link } from 'react-router-dom'; + +function SignInLanding({ user }) { + return ( +
+

+ Welcome to AptivaAI {user?.firstname}! +

+

+ At AptivaAI, we aim to arm you with all the knowledge and guidance we wish we had when making our own career decisions. Today’s workplace is changing faster than ever, driven largely by AI—but our goal is to use that same technology to empower job seekers, not replace them. + +We blend data-backed insights with human-centered design, giving you practical recommendations and real-world context so you stay in the driver’s seat of your career. Whether you’re planning your first step, enhancing your current role, or ready to pivot entirely, our platform keeps you in control—helping you adapt, grow, and thrive on your own terms. +

+
    +
  • Planning: Just starting out? Looking for a different career that is a better fit? Explore options and figure out what careers match your interests and skills.
  • +
  • Preparing: Know what you want but just not how to get there? Gain education, skills, or certifications required to start or transition.
  • +
  • Enhancing: You've got some experience in your field but want to know how to get to the next level? Advance, seek promotions, or shift roles for an established professional.
  • +
  • Retirement: On your happy path and want to make sure you're financially ready when the time comes? Prepare financially and strategically for retirement.
  • +
+

+ Where would you like to go next? +

+
+ + Go to Planning + + + Go to Preparing + + + Go to Enhancing + + + Go to Retirement + +
+
+ ); +} + +export default SignInLanding; diff --git a/src/components/SignUp.js b/src/components/SignUp.js index 6a45141..3f02109 100644 --- a/src/components/SignUp.js +++ b/src/components/SignUp.js @@ -174,47 +174,56 @@ function SignUp() { }; - const handleSituationConfirm = async () => { - - try { - console.log("Payload sent to backend:", { - username, password, firstname, lastname, email, zipcode, state, area, + // SignUp.jsx +const handleSituationConfirm = async () => { + setShowPrompt(false); + + try { + const response = await fetch('/api/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username, + password, + firstname, + lastname, + email, + zipcode, + state, + area, career_situation: selectedSituation.id - }); - - const response = await fetch('/api/register', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - username, - password, - firstname, - lastname, - email, - zipcode, - state, - area, - career_situation: selectedSituation.id - }), -}); - - const data = await response.json(); - - if (!response.ok) { - setError(data.error || 'Registration failed. Please try again.'); - setShowPrompt(false); - return; - } - - navigate(selectedSituation.route); - } catch (err) { - console.error(err); - setError('An unexpected error occurred. Please try again later.'); - setShowPrompt(false); + }), + }); + + const data = await response.json(); + if (!response.ok) { + setError(data.error || 'Registration failed. Please try again.'); + return; } - }; - - + + // If the server returns a token, store it so that App.js will consider them authenticated + if (data.token) { + localStorage.setItem('token', data.token); + } + + // If the server also returned 'user', set it in the main state + // But we need a way to pass "setUser" down to SignUp or use a context + if (data.user) { + setUsername(data.user); + } else { + // Optionally, fetch user from /api/user-profile: + // This ensures your user object is set in App.js + // But if your App.js auto-fetches the user from the token, you can skip this + } + + // Now that we have a token + user, let's direct them to the route + navigate(selectedSituation.route); + } catch (err) { + console.error('Registration error:', err); + setError('An unexpected error occurred. Please try again later.'); + } +}; + return (