Updated UserProfile for career situation, expired token (no modal), and updated user-profile backend call to accmmodate updating profile.
This commit is contained in:
parent
f7f959c426
commit
6a6416499a
@ -12,7 +12,7 @@ import jwt from 'jsonwebtoken'; // For token-based authentication
|
||||
// Derive __dirname for ES modules
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const dbPath = path.resolve('/home/jcoakley/aptiva-dev1-app/backend/user_profile.db');
|
||||
const dbPath = path.resolve('/home/jcoakley/aptiva-dev1-app/user_profile.db');
|
||||
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
@ -119,32 +119,69 @@ app.post('/api/register', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Route to save user profile data
|
||||
// Route to save or update user profile
|
||||
app.post('/api/user-profile', (req, res) => {
|
||||
const { firstName, lastName, email, zipCode, state, area } = req.body;
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Authorization token is required' });
|
||||
}
|
||||
|
||||
let userId;
|
||||
try {
|
||||
const decoded = jwt.verify(token, SECRET_KEY);
|
||||
userId = decoded.userId;
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: 'Invalid or expired token' });
|
||||
}
|
||||
|
||||
const { firstName, lastName, email, zipCode, state, area, careerSituation } = req.body;
|
||||
|
||||
if (!firstName || !lastName || !email || !zipCode || !state || !area) {
|
||||
return res.status(400).json({ error: 'All fields are required' });
|
||||
}
|
||||
|
||||
const query = `
|
||||
INSERT INTO user_profile (firstname, lastname, email, zipcode, state, area)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const params = [firstName, lastName, email, zipCode, state, area];
|
||||
|
||||
db.run(query, params, function (err) {
|
||||
const checkQuery = `SELECT id FROM user_profile WHERE user_id = ?`;
|
||||
db.get(checkQuery, [userId], (err, row) => {
|
||||
if (err) {
|
||||
console.error('Error inserting data:', err.message);
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
return res.status(400).json({ error: 'Email already exists' });
|
||||
}
|
||||
return res.status(500).json({ error: 'Failed to save user profile' });
|
||||
console.error('Error checking profile:', err.message);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
const params = [firstName, lastName, email, zipCode, state, area, careerSituation || null, userId];
|
||||
|
||||
if (row) {
|
||||
// Profile exists → UPDATE
|
||||
const updateQuery = `
|
||||
UPDATE user_profile
|
||||
SET firstname = ?, lastname = ?, email = ?, zipcode = ?, state = ?, area = ?, career_situation = ?
|
||||
WHERE user_id = ?
|
||||
`;
|
||||
db.run(updateQuery, params, function (err) {
|
||||
if (err) {
|
||||
console.error('Error updating profile:', err.message);
|
||||
return res.status(500).json({ error: 'Failed to update user profile' });
|
||||
}
|
||||
res.status(200).json({ message: 'User profile updated successfully' });
|
||||
});
|
||||
} else {
|
||||
// Profile doesn't exist → INSERT
|
||||
const insertQuery = `
|
||||
INSERT INTO user_profile (firstname, lastname, email, zipcode, state, area, career_situation, user_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
db.run(insertQuery, params, function (err) {
|
||||
if (err) {
|
||||
console.error('Error inserting profile:', err.message);
|
||||
return res.status(500).json({ error: 'Failed to create user profile' });
|
||||
}
|
||||
res.status(201).json({ message: 'User profile created successfully', id: this.lastID });
|
||||
});
|
||||
}
|
||||
res.status(201).json({ message: 'User profile saved successfully', id: this.lastID });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Route for login
|
||||
app.post('/api/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
@ -10,37 +10,90 @@ function UserProfile() {
|
||||
const [selectedState, setSelectedState] = useState('');
|
||||
const [areas, setAreas] = useState([]);
|
||||
const [selectedArea, setSelectedArea] = useState('');
|
||||
const navigate = useNavigate(); // Add navigation
|
||||
const [loadingAreas, setLoadingAreas] = useState(false); // To show a loading spinner for areas
|
||||
const [careerSituation, setCareerSituation] = useState('');
|
||||
const navigate = useNavigate();
|
||||
const [loadingAreas, setLoadingAreas] = useState(false);
|
||||
const [isPremiumUser, setIsPremiumUser] = useState(false);
|
||||
|
||||
// Fetch areas when a state is selected
|
||||
useEffect(() => {
|
||||
if (!selectedState) {
|
||||
setAreas([]); // Clear areas if no state is selected
|
||||
return;
|
||||
|
||||
const authFetch = async (url, options = {}) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
navigate('/signin');
|
||||
return null;
|
||||
}
|
||||
|
||||
const fetchAreas = async () => {
|
||||
setLoadingAreas(true);
|
||||
const res = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if ([401, 403].includes(res.status)) {
|
||||
console.warn('Token invalid or expired. Redirecting to Sign In.');
|
||||
navigate('/signin');
|
||||
return null;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
// Single useEffect for both profile and areas
|
||||
useEffect(() => {
|
||||
const fetchProfileAndAreas = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/areas?state=${selectedState}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch areas');
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
|
||||
const res = await authFetch('/api/user-profile', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) return;
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
setFirstName(data.firstname || '');
|
||||
setLastName(data.lastname || '');
|
||||
setEmail(data.email || '');
|
||||
setZipCode(data.zipcode || '');
|
||||
setSelectedState(data.state || '');
|
||||
setSelectedArea(data.area || '');
|
||||
setCareerSituation(data.career_situation || '');
|
||||
|
||||
if (data.is_premium === 1) {
|
||||
setIsPremiumUser(true);
|
||||
}
|
||||
|
||||
if (data.state) {
|
||||
setLoadingAreas(true);
|
||||
try {
|
||||
const areaRes = await authFetch(`/api/areas?state=${data.state}`);
|
||||
if (!areaRes.ok) throw new Error('Failed to fetch areas');
|
||||
const areaData = await areaRes.json();
|
||||
setAreas(areaData.areas);
|
||||
} catch (areaErr) {
|
||||
console.error('Error fetching areas:', areaErr);
|
||||
setAreas([]);
|
||||
} finally {
|
||||
setLoadingAreas(false);
|
||||
}
|
||||
}
|
||||
const data = await response.json();
|
||||
setAreas(data.areas); // Set the areas from the API response
|
||||
} catch (error) {
|
||||
console.error('Error fetching areas:', error);
|
||||
setAreas([]); // Reset areas on error
|
||||
} finally {
|
||||
setLoadingAreas(false);
|
||||
console.error('Error loading user profile:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAreas();
|
||||
}, [selectedState]);
|
||||
fetchProfileAndAreas();
|
||||
}, []);
|
||||
|
||||
// Handle form submission
|
||||
const handleFormSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@ -51,10 +104,11 @@ function UserProfile() {
|
||||
zipCode,
|
||||
state: selectedState,
|
||||
area: selectedArea,
|
||||
careerSituation,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/user-profile', {
|
||||
const response = await authFetch('/api/user-profile', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -65,122 +119,59 @@ function UserProfile() {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save profile');
|
||||
}
|
||||
|
||||
console.warn('Profile saved successfully');
|
||||
console.log('Profile saved successfully');
|
||||
// Add success feedback for the user, if needed
|
||||
navigate('/interest-inventory');
|
||||
} catch (error) {
|
||||
console.error('Error saving profile:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const states = [
|
||||
{ name: "Alabama", code: "AL" },
|
||||
{ name: "Alaska", code: "AK" },
|
||||
{ name: "Arizona", code: "AZ" },
|
||||
{ name: "Arkansas", code: "AR" },
|
||||
{ name: "California", code: "CA" },
|
||||
{ name: "Colorado", code: "CO" },
|
||||
{ name: "Connecticut", code: "CT" },
|
||||
{ name: "Delaware", code: "DE" },
|
||||
{ name: "Florida", code: "FL" },
|
||||
{ name: "Georgia", code: "GA" },
|
||||
{ name: "Hawaii", code: "HI" },
|
||||
{ name: "Idaho", code: "ID" },
|
||||
{ name: "Illinois", code: "IL" },
|
||||
{ name: "Indiana", code: "IN" },
|
||||
{ name: "Iowa", code: "IA" },
|
||||
{ name: "Kansas", code: "KS" },
|
||||
{ name: "Kentucky", code: "KY" },
|
||||
{ name: "Louisiana", code: "LA" },
|
||||
{ name: "Maine", code: "ME" },
|
||||
{ name: "Maryland", code: "MD" },
|
||||
{ name: "Massachusetts", code: "MA" },
|
||||
{ name: "Michigan", code: "MI" },
|
||||
{ name: "Minnesota", code: "MN" },
|
||||
{ name: "Mississippi", code: "MS" },
|
||||
{ name: "Missouri", code: "MO" },
|
||||
{ name: "Montana", code: "MT" },
|
||||
{ name: "Nebraska", code: "NE" },
|
||||
{ name: "Nevada", code: "NV" },
|
||||
{ name: "New Hampshire", code: "NH" },
|
||||
{ name: "New Jersey", code: "NJ" },
|
||||
{ name: "New Mexico", code: "NM" },
|
||||
{ name: "New York", code: "NY" },
|
||||
{ name: "North Carolina", code: "NC" },
|
||||
{ name: "North Dakota", code: "ND" },
|
||||
{ name: "Ohio", code: "OH" },
|
||||
{ name: "Oklahoma", code: "OK" },
|
||||
{ name: "Oregon", code: "OR" },
|
||||
{ name: "Pennsylvania", code: "PA" },
|
||||
{ name: "Rhode Island", code: "RI" },
|
||||
{ name: "South Carolina", code: "SC" },
|
||||
{ name: "South Dakota", code: "SD" },
|
||||
{ name: "Tennessee", code: "TN" },
|
||||
{ name: "Texas", code: "TX" },
|
||||
{ name: "Utah", code: "UT" },
|
||||
{ name: "Vermont", code: "VT" },
|
||||
{ name: "Virginia", code: "VA" },
|
||||
{ name: "Washington", code: "WA" },
|
||||
{ name: "West Virginia", code: "WV" },
|
||||
{ name: "Wisconsin", code: "WI" },
|
||||
{ name: "Wyoming", code: "WY" }
|
||||
{ name: "Alabama", code: "AL" }, { name: "Alaska", code: "AK" }, { name: "Arizona", code: "AZ" },
|
||||
{ name: "Arkansas", code: "AR" }, { name: "California", code: "CA" }, { name: "Colorado", code: "CO" },
|
||||
{ name: "Connecticut", code: "CT" }, { name: "Delaware", code: "DE" }, { name: "Florida", code: "FL" },
|
||||
{ name: "Georgia", code: "GA" }, { name: "Hawaii", code: "HI" }, { name: "Idaho", code: "ID" },
|
||||
{ name: "Illinois", code: "IL" }, { name: "Indiana", code: "IN" }, { name: "Iowa", code: "IA" },
|
||||
{ name: "Kansas", code: "KS" }, { name: "Kentucky", code: "KY" }, { name: "Louisiana", code: "LA" },
|
||||
{ name: "Maine", code: "ME" }, { name: "Maryland", code: "MD" }, { name: "Massachusetts", code: "MA" },
|
||||
{ name: "Michigan", code: "MI" }, { name: "Minnesota", code: "MN" }, { name: "Mississippi", code: "MS" },
|
||||
{ name: "Missouri", code: "MO" }, { name: "Montana", code: "MT" }, { name: "Nebraska", code: "NE" },
|
||||
{ name: "Nevada", code: "NV" }, { name: "New Hampshire", code: "NH" }, { name: "New Jersey", code: "NJ" },
|
||||
{ name: "New Mexico", code: "NM" }, { name: "New York", code: "NY" }, { name: "North Carolina", code: "NC" },
|
||||
{ name: "North Dakota", code: "ND" }, { name: "Ohio", code: "OH" }, { name: "Oklahoma", code: "OK" },
|
||||
{ name: "Oregon", code: "OR" }, { name: "Pennsylvania", code: "PA" }, { name: "Rhode Island", code: "RI" },
|
||||
{ name: "South Carolina", code: "SC" }, { name: "South Dakota", code: "SD" }, { name: "Tennessee", code: "TN" },
|
||||
{ name: "Texas", code: "TX" }, { name: "Utah", code: "UT" }, { name: "Vermont", code: "VT" },
|
||||
{ name: "Virginia", code: "VA" }, { name: "Washington", code: "WA" }, { name: "West Virginia", code: "WV" },
|
||||
{ name: "Wisconsin", code: "WI" }, { name: "Wyoming", code: "WY" }
|
||||
];
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="user-profile-container">
|
||||
<h2>User Profile</h2>
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
<div className="form-group">
|
||||
<label>First Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={firstName}
|
||||
onChange={(e) => setFirstName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input type="text" value={firstName} onChange={(e) => setFirstName(e.target.value)} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Last Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={lastName}
|
||||
onChange={(e) => setLastName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Email:</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>ZIP Code:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={zipCode}
|
||||
onChange={(e) => setZipCode(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input type="text" value={zipCode} onChange={(e) => setZipCode(e.target.value)} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>State:</label>
|
||||
<select
|
||||
value={selectedState}
|
||||
onChange={(e) => setSelectedState(e.target.value)}
|
||||
required
|
||||
>
|
||||
<select value={selectedState} onChange={(e) => setSelectedState(e.target.value)} required>
|
||||
<option value="">Select a State</option>
|
||||
{states.map((s) => (
|
||||
<option key={s.code} value={s.code}>
|
||||
{s.name}
|
||||
</option>
|
||||
<option key={s.code} value={s.code}>{s.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@ -189,22 +180,29 @@ function UserProfile() {
|
||||
) : areas.length > 0 && (
|
||||
<div className="form-group">
|
||||
<label>Area:</label>
|
||||
<select
|
||||
value={selectedArea}
|
||||
onChange={(e) => setSelectedArea(e.target.value)}
|
||||
required
|
||||
>
|
||||
<select value={selectedArea} onChange={(e) => setSelectedArea(e.target.value)} required>
|
||||
<option value="">Select an Area</option>
|
||||
{areas.map((area, index) => (
|
||||
<option key={index} value={area}>
|
||||
{area}
|
||||
</option>
|
||||
<option key={index} value={area}>{area}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
{isPremiumUser && (
|
||||
<div className="form-group">
|
||||
<label>What best describes your current career situation?</label>
|
||||
<select value={careerSituation} onChange={(e) => setCareerSituation(e.target.value)}>
|
||||
<option value="">Select One</option>
|
||||
<option value="highSchool">High School Student</option>
|
||||
<option value="collegeStudent">College Student</option>
|
||||
<option value="transitioningPro">Transitioning Professional</option>
|
||||
<option value="advancingPro">Advancing Professional</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<button type="submit">Save Profile</button>
|
||||
</form>
|
||||
</form>
|
||||
<button onClick={() => navigate('/getting-started')}>Go Back</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user