diff --git a/AutoSuggestFields.js b/AutoSuggestFields.js
index fe076ca..e69de29 100644
--- a/AutoSuggestFields.js
+++ b/AutoSuggestFields.js
@@ -1,83 +0,0 @@
-import React, { useState } from 'react';
-
-const AutoSuggestFields = () => {
- const [cluster, setCluster] = useState('');
- const [subdivision, setSubdivision] = useState('');
- const [career, setCareer] = useState('');
-
- const clusters = ['Cluster A', 'Cluster B', 'Cluster C'];
- const subdivisions = {
- 'Cluster A': ['Subdivision A1', 'Subdivision A2'],
- 'Cluster B': ['Subdivision B1', 'Subdivision B2'],
- 'Cluster C': ['Subdivision C1', 'Subdivision C2'],
- };
- const careers = {
- 'Subdivision A1': ['Career A1-1', 'Career A1-2'],
- 'Subdivision B1': ['Career B1-1', 'Career B1-2'],
- 'Subdivision C1': ['Career C1-1', 'Career C1-2'],
- };
-
- const handleClusterChange = (e) => {
- setCluster(e.target.value);
- setSubdivision('');
- setCareer('');
- };
-
- const handleSubdivisionChange = (e) => {
- setSubdivision(e.target.value);
- setCareer('');
- };
-
- return (
-
-
-
-
-
-
-
- );
-};
-
-export default AutoSuggestFields;
diff --git a/MilestoneTracker.js b/MilestoneTracker.js
new file mode 100644
index 0000000..02f107f
--- /dev/null
+++ b/MilestoneTracker.js
@@ -0,0 +1,23 @@
+// ...existing code...
+const loadLastSelectedCareer = async () => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ throw new Error('Authorization token is missing');
+ }
+
+ const response = await fetch('https://dev1.aptivaai.com:5002/api/premium/planned-path/latest', {
+ headers: { Authorization: `Bearer ${token}` }, // Fixed template literal syntax
+ });
+
+ const data = await response.json();
+ if (data?.id) {
+ setSelectedCareer(data.job_title);
+ setCareerPathId(data.id); // Store the career_path_id
+ loadMilestonesFromServer(data.id);
+ }
+ } catch (error) {
+ console.error('Error loading last selected career:', error);
+ }
+};
+// ...existing code...
\ No newline at end of file
diff --git a/backend/server3.js b/backend/server3.js
new file mode 100644
index 0000000..f58adcd
--- /dev/null
+++ b/backend/server3.js
@@ -0,0 +1,240 @@
+// server3.js - Premium Services API
+import express from 'express';
+import cors from 'cors';
+import helmet from 'helmet';
+import dotenv from 'dotenv';
+import { open } from 'sqlite';
+import sqlite3 from 'sqlite3';
+import jwt from 'jsonwebtoken';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+dotenv.config({ path: path.resolve(__dirname, '..', '.env') });
+
+const app = express();
+const PORT = process.env.PREMIUM_PORT || 5002;
+
+let db;
+const initDB = async () => {
+ try {
+ db = await open({
+ filename: '/home/jcoakley/aptiva-dev1-app/user_profile.db',
+ driver: sqlite3.Database
+ });
+ console.log('Connected to user_profile.db for Premium Services.');
+ } catch (error) {
+ console.error('Error connecting to premium database:', error);
+ }
+};
+initDB();
+
+app.use(helmet());
+app.use(express.json());
+
+const allowedOrigins = ['https://dev1.aptivaai.com'];
+app.use(cors({ origin: allowedOrigins, credentials: true }));
+
+const authenticatePremiumUser = (req, res, next) => {
+ const token = req.headers.authorization?.split(' ')[1];
+ if (!token) return res.status(401).json({ error: 'Premium authorization required' });
+
+ try {
+ const { userId } = jwt.verify(token, process.env.SECRET_KEY);
+ req.userId = userId;
+ next();
+ } catch (error) {
+ return res.status(403).json({ error: 'Invalid or expired token' });
+ }
+};
+
+// Get latest selected planned path
+app.get('/api/premium/planned-path/latest', authenticatePremiumUser, async (req, res) => {
+ try {
+ const row = await db.get(
+ `SELECT * FROM career_path WHERE user_id = ? ORDER BY start_date DESC LIMIT 1`,
+ [req.userId]
+ );
+ res.json(row || {});
+ } catch (error) {
+ console.error('Error fetching latest career path:', error);
+ res.status(500).json({ error: 'Failed to fetch latest planned path' });
+ }
+});
+
+// Get all planned paths for the user
+app.get('/api/premium/planned-path/all', authenticatePremiumUser, async (req, res) => {
+ try {
+ const rows = await db.all(
+ `SELECT * FROM career_path WHERE user_id = ? ORDER BY start_date ASC`,
+ [req.userId]
+ );
+ res.json({ careerPath: rows });
+ } catch (error) {
+ console.error('Error fetching career paths:', error);
+ res.status(500).json({ error: 'Failed to fetch planned paths' });
+ }
+});
+
+// Save a new planned path
+app.post('/api/premium/planned-path', authenticatePremiumUser, async (req, res) => {
+ const { job_title, projected_end_date } = req.body;
+
+ if (!job_title) {
+ return res.status(400).json({ error: 'Job title is required' });
+ }
+
+ try {
+ await db.run(
+ `INSERT INTO career_path (user_id, job_title, status, start_date, projected_end_date)
+ VALUES (?, ?, 'Active', DATE('now'), ?)`,
+ [req.userId, job_title, projected_end_date || null]
+ );
+
+ res.status(201).json({ message: 'Planned path added successfully' });
+ } catch (error) {
+ console.error('Error adding planned path:', error);
+ res.status(500).json({ error: 'Failed to add planned path' });
+ }
+});
+
+// Save a new milestone
+app.post('/api/premium/milestones', authenticatePremiumUser, async (req, res) => {
+ const { milestone_type, description, date, career_path_id, progress } = req.body;
+
+ if (!milestone_type || !description || !date) {
+ return res.status(400).json({ error: 'Missing required fields' });
+ }
+
+ try {
+ await db.run(
+ `INSERT INTO milestones (user_id, milestone_type, description, date, career_path_id, progress, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ [req.userId, milestone_type, description, date, career_path_id, progress || 0]
+ );
+ res.status(201).json({ message: 'Milestone saved successfully' });
+ } catch (error) {
+ console.error('Error saving milestone:', error);
+ res.status(500).json({ error: 'Failed to save milestone' });
+ }
+});
+
+// Get all milestones
+app.get('/api/premium/milestones', authenticatePremiumUser, async (req, res) => {
+ try {
+ const milestones = await db.all(
+ `SELECT * FROM milestones WHERE user_id = ? ORDER BY date ASC`,
+ [req.userId]
+ );
+
+ const mapped = milestones.map(m => ({
+ id: m.id,
+ title: m.description,
+ date: m.date,
+ type: m.milestone_type,
+ progress: m.progress || 0,
+ career_path_id: m.career_path_id
+ }));
+
+ res.json({ milestones: mapped });
+ } catch (error) {
+ console.error('Error fetching milestones:', error);
+ res.status(500).json({ error: 'Failed to fetch milestones' });
+ }
+});
+
+/// Update an existing milestone
+app.put('/api/premium/milestones/:id', authenticatePremiumUser, async (req, res) => {
+ const { id } = req.params;
+ const { milestone_type, description, date, progress } = req.body;
+
+ if (!milestone_type || !description || !date) {
+ return res.status(400).json({ error: 'Missing required fields' });
+ }
+
+ try {
+ await db.run(
+ `UPDATE milestones SET milestone_type = ?, description = ?, date = ?, progress = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?`,
+ [milestone_type, description, date, progress || 0, id, req.userId]
+ );
+ res.status(200).json({ message: 'Milestone updated successfully' });
+ } catch (error) {
+ console.error('Error updating milestone:', error);
+ res.status(500).json({ error: 'Failed to update milestone' });
+ }
+});
+
+// Archive current career to history
+app.post('/api/premium/career-history', authenticatePremiumUser, async (req, res) => {
+ const { career_path_id, company } = req.body;
+
+ if (!career_path_id || !company) {
+ return res.status(400).json({ error: 'Career path ID and company are required' });
+ }
+
+ try {
+ const career = await db.get(`SELECT * FROM career_path WHERE id = ? AND user_id = ?`, [career_path_id, req.userId]);
+
+ if (!career) {
+ return res.status(404).json({ error: 'Career path not found' });
+ }
+
+ await db.run(
+ `INSERT INTO career_history (user_id, job_title, company, start_date)
+ VALUES (?, ?, ?, DATE('now'))`,
+ [req.userId, career.job_title, company]
+ );
+
+ await db.run(`DELETE FROM career_path WHERE id = ?`, [career_path_id]);
+
+ res.status(201).json({ message: 'Career moved to history successfully' });
+ } catch (error) {
+ console.error('Error moving career to history:', error);
+ res.status(500).json({ error: 'Failed to update career history' });
+ }
+});
+
+// Retrieve career history
+app.get('/api/premium/career-history', authenticatePremiumUser, async (req, res) => {
+ try {
+ const history = await db.all(
+ `SELECT * FROM career_history WHERE user_id = ? ORDER BY start_date DESC;`,
+ [req.userId]
+ );
+
+ res.json({ careerHistory: history });
+ } catch (error) {
+ console.error('Error fetching career history:', error);
+ res.status(500).json({ error: 'Failed to fetch career history' });
+ }
+});
+
+// ROI Analysis (placeholder logic)
+app.get('/api/premium/roi-analysis', authenticatePremiumUser, async (req, res) => {
+ try {
+ const userCareer = await db.get(
+ `SELECT * FROM career_path WHERE user_id = ? ORDER BY start_date DESC LIMIT 1`,
+ [req.userId]
+ );
+
+ if (!userCareer) return res.status(404).json({ error: 'No planned path found for user' });
+
+ const roi = {
+ jobTitle: userCareer.job_title,
+ salary: userCareer.salary,
+ tuition: 50000,
+ netGain: userCareer.salary - 50000
+ };
+
+ res.json(roi);
+ } catch (error) {
+ console.error('Error calculating ROI:', error);
+ res.status(500).json({ error: 'Failed to calculate ROI' });
+ }
+});
+
+app.listen(PORT, () => {
+ console.log(`Premium server running on http://localhost:${PORT}`);
+});
\ No newline at end of file
diff --git a/src/components/CareerSearch.js b/src/components/CareerSearch.js
index 5678162..1b71cdb 100644
--- a/src/components/CareerSearch.js
+++ b/src/components/CareerSearch.js
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { Input } from "./ui/input.js"; // Assuming Input is a basic text input component
-const CareerSearch = () => {
+const CareerSearch = ({ onSelectCareer, initialCareer }) => {
const [careerClusters, setCareerClusters] = useState({});
const [selectedCluster, setSelectedCluster] = useState("");
const [selectedSubdivision, setSelectedSubdivision] = useState("");
@@ -22,6 +22,22 @@ const CareerSearch = () => {
fetchCareerClusters();
}, []);
+ useEffect(() => {
+ if (selectedCareer && careerClusters) {
+ for (const cluster in careerClusters) {
+ for (const subdivision in careerClusters[cluster]) {
+ if (careerClusters[cluster][subdivision].some(job => job.title === selectedCareer)) {
+ setSelectedCluster(cluster);
+ setSelectedSubdivision(subdivision);
+ return;
+ }
+ }
+ }
+ }
+ setSelectedCluster('');
+ setSelectedSubdivision('');
+ }, [selectedCareer, careerClusters]);
+
// Handle Cluster Selection
const handleClusterSelect = (cluster) => {
setSelectedCluster(cluster);
diff --git a/src/components/Dashboard.js b/src/components/Dashboard.js
index 39dc5e9..b758d91 100644
--- a/src/components/Dashboard.js
+++ b/src/components/Dashboard.js
@@ -5,6 +5,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
import { CareerSuggestions } from './CareerSuggestions.js';
import PopoutPanel from './PopoutPanel.js';
+import MilestoneTracker from './MilestoneTracker.js'
import './Dashboard.css';
import Chatbot from "./Chatbot.js";
import { Bar } from 'react-chartjs-2';
@@ -144,25 +145,24 @@ function Dashboard() {
useEffect(() => {
const fetchUserProfile = async () => {
try {
- const token = localStorage.getItem('token');
- const profileResponse = await fetch(`${apiUrl}/user-profile`, {
- headers: { Authorization: `Bearer ${token}` },
- });
-
- if (profileResponse.ok) {
- const profileData = await profileResponse.json();
-
- const { state, area, zipcode } = profileData;
- setUserState(state);
- setAreaTitle(area && area.trim() ? area.trim() : '');
- setUserZipcode(zipcode);
- } else {
- console.error('Failed to fetch user profile');
- }
+ const token = localStorage.getItem('token');
+ const profileResponse = await fetch(`${apiUrl}/user-profile`, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+
+ if (profileResponse.ok) {
+ const profileData = await profileResponse.json();
+ setUserState(profileData.state);
+ setAreaTitle(profileData.area.trim() || '');
+ setUserZipcode(profileData.zipcode);
+ } else {
+ console.error('Failed to fetch user profile');
+ }
} catch (error) {
- console.error('Error fetching user profile:', error);
+ console.error('Error fetching user profile:', error);
}
- };
+ };
+
fetchUserProfile();
}, [apiUrl]);
@@ -283,6 +283,7 @@ function Dashboard() {
} finally {
setLoading(false);
}
+
},
[userState, apiUrl, areaTitle, userZipcode]
);
diff --git a/src/components/MilestoneTracker.css b/src/components/MilestoneTracker.css
new file mode 100644
index 0000000..57e63fa
--- /dev/null
+++ b/src/components/MilestoneTracker.css
@@ -0,0 +1,90 @@
+/* src/components/MilestoneTracker.css */
+
+.milestone-tracker {
+ width: 100%;
+ max-width: 900px;
+ margin: auto;
+ text-align: center;
+ }
+
+ .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ }
+
+ .view-selector {
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ margin-bottom: 20px;
+ }
+
+ .view-selector button {
+ padding: 10px 15px;
+ border: none;
+ cursor: pointer;
+ background: #4caf50;
+ color: white;
+ border-radius: 5px;
+ }
+
+ .view-selector button.active {
+ background: #2e7d32;
+ }
+
+ .timeline-container {
+ position: relative;
+ width: 100%;
+ height: 100px;
+ border-top: 2px solid #ddd;
+ margin-top: 20px;
+ display: flex;
+ align-items: center;
+ position: relative;
+ }
+
+ .timeline-line {
+ position: absolute;
+ width: 100%;
+ height: 2px;
+ background-color: #ccc;
+ top: 50%;
+ left: 0;
+ }
+
+ .milestone-post {
+ position: absolute;
+ transform: translateX(-50%);
+ cursor: pointer;
+ text-align: center;
+ }
+
+ .milestone-dot {
+ width: 10px;
+ height: 10px;
+ background-color: #4caf50;
+ border-radius: 50%;
+ margin: 0 auto 5px;
+ }
+
+ .milestone-content {
+ font-size: 12px;
+ }
+
+ .progress-bar {
+ height: 5px;
+ background: #ddd;
+ width: 50px;
+ margin: 5px auto;
+ position: relative;
+ }
+
+ .progress {
+ height: 5px;
+ background: #4caf50;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
\ No newline at end of file
diff --git a/src/components/MilestoneTracker.js b/src/components/MilestoneTracker.js
new file mode 100644
index 0000000..f23a4d7
--- /dev/null
+++ b/src/components/MilestoneTracker.js
@@ -0,0 +1,301 @@
+// src/components/MilestoneTracker.js
+import React, { useState, useEffect } from 'react';
+import CareerSearch from './CareerSearch.js';
+import './MilestoneTracker.css';
+
+const today = new Date();
+
+const MilestoneTracker = ({ selectedCareer: initialCareer, careerClusters }) => {
+ const [activeView, setActiveView] = useState('Career');
+ const [milestones, setMilestones] = useState({
+ Career: [],
+ Financial: [],
+ Retirement: [],
+ });
+
+ const [careerPathId, setCareerPathId] = useState(null);
+ const [showForm, setShowForm] = useState(false);
+ const [newMilestone, setNewMilestone] = useState({ title: '', date: '', progress: 0 });
+ const [editingMilestone, setEditingMilestone] = useState(null);
+ const [selectedCareer, setSelectedCareer] = useState(initialCareer || '');
+ const [suggestedMilestones, setSuggestedMilestones] = useState([]);
+ const [careerCluster, setCareerCluster] = useState('');
+ const [careerSubdivision, setCareerSubdivision] = useState('');
+
+ useEffect(() => {
+ loadMilestonesFromServer();
+ }, [selectedCareer]);
+
+ useEffect(() => {
+ loadLastSelectedCareer();
+ }, []);
+
+ const loadLastSelectedCareer = async () => {
+ try {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ throw new Error('Authorization token is missing');
+ }
+
+ const response = await fetch('https://dev1.aptivaai.com:5002/api/premium/planned-path/latest', {
+ headers: { Authorization: `Bearer ${token}` }, // Fixed template literal syntax
+ });
+
+ const data = await response.json();
+ if (data?.id) {
+ setSelectedCareer(data.job_title);
+ setCareerPathId(data.id); // Store the career_path_id
+ loadMilestonesFromServer(data.id);
+ }
+ } catch (error) {
+ console.error('Error loading last selected career:', error);
+ }
+ };
+
+ const loadMilestonesFromServer = async (pathId = careerPathId) => {
+ if (!pathId) return;
+
+ try {
+ const data = await 'https://dev1.aptivaai.com/api/premium/milestones';
+ const filtered = data.milestones.filter(m => m.career_path_id === pathId && m.milestone_type === activeView);
+
+ setMilestones(prev => ({
+ ...prev,
+ [activeView]: filtered.map(row => ({
+ id: row.id,
+ title: row.description,
+ date: row.date,
+ progress: row.progress || 0,
+ })),
+ }));
+ } catch (error) {
+ console.error('Error loading milestones:', error);
+ }
+};
+
+
+ useEffect(() => {
+ if (selectedCareer) {
+ fetchAISuggestedMilestones(selectedCareer);
+ prepopulateCareerFields(selectedCareer);
+ }
+ }, [selectedCareer]);
+
+ const prepopulateCareerFields = (career) => {
+ if (!careerClusters) return;
+ for (const cluster in careerClusters) {
+ for (const subdivision in careerClusters[cluster]) {
+ if (careerClusters[cluster][subdivision].some(job => job.title === career)) {
+ setCareerCluster(cluster);
+ setCareerSubdivision(subdivision);
+ return;
+ }
+ }
+ }
+ setCareerCluster('');
+ setCareerSubdivision('');
+ };
+
+ const handleAddMilestone = async () => {
+ if (!careerPathId) {
+ console.error('No career_path_id available for milestone.');
+ return;
+ }
+
+ const token = localStorage.getItem('token');
+ try {
+ const url = 'https://dev1.aptivaai.com:5002/api/premium/milestones';
+ const method = editingMilestone !== null ? 'PUT' : 'POST';
+ const payload = {
+ milestone_type: activeView,
+ description: newMilestone.title,
+ date: newMilestone.date,
+ career_path_id: careerPathId, // Use the correct ID
+ progress: newMilestone.progress,
+ };
+ const response = await fetch(url, {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify(payload),
+ });
+
+ if (!response.ok) throw new Error('Error saving milestone');
+
+ setNewMilestone({ title: '', date: '', progress: 0 });
+ setShowForm(false);
+ setEditingMilestone(null);
+ loadMilestonesFromServer(careerPathId);
+ } catch (error) {
+ console.error('Error saving milestone:', error);
+ }
+ };
+
+
+ const handleEditMilestone = async () => {
+ if (!careerPathId || !editingMilestone) {
+ console.error('Missing career path ID or milestone ID for update.');
+ return;
+ }
+
+ const token = localStorage.getItem('token');
+ try {
+ const url = `https://dev1.aptivaai.com:5002/api/premium/milestones/${editingMilestone.id}`; // ✅ ID in URL
+ const payload = {
+ milestone_type: activeView,
+ description: newMilestone.title,
+ date: newMilestone.date,
+ career_path_id: careerPathId, // Ensure milestone is linked correctly
+ progress: newMilestone.progress,
+ };
+
+ const response = await fetch(url, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify(payload),
+ });
+
+ if (!response.ok) throw new Error('Error updating milestone');
+
+ setNewMilestone({ title: '', date: '', progress: 0 });
+ setShowForm(false);
+ setEditingMilestone(null);
+ loadMilestonesFromServer(careerPathId);
+ } catch (error) {
+ console.error('Error updating milestone:', error);
+ }
+ };
+
+
+ const handleCareerSelection = async (career) => {
+ setSelectedCareer(career);
+ prepopulateCareerFields(career);
+
+ const token = localStorage.getItem('token');
+ try {
+ const response = await fetch('https://dev1.aptivaai.com:5002/api/premium/planned-path', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({ job_title: career }),
+ });
+
+ if (!response.ok) throw new Error('Error saving career path');
+
+ const newPath = await response.json();
+ setCareerPathId(newPath.id); // ✅ Update stored career path ID
+ loadMilestonesFromServer(newPath.id);
+ } catch (error) {
+ console.error('Error selecting career:', error);
+ }
+ };
+
+
+ const fetchAISuggestedMilestones = (career) => {
+ console.log(`Fetching AI suggested milestones for: ${career}`);
+ const mockSuggestedMilestones = [
+ { title: `Entry-Level Certification for ${career}`, date: '2025-06-01', progress: 0 },
+ { title: `Mid-Level Position in ${career}`, date: '2027-01-01', progress: 0 },
+ { title: `Senior-Level Mastery in ${career}`, date: '2030-01-01', progress: 0 },
+ ];
+ setSuggestedMilestones(mockSuggestedMilestones);
+ };
+
+ const confirmSuggestedMilestones = () => {
+ setMilestones((prev) => ({
+ ...prev,
+ [activeView]: [...prev[activeView], ...suggestedMilestones].sort((a, b) => new Date(a.date) - new Date(b.date)),
+ }));
+ setSuggestedMilestones([]);
+ };
+
+ const lastMilestoneDate = () => {
+ const allDates = milestones[activeView].map((m) => new Date(m.date));
+ return allDates.length ? new Date(Math.max(...allDates)) : today;
+ };
+
+ const calculatePosition = (date) => {
+ const start = today.getTime();
+ const end = lastMilestoneDate().getTime();
+ return ((new Date(date).getTime() - start) / (end - start)) * 100;
+ };
+
+ return (
+
+
+
Milestone Tracker
+
Selected Career: {selectedCareer || 'Not Selected'}
+
+
+
+ {suggestedMilestones.length > 0 && (
+
+
AI-Suggested Milestones
+
+ {suggestedMilestones.map((milestone, idx) => (
+ - {milestone.title} - {milestone.date}
+ ))}
+
+
+
+ )}
+
+
+ {['Career', 'Financial', 'Retirement'].map((view) => (
+
+ ))}
+
+
+ {showForm && (
+
+ setNewMilestone({ ...newMilestone, title: e.target.value })} />
+ setNewMilestone({ ...newMilestone, date: e.target.value })} />
+
+ setNewMilestone({ ...newMilestone, progress: Number(e.target.value) })} />
+
+
+ )}
+
+
+
+ {milestones[activeView].map((milestone, idx) => (
+
handleEditMilestone(milestone)}
+ >
+
+
+
{milestone.title}
+
+
{milestone.date}
+
+
+ ))}
+
+
+
+
Not sure about this career path? Choose a different one here.
+
+
+
+ );
+};
+
+export default MilestoneTracker;
diff --git a/src/utils/api.js b/src/utils/api.js
new file mode 100644
index 0000000..c6947de
--- /dev/null
+++ b/src/utils/api.js
@@ -0,0 +1,29 @@
+export const fetchWithAuth = async (url, options = {}) => {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ window.location.href = '/signin'; // Redirect if no token
+ return;
+ }
+
+ const headers = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ ...options.headers,
+ };
+
+ try {
+ const response = await fetch(url, { ...options, headers });
+
+ if (response.status === 401 || response.status === 403) {
+ console.warn('Token expired or unauthorized access, redirecting to sign-in.');
+ localStorage.removeItem('token'); // Clear expired token
+ window.location.href = '/signin'; // Redirect user
+ return;
+ }
+
+ return await response.json(); // Return the JSON response
+ } catch (error) {
+ console.error('Error fetching API:', error);
+ throw error;
+ }
+};
diff --git a/user_profile.db b/user_profile.db
index 9bbe93a..8d23fb2 100644
Binary files a/user_profile.db and b/user_profile.db differ