}/>
} />
} />
} />
diff --git a/src/components/CareerExplorer.js b/src/components/CareerExplorer.js
index 9fb614d..be992ea 100644
--- a/src/components/CareerExplorer.js
+++ b/src/components/CareerExplorer.js
@@ -404,7 +404,7 @@ function CareerExplorer({ }) {
: fitRatingMap[career.fit] || masterRatings.interests || 3;
const meaningRating = parseInt(
- prompt("How meaningful is this career to you? (1-5):", "3"),
+ prompt("How important do you feel this job is to society or the world? (1-5):", "3"),
10
);
diff --git a/src/components/EducationalProgramsPage.js b/src/components/EducationalProgramsPage.js
new file mode 100644
index 0000000..3b38d97
--- /dev/null
+++ b/src/components/EducationalProgramsPage.js
@@ -0,0 +1,284 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { useLocation } from 'react-router-dom';
+
+// Your existing search component
+import CareerSearch from './CareerSearch.js';
+
+// The existing utility calls
+import { fetchSchools, clientGeocodeZip, haversineDistance } from '../utils/apiUtils.js';
+
+// A simple Button component (if you don’t already import from elsewhere)
+function Button({ onClick, children, ...props }) {
+ return (
+
+ );
+}
+
+/**
+ * EducationalProgramsPage
+ * - If we have a CIP code (from location.state or otherwise), we fetch + display schools.
+ * - If no CIP code is provided, user sees a CareerSearch to pick a career => sets CIP code.
+ * - Then the user can filter & sort (tuition, distance, optional in-state only).
+ */
+function EducationalProgramsPage() {
+ // 1) Get CIP code from React Router location.state (if available)
+ // If no CIP code in route state, default to an empty string
+ const location = useLocation();
+ const [cipCode, setCipCode] = useState(location.state?.cipCode || '');
+
+ // Optionally, you can also read userState / userZip from location.state or from user’s profile
+ const [userState, setUserState] = useState(location.state?.userState || '');
+ const [userZip, setUserZip] = useState(location.state?.userZip || '');
+
+ // ============ Data + UI state ============
+ const [schools, setSchools] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ // Filter states
+ const [sortBy, setSortBy] = useState('tuition'); // 'tuition' or 'distance'
+ const [maxTuition, setMaxTuition] = useState(99999);
+ const [maxDistance, setMaxDistance] = useState(99999);
+ // Optional “in-state only” toggle
+ const [inStateOnly, setInStateOnly] = useState(false);
+
+ // ============ Handle Career Search -> CIP code ============
+ const handleCareerSelected = (foundObj) => {
+ // foundObj = { title, soc_code, cip_code } from CareerSearch
+ if (foundObj?.cip_code) {
+ setCipCode(foundObj.cip_code);
+ }
+ };
+
+ // ============ Fetch + Compute Distance once we have a CIP code ============
+ useEffect(() => {
+ // If no CIP code is set yet, do nothing.
+ if (!cipCode) return;
+
+ const fetchData = async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ // 1) Fetch schools by CIP code (and userState if your API still uses it)
+ const fetchedSchools = await fetchSchools(cipCode, userState);
+
+ // 2) Optionally geocode user ZIP to compute distances
+ let userLat = null;
+ let userLng = null;
+ if (userZip) {
+ try {
+ const geoResult = await clientGeocodeZip(userZip);
+ userLat = geoResult.lat;
+ userLng = geoResult.lng;
+ } catch (geoErr) {
+ console.warn('Unable to geocode user ZIP:', geoErr.message);
+ }
+ }
+
+ // 3) Compute distance for each school (if lat/lng is available)
+ const schoolsWithDistance = fetchedSchools.map((sch) => {
+ const lat2 = sch.LATITUDE ? parseFloat(sch.LATITUDE) : null;
+ const lon2 = sch.LONGITUD ? parseFloat(sch.LONGITUD) : null;
+
+ if (userLat && userLng && lat2 && lon2) {
+ const distMiles = haversineDistance(userLat, userLng, lat2, lon2);
+ return { ...sch, distance: distMiles.toFixed(1) };
+ } else {
+ return { ...sch, distance: null };
+ }
+ });
+
+ setSchools(schoolsWithDistance);
+ } catch (err) {
+ console.error('Error fetching/processing schools:', err);
+ setError('Failed to load schools.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+ }, [cipCode, userState, userZip]);
+
+ // ============ Filter + Sort ============
+ const filteredAndSortedSchools = useMemo(() => {
+ if (!schools) return [];
+ let result = [...schools];
+
+ // 1) (Optional) In-state only
+ if (inStateOnly && userState) {
+ result = result.filter((sch) => sch.STABBR === userState);
+ }
+
+ // 2) Filter by max tuition
+ // We’ll use “In_state cost” if your data references that, or you can adapt.
+ result = result.filter((sch) => {
+ const inStateCost = sch['In_state cost']
+ ? parseFloat(sch['In_state cost'])
+ : 999999;
+ return inStateCost <= maxTuition;
+ });
+
+ // 3) Filter by max distance
+ result = result.filter((sch) => {
+ if (sch.distance === null) {
+ // If distance is unknown, decide if you want to include or exclude it
+ return true; // let’s include unknown
+ }
+ return parseFloat(sch.distance) <= maxDistance;
+ });
+
+ // 4) Sort
+ if (sortBy === 'distance') {
+ result.sort((a, b) => {
+ const distA = a.distance !== null ? parseFloat(a.distance) : Infinity;
+ const distB = b.distance !== null ? parseFloat(b.distance) : Infinity;
+ return distA - distB;
+ });
+ } else {
+ // sort by tuition
+ result.sort((a, b) => {
+ const tA = a['In_state cost'] ? parseFloat(a['In_state cost']) : Infinity;
+ const tB = b['In_state cost'] ? parseFloat(b['In_state cost']) : Infinity;
+ return tA - tB;
+ });
+ }
+
+ return result;
+ }, [schools, sortBy, maxTuition, maxDistance, inStateOnly, userState]);
+
+ // ============ Render UI ============
+
+ // 1) If we have NO CIP code yet, show the fallback “CareerSearch”
+ if (!cipCode) {
+ return (
+
+
Educational Programs
+
+ You have not selected a career yet. Please search for one below:
+
+
+
+ After you pick a career, we’ll display matching educational programs.
+
+
+ );
+ }
+
+ // 2) If we DO have a CIP code, show the filterable school list
+ if (loading) {
+ return Loading schools...
;
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+ Schools Offering Programs for CIP: {cipCode}
+
+
+ {/* Filter Bar */}
+
+ {/* Sort */}
+
+
+ {/* Tuition */}
+
+
+ {/* Distance */}
+
+
+ {/* Optional: In-State Only Toggle */}
+ {userState && (
+
+ )}
+
+
+ {/* School List */}
+ {filteredAndSortedSchools.length > 0 ? (
+
+ {filteredAndSortedSchools.map((school, idx) => (
+
+
{school['INSTNM'] || 'Unnamed School'}
+
Degree Type: {school['CREDDESC'] || 'N/A'}
+
In-State Tuition: ${school['In_state cost'] || 'N/A'}
+
Out-of-State Tuition: ${school['Out_state cost'] || 'N/A'}
+
+ Distance:{' '}
+ {school.distance !== null ? `${school.distance} mi` : 'N/A'}
+
+
+ Website:{' '}
+ {school['Website'] ? (
+
+ {school['Website']}
+
+ ) : (
+ 'N/A'
+ )}
+
+
+ ))}
+
+ ) : (
+
+ No schools matching your filters.
+
+ )}
+
+ );
+}
+
+export default EducationalProgramsPage;
diff --git a/src/components/SignUp.js b/src/components/SignUp.js
index a6f81e4..50ad145 100644
--- a/src/components/SignUp.js
+++ b/src/components/SignUp.js
@@ -252,7 +252,7 @@ function SignUp() {
>
) : (
<>
- Choose Your Career Stage
+ Where are you in your career journey right now?
{careerSituations.map((situation) => (
{
const token = localStorage.getItem('token');
if (!token) {
@@ -48,10 +49,6 @@ function UserProfile() {
const res = await authFetch('/api/user-profile', {
method: 'GET',
- headers: {
- Authorization: `Bearer ${token}`,
- 'Content-Type': 'application/json',
- },
});
if (!res || !res.ok) return;
@@ -70,6 +67,7 @@ function UserProfile() {
setIsPremiumUser(true);
}
+ // If we have a state, load its areas
if (data.state) {
setLoadingAreas(true);
try {
@@ -92,8 +90,10 @@ function UserProfile() {
};
fetchProfileAndAreas();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // only runs once
+ // Whenever user changes "selectedState", re-fetch areas
useEffect(() => {
const fetchAreasByState = async () => {
if (!selectedState) {
@@ -144,9 +144,6 @@ function UserProfile() {
try {
const response = await authFetch('/api/user-profile', {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
body: JSON.stringify(profileData),
});
@@ -159,59 +156,45 @@ function UserProfile() {
}
};
- // FULL list of states, including all 50 states (+ DC if desired)
+ // FULL list of states for your dropdown
const states = [
- { name: 'Alabama', code: 'AL' },
- { name: 'Alaska', code: 'AK' },
- { name: 'Arizona', code: 'AZ' },
- { name: 'Arkansas', code: 'AR' },
- { name: 'California', code: 'CA' },
- { name: 'Colorado', code: 'CO' },
- { name: 'Connecticut', code: 'CT' },
- { name: 'Delaware', code: 'DE' },
- { name: 'District of Columbia', code: 'DC' },
- { name: 'Florida', code: 'FL' },
- { name: 'Georgia', code: 'GA' },
- { name: 'Hawaii', code: 'HI' },
- { name: 'Idaho', code: 'ID' },
- { name: 'Illinois', code: 'IL' },
- { name: 'Indiana', code: 'IN' },
- { name: 'Iowa', code: 'IA' },
- { name: 'Kansas', code: 'KS' },
- { name: 'Kentucky', code: 'KY' },
- { name: 'Louisiana', code: 'LA' },
- { name: 'Maine', code: 'ME' },
- { name: 'Maryland', code: 'MD' },
- { name: 'Massachusetts', code: 'MA' },
- { name: 'Michigan', code: 'MI' },
- { name: 'Minnesota', code: 'MN' },
- { name: 'Mississippi', code: 'MS' },
- { name: 'Missouri', code: 'MO' },
- { name: 'Montana', code: 'MT' },
- { name: 'Nebraska', code: 'NE' },
- { name: 'Nevada', code: 'NV' },
- { name: 'New Hampshire', code: 'NH' },
- { name: 'New Jersey', code: 'NJ' },
- { name: 'New Mexico', code: 'NM' },
- { name: 'New York', code: 'NY' },
- { name: 'North Carolina', code: 'NC' },
- { name: 'North Dakota', code: 'ND' },
- { name: 'Ohio', code: 'OH' },
- { name: 'Oklahoma', code: 'OK' },
- { name: 'Oregon', code: 'OR' },
- { name: 'Pennsylvania', code: 'PA' },
- { name: 'Rhode Island', code: 'RI' },
- { name: 'South Carolina', code: 'SC' },
- { name: 'South Dakota', code: 'SD' },
- { name: 'Tennessee', code: 'TN' },
- { name: 'Texas', code: 'TX' },
- { name: 'Utah', code: 'UT' },
- { name: 'Vermont', code: 'VT' },
- { name: 'Virginia', code: 'VA' },
- { name: 'Washington', code: 'WA' },
- { name: 'West Virginia', code: 'WV' },
- { name: 'Wisconsin', code: 'WI' },
- { name: 'Wyoming', code: 'WY' },
+ { name: 'Alabama', code: 'AL' }, { name: 'Alaska', code: 'AK' }, { name: 'Arizona', code: 'AZ' },
+ { name: 'Arkansas', code: 'AR' }, { name: 'California', code: 'CA' }, { name: 'Colorado', code: 'CO' },
+ { name: 'Connecticut', code: 'CT' }, { name: 'Delaware', code: 'DE' }, { name: 'District of Columbia', code: 'DC' },
+ { name: 'Florida', code: 'FL' }, { name: 'Georgia', code: 'GA' }, { name: 'Hawaii', code: 'HI' },
+ { name: 'Idaho', code: 'ID' }, { name: 'Illinois', code: 'IL' }, { name: 'Indiana', code: 'IN' },
+ { name: 'Iowa', code: 'IA' }, { name: 'Kansas', code: 'KS' }, { name: 'Kentucky', code: 'KY' },
+ { name: 'Louisiana', code: 'LA' }, { name: 'Maine', code: 'ME' }, { name: 'Maryland', code: 'MD' },
+ { name: 'Massachusetts', code: 'MA' }, { name: 'Michigan', code: 'MI' }, { name: 'Minnesota', code: 'MN' },
+ { name: 'Mississippi', code: 'MS' }, { name: 'Missouri', code: 'MO' }, { name: 'Montana', code: 'MT' },
+ { name: 'Nebraska', code: 'NE' }, { name: 'Nevada', code: 'NV' }, { name: 'New Hampshire', code: 'NH' },
+ { name: 'New Jersey', code: 'NJ' }, { name: 'New Mexico', code: 'NM' }, { name: 'New York', code: 'NY' },
+ { name: 'North Carolina', code: 'NC' }, { name: 'North Dakota', code: 'ND' }, { name: 'Ohio', code: 'OH' },
+ { name: 'Oklahoma', code: 'OK' }, { name: 'Oregon', code: 'OR' }, { name: 'Pennsylvania', code: 'PA' },
+ { name: 'Rhode Island', code: 'RI' }, { name: 'South Carolina', code: 'SC' }, { name: 'South Dakota', code: 'SD' },
+ { name: 'Tennessee', code: 'TN' }, { name: 'Texas', code: 'TX' }, { name: 'Utah', code: 'UT' },
+ { name: 'Vermont', code: 'VT' }, { name: 'Virginia', code: 'VA' }, { name: 'Washington', code: 'WA' },
+ { name: 'West Virginia', code: 'WV' }, { name: 'Wisconsin', code: 'WI' }, { name: 'Wyoming', code: 'WY' },
+ ];
+
+ // The updated career situations (same as in SignUp.js)
+ const careerSituations = [
+ {
+ id: 'planning',
+ title: 'Planning Your Career',
+ },
+ {
+ id: 'preparing',
+ title: 'Preparing for Your (Next) Career',
+ },
+ {
+ id: 'enhancing',
+ title: 'Enhancing Your Career',
+ },
+ {
+ id: 'retirement',
+ title: 'Retirement Planning',
+ },
];
return (
@@ -296,54 +279,52 @@ function UserProfile() {
- {loadingAreas && Loading areas...
}
-
- {/* Areas Dropdown */}
- {loadingAreas ? (
+ {/* Loading indicator for areas */}
+ {loadingAreas && (
Loading areas...
- ) : (
- areas.length > 0 && (
-
-
-
-
- )
)}
- {/* Premium-Only Field */}
- {isPremiumUser && (
+ {/* Areas Dropdown */}
+ {!loadingAreas && areas.length > 0 && (
)}
+ {/* Career Situation */}
+
+
+
+
+
{/* Form Buttons */}