Added the 4 user journeys, landing pages, and navigation from SignIn.
This commit is contained in:
parent
988d8b860e
commit
6388522f8a
@ -1,6 +1,7 @@
|
||||
import express from 'express';
|
||||
import axios from 'axios';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import dotenv from 'dotenv';
|
||||
import { fileURLToPath } from 'url';
|
||||
import path from 'path';
|
||||
@ -35,6 +36,11 @@ const allowedOrigins = ['http://localhost:3000', 'http://34.16.120.118:3000', 'h
|
||||
app.disable('x-powered-by');
|
||||
app.use(bodyParser.json());
|
||||
app.use(express.json());
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: false,
|
||||
crossOriginEmbedderPolicy: false,
|
||||
})
|
||||
);
|
||||
|
||||
// Enable CORS with dynamic origin checking
|
||||
app.use(
|
||||
@ -71,6 +77,13 @@ app.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// Force Content-Type to application/json on all responses
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
// Route for user registration
|
||||
app.post('/api/register', async (req, res) => {
|
||||
const {
|
||||
@ -82,12 +95,13 @@ app.post('/api/register', async (req, res) => {
|
||||
email,
|
||||
zipcode,
|
||||
state,
|
||||
area
|
||||
area,
|
||||
career_situation
|
||||
} = req.body;
|
||||
|
||||
// Validate all required fields
|
||||
if (!userId || !username || !password || !firstname || !lastname || !email || !zipcode || !state || !area) {
|
||||
return res.status(400).json({ error: 'All fields are required' });
|
||||
// Validate all required fields, including career_situation
|
||||
if (!userId || !username || !password || !firstname || !lastname || !email || !zipcode || !state || !area || !career_situation) {
|
||||
return res.status(400).json({ error: 'All fields including career_situation are required' });
|
||||
}
|
||||
|
||||
try {
|
||||
@ -98,6 +112,7 @@ app.post('/api/register', async (req, res) => {
|
||||
INSERT INTO user_auth (user_id, username, hashed_password)
|
||||
VALUES (?, ?, ?)
|
||||
`;
|
||||
|
||||
db.run(authQuery, [userId, username, hashedPassword], function (err) {
|
||||
if (err) {
|
||||
console.error('Error inserting into user_auth:', err.message);
|
||||
@ -107,143 +122,28 @@ app.post('/api/register', async (req, res) => {
|
||||
return res.status(500).json({ error: 'Failed to register user' });
|
||||
}
|
||||
|
||||
// Insert into user_profile with actual provided values
|
||||
// Insert into user_profile including career_situation
|
||||
const profileQuery = `
|
||||
INSERT INTO user_profile (user_id, firstname, lastname, email, zipcode, state, area)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO user_profile (user_id, firstname, lastname, email, zipcode, state, area, career_situation)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
db.run(profileQuery, [userId, firstname, lastname, email, zipcode, state, area], (err) => {
|
||||
|
||||
db.run(profileQuery, [userId, firstname, lastname, email, zipcode, state, area, career_situation], (err) => {
|
||||
if (err) {
|
||||
console.error('Error inserting into user_profile:', err.message);
|
||||
return res.status(500).json({ error: 'Failed to create user profile' });
|
||||
}
|
||||
|
||||
res.status(201).json({ message: 'User registered successfully', userId });
|
||||
return res.status(201).json({ message: 'User registered successfully', userId });
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error during registration:', error.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Route to save or update user profile
|
||||
app.post('/api/user-profile', (req, res) => {
|
||||
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,
|
||||
interest_inventory_answers,
|
||||
} = req.body;
|
||||
|
||||
if (!firstName || !lastName || !email || !zipCode || !state || !area) {
|
||||
return res.status(400).json({ error: 'All fields are required (except IIA)' });
|
||||
}
|
||||
|
||||
// 1) Check if we have an existing user_profile row for this userId
|
||||
const checkQuery = `SELECT * FROM user_profile WHERE user_id = ?`;
|
||||
db.get(checkQuery, [userId], (err, existingRow) => {
|
||||
if (err) {
|
||||
console.error('Error checking profile:', err.message);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// 2) If interest_inventory_answers was omitted in the request,
|
||||
// keep the old value from existingRow.
|
||||
let finalAnswers;
|
||||
if (existingRow) {
|
||||
// If the row exists, keep or replace answers:
|
||||
finalAnswers =
|
||||
interest_inventory_answers === undefined
|
||||
? existingRow.interest_inventory_answers
|
||||
: interest_inventory_answers;
|
||||
} else {
|
||||
// If no row yet, use whatever is passed or null
|
||||
finalAnswers = interest_inventory_answers || null;
|
||||
}
|
||||
|
||||
if (existingRow) {
|
||||
// 3) Update
|
||||
const updateQuery = `
|
||||
UPDATE user_profile
|
||||
SET firstname = ?,
|
||||
lastname = ?,
|
||||
email = ?,
|
||||
zipcode = ?,
|
||||
state = ?,
|
||||
area = ?,
|
||||
career_situation = ?,
|
||||
interest_inventory_answers = ?
|
||||
WHERE user_id = ?
|
||||
`;
|
||||
const params = [
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
zipCode,
|
||||
state,
|
||||
area,
|
||||
careerSituation || null,
|
||||
finalAnswers,
|
||||
userId,
|
||||
];
|
||||
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' });
|
||||
}
|
||||
return res.status(200).json({ message: 'User profile updated successfully' });
|
||||
});
|
||||
} else {
|
||||
// 4) Insert new
|
||||
const insertQuery = `
|
||||
INSERT INTO user_profile
|
||||
(firstname, lastname, email, zipcode, state, area, career_situation, interest_inventory_answers, user_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const params = [
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
zipCode,
|
||||
state,
|
||||
area,
|
||||
careerSituation || null,
|
||||
finalAnswers,
|
||||
userId,
|
||||
];
|
||||
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' });
|
||||
}
|
||||
return res
|
||||
.status(201)
|
||||
.json({ message: 'User profile created successfully', id: this.lastID });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Route for login
|
||||
app.post('/api/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
@ -290,6 +190,8 @@ app.post('/api/signin', async (req, res) => {
|
||||
user_auth.hashed_password,
|
||||
user_profile.zipcode,
|
||||
user_profile.is_premium,
|
||||
user_profile.is_pro_premium,
|
||||
user_profile.career_situation,
|
||||
user_profile.email,
|
||||
user_profile.firstname,
|
||||
user_profile.lastname
|
||||
@ -334,11 +236,33 @@ app.post('/api/signin', async (req, res) => {
|
||||
email: row.email,
|
||||
zipcode: row.zipcode,
|
||||
is_premium: row.is_premium,
|
||||
is_pro_premium: row.is_pro_premium,
|
||||
career_situation: row.career_situation,
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/// Check if username already exists
|
||||
app.get('/api/check-username/:username', (req, res) => {
|
||||
const { username } = req.params;
|
||||
|
||||
const query = `SELECT username FROM user_auth WHERE username = ?`;
|
||||
|
||||
db.get(query, [username], (err, row) => {
|
||||
if (err) {
|
||||
console.error('Error checking username:', err.message);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
if (row) {
|
||||
res.status(200).json({ exists: true });
|
||||
} else {
|
||||
res.status(200).json({ exists: false });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Route to fetch user profile
|
||||
|
@ -15,6 +15,10 @@ import SessionExpiredHandler from './components/SessionExpiredHandler.js';
|
||||
import GettingStarted from './components/GettingStarted.js';
|
||||
import SignIn from './components/SignIn.js';
|
||||
import SignUp from './components/SignUp.js';
|
||||
import PlanningLanding from './components/PlanningLanding.js';
|
||||
import PreparingLanding from './components/PreparingLanding.js';
|
||||
import EnhancingLanding from './components/EnhancingLanding.js';
|
||||
import RetirementLanding from './components/RetirementLanding.js';
|
||||
import InterestInventory from './components/InterestInventory.js';
|
||||
import Dashboard from './components/Dashboard.js';
|
||||
import UserProfile from './components/UserProfile.js';
|
||||
@ -231,6 +235,10 @@ function App() {
|
||||
<Route path="/interest-inventory" element={<InterestInventory />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/profile" element={<UserProfile />} />
|
||||
<Route path="/planning" element={<PlanningLanding />} />
|
||||
<Route path="/preparing" element={<PreparingLanding />} />
|
||||
<Route path="/enhancing" element={<EnhancingLanding />} />
|
||||
<Route path="/retirement" element={<RetirementLanding />} />
|
||||
|
||||
{/* Premium-only routes */}
|
||||
<Route
|
||||
|
26
src/components/EnhancingLanding.js
Normal file
26
src/components/EnhancingLanding.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from './ui/button.js';
|
||||
|
||||
function EnhancingLanding() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
|
||||
<div className="max-w-2xl w-full bg-white shadow-lg rounded-lg p-8">
|
||||
<h1 className="text-3xl font-bold mb-4 text-center">
|
||||
Enhancing Your Career
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-6 text-center">
|
||||
AptivaAI helps you advance your career. Plan career milestones, enhance your skill set, optimize your resume, and prepare for promotions or transitions.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<Button onClick={() => navigate('/resume-optimizer')}>Optimize Resume</Button>
|
||||
<Button onClick={() => navigate('/milestone-tracker')}>Set Career Milestones</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EnhancingLanding;
|
47
src/components/PlanningLanding.js
Normal file
47
src/components/PlanningLanding.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from './ui/button.js';
|
||||
|
||||
function PlanningLanding() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
|
||||
<div className="max-w-2xl w-full bg-white shadow-lg rounded-lg p-8">
|
||||
<h1 className="text-3xl font-bold mb-4 text-center">
|
||||
Planning Your Career
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-6 text-center">
|
||||
Discover career options that match your interests, skills, and potential.
|
||||
AptivaAI helps you find your ideal career path, provides insights into the educational requirements,
|
||||
expected salaries, job market trends, and more.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => navigate('/interest-inventory')}
|
||||
>
|
||||
Take Interest Inventory
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => navigate('/career-explorer')}
|
||||
>
|
||||
Explore Career Paths
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => navigate('/educational-programs')}
|
||||
>
|
||||
Discover Educational Programs
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlanningLanding;
|
26
src/components/PreparingLanding.js
Normal file
26
src/components/PreparingLanding.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from './ui/button.js';
|
||||
|
||||
function PreparingLanding() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
|
||||
<div className="max-w-2xl w-full bg-white shadow-lg rounded-lg p-8">
|
||||
<h1 className="text-3xl font-bold mb-4 text-center">
|
||||
Preparing for Your (Next) Career
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-6 text-center">
|
||||
Identify skills, education, or certifications you need, discover educational opportunities, and set clear milestones to start or transition into your next career.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<Button onClick={() => navigate('/interest-inventory')}>Retake Interest Inventory</Button>
|
||||
<Button onClick={() => navigate('/educational-programs')}>Explore Education Options</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PreparingLanding;
|
26
src/components/RetirementLanding.js
Normal file
26
src/components/RetirementLanding.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from './ui/button.js';
|
||||
|
||||
function RetirementLanding() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
|
||||
<div className="max-w-2xl w-full bg-white shadow-lg rounded-lg p-8">
|
||||
<h1 className="text-3xl font-bold mb-4 text-center">
|
||||
Retirement Planning
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-6 text-center">
|
||||
Plan strategically and financially for retirement. AptivaAI provides you with clear financial projections, milestone tracking, and scenario analysis for a secure future.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<Button onClick={() => navigate('/financial-profile')}>Update Financial Profile</Button>
|
||||
<Button onClick={() => navigate('/milestone-tracker')}>Set Retirement Milestones</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RetirementLanding;
|
@ -47,8 +47,21 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
||||
setUser(user);
|
||||
}
|
||||
|
||||
// Navigate to next screen
|
||||
navigate('/getting-started');
|
||||
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
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
}
|
||||
|
@ -1,20 +1,59 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from './ui/button.js';
|
||||
import SituationCard from './ui/SituationCard.js';
|
||||
import PromptModal from './ui/PromptModal.js';
|
||||
|
||||
const careerSituations = [
|
||||
{
|
||||
id: "planning",
|
||||
title: "Planning Your Career",
|
||||
description: "I'm exploring options and figuring out what careers fit my interests and skills.",
|
||||
route: "/planning"
|
||||
},
|
||||
{
|
||||
id: "preparing",
|
||||
title: "Preparing for Your (Next) Career",
|
||||
description: "I'm gaining education, skills, or certifications required to start or transition into a new career.",
|
||||
route: "/preparing"
|
||||
},
|
||||
{
|
||||
id: "enhancing",
|
||||
title: "Enhancing Your Career",
|
||||
description: "I'm established professionally and want to advance, seek promotions, or shift roles.",
|
||||
route: "/enhancing"
|
||||
},
|
||||
{
|
||||
id: "retirement",
|
||||
title: "Retirement Planning",
|
||||
description: "I'm preparing financially and strategically for retirement.",
|
||||
route: "/retirement"
|
||||
}
|
||||
];
|
||||
|
||||
function SignUp() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// existing states
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [firstname, setFirstname] = useState('');
|
||||
const [lastname, setLastname] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [confirmEmail, setConfirmEmail] = useState('');
|
||||
const [zipcode, setZipcode] = useState('');
|
||||
const [state, setState] = useState('');
|
||||
const [area, setArea] = useState('');
|
||||
const [areas, setAreas] = useState([]);
|
||||
const [error, setError] = useState('');
|
||||
const [loadingAreas, setLoadingAreas] = useState(false);
|
||||
|
||||
// new states
|
||||
const [showCareerSituations, setShowCareerSituations] = useState(false);
|
||||
const [selectedSituation, setSelectedSituation] = useState(null);
|
||||
const [showPrompt, setShowPrompt] = useState(false);
|
||||
|
||||
|
||||
const states = [
|
||||
{ name: 'Alabama', code: 'AL' }, { name: 'Alaska', code: 'AK' }, { name: 'Arizona', code: 'AZ' },
|
||||
@ -35,13 +74,15 @@ function SignUp() {
|
||||
{ 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' },
|
||||
];
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAreas = async () => {
|
||||
if (!state) {
|
||||
setAreas([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingAreas(true); // Start loading
|
||||
try {
|
||||
const res = await fetch(`/api/areas?state=${state}`);
|
||||
const data = await res.json();
|
||||
@ -49,97 +90,197 @@ function SignUp() {
|
||||
} catch (err) {
|
||||
console.error('Error fetching areas:', err);
|
||||
setAreas([]);
|
||||
} finally {
|
||||
setLoadingAreas(false); // Done loading
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
fetchAreas();
|
||||
}, [state]);
|
||||
|
||||
const handleSignUp = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
|
||||
const validateFields = async () => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
||||
const zipRegex = /^\d{5}$/;
|
||||
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/;
|
||||
|
||||
if (!username || !password || !firstname || !lastname || !email || !zipcode || !state || !area) {
|
||||
|
||||
if (
|
||||
!username || !password || !confirmPassword ||
|
||||
!firstname || !lastname ||
|
||||
!email || !confirmEmail ||
|
||||
!zipcode || !state || !area
|
||||
) {
|
||||
setError('All fields are required.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emailRegex.test(email)) {
|
||||
setError('Enter a valid email address.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (email !== confirmEmail) {
|
||||
setError('Emails do not match.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!zipRegex.test(zipcode)) {
|
||||
setError('ZIP code must be exactly 5 digits.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!passwordRegex.test(password)) {
|
||||
setError('Password must include at least 8 characters, one uppercase, one lowercase, one number, and one special character.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError('Passwords do not match.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Explicitly check if username exists before proceeding
|
||||
try {
|
||||
const res = await fetch(`/api/check-username/${encodeURIComponent(username)}`);
|
||||
const data = await res.json();
|
||||
if (data.exists) {
|
||||
setError('Username already exists. Please choose a different username.');
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error checking username:', err);
|
||||
setError('Could not verify username availability. Please try again.');
|
||||
return false;
|
||||
}
|
||||
|
||||
setError('');
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
const handleNextClick = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const isValid = await validateFields();
|
||||
if (isValid) {
|
||||
setShowCareerSituations(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSituationConfirm = async () => {
|
||||
try {
|
||||
// Verify payload clearly:
|
||||
console.log("Payload sent to backend:", {
|
||||
userId: Math.floor(Math.random() * 1000000000),
|
||||
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({
|
||||
userId: Math.floor(Math.random() * 1000000000),
|
||||
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('/getting-started');
|
||||
|
||||
navigate(selectedSituation.route);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError('An unexpected error occurred. Please try again later.');
|
||||
setShowPrompt(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<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">
|
||||
<h2 className="mb-4 text-2xl font-semibold text-center">Sign Up</h2>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-2 text-sm text-red-600 bg-red-100 rounded">
|
||||
{error}
|
||||
</div>
|
||||
{!showCareerSituations ? (
|
||||
<>
|
||||
<h2 className="mb-4 text-2xl font-semibold text-center">Sign Up</h2>
|
||||
{error && (
|
||||
<div className="mb-4 p-2 text-sm text-red-600 bg-red-100 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<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 type="password" 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) => (
|
||||
<option key={s.code} value={s.code}>{s.name}</option>
|
||||
))}
|
||||
</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
|
||||
>
|
||||
<option value="">Select Area</option>
|
||||
{areas.map((a, i) => (
|
||||
<option key={i} value={a}>{a}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{loadingAreas && (
|
||||
<span className="absolute right-3 top-2.5 text-gray-400 text-sm animate-pulse">
|
||||
Loading...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Next
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="mb-4 text-xl font-semibold text-center">Choose Your Career Stage</h2>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{careerSituations.map((situation) => (
|
||||
<SituationCard
|
||||
key={situation.id}
|
||||
title={situation.title}
|
||||
description={situation.description}
|
||||
selected={selectedSituation?.id === situation.id}
|
||||
onClick={() => {
|
||||
setSelectedSituation(situation);
|
||||
setShowPrompt(true);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{showPrompt && <div className="mt-4 p-4 bg-yellow-200">Modal should be open!</div>}
|
||||
<PromptModal
|
||||
open={showPrompt}
|
||||
title="Confirm Your Selection"
|
||||
message={`Are you sure you want to select "${selectedSituation?.title}"? You can change this later in your profile settings.`}
|
||||
confirmText="Confirm"
|
||||
cancelText="Cancel"
|
||||
onConfirm={handleSituationConfirm}
|
||||
onCancel={() => setShowPrompt(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSignUp} className="space-y-3">
|
||||
<input className="w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
<input className="w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="Password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
<input className="w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="First Name" value={firstname} onChange={(e) => setFirstname(e.target.value)} />
|
||||
<input className="w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="Last Name" value={lastname} onChange={(e) => setLastname(e.target.value)} />
|
||||
<input className="w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
<input className="w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="Zip Code" value={zipcode} onChange={(e) => setZipcode(e.target.value)} />
|
||||
|
||||
<select className="w-full px-3 py-2 border border-gray-300 rounded-md" value={state} onChange={(e) => setState(e.target.value)}>
|
||||
<option value="">Select State</option>
|
||||
{states.map((s) => <option key={s.code} value={s.code}>{s.name}</option>)}
|
||||
</select>
|
||||
|
||||
<select className="w-full px-3 py-2 border border-gray-300 rounded-md" value={area} onChange={(e) => setArea(e.target.value)}>
|
||||
<option value="">Select Area</option>
|
||||
{areas.map((a, i) => <option key={i} value={a}>{a}</option>)}
|
||||
</select>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Sign Up
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignUp;
|
||||
export default SignUp;
|
27
src/components/ui/PromptModal.js
Normal file
27
src/components/ui/PromptModal.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from './dialog.js';
|
||||
import { Button } from './button.js';
|
||||
import { cn } from '../../utils/cn.js'; // <-- explicitly added missing import
|
||||
|
||||
const PromptModal = ({ open, title, message, confirmText, cancelText, onConfirm, onCancel }) => (
|
||||
<Dialog open={open} onOpenChange={(isOpen) => { if (!isOpen) onCancel(); }}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className={cn("py-4 text-sm text-gray-700")}>
|
||||
{message}
|
||||
</div>
|
||||
<DialogFooter className={cn("gap-2")}>
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
{cancelText || 'Cancel'}
|
||||
</Button>
|
||||
<Button onClick={onConfirm}>
|
||||
{confirmText || 'Confirm'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
export default PromptModal;
|
20
src/components/ui/SituationCard.js
Normal file
20
src/components/ui/SituationCard.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent } from './card.js';
|
||||
import { cn } from '../../utils/cn.js';
|
||||
|
||||
const SituationCard = ({ title, description, selected, onClick }) => (
|
||||
<Card
|
||||
className={cn(
|
||||
'cursor-pointer transition-shadow duration-200 hover:shadow-lg',
|
||||
selected ? 'border-blue-600 ring-2 ring-blue-500' : 'border-gray-300'
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<CardContent>
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">{description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export default SituationCard;
|
@ -1,18 +1,26 @@
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { cn } from '../../utils/cn.js'; // <-- explicitly added missing import
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
const DialogContent = React.forwardRef(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className="fixed inset-0 bg-white shadow-lg p-4 max-w-lg mx-auto mt-20 rounded-md"
|
||||
{...props}
|
||||
/>
|
||||
<DialogPrimitive.Portal>
|
||||
<DialogPrimitive.Overlay className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm" />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-[50%] left-[50%] w-full max-w-md translate-x-[-50%] translate-y-[-50%] rounded-md bg-white p-6 shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DialogPrimitive.Portal>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
const DialogHeader = ({ children }) => {
|
||||
return <div className="text-lg font-semibold border-b pb-2 mb-4">{children}</div>;
|
||||
};
|
||||
|
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user