Removed UserID since ChatGPT can't code two similar variables

This commit is contained in:
Josh 2025-05-22 14:26:19 +00:00
parent 56555b9e8f
commit 9d5471b55e
6 changed files with 445 additions and 241 deletions

View File

@ -5,18 +5,14 @@ import helmet from 'helmet';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken'; // For token-based authentication import jwt from 'jsonwebtoken'; // For token-based authentication
import mysql from 'mysql2'; import mysql from 'mysql2';
import sqlite3 from 'sqlite3'; import sqlite3 from 'sqlite3';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@ -33,7 +29,6 @@ const DB_USER = process.env.DB_USER || 'sqluser';
const DB_PASSWORD = process.env.DB_PASSWORD || ''; const DB_PASSWORD = process.env.DB_PASSWORD || '';
const DB_NAME = process.env.DB_NAME || 'user_profile_db'; const DB_NAME = process.env.DB_NAME || 'user_profile_db';
// Create a MySQL pool for user_profile data // Create a MySQL pool for user_profile data
const pool = mysql.createPool({ const pool = mysql.createPool({
host: DB_HOST, host: DB_HOST,
@ -41,7 +36,7 @@ const pool = mysql.createPool({
user: DB_USER, user: DB_USER,
password: DB_PASSWORD, password: DB_PASSWORD,
database: DB_NAME, database: DB_NAME,
connectionLimit: 10, // optional connectionLimit: 10,
}); });
// Test a quick query (optional) // Test a quick query (optional)
@ -55,7 +50,13 @@ pool.query('SELECT 1', (err) => {
const app = express(); const app = express();
const PORT = 5000; const PORT = 5000;
const allowedOrigins = ['http://localhost:3000', 'http://34.16.120.118:3000', 'https://dev1.aptivaai.com'];
// Allowed origins for CORS
const allowedOrigins = [
'http://localhost:3000',
'http://34.16.120.118:3000',
'https://dev1.aptivaai.com',
];
app.disable('x-powered-by'); app.disable('x-powered-by');
app.use(bodyParser.json()); app.use(bodyParser.json());
@ -79,7 +80,13 @@ app.use(
} }
}, },
methods: ['GET', 'POST', 'OPTIONS'], methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Authorization', 'Content-Type', 'Accept', 'Origin', 'X-Requested-With'], allowedHeaders: [
'Authorization',
'Content-Type',
'Accept',
'Origin',
'X-Requested-With',
],
credentials: true, credentials: true,
}) })
); );
@ -88,15 +95,21 @@ app.use(
app.options('*', (req, res) => { app.options('*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, Accept, Origin, X-Requested-With'); res.setHeader(
'Access-Control-Allow-Headers',
'Authorization, Content-Type, Accept, Origin, X-Requested-With'
);
res.status(200).end(); res.status(200).end();
}); });
// Add HTTP headers for security and caching // Add HTTP headers for security
app.use((req, res, next) => { app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
res.setHeader('Content-Security-Policy', "default-src 'self';"); res.setHeader('Content-Security-Policy', "default-src 'self';");
res.removeHeader('X-Powered-By'); res.removeHeader('X-Powered-By');
next(); next();
@ -108,11 +121,16 @@ app.use((req, res, next) => {
next(); next();
}); });
// =============== USER REGISTRATION (MySQL) =============== /* ------------------------------------------------------------------
// /api/register USER REGISTRATION (MySQL)
------------------------------------------------------------------ */
/**
* POST /api/register
* Body:
* username, password, firstname, lastname, email, zipcode, state, area, career_situation
*/
app.post('/api/register', async (req, res) => { app.post('/api/register', async (req, res) => {
const { const {
userId, // random ID from the front end
username, username,
password, password,
firstname, firstname,
@ -121,76 +139,107 @@ app.post('/api/register', async (req, res) => {
zipcode, zipcode,
state, state,
area, area,
career_situation career_situation,
} = req.body; } = req.body;
if (
!username ||
!password ||
!firstname ||
!lastname ||
!email ||
!zipcode ||
!state ||
!area
) {
return res.status(400).json({ error: 'Missing required fields.' });
}
try { try {
const hashedPassword = await bcrypt.hash(password, 10); const hashedPassword = await bcrypt.hash(password, 10);
// Insert row in user_profile, storing both user_id (random) and letting id auto-increment // 1) Insert into user_profile
const profileQuery = ` const profileQuery = `
INSERT INTO user_profile INSERT INTO user_profile
(user_id, firstname, lastname, email, zipcode, state, area, career_situation) (firstname, lastname, email, zipcode, state, area, career_situation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
`; `;
pool.query( pool.query(
profileQuery, profileQuery,
[userId, firstname, lastname, email, zipcode, state, area, career_situation], [firstname, lastname, email, zipcode, state, area, career_situation],
(errProfile, resultProfile) => { (errProfile, resultProfile) => {
if (errProfile) { if (errProfile) {
console.error('Error inserting user_profile:', errProfile.message); console.error('Error inserting user_profile:', errProfile.message);
return res.status(500).json({ error: 'Failed to create user profile' }); return res
.status(500)
.json({ error: 'Failed to create user profile' });
} }
const newProfileAutoId = resultProfile.insertId; // auto-increment PK const newProfileId = resultProfile.insertId; // auto-increment PK
// Insert into user_auth, referencing the auto-increment PK // 2) Insert into user_auth, referencing user_profile.id
const authQuery = ` const authQuery = `
INSERT INTO user_auth (user_id, username, hashed_password) INSERT INTO user_auth (user_id, username, hashed_password)
VALUES (?, ?, ?) VALUES (?, ?, ?)
`; `;
pool.query(authQuery, [newProfileAutoId, username, hashedPassword], (errAuth) => { pool.query(
authQuery,
[newProfileId, username, hashedPassword],
(errAuth) => {
if (errAuth) { if (errAuth) {
console.error('Error inserting user_auth:', errAuth.message); console.error('Error inserting user_auth:', errAuth.message);
if (errAuth.code === 'ER_DUP_ENTRY') { if (errAuth.code === 'ER_DUP_ENTRY') {
return res.status(400).json({ error: 'Username already exists' }); return res
.status(400)
.json({ error: 'Username already exists' });
} }
return res.status(500).json({ error: 'Failed to register user' }); return res.status(500).json({ error: 'Failed to register user' });
} }
return res.status(201).json({ return res.status(201).json({
message: 'User registered successfully', message: 'User registered successfully',
dbId: newProfileAutoId, // the auto-increment PK profileId: newProfileId, // the user_profile.id
customId: userId, // the random ID
});
}); });
} }
); );
}
);
} catch (err) { } catch (err) {
console.error('Error during registration:', err.message); console.error('Error during registration:', err.message);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({ error: 'Internal server error' });
} }
}); });
/* ------------------------------------------------------------------
// =============== SIGN-IN (MySQL) =============== SIGN-IN (MySQL)
app.post('/api/signin', async (req, res) => { ------------------------------------------------------------------ */
/**
* POST /api/signin
* Body: { username, password }
* Returns JWT signed with user_profile.id
*/
app.post('/api/signin', (req, res) => {
const { username, password } = req.body; const { username, password } = req.body;
if (!username || !password) { if (!username || !password) {
return res.status(400).json({ error: 'Both username and password are required' }); return res
.status(400)
.json({ error: 'Both username and password are required' });
} }
const query = ` const query = `
SELECT SELECT
user_auth.user_id, user_auth.user_id,
user_auth.hashed_password, user_auth.hashed_password,
user_profile.id, user_profile.firstname,
user_profile.lastname,
user_profile.email,
user_profile.zipcode, user_profile.zipcode,
user_profile.state,
user_profile.area,
user_profile.is_premium, user_profile.is_premium,
user_profile.is_pro_premium, user_profile.is_pro_premium,
user_profile.career_situation, user_profile.career_situation,
user_profile.email,
user_profile.firstname,
user_profile.lastname,
user_profile.career_priorities, user_profile.career_priorities,
user_profile.career_list user_profile.career_list
FROM user_auth FROM user_auth
@ -200,7 +249,9 @@ app.post('/api/signin', async (req, res) => {
pool.query(query, [username], async (err, results) => { pool.query(query, [username], async (err, results) => {
if (err) { if (err) {
console.error('Error querying user_auth:', err.message); console.error('Error querying user_auth:', err.message);
return res.status(500).json({ error: 'Failed to query user authentication data' }); return res
.status(500)
.json({ error: 'Failed to query user authentication data' });
} }
if (!results || results.length === 0) { if (!results || results.length === 0) {
@ -208,22 +259,29 @@ app.post('/api/signin', async (req, res) => {
} }
const row = results[0]; const row = results[0];
// Compare password
const isMatch = await bcrypt.compare(password, row.hashed_password); const isMatch = await bcrypt.compare(password, row.hashed_password);
if (!isMatch) { if (!isMatch) {
return res.status(401).json({ error: 'Invalid username or password' }); return res.status(401).json({ error: 'Invalid username or password' });
} }
const token = jwt.sign({ userId: row.id }, SECRET_KEY, { expiresIn: '2h' }); // The user_profile id is stored in user_auth.user_id
const token = jwt.sign({ id: row.user_id }, SECRET_KEY, {
expiresIn: '2h',
});
// Return the user info + token
res.status(200).json({ res.status(200).json({
message: 'Login successful', message: 'Login successful',
token, token,
userId: row.id, userId: row.user_id, // The user_profile.id
user: { user: {
user_id: row.user_id,
firstname: row.firstname, firstname: row.firstname,
lastname: row.lastname, lastname: row.lastname,
email: row.email, email: row.email,
zipcode: row.zipcode, zipcode: row.zipcode,
state: row.state,
area: row.area,
is_premium: row.is_premium, is_premium: row.is_premium,
is_pro_premium: row.is_pro_premium, is_pro_premium: row.is_pro_premium,
career_situation: row.career_situation, career_situation: row.career_situation,
@ -234,7 +292,9 @@ app.post('/api/signin', async (req, res) => {
}); });
}); });
// =============== CHECK USERNAME (MySQL) =============== /* ------------------------------------------------------------------
CHECK USERNAME (MySQL)
------------------------------------------------------------------ */
app.get('/api/check-username/:username', (req, res) => { app.get('/api/check-username/:username', (req, res) => {
const { username } = req.params; const { username } = req.params;
const query = `SELECT username FROM user_auth WHERE username = ?`; const query = `SELECT username FROM user_auth WHERE username = ?`;
@ -251,17 +311,27 @@ app.get('/api/check-username/:username', (req, res) => {
}); });
}); });
// =============== UPSERT USER PROFILE (MySQL) =============== /* ------------------------------------------------------------------
UPSERT USER PROFILE (MySQL)
------------------------------------------------------------------ */
/**
* POST /api/user-profile
* Headers: { Authorization: Bearer <token> }
* Body: { firstName, lastName, email, zipCode, state, area, ... }
*
* If user_profile row exists (id = token.id), update
* else insert
*/
app.post('/api/user-profile', (req, res) => { app.post('/api/user-profile', (req, res) => {
const token = req.headers.authorization?.split(' ')[1]; const token = req.headers.authorization?.split(' ')[1];
if (!token) { if (!token) {
return res.status(401).json({ error: 'Authorization token is required' }); return res.status(401).json({ error: 'Authorization token is required' });
} }
let userId; let profileId;
try { try {
const decoded = jwt.verify(token, SECRET_KEY); const decoded = jwt.verify(token, SECRET_KEY);
userId = decoded.userId; profileId = decoded.id; // user_profile.id from sign-in
} catch (error) { } catch (error) {
console.error('JWT verification failed:', error); console.error('JWT verification failed:', error);
return res.status(401).json({ error: 'Invalid or expired token' }); return res.status(401).json({ error: 'Invalid or expired token' });
@ -280,16 +350,25 @@ app.post('/api/user-profile', (req, res) => {
career_list, career_list,
} = req.body; } = req.body;
// Check existing profile // Check if profile row exists
pool.query(`SELECT * FROM user_profile WHERE user_id = ?`, [userId], (err, results) => { pool.query(
`SELECT * FROM user_profile WHERE id = ?`,
[profileId],
(err, results) => {
if (err) { if (err) {
console.error('Error checking profile:', err.message); console.error('Error checking profile:', err.message);
return res.status(500).json({ error: 'Database error' }); return res.status(500).json({ error: 'Database error' });
} }
const existingRow = results && results.length > 0 ? results[0] : null; const existingRow = results && results.length > 0 ? results[0] : null;
if (!existingRow && (!firstName || !lastName || !email || !zipCode || !state || !area)) { // If creating a brand-new profile, ensure required fields
return res.status(400).json({ error: 'All fields are required for initial profile creation.' }); if (
!existingRow &&
(!firstName || !lastName || !email || !zipCode || !state || !area)
) {
return res
.status(400)
.json({ error: 'All fields are required for initial profile creation.' });
} }
const finalAnswers = const finalAnswers =
@ -308,12 +387,13 @@ app.post('/api/user-profile', (req, res) => {
: existingRow?.career_list || null; : existingRow?.career_list || null;
if (existingRow) { if (existingRow) {
// Update // Update the existing user_profile
const updateQuery = ` const updateQuery = `
UPDATE user_profile UPDATE user_profile
SET firstname = ?, lastname = ?, email = ?, zipcode = ?, state = ?, area = ?, career_situation = ?, SET firstname = ?, lastname = ?, email = ?, zipcode = ?, state = ?, area = ?,
interest_inventory_answers = ?, career_priorities = ?, career_list = ? career_situation = ?, interest_inventory_answers = ?, career_priorities = ?,
WHERE user_id = ? career_list = ?
WHERE id = ?
`; `;
const params = [ const params = [
firstName || existingRow.firstname, firstName || existingRow.firstname,
@ -326,25 +406,30 @@ app.post('/api/user-profile', (req, res) => {
finalAnswers, finalAnswers,
finalCareerPriorities, finalCareerPriorities,
finalCareerList, finalCareerList,
userId profileId,
]; ];
pool.query(updateQuery, params, (err2, result2) => {
pool.query(updateQuery, params, (err2) => {
if (err2) { if (err2) {
console.error('Update error:', err2.message); console.error('Update error:', err2.message);
return res.status(500).json({ error: 'Failed to update user profile' }); return res
.status(500)
.json({ error: 'Failed to update user profile' });
} }
res.status(200).json({ message: 'User profile updated successfully' }); return res
.status(200)
.json({ message: 'User profile updated successfully' });
}); });
} else { } else {
// Insert new profile // Insert a new profile (the user_auth record exists, but the user_profile row is missing)
const insertQuery = ` const insertQuery = `
INSERT INTO user_profile INSERT INTO user_profile
(user_id, firstname, lastname, email, zipcode, state, area, career_situation, (id, firstname, lastname, email, zipcode, state, area, career_situation,
interest_inventory_answers, career_priorities, career_list) interest_inventory_answers, career_priorities, career_list)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`; `;
const params = [ const params = [
userId, profileId, // Force the row's primary key to match the existing user ID
firstName, firstName,
lastName, lastName,
email, email,
@ -356,34 +441,43 @@ app.post('/api/user-profile', (req, res) => {
finalCareerPriorities, finalCareerPriorities,
finalCareerList, finalCareerList,
]; ];
pool.query(insertQuery, params, (err3, result3) => {
pool.query(insertQuery, params, (err3) => {
if (err3) { if (err3) {
console.error('Insert error:', err3.message); console.error('Insert error:', err3.message);
return res.status(500).json({ error: 'Failed to create user profile' }); return res
.status(500)
.json({ error: 'Failed to create user profile' });
} }
res.status(201).json({ message: 'User profile created successfully', id: result3.insertId }); return res
.status(201)
.json({ message: 'User profile created successfully', id: profileId });
}); });
} }
}); }
);
}); });
// =============== FETCH USER PROFILE (MySQL) =============== /* ------------------------------------------------------------------
FETCH USER PROFILE (MySQL)
------------------------------------------------------------------ */
app.get('/api/user-profile', (req, res) => { app.get('/api/user-profile', (req, res) => {
const token = req.headers.authorization?.split(' ')[1]; const token = req.headers.authorization?.split(' ')[1];
if (!token) { if (!token) {
return res.status(401).json({ error: 'Authorization token is required' }); return res.status(401).json({ error: 'Authorization token is required' });
} }
let userId;
let profileId;
try { try {
const decoded = jwt.verify(token, SECRET_KEY); const decoded = jwt.verify(token, SECRET_KEY);
userId = decoded.userId; profileId = decoded.id; // user_profile.id
} catch (error) { } catch (error) {
console.error('Error verifying token:', error.message); console.error('Error verifying token:', error.message);
return res.status(401).json({ error: 'Invalid or expired token' }); return res.status(401).json({ error: 'Invalid or expired token' });
} }
const query = 'SELECT * FROM user_profile WHERE user_id = ?'; const query = 'SELECT * FROM user_profile WHERE id = ?';
pool.query(query, [userId], (err, results) => { pool.query(query, [profileId], (err, results) => {
if (err) { if (err) {
console.error('Error fetching user profile:', err.message); console.error('Error fetching user profile:', err.message);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({ error: 'Internal server error' });
@ -395,20 +489,28 @@ app.get('/api/user-profile', (req, res) => {
}); });
}); });
// =============== SALARY_INFO REMAINS IN SQLITE =============== /* ------------------------------------------------------------------
SALARY_INFO REMAINS IN SQLITE
------------------------------------------------------------------ */
app.get('/api/areas', (req, res) => { app.get('/api/areas', (req, res) => {
const { state } = req.query; const { state } = req.query;
if (!state) { if (!state) {
return res.status(400).json({ error: 'State parameter is required' }); return res.status(400).json({ error: 'State parameter is required' });
} }
const salaryDbPath = path.resolve('/home/jcoakley/aptiva-dev1-app/salary_info.db'); const salaryDbPath = path.resolve(
const salaryDb = new sqlite3.Database(salaryDbPath, sqlite3.OPEN_READONLY, (err) => { '/home/jcoakley/aptiva-dev1-app/salary_info.db'
);
const salaryDb = new sqlite3.Database(
salaryDbPath,
sqlite3.OPEN_READONLY,
(err) => {
if (err) { if (err) {
console.error('Error connecting to database:', err.message); console.error('Error connecting to database:', err.message);
return res.status(500).json({ error: 'Failed to connect to database' }); return res.status(500).json({ error: 'Failed to connect to database' });
} }
}); }
);
const query = `SELECT DISTINCT AREA_TITLE FROM salary_data WHERE PRIM_STATE = ?`; const query = `SELECT DISTINCT AREA_TITLE FROM salary_data WHERE PRIM_STATE = ?`;
salaryDb.all(query, [state], (err, rows) => { salaryDb.all(query, [state], (err, rows) => {
@ -428,30 +530,29 @@ app.get('/api/areas', (req, res) => {
}); });
/* ------------------------------------------------------------------ /* ------------------------------------------------------------------
PREMIUM UPGRADE ENDPOINT PREMIUM UPGRADE ENDPOINT
------------------------------------------------------------------ */ ------------------------------------------------------------------ */
app.post('/api/activate-premium', (req, res) => { app.post('/api/activate-premium', (req, res) => {
const token = req.headers.authorization?.split(' ')[1]; const token = req.headers.authorization?.split(' ')[1];
if (!token) { if (!token) {
return res.status(401).json({ error: 'Authorization token is required' }); return res.status(401).json({ error: 'Authorization token is required' });
} }
let userId; // Will hold the auto-increment "id" from user_profile let profileId;
try { try {
const decoded = jwt.verify(token, SECRET_KEY); const decoded = jwt.verify(token, SECRET_KEY);
userId = decoded.userId; // => user_profile.id (auto-inc PK) profileId = decoded.id;
} catch (error) { } catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' }); return res.status(401).json({ error: 'Invalid or expired token' });
} }
// Use MySQL pool.query instead of db.run (which is for SQLite)
const query = ` const query = `
UPDATE user_profile UPDATE user_profile
SET is_premium = 1, SET is_premium = 1,
is_pro_premium = 1 is_pro_premium = 1
WHERE id = ? WHERE id = ?
`; `;
pool.query(query, [userId], (err, results) => { pool.query(query, [profileId], (err) => {
if (err) { if (err) {
console.error('Error updating premium status:', err.message); console.error('Error updating premium status:', err.message);
return res.status(500).json({ error: 'Failed to activate premium' }); return res.status(500).json({ error: 'Failed to activate premium' });
@ -460,7 +561,9 @@ app.post('/api/activate-premium', (req, res) => {
}); });
}); });
// =============== START SERVER =============== /* ------------------------------------------------------------------
START SERVER
------------------------------------------------------------------ */
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`); console.log(`Server running on http://localhost:${PORT}`);
}); });

View File

@ -39,6 +39,13 @@ body, #root {
color: white; color: white;
} }
.dialog-overlay,
[data-state="open"].dialog-overlay {
background-color: transparent !important;
pointer-events: none !important;
z-index: 0 !important;
}
/* Animation for the logo */ /* Animation for the logo */
.App-logo { .App-logo {
height: 40vmin; height: 40vmin;

View File

@ -249,9 +249,14 @@ function CareerExplorer() {
if (profileData.career_list) { if (profileData.career_list) {
setCareerList(JSON.parse(profileData.career_list)); setCareerList(JSON.parse(profileData.career_list));
} }
} else {
if (!profileData.career_priorities) {
setShowModal(true); setShowModal(true);
} }
} else {
setShowModal(true);
}
} catch (err) { } catch (err) {
console.error('Error fetching user profile:', err); console.error('Error fetching user profile:', err);
setShowModal(true); setShowModal(true);

View File

@ -406,7 +406,7 @@ function EducationalProgramsPage() {
<span className="relative inline-block"> <span className="relative inline-block">
<span>Why no courses?</span> <span>Why no courses?</span>
<span <span
className="ml-1 inline-flex h-2.5 w-2.5 items-center justify-center className="top-0 left-0 -translate-y-3/4 translate-x-1/8 ml-.5 inline-flex h-2.5 w-2.5 items-center justify-center
rounded-full bg-blue-500 text-[10px] font-italics text-white cursor-help" rounded-full bg-blue-500 text-[10px] font-italics text-white cursor-help"
title="Abilities are more innate in nature, and difficult to offer courses for them." title="Abilities are more innate in nature, and difficult to offer courses for them."
> >

View File

@ -48,7 +48,6 @@ function SignUp() {
const [areas, setAreas] = useState([]); const [areas, setAreas] = useState([]);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [loadingAreas, setLoadingAreas] = useState(false); const [loadingAreas, setLoadingAreas] = useState(false);
const [frontendUserId] = useState(() => Math.floor(Math.random() * 1000000000));
// new states // new states
const [showCareerSituations, setShowCareerSituations] = useState(false); const [showCareerSituations, setShowCareerSituations] = useState(false);
@ -172,7 +171,6 @@ function SignUp() {
try { try {
console.log("Payload sent to backend:", { console.log("Payload sent to backend:", {
userId: frontendUserId,
username, password, firstname, lastname, email, zipcode, state, area, username, password, firstname, lastname, email, zipcode, state, area,
career_situation: selectedSituation.id career_situation: selectedSituation.id
}); });
@ -181,7 +179,6 @@ function SignUp() {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
userId: frontendUserId,
username, username,
password, password,
firstname, firstname,
@ -212,11 +209,10 @@ function SignUp() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-gray-100 p-4"> <div className="flex min-h-screen items-center justify-center bg-gray-100 p-4">
<div className="w-full max-w-md bg-white p-6 rounded-lg shadow-lg">
{!showCareerSituations ? ( {!showCareerSituations ? (
<> <div className="w-full max-w-md bg-white p-6 rounded-lg shadow-lg">
<h2 className="mb-4 text-2xl font-semibold text-center">Sign Up</h2> <h2 className="mb-4 text-2xl font-semibold text-center">Sign Up</h2>
{error && ( {error && (
<div className="mb-4 p-2 text-sm text-red-600 bg-red-100 rounded"> <div className="mb-4 p-2 text-sm text-red-600 bg-red-100 rounded">
@ -224,28 +220,95 @@ function SignUp() {
</div> </div>
)} )}
<form className="space-y-3" onSubmit={handleNextClick}> <form className="space-y-3" onSubmit={handleNextClick}>
<input className="w-full px-3 py-2 border rounded-md" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} /> <input
<input type="password" className="w-full px-3 py-2 border rounded-md" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> className="w-full px-3 py-2 border rounded-md"
<input className="w-full px-3 py-2 border rounded-md" placeholder="Retype Password" type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)}/> placeholder="Username"
<input className="w-full px-3 py-2 border rounded-md" placeholder="First Name" value={firstname} onChange={(e) => setFirstname(e.target.value)} /> value={username}
<input className="w-full px-3 py-2 border rounded-md" placeholder="Last Name" value={lastname} onChange={(e) => setLastname(e.target.value)} /> onChange={(e) => setUsername(e.target.value)}
<input className="w-full px-3 py-2 border rounded-md" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> />
<input className="w-full px-3 py-2 border rounded-md" placeholder="Retype Email" type="email" value={confirmEmail} onChange={(e) => setConfirmEmail(e.target.value)}/> <input
<input className="w-full px-3 py-2 border rounded-md" placeholder="Zip Code" value={zipcode} onChange={(e) => setZipcode(e.target.value)} /> type="password"
<select className="w-full px-3 py-2 border rounded-md" value={state} onChange={(e) => setState(e.target.value)}><option value="">Select State</option> className="w-full px-3 py-2 border rounded-md"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<input
className="w-full px-3 py-2 border rounded-md"
placeholder="Retype Password"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<input
className="w-full px-3 py-2 border rounded-md"
placeholder="First Name"
value={firstname}
onChange={(e) => setFirstname(e.target.value)}
/>
<input
className="w-full px-3 py-2 border rounded-md"
placeholder="Last Name"
value={lastname}
onChange={(e) => setLastname(e.target.value)}
/>
<input
className="w-full px-3 py-2 border rounded-md"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="w-full px-3 py-2 border rounded-md"
placeholder="Retype Email"
type="email"
value={confirmEmail}
onChange={(e) => setConfirmEmail(e.target.value)}
/>
<input
className="w-full px-3 py-2 border rounded-md"
placeholder="Zip Code"
value={zipcode}
onChange={(e) => setZipcode(e.target.value)}
/>
<select
className="w-full px-3 py-2 border rounded-md"
value={state}
onChange={(e) => setState(e.target.value)}
>
<option value="">Select State</option>
{states.map((s) => ( {states.map((s) => (
<option key={s.code} value={s.code}>{s.name}</option> <option key={s.code} value={s.code}>
{s.name}
</option>
))} ))}
</select> </select>
<div className="relative w-full"><select className="w-full px-3 py-2 border rounded-md" value={area} onChange={(e) => setArea(e.target.value)} disabled={loadingAreas} // Disable select while loading <div className="relative w-full">
<label>
<span
title="Selecting an Area will allow us to provide regional salary data for your career choices."
className="absolute top-2 right-2 translate-x-7 -translate-y-3
inline-flex h-4 w-4 items-center justify-center
rounded-full bg-blue-500 text-xs font-bold text-white cursor-help"
>
i
</span>
</label>
<select
id="area"
className="w-full px-3 py-2 border rounded-md"
value={area}
onChange={(e) => setArea(e.target.value)}
disabled={loadingAreas}
> >
<option value="">Select Area</option> <option value="">Select Area</option>
{areas.map((a, i) => ( {areas.map((a, i) => (
<option key={i} value={a}>{a}</option> <option key={i} value={a}>
{a}
</option>
))} ))}
</select> </select>
{loadingAreas && ( {loadingAreas && (
<span className="absolute right-3 top-2.5 text-gray-400 text-sm animate-pulse"> <span className="absolute right-3 top-2.5 text-gray-400 text-sm animate-pulse">
Loading... Loading...
@ -257,11 +320,13 @@ function SignUp() {
Next Next
</Button> </Button>
</form> </form>
</> </div>
) : ( ) : (
<> <div className="w-full max-w-screen-lg mx-auto bg-white p-6 rounded-lg shadow-lg">
<h2 className="mb-4 text-xl font-semibold text-center">Where are you in your career journey right now?</h2> <h2 className="mb-4 text-xl font-semibold text-center">
<div className="grid grid-cols-1 gap-4"> Where are you in your career journey right now?
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{careerSituations.map((situation) => ( {careerSituations.map((situation) => (
<SituationCard <SituationCard
key={situation.id} key={situation.id}
@ -275,9 +340,12 @@ function SignUp() {
/> />
))} ))}
</div> </div>
{showPrompt && <div className="mt-4 p-4 bg-yellow-200">Modal should be open!</div>} </div>
)}
{showPrompt && (
<PromptModal <PromptModal
open={showPrompt} open={true}
title="Confirm Your Selection" title="Confirm Your Selection"
message={`Are you sure you want to select "${selectedSituation?.title}"? You can change this later in your profile settings.`} message={`Are you sure you want to select "${selectedSituation?.title}"? You can change this later in your profile settings.`}
confirmText="Confirm" confirmText="Confirm"
@ -285,10 +353,10 @@ function SignUp() {
onConfirm={handleSituationConfirm} onConfirm={handleSituationConfirm}
onCancel={() => setShowPrompt(false)} onCancel={() => setShowPrompt(false)}
/> />
</> )}
)}
</div> </div>
</div> );
);
} }
export default SignUp; export default SignUp;

View File

@ -0,0 +1,21 @@
// FadingPromptModal.jsx
import React from 'react';
import PromptModal from './PromptModal.js';
export default function FadingPromptModal({ open, ...props }) {
return (
<div
className={`
fixed inset-0 z-50
flex items-center justify-center
transition-opacity duration-300
${open ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}
`}
>
<div className="absolute inset-0 bg-gray-800 bg-opacity-50 z-10" />
<div className="relative z-20">
<PromptModal open={open} {...props} />
</div>
</div>
);
}