UX issues, schools issues

This commit is contained in:
Josh 2024-12-27 19:03:32 +00:00
parent e07dc32b51
commit 9696943f9b
12 changed files with 394 additions and 215 deletions

View File

@ -171,7 +171,7 @@ app.post('/api/login', (req, res) => {
} }
// Generate JWT // Generate JWT
const token = jwt.sign({ userId: row.user_id }, SECRET_KEY, { expiresIn: '1h' }); const token = jwt.sign({ userId: row.user_id }, SECRET_KEY, { expiresIn: '2h' });
res.status(200).json({ token }); res.status(200).json({ token });
}); });
}); });

View File

@ -346,10 +346,16 @@ app.get('/api/cip/:socCode', (req, res) => {
res.status(404).json({ error: 'CIP code not found for this SOC code' }); res.status(404).json({ error: 'CIP code not found for this SOC code' });
}); });
app.get('/api/CIP_institution_mapping_fixed.json', (req, res) => { // Filtered schools endpoint
const filePath = path.join(__dirname, 'CIP_institution_mapping_fixed.json'); // Adjust the path if needed app.get('/api/schools', (req, res) => {
res.sendFile(filePath); const { cipCode, state, level, type } = req.query;
}); const filePath = path.join(__dirname, 'CIP_institution_mapping_fixed.json');
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return res.status(500).json({ error: 'Failed to load data' });
}

View File

@ -1,18 +1,25 @@
/* src/App.css */ /* src/App.css */
/* Body styling for the entire application */ /* Body styling for the entire application */
body { /* General body and root styling for full-page coverage */
background-color: #e9ecef; /* Light gray-blue background */ body, #root {
background-color: #f4f7fb; /* Light gray-blue background */
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
margin: 0; margin: 0;
padding: 0; padding: 0;
min-height: 100vh; /* Full viewport height */
display: flex;
flex-direction: column;
} }
/* Main container to center and constrain content */ /* Main App container for consistent centering */
.container { .container {
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 20px auto;
padding: 20px; padding: 20px;
background-color: #ffffff; /* White background for sections */
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
} }
/* Main App styling */ /* Main App styling */
@ -110,3 +117,14 @@ input, select {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #ddd; border: 1px solid #ddd;
} }
/* Responsive Adjustments */
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr; /* Stack vertically on smaller screens */
}
.riasec-scores, .riasec-descriptions {
padding: 15px;
}
}

View File

@ -8,7 +8,7 @@ export function CareerSuggestions({ careerSuggestions = [], onCareerClick }) {
return ( return (
<div> <div>
<h3>Career Suggestions</h3> <h2>Career Suggestions</h2>
<ul> <ul>
{careerSuggestions.map((career, index) => { {careerSuggestions.map((career, index) => {
return ( return (

View File

@ -3,8 +3,8 @@
.dashboard { .dashboard {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* Ensure responsive layout */ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* Ensure responsive layout */
gap: 15px; gap: 20px;
padding: 15px; padding: 20px;
background-color: #f4f7fa; background-color: #f4f7fa;
} }
@ -108,3 +108,26 @@ h2 {
.career-item:hover { .career-item:hover {
background-color: #e6f7ff; /* Lighter blue when hovering */ background-color: #e6f7ff; /* Lighter blue when hovering */
} }
.riasec-descriptions {
margin-top: 20px;
padding: 15px;
border-radius: 8px;
background-color: #f1f8ff;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.riasec-descriptions ul {
list-style: none;
padding: 0;
}
.riasec-descriptions li {
margin-bottom: 10px;
line-height: 1.6;
}
.riasec-descriptions strong {
color: #007bff;
}

View File

@ -16,6 +16,7 @@ function Dashboard() {
const navigate = useNavigate(); const navigate = useNavigate();
const [careerSuggestions, setCareerSuggestions] = useState([]); const [careerSuggestions, setCareerSuggestions] = useState([]);
const [careerDetails, setCareerDetails] = useState(null);
const [riaSecScores, setRiaSecScores] = useState([]); const [riaSecScores, setRiaSecScores] = useState([]);
const [selectedCareer, setSelectedCareer] = useState(null); const [selectedCareer, setSelectedCareer] = useState(null);
const [schools, setSchools] = useState([]); const [schools, setSchools] = useState([]);
@ -26,14 +27,19 @@ function Dashboard() {
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [userState, setUserState] = useState(null); const [userState, setUserState] = useState(null);
const [areaTitle, setAreaTitle] = useState(null); const [areaTitle, setAreaTitle] = useState(null);
const [riaSecDescriptions, setRiaSecDescriptions] = useState([]);
const apiUrl = process.env.REACT_APP_API_URL; const apiUrl = process.env.REACT_APP_API_URL;
useEffect(() => { useEffect(() => {
let descriptions = []; // Declare outside for scope accessibility
if (location.state) { if (location.state) {
const { careerSuggestions: suggestions, riaSecScores: scores } = location.state || {}; const { careerSuggestions: suggestions, riaSecScores: scores } = location.state || {};
setCareerSuggestions(suggestions || []); descriptions = scores.map((score) => score.description || "No description available.");
setCareerSuggestions(suggestions || []);
setRiaSecScores(scores || []); setRiaSecScores(scores || []);
} else { setRiaSecDescriptions(descriptions); // Set descriptions
} else {
console.warn('No data found, redirecting to Interest Inventory'); console.warn('No data found, redirecting to Interest Inventory');
navigate('/interest-inventory'); navigate('/interest-inventory');
} }
@ -68,7 +74,16 @@ function Dashboard() {
const handleCareerClick = useCallback( const handleCareerClick = useCallback(
async (career) => { async (career) => {
const socCode = career.code; const socCode = career.code; // Extract SOC code from career object
setSelectedCareer(career); // Set career first to trigger loading panel
setLoading(true); // Enable loading state only when career is clicked
setError(null); // Clear previous errors
setCareerDetails({}); // Reset career details to avoid undefined errors
setSchools([]);
setSalaryData([]);
setEconomicProjections({});
setTuitionData([]);
if (!socCode) { if (!socCode) {
console.error('SOC Code is missing'); console.error('SOC Code is missing');
setError('SOC Code is missing'); setError('SOC Code is missing');
@ -76,35 +91,25 @@ function Dashboard() {
} }
try { try {
setLoading(true); // Step 1: Fetch CIP Code
setError(null); const cipResponse = await fetch(`${apiUrl}/api/cip/${socCode}`);
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
const { cipCode } = await cipResponse.json();
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
const cipResponse = await fetch(`${apiUrl}/api/cip/${socCode}`); // Step 2: Fetch Data in Parallel
const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([
if (!cipResponse.ok) { fetchSchools(cleanedCipCode, userState),
console.error('Failed to fetch CIP Code'); axios.get(`http://localhost:5001/api/projections/${socCode.split('.')[0]}`),
setError('Failed to fetch CIP Code'); axios.get(`http://localhost:5001/api/tuition/${cleanedCipCode}`, {
return;
}
const { cipCode } = await cipResponse.json();
if (!cipCode) throw new Error('Failed to fetch CIP Code');
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
const filteredSchools = await fetchSchools(cleanedCipCode, userState);
const cleanedSocCode = socCode.split('.')[0];
const economicResponse = await axios.get(`http://localhost:5001/api/projections/${cleanedSocCode}`);
const tuitionResponse = await axios.get(`http://localhost:5001/api/tuition/${cleanedCipCode}`, {
params: { state: userState }, params: { state: userState },
}); }),
axios.get(`http://localhost:5001/api/salary`, {
console.log('Salary Data Request Params:', { socCode: cleanedSocCode, area: areaTitle }); params: { socCode: socCode.split('.')[0], area: areaTitle },
const salaryResponse = await axios.get(`http://localhost:5001/api/salary`, { }),
params: { socCode: cleanedSocCode, area: areaTitle }, ]);
});
// Step 3: Format Salary Data
const salaryDataPoints = [ const salaryDataPoints = [
{ percentile: '10th Percentile', value: salaryResponse.data.A_PCT10 || 0 }, { percentile: '10th Percentile', value: salaryResponse.data.A_PCT10 || 0 },
{ percentile: '25th Percentile', value: salaryResponse.data.A_PCT25 || 0 }, { percentile: '25th Percentile', value: salaryResponse.data.A_PCT25 || 0 },
@ -113,11 +118,14 @@ function Dashboard() {
{ percentile: '90th Percentile', value: salaryResponse.data.A_PCT90 || 0 }, { percentile: '90th Percentile', value: salaryResponse.data.A_PCT90 || 0 },
]; ];
setSelectedCareer(career); // Step 4: Consolidate Career Details
setSchools(filteredSchools); setCareerDetails({
setEconomicProjections(economicResponse.data); ...career,
setTuitionData(tuitionResponse.data); economicProjections: economicResponse.data,
setSalaryData(salaryDataPoints); salaryData: salaryDataPoints,
schools: filteredSchools,
tuitionData: tuitionResponse.data,
});
} catch (error) { } catch (error) {
console.error('Error processing career click:', error.message); console.error('Error processing career click:', error.message);
setError('Failed to load data'); setError('Failed to load data');
@ -144,25 +152,43 @@ function Dashboard() {
return ( return (
<div className="dashboard"> <div className="dashboard">
<div className="career-suggestions-container"> <div className="career-suggestions-container">
<h2>Career Suggestions</h2>
<CareerSuggestions careerSuggestions={careerSuggestions} onCareerClick={handleCareerClick} /> <CareerSuggestions careerSuggestions={careerSuggestions} onCareerClick={handleCareerClick} />
</div> </div>
<div className="riasec-scores"> {/* Right RIASEC Chart + Descriptions */}
<h2>RIASEC Scores</h2> <div className="riasec-container">
<Bar data={chartData} /> <div className="riasec-scores">
</div> <h2>RIASEC Scores</h2>
<Bar data={chartData} />
</div>
<div className="riasec-descriptions">
<h3>RIASEC Personality Descriptions</h3>
{riaSecDescriptions.length > 0 ? (
<ul>
{riaSecDescriptions.map((desc, index) => (
<li key={index}>
<strong>{riaSecScores[index]?.area}:</strong> {desc}
</li>
))}
</ul>
) : (
<p>Loading descriptions...</p>
)}
</div>
</div>
{selectedCareer && ( {selectedCareer && (
<PopoutPanel <PopoutPanel
career={selectedCareer} data={careerDetails}
schools={schools || []} schools={schools}
salaryData={salaryData || []} salaryData={salaryData}
economicProjections={economicProjections || {}} economicProjections={economicProjections}
tuitionData={tuitionData || {}} tuitionData={tuitionData}
closePanel={() => setSelectedCareer(null)} closePanel={() => setSelectedCareer(null)}
loading={loading} loading={loading}
error={error} error={error}
userState={userState}
/> />
)} )}
</div> </div>

View File

@ -1,78 +1,121 @@
/* src/components/InterestInventory.css */ /* src/components/InterestInventory.css */
/* Container Styling */
.interest-inventory-container { .interest-inventory-container {
max-width: 600px; max-width: 600px;
margin: 40px auto; /* Center the container */ margin: 40px auto; /* Center the container */
padding: 20px; padding: 20px;
background-color: #8d98f8; /* White background */ background-color: #f0f8ff; /* Sky-white background */
border-radius: 8px; border-radius: 12px; /* Rounded borders */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Subtle shadow effect */
border: 1px solid #060c02; border: 4px solid #87ceeb; /* Sky-blue trim */
} }
.interest-inventory-container h2 { /* Header Styling */
color: #0e0202; .interest-inventory-container h2 {
text-align: center; color: #0056b3; /* Dark blue header */
margin-bottom: 20px; text-align: center;
} font-size: 28px; /* Larger font size for emphasis */
margin-bottom: 20px;
.question { font-weight: bold;
margin-bottom: 20px; }
}
/* Question Container */
/* Label styling for each question */ .question {
.question label { margin-bottom: 20px;
display: block; }
font-weight: bold;
margin-bottom: 8px; /* Label Styling */
color: #0f0101; .question label {
} display: block;
font-size: 18px; /* Slightly larger text for questions */
/* Set fixed width and max-width for dropdowns within InterestInventory */ font-weight: 600; /* Semi-bold text */
.question select { margin-bottom: 8px;
width: auto; /* Allow it to adjust based on content */ color: #333; /* Darker text for readability */
max-width: 300px; /* Set a maximum width to prevent stretching */ }
padding: 8px;
border: 1px solid #ccc; /* Dropdown Styling */
border-radius: 4px; .question select {
font-size: 16px; width: 80%; /* Use percentage for responsiveness */
} max-width: 400px; /* Limit maximum size */
padding: 10px;
/* Button styling for a uniform appearance */ border: 1px solid #87ceeb; /* Sky-blue border */
button { border-radius: 8px; /* Rounded edges */
margin-top: 20px; font-size: 16px;
padding: 10px 20px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Subtle shadow */
background-color: #007bff; transition: border-color 0.3s ease;
color: #fff; display: block;
border: none; margin: 0 auto; /* Center the dropdown */
border-radius: 4px; }
cursor: pointer;
font-size: 16px; /* Dropdown Hover Effect */
} .question select:hover {
border-color: #007bff; /* Brighter blue on hover */
button:hover { }
background-color: #0056b3;
} /* Dropdown Focus Effect */
.question select:focus {
/* Submission message styling */ border-color: #0056b3;
.submit-message { outline: none;
color: #28a745; }
font-weight: bold;
text-align: center; /* Buttons */
margin-top: 20px; button {
} margin: 10px 5px; /* Add spacing between buttons */
padding: 10px 20px;
.validation-error-container { background-color: #007bff; /* Blue button */
margin-top: 10px; /* Position the error message below the questions */ color: #fff;
} border: none;
border-radius: 8px; /* Rounded edges */
.validation-error { cursor: pointer;
color: red; font-size: 16px;
font-weight: bold; transition: background-color 0.3s ease;
} }
.unanswered { /* Hover Effect for Buttons */
border: 2px solid #b22222; /* Darker red (Firebrick) */ button:hover {
background-color: #ee6161; /* Light red background remains unchanged */ background-color: #0056b3; /* Darker blue */
} }
/* Submit Button Styling */
button:disabled {
background-color: #a0aec0;
cursor: not-allowed;
}
/* Submission Message Styling */
.submit-message {
color: #28a745; /* Green success text */
font-weight: bold;
text-align: center;
margin-top: 20px;
}
/* Validation Error Message */
.validation-error {
color: red;
font-weight: bold;
}
/* Highlight Unanswered Questions */
.unanswered {
border: 2px solid #b22222; /* Darker red (Firebrick) */
background-color: #ffe6e6; /* Light red background */
}
/* Pagination Buttons Styling */
.pagination-buttons {
display: flex;
justify-content: center;
margin-top: 20px;
}
/* Randomize Answers Button */
.randomize-button {
background-color: #17a2b8; /* Teal button */
color: #fff;
}
.randomize-button:hover {
background-color: #138496; /* Darker teal */
}

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ClipLoader } from 'react-spinners';
import './InterestInventory.css'; import './InterestInventory.css';
const InterestInventory = () => { const InterestInventory = () => {
@ -9,9 +10,14 @@ const InterestInventory = () => {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const questionsPerPage = 6; const questionsPerPage = 6;
const navigate = useNavigate(); const navigate = useNavigate();
const [careerSuggestions, setCareerSuggestions] = useState([]); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchQuestions = async () => { const fetchQuestions = async () => {
setLoading(true); // Start loading
setError(null); // Reset error state
const baseUrl = process.env.REACT_APP_API_URL || 'http://localhost:5001'; // Default to localhost:5001 for development const baseUrl = process.env.REACT_APP_API_URL || 'http://localhost:5001'; // Default to localhost:5001 for development
const url = `${baseUrl}/api/onet/questions?start=1&end=60`; // Make sure the endpoint is correctly appended to the base URL const url = `${baseUrl}/api/onet/questions?start=1&end=60`; // Make sure the endpoint is correctly appended to the base URL
@ -27,17 +33,17 @@ const InterestInventory = () => {
const data = await response.json(); const data = await response.json();
// Adjusted to properly access the questions array
if (data && Array.isArray(data.questions)) { if (data && Array.isArray(data.questions)) {
setQuestions(data.questions); setQuestions(data.questions);
console.log("Questions fetched:", data.questions); console.log("Questions fetched:", data.questions);
} else { } else {
console.error("Invalid question format:", data); throw new Error("Invalid question format.");
setQuestions([]);
} }
} catch (error) { } catch (error) {
console.error("Error fetching questions:", error.message); console.error("Error fetching questions:", error.message);
alert("Failed to load questions. Please try again later."); setError(error.message); // Set error message
} finally {
setLoading(false); // Stop loading
} }
}; };
@ -94,6 +100,7 @@ const InterestInventory = () => {
try { try {
setIsSubmitting(true); setIsSubmitting(true);
setError(null); // Clear previous errors
const baseUrl = process.env.NODE_ENV === 'production' const baseUrl = process.env.NODE_ENV === 'production'
? '/api/onet/submit_answers' // In production, this is proxied by Nginx to server2 (port 5001) ? '/api/onet/submit_answers' // In production, this is proxied by Nginx to server2 (port 5001)
: 'http://localhost:5001/api/onet/submit_answers'; // In development, server2 runs on port 5001 : 'http://localhost:5001/api/onet/submit_answers'; // In development, server2 runs on port 5001
@ -135,6 +142,9 @@ const InterestInventory = () => {
return ( return (
<div className="interest-inventory-container"> <div className="interest-inventory-container">
<h2>Interest Inventory</h2> <h2>Interest Inventory</h2>
{loading && <ClipLoader size={35} color="#4A90E2" />}
{error && <p style={{ color: 'red' }}>{error}</p>}
{questions.length > 0 ? ( {questions.length > 0 ? (
<div className="questions-container"> <div className="questions-container">
{currentQuestions.map((question) => ( {currentQuestions.map((question) => (

View File

@ -1,26 +1,18 @@
// PopoutPanel.js // PopoutPanel.js
import ClipLoader from 'react-spinners/ClipLoader.js'; import { ClipLoader } from 'react-spinners';
import LoanRepayment from './LoanRepayment.js'; import LoanRepayment from './LoanRepayment.js';
import './PopoutPanel.css'; import './PopoutPanel.css';
function PopoutPanel({ function PopoutPanel({
career = {}, data = {},
schools = [], userState = 'N/A', // Passed explicitly from Dashboard
salaryData = {},
economicProjections = {},
tuitionData = {},
loading = false, loading = false,
error = null, error = null,
closePanel closePanel
}) { }) {
console.log('PopoutPanel Props:', { career, schools, salaryData, economicProjections, tuitionData, loading, error });
// Validation Checks
const isValidCareer = career && career.title && career.code;
const isValidSchools = Array.isArray(schools) && schools.length > 0;
const isValidSalaryData = salaryData && Object.keys(salaryData).length > 0;
const isValidProjections = economicProjections && Object.keys(economicProjections).length > 0;
console.log('PopoutPanel Props:', { data, loading, error, userState });
if (loading) { if (loading) {
return ( return (
<div className="popout-panel"> <div className="popout-panel">
@ -41,43 +33,86 @@ function PopoutPanel({
); );
} }
if (!career) { // Handle empty data gracefully
if (!data || Object.keys(data).length === 0) {
return ( return (
<div className="popout-panel"> <div className="popout-panel">
<button className="close-btn" onClick={closePanel}>X</button> <button onClick={closePanel}>Close</button>
<h2>No Career Selected</h2> <h2>No Career Data Available</h2>
</div> </div>
); );
} }
// Handle empty data gracefully
if (!data || Object.keys(data).length === 0) {
return (
<div className="popout-panel">
<button onClick={closePanel}>Close</button>
<h2>No Career Data Available</h2>
</div>
);
}
// Safely access nested data with fallbacks
const {
title = 'Career Details',
description = 'No description available.',
economicProjections = {},
salaryData = [],
schools = [],
tuitionData = []
} = data;
const tenthPercentileSalary = salaryData?.find( const tenthPercentileSalary = salaryData?.find(
(point) => point.percentile === '10th Percentile' (point) => point.percentile === '10th Percentile'
)?.value || 0; )?.value || 0;
const getProgramLength = (degreeType) => {
if (degreeType?.includes("Associate")) return 2;
if (degreeType?.includes("Bachelor")) return 4;
if (degreeType?.includes("Master")) return 6;
if (degreeType?.includes("Doctoral") || degreeType?.includes("First Professional")) return 8;
if (degreeType?.includes("Certificate")) return 1;
return 4; // Default to 4 years if unspecified
};
console.log('PopoutPanel Props:', { data, schools, salaryData, economicProjections, tuitionData, loading, error, userState });
return ( return (
<div className="popout-panel"> <div className="popout-panel">
<button className="close-btn" onClick={closePanel}>X</button> <button onClick={closePanel}>Close</button>
<h2>{career?.title || 'Career Details'}</h2> <h2>{data.title || 'Career Details'}</h2>
<p>Description: {data.description || 'No description available.'}</p>
{/* Schools Offering Programs */} {/* Schools Offering Programs */}
<h3>Schools Offering Programs</h3> <h3>Schools Offering Programs</h3>
{Array.isArray(schools) && schools.length > 0 ? ( {Array.isArray(schools) && schools.length > 0 ? (
<ul> <ul>
{schools.map((school, index) => ( {schools.map((school, index) => {
<li key={index}> const matchingTuitionData = tuitionData.find(
<strong>{school['Institution Name']}</strong> <br /> (tuition) =>
In-State Tuition: ${school.inStateTuition || 'N/A'} <br /> tuition['school.name']?.toLowerCase().trim() ===
Out-of-State Tuition: ${school.outOfStateTuition || 'N/A'} school['Institution Name']?.toLowerCase().trim()
</li> );
))}
return (
<li key={index}>
<strong>{school['Institution Name']}</strong>
<br />
Degree Type: {school['CREDDESC'] || 'N/A'}
<br />
In-State Tuition: ${matchingTuitionData?.['latest.cost.tuition.in_state'] || 'N/A'}
<br />
Out-of-State Tuition: ${matchingTuitionData?.['latest.cost.tuition.out_of_state'] || 'N/A'}
</li>
);
})}
</ul> </ul>
) : ( ) : (
<p>No schools available.</p> <p>No schools available.</p>
)} )}
{/* Economic Projections */} {/* Economic Projections */}
<h3>Economic Projections</h3> <h3>Economic Projections for {userState}</h3>
{economicProjections && typeof economicProjections === 'object' ? ( {economicProjections && typeof economicProjections === 'object' ? (
<ul> <ul>
<li>2022 Employment: {economicProjections['2022 Employment'] || 'N/A'}</li> <li>2022 Employment: {economicProjections['2022 Employment'] || 'N/A'}</li>
@ -117,8 +152,24 @@ function PopoutPanel({
{tenthPercentileSalary > 0 && ( {tenthPercentileSalary > 0 && (
<LoanRepayment <LoanRepayment
tuitionCosts={{ tuitionCosts={{
inState: schools.map((s) => parseFloat(s.inStateTuition) || 0), inState: schools.map((school) => {
outOfState: schools.map((s) => parseFloat(s.outOfStateTuition) || 0), const matchingTuitionData = tuitionData.find(
(tuition) =>
tuition['school.name']?.toLowerCase().trim() ===
school['Institution Name']?.toLowerCase().trim()
);
const years = getProgramLength(school['CREDDESC']);
return parseFloat(matchingTuitionData?.['latest.cost.tuition_in_state'] * years) || 0;
}),
outOfState: schools.map((school) => {
const matchingTuitionData = tuitionData.find(
(tuition) =>
tuition['school.name']?.toLowerCase().trim() ===
school['Institution Name']?.toLowerCase().trim()
);
const years = getProgramLength(school['CREDDESC']);
return parseFloat(matchingTuitionData?.['latest.cost.tuition_out_of_state'] * years) || 0;
}),
}} }}
salaryData={[{ percentile: '10th Percentile', value: tenthPercentileSalary, growthRate: 0.03 }]} salaryData={[{ percentile: '10th Percentile', value: tenthPercentileSalary, growthRate: 0.03 }]}
earningHorizon={10} earningHorizon={10}
@ -129,3 +180,4 @@ function PopoutPanel({
} }
export default PopoutPanel; export default PopoutPanel;

View File

@ -8,30 +8,36 @@
} }
.signin-form { .signin-form {
width: 300px; width: 100%; /* Make the form responsive */
max-width: 350px; /* Set maximum width for the form */
padding: 20px; padding: 20px;
background-color: #fff; background-color: #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.1); box-shadow: 0 0 10px rgba(0,0,0,0.1);
text-align: center; text-align: center;
} border-radius: 8px; /* Optional rounded corners */
}
.signin-input { .signin-input {
width: 100%; width: 90%; /* Adjust input width to leave margin */
padding: 10px; max-width: 280px; /* Optional max width */
margin: 10px 0; padding: 10px;
border: 1px solid #ddd; margin: 10px auto; /* Center input fields with auto margin */
border-radius: 5px; border: 1px solid #ddd;
} border-radius: 5px;
display: block; /* Ensures inputs take full width of their container */
}
.signin-button { .signin-button {
width: 100%; width: 90%; /* Match input width */
padding: 10px; max-width: 280px; /* Optional max width */
background-color: #4CAF50; padding: 10px;
color: #fff; background-color: #4CAF50;
border: none; color: #fff;
border-radius: 5px; border: none;
cursor: pointer; border-radius: 5px;
} cursor: pointer;
margin-top: 10px; /* Add space above the button */
}
.signin-button:hover { .signin-button:hover {
background-color: #45a049; background-color: #45a049;

View File

@ -1,23 +1,23 @@
// components/SignIn.js import React, { useRef, useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import './SignIn.css'; import './SignIn.css';
function SignIn({ setIsAuthenticated }) { function SignIn({ setIsAuthenticated }) {
console.log('SignIn component loaded'); console.log('SignIn component loaded');
console.log('Rendering SignIn component');
console.log('Authentication state in SignIn:', setIsAuthenticated);
const navigate = useNavigate(); const navigate = useNavigate();
const [username, setUsername] = useState(''); const usernameRef = useRef('');
const [password, setPassword] = useState(''); const passwordRef = useRef('');
const [error, setError] = useState(''); const [error, setError] = useState(''); // Still needs state for UI updates
const handleSignIn = async (event) => { const handleSignIn = async (event) => {
event.preventDefault(); event.preventDefault();
setError(''); setError('');
const username = usernameRef.current.value;
const password = passwordRef.current.value;
if (!username || !password) { if (!username || !password) {
setError('Please enter both username and password'); setError('Please enter both username and password');
return; return;
@ -38,24 +38,22 @@ function SignIn({ setIsAuthenticated }) {
} }
const data = await response.json(); const data = await response.json();
console.log('SignIn response data:', data); console.log('SignIn response data:', data);
const { token, userId } = data; const { token, userId } = data;
localStorage.setItem('token', token); localStorage.setItem('token', token);
localStorage.setItem('userId', userId); localStorage.setItem('userId', userId);
console.log('Token and userId saved in localStorage'); console.log('Token and userId saved in localStorage');
// Call setIsAuthenticated(true) to update the state // Call setIsAuthenticated(true) to update the state
setIsAuthenticated(true); setIsAuthenticated(true);
navigate('/getting-started'); // Redirect to GettingStarted after SignIn navigate('/getting-started'); // Redirect to GettingStarted after SignIn
} catch (error) { } catch (error) {
console.error('Sign-In Error:', error.message); console.error('Sign-In Error:', error.message);
setError(error.message); // Display error message to the user setError(error.message); // Display error message to the user
} }
}; };
return ( return (
<div className="signin-container"> <div className="signin-container">
@ -66,22 +64,20 @@ function SignIn({ setIsAuthenticated }) {
<input <input
type="text" type="text"
placeholder="Username" placeholder="Username"
value={username} ref={usernameRef} // Use ref instead of state
onChange={(e) => setUsername(e.target.value)}
className="signin-input" className="signin-input"
/> />
<input <input
type="password" type="password"
placeholder="Password" placeholder="Password"
value={password} ref={passwordRef} // Use ref instead of state
onChange={(e) => setPassword(e.target.value)}
className="signin-input" className="signin-input"
/> />
<button type="submit" className="signin-button"> <button type="submit" className="signin-button">
Sign In Sign In
</button> </button>
</form> </form>
<p>Dont have an account? <Link to="/signup">Sign Up</Link></p> {/* Sign-Up Link */} <p>Dont have an account? <Link to="/signup">Sign Up</Link></p>
</div> </div>
</div> </div>
); );

View File

@ -19,22 +19,21 @@ export const fetchAreasByState = async (state) => {
}; };
//fetch schools // Fetch schools
export const fetchSchools = async (cipCode, userState) => { export const fetchSchools = async (cipCode, state = '', level = '', type = '') => {
try { try {
const response = await axios.get('http://127.0.0.1:5001/api/CIP_institution_mapping_fixed.json'); const response = await axios.get('http://127.0.0.1:5001/api/schools', {
const schoolsData = response.data; params: {
cipCode,
state,
level,
type,
},
});
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4); return response.data; // Return filtered data
return schoolsData.filter(
(school) =>
typeof school['CIP Code'] === 'string' &&
school['CIP Code'].replace('.', '') === cleanedCipCode &&
school['State'] === userState
);
} catch (error) { } catch (error) {
console.error('Error fetching schools:', error); console.error('Error fetching schools:', error);
return []; return [];
} }
}; };