143 lines
5.0 KiB
JavaScript
143 lines
5.0 KiB
JavaScript
import React, { useRef, useState, useEffect, useContext } from 'react';
|
||
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
||
import { ProfileCtx } from '../App.js';
|
||
|
||
function SignIn({ setIsAuthenticated, setUser }) {
|
||
const navigate = useNavigate();
|
||
const { setFinancialProfile, setScenario } = useContext(ProfileCtx);
|
||
const usernameRef = useRef('');
|
||
const passwordRef = useRef('');
|
||
const [error, setError] = useState('');
|
||
const [showSessionExpiredMsg, setShowSessionExpiredMsg] = useState(false);
|
||
const location = useLocation();
|
||
const apiUrl = process.env.REACT_APP_API_URL;
|
||
|
||
useEffect(() => {
|
||
// Check if the URL query param has ?session=expired
|
||
const query = new URLSearchParams(location.search);
|
||
if (query.get('session') === 'expired') {
|
||
setShowSessionExpiredMsg(true);
|
||
}
|
||
}, [location.search]);
|
||
|
||
const handleSignIn = async (event) => {
|
||
event.preventDefault();
|
||
setError('');
|
||
|
||
// 0️⃣ clear everything that belongs to the *previous* user
|
||
localStorage.removeItem('careerSuggestionsCache');
|
||
localStorage.removeItem('lastSelectedCareerProfileId');
|
||
localStorage.removeItem('aiClickCount');
|
||
localStorage.removeItem('aiClickDate');
|
||
localStorage.removeItem('aiRecommendations');
|
||
localStorage.removeItem('premiumOnboardingState');
|
||
localStorage.removeItem('financialProfile'); // if you cache it
|
||
localStorage.removeItem('selectedScenario');
|
||
|
||
const username = usernameRef.current.value;
|
||
const password = passwordRef.current.value;
|
||
|
||
if (!username || !password) {
|
||
setError('Please enter both username and password');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const resp = await fetch(`${apiUrl}/signin`, {
|
||
method : 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body : JSON.stringify(username, password),
|
||
});
|
||
|
||
const data = await resp.json(); // ← read ONCE
|
||
|
||
if (!resp.ok) throw new Error(data.error || 'Failed to sign in');
|
||
|
||
/* ---------------- success path ---------------- */
|
||
const { token, id, user } = data;
|
||
|
||
// fetch current user profile immediately
|
||
const profileRes = await fetch('/api/user-profile', {
|
||
headers: { Authorization: `Bearer ${token}` }
|
||
});
|
||
const profile = await profileRes.json();
|
||
setFinancialProfile(profile);
|
||
setScenario(null); // or fetch latest scenario separately
|
||
|
||
/* purge any leftovers from prior session */
|
||
['careerSuggestionsCache',
|
||
'lastSelectedCareerProfileId',
|
||
'aiClickCount',
|
||
'aiClickDate',
|
||
'aiRecommendations',
|
||
'premiumOnboardingState',
|
||
'financialProfile',
|
||
'selectedScenario'
|
||
].forEach(k => localStorage.removeItem(k));
|
||
|
||
/* store new session data */
|
||
localStorage.setItem('token', token);
|
||
localStorage.setItem('id', id);
|
||
|
||
setIsAuthenticated(true);
|
||
setUser(user);
|
||
navigate('/signin-landing');
|
||
} catch (err) {
|
||
setError(err.message);
|
||
}
|
||
};
|
||
|
||
return (
|
||
|
||
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-100 p-4">
|
||
{showSessionExpiredMsg && (
|
||
<div className="mb-4 p-2 bg-red-100 border border-red-300 text-red-700 rounded">
|
||
Your session has expired. Please sign in again.
|
||
</div>
|
||
)}
|
||
<div className="w-full max-w-sm rounded-md bg-white p-6 shadow-md">
|
||
<h1 className="mb-6 text-center text-2xl font-semibold">Sign In</h1>
|
||
|
||
{error && (
|
||
<p className="mb-4 rounded bg-red-50 p-2 text-sm text-red-600">
|
||
{error}
|
||
</p>
|
||
)}
|
||
|
||
<form onSubmit={handleSignIn} className="flex flex-col space-y-4">
|
||
<input
|
||
type="text"
|
||
placeholder="Username"
|
||
ref={usernameRef}
|
||
className="w-full rounded border border-gray-300 p-2 focus:border-blue-500 focus:outline-none"
|
||
/>
|
||
<input
|
||
type="password"
|
||
placeholder="Password"
|
||
ref={passwordRef}
|
||
className="w-full rounded border border-gray-300 p-2 focus:border-blue-500 focus:outline-none"
|
||
/>
|
||
<button
|
||
type="submit"
|
||
className="mx-auto rounded bg-blue-600 px-6 py-2 text-center text-white transition-colors hover:bg-blue-700 focus:outline-none"
|
||
>
|
||
Sign In
|
||
</button>
|
||
</form>
|
||
|
||
<p className="mt-4 text-center text-sm text-gray-600">
|
||
Don’t have an account?{' '}
|
||
<Link
|
||
to="/signup" // <- here
|
||
className="font-medium text-blue-600 hover:text-blue-500"
|
||
>
|
||
Sign Up
|
||
</Link>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default SignIn;
|