Added the 4 user journeys, landing pages, and navigation from SignIn.

This commit is contained in:
Josh 2025-05-12 17:06:12 +00:00
parent 1d585c2041
commit f9674643d5
12 changed files with 448 additions and 182 deletions

View File

@ -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

View File

@ -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

View 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;

View 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;

View 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;

View 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;

View File

@ -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);
}

View File

@ -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;

View 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;

View 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;

View File

@ -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>;
};

Binary file not shown.