Removed stray documentation from repo and added the /demo endpoint
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
This commit is contained in:
parent
e6f98f9653
commit
6a58f62075
@ -1 +1 @@
|
|||||||
ec484f55e8a48bee6d0e6223d06b815335ea5e74-006fe5ea7fd8cb46290bcd0cf210f88f2ec04061-e9eccd451b778829eb2f2c9752c670b707e1268b
|
7525e7b74f06b3341cb73a157afaea13b4af1f5d-006fe5ea7fd8cb46290bcd0cf210f88f2ec04061-e9eccd451b778829eb2f2c9752c670b707e1268b
|
||||||
|
|||||||
@ -37,3 +37,13 @@ test-results/
|
|||||||
blob-report/
|
blob-report/
|
||||||
*.trace.zip
|
*.trace.zip
|
||||||
|
|
||||||
|
# Conference and business planning documents (not needed in containers)
|
||||||
|
COMPETITIVE_ANALYSIS.md
|
||||||
|
PRICING_OPERATIONS_ANALYSIS.md
|
||||||
|
INFRASTRUCTURE_SCALING_ANALYSIS.md
|
||||||
|
COST_PROJECTION_DATA_NEEDED.md
|
||||||
|
ACCURATE_COST_PROJECTIONS.md
|
||||||
|
GAETC_PRINT_MATERIALS_FINAL.md
|
||||||
|
CONFERENCE_MATERIALS.md
|
||||||
|
APTIVA_AI_FEATURES_DOCUMENTATION.md
|
||||||
|
|
||||||
|
|||||||
10
.gitignore
vendored
10
.gitignore
vendored
@ -28,3 +28,13 @@ uploads/.env
|
|||||||
scan-env.sh
|
scan-env.sh
|
||||||
.aptiva-test-user.json
|
.aptiva-test-user.json
|
||||||
APTIVA_AI_FEATURES_DOCUMENTATION.md
|
APTIVA_AI_FEATURES_DOCUMENTATION.md
|
||||||
|
|
||||||
|
# Conference and business planning documents (sensitive)
|
||||||
|
COMPETITIVE_ANALYSIS.md
|
||||||
|
PRICING_OPERATIONS_ANALYSIS.md
|
||||||
|
INFRASTRUCTURE_SCALING_ANALYSIS.md
|
||||||
|
COST_PROJECTION_DATA_NEEDED.md
|
||||||
|
ACCURATE_COST_PROJECTIONS.md
|
||||||
|
GAETC_PRINT_MATERIALS_FINAL.md
|
||||||
|
CONFERENCE_MATERIALS.md
|
||||||
|
APTIVA_AI_FEATURES_DOCUMENTATION.md
|
||||||
|
|||||||
@ -1734,6 +1734,86 @@ ${body}`;
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* ----------------- Demo Request (Conference Lead Capture) ----------------- */
|
||||||
|
|
||||||
|
app.post('/api/demo-request', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, email, organization_name, phone, message } = req.body;
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!name || !email || !organization_name) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Name, email, and organization name are required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email validation
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid email format' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert to database (fields will be encrypted via withEncryption wrapper)
|
||||||
|
const query = `
|
||||||
|
INSERT INTO demo_requests
|
||||||
|
(name, email, organization_name, phone, message, source)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
`;
|
||||||
|
|
||||||
|
const source = 'gaetc_nov2025'; // TODO: Update this before each conference
|
||||||
|
|
||||||
|
await pool.execute(query, [
|
||||||
|
name.trim(),
|
||||||
|
email.trim().toLowerCase(),
|
||||||
|
organization_name.trim(),
|
||||||
|
phone?.trim() || null,
|
||||||
|
message?.trim() || null,
|
||||||
|
source
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Send email notification
|
||||||
|
if (SENDGRID_KEY) {
|
||||||
|
try {
|
||||||
|
const emailBody = `New demo request received:
|
||||||
|
|
||||||
|
Name: ${name}
|
||||||
|
Email: ${email}
|
||||||
|
Organization: ${organization_name}
|
||||||
|
Phone: ${phone || 'Not provided'}
|
||||||
|
Message: ${message || 'None'}
|
||||||
|
|
||||||
|
Source: ${source}
|
||||||
|
Submitted: ${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })}
|
||||||
|
|
||||||
|
---
|
||||||
|
Reply directly to ${email} to follow up.`;
|
||||||
|
|
||||||
|
await sgMail.send({
|
||||||
|
to: 'jcoakley@aptivaai.com',
|
||||||
|
from: 'noreply@aptivaai.com',
|
||||||
|
replyTo: email,
|
||||||
|
subject: `New Demo Request: ${organization_name}`,
|
||||||
|
text: emailBody,
|
||||||
|
html: `<pre style="font-family: ui-monospace, Menlo, monospace; white-space: pre-wrap">${emailBody}</pre>`,
|
||||||
|
categories: ['demo-request', source]
|
||||||
|
});
|
||||||
|
} catch (emailErr) {
|
||||||
|
console.error('[demo-request] Email notification failed:', emailErr?.message || emailErr);
|
||||||
|
// Don't fail the request if email fails - data is still saved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Demo request received! We\'ll reach out soon to schedule a time that works for you.'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[demo-request] Error:', error?.message || error);
|
||||||
|
res.status(500).json({ error: 'Failed to submit request. Please try again.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* ----------------- Support bot chat (server2) ----------------- */
|
/* ----------------- Support bot chat (server2) ----------------- */
|
||||||
|
|
||||||
/* CREATE thread */
|
/* CREATE thread */
|
||||||
|
|||||||
@ -5043,7 +5043,7 @@ app.post(
|
|||||||
userPlan = 'premium';
|
userPlan = 'premium';
|
||||||
}
|
}
|
||||||
|
|
||||||
const weeklyLimits = { premium: 3, pro: 5 };
|
const weeklyLimits = { premium: 10, pro: 10 };
|
||||||
const userWeeklyLimit = weeklyLimits[userPlan] || 0;
|
const userWeeklyLimit = weeklyLimits[userPlan] || 0;
|
||||||
|
|
||||||
let resetDate = new Date(userProfile.resume_limit_reset);
|
let resetDate = new Date(userProfile.resume_limit_reset);
|
||||||
@ -5143,7 +5143,7 @@ app.get('/api/premium/resume/remaining', authenticatePremiumUser, async (req, re
|
|||||||
userPlan = 'premium';
|
userPlan = 'premium';
|
||||||
}
|
}
|
||||||
|
|
||||||
const weeklyLimits = { basic: 1, premium: 2, pro: 5 };
|
const weeklyLimits = { basic: 0, premium: 10, pro: 10 };
|
||||||
const userWeeklyLimit = weeklyLimits[userPlan] || 0;
|
const userWeeklyLimit = weeklyLimits[userPlan] || 0;
|
||||||
|
|
||||||
let resetDate = new Date(userProfile.resume_limit_reset);
|
let resetDate = new Date(userProfile.resume_limit_reset);
|
||||||
|
|||||||
@ -266,3 +266,16 @@ ALTER TABLE career_profiles
|
|||||||
ADD COLUMN resume_filesize INT UNSIGNED NULL AFTER resume_filename,
|
ADD COLUMN resume_filesize INT UNSIGNED NULL AFTER resume_filename,
|
||||||
ADD COLUMN resume_uploaded_at DATETIME NULL AFTER resume_filesize;
|
ADD COLUMN resume_uploaded_at DATETIME NULL AFTER resume_filesize;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS demo_requests (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(512) NOT NULL,
|
||||||
|
email VARCHAR(512) NOT NULL,
|
||||||
|
organization_name VARCHAR(512) NOT NULL,
|
||||||
|
phone VARCHAR(512),
|
||||||
|
message TEXT,
|
||||||
|
source VARCHAR(512) DEFAULT 'conference',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_created (created_at),
|
||||||
|
INDEX idx_source (source(255))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|||||||
10
nginx.conf
10
nginx.conf
@ -191,10 +191,14 @@ http {
|
|||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /api/user-profile { limit_conn perip 5;
|
location = /api/user-profile { limit_conn perip 5;
|
||||||
limit_req zone=reqperip burst=10 nodelay;
|
limit_req zone=reqperip burst=10 nodelay;
|
||||||
proxy_pass http://backend5000; }
|
proxy_pass http://backend5000; }
|
||||||
|
|
||||||
|
location = /api/demo-request { limit_conn perip 5;
|
||||||
|
limit_req zone=reqperip burst=10 nodelay;
|
||||||
|
proxy_pass http://backend5001; }
|
||||||
|
|
||||||
# General API (anything not matched above) – rate-limited
|
# General API (anything not matched above) – rate-limited
|
||||||
location ^~ /api/ { proxy_pass http://backend5000; }
|
location ^~ /api/ { proxy_pass http://backend5000; }
|
||||||
|
|
||||||
|
|||||||
10
src/App.js
10
src/App.js
@ -51,6 +51,7 @@ import { initNetObserver } from './utils/net.js';
|
|||||||
import PrivacyPolicy from './components/PrivacyPolicy.js';
|
import PrivacyPolicy from './components/PrivacyPolicy.js';
|
||||||
import TermsOfService from './components/TermsOfService.js';
|
import TermsOfService from './components/TermsOfService.js';
|
||||||
import HomePage from './components/HomePage.js';
|
import HomePage from './components/HomePage.js';
|
||||||
|
import DemoRequest from './components/DemoRequest.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -231,7 +232,8 @@ if (loggingOut) return;
|
|||||||
location.pathname === '/forgot-password' ||
|
location.pathname === '/forgot-password' ||
|
||||||
location.pathname === '/privacy' ||
|
location.pathname === '/privacy' ||
|
||||||
location.pathname === '/terms' ||
|
location.pathname === '/terms' ||
|
||||||
location.pathname === '/home'
|
location.pathname === '/home' ||
|
||||||
|
location.pathname === '/demo'
|
||||||
) {
|
) {
|
||||||
try { localStorage.removeItem('id'); } catch {}
|
try { localStorage.removeItem('id'); } catch {}
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
@ -269,7 +271,8 @@ if (loggingOut) return;
|
|||||||
p === '/paywall' ||
|
p === '/paywall' ||
|
||||||
p === '/privacy' ||
|
p === '/privacy' ||
|
||||||
p === '/terms' ||
|
p === '/terms' ||
|
||||||
p === '/home';
|
p === '/home' ||
|
||||||
|
p === '/demo';
|
||||||
if (!onPublic) navigate('/signin?session=expired', { replace: true });
|
if (!onPublic) navigate('/signin?session=expired', { replace: true });
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) setIsLoading(false);
|
if (!cancelled) setIsLoading(false);
|
||||||
@ -810,6 +813,9 @@ const cancelLogout = () => {
|
|||||||
{/* Public Home Page */}
|
{/* Public Home Page */}
|
||||||
<Route path="/home" element={<HomePage />} />
|
<Route path="/home" element={<HomePage />} />
|
||||||
|
|
||||||
|
{/* Public Demo Request (Conference Lead Capture) */}
|
||||||
|
<Route path="/demo" element={<DemoRequest />} />
|
||||||
|
|
||||||
{/* Default */}
|
{/* Default */}
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
|
|||||||
216
src/components/DemoRequest.js
Normal file
216
src/components/DemoRequest.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const DemoRequest = () => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
organization_name: '',
|
||||||
|
phone: '',
|
||||||
|
message: "I'd like to schedule a demo"
|
||||||
|
});
|
||||||
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value
|
||||||
|
}));
|
||||||
|
// Clear error when user starts typing
|
||||||
|
if (error) setError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
if (!formData.name.trim()) {
|
||||||
|
setError('Please enter your name');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!formData.email.trim()) {
|
||||||
|
setError('Please enter your email');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Email format validation
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(formData.email)) {
|
||||||
|
setError('Please enter a valid email address');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!formData.organization_name.trim()) {
|
||||||
|
setError('Please enter your organization name');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!validateForm()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/demo-request', formData);
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
setSubmitted(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Demo request error:', err);
|
||||||
|
setError(
|
||||||
|
err.response?.data?.error ||
|
||||||
|
'Failed to submit request. Please try again or email us directly.'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (submitted) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center p-5 bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500">
|
||||||
|
<div className="bg-white rounded-xl shadow-2xl max-w-md w-full p-12 text-center">
|
||||||
|
<div className="w-20 h-20 bg-gradient-to-br from-green-500 to-green-600 text-white text-5xl rounded-full flex items-center justify-center mx-auto mb-6 animate-[scaleIn_0.5s_ease-out]">
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-semibold text-gray-900 mb-4">Request Received!</h2>
|
||||||
|
<p className="text-base text-gray-600 leading-relaxed mb-8">
|
||||||
|
We've received your demo request and will reach out soon to schedule a time that works for you.
|
||||||
|
</p>
|
||||||
|
<div className="pt-6 border-t border-gray-200">
|
||||||
|
<p className="text-sm text-gray-500 mb-3">Want to connect sooner?</p>
|
||||||
|
<a
|
||||||
|
href={`mailto:jcoakley@aptivaai.com?subject=Demo Request: ${formData.organization_name}`}
|
||||||
|
className="inline-block bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-6 py-3 rounded-lg font-semibold text-sm transition-all hover:-translate-y-0.5 hover:shadow-[0_6px_20px_rgba(102,126,234,0.4)]"
|
||||||
|
>
|
||||||
|
Email jcoakley@aptivaai.com directly
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center p-5 bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500">
|
||||||
|
<div className="bg-white rounded-xl shadow-2xl max-w-lg w-full p-10">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-3xl font-semibold text-gray-900 mb-2">Schedule a Demo</h1>
|
||||||
|
<p className="text-base text-gray-500">Career Planning for the AI Era</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="name" className="text-sm font-medium text-gray-700 mb-1.5">
|
||||||
|
Name <span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Your full name"
|
||||||
|
disabled={loading}
|
||||||
|
autoComplete="name"
|
||||||
|
className="px-3.5 py-3 text-base border-2 border-gray-200 rounded-lg transition-all focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 disabled:bg-gray-50 disabled:cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="email" className="text-sm font-medium text-gray-700 mb-1.5">
|
||||||
|
Email <span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="you@organization.edu"
|
||||||
|
disabled={loading}
|
||||||
|
autoComplete="email"
|
||||||
|
className="px-3.5 py-3 text-base border-2 border-gray-200 rounded-lg transition-all focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 disabled:bg-gray-50 disabled:cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="organization_name" className="text-sm font-medium text-gray-700 mb-1.5">
|
||||||
|
Organization <span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="organization_name"
|
||||||
|
name="organization_name"
|
||||||
|
value={formData.organization_name}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="School, district, or college name"
|
||||||
|
disabled={loading}
|
||||||
|
autoComplete="organization"
|
||||||
|
className="px-3.5 py-3 text-base border-2 border-gray-200 rounded-lg transition-all focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 disabled:bg-gray-50 disabled:cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="phone" className="text-sm font-medium text-gray-700 mb-1.5">
|
||||||
|
Phone (optional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
id="phone"
|
||||||
|
name="phone"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="(555) 123-4567"
|
||||||
|
disabled={loading}
|
||||||
|
autoComplete="tel"
|
||||||
|
className="px-3.5 py-3 text-base border-2 border-gray-200 rounded-lg transition-all focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 disabled:bg-gray-50 disabled:cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="message" className="text-sm font-medium text-gray-700 mb-1.5">
|
||||||
|
Message (optional)
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="message"
|
||||||
|
name="message"
|
||||||
|
value={formData.message}
|
||||||
|
onChange={handleChange}
|
||||||
|
rows="3"
|
||||||
|
disabled={loading}
|
||||||
|
placeholder="Tell us about your needs..."
|
||||||
|
className="px-3.5 py-3 text-base border-2 border-gray-200 rounded-lg transition-all focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 disabled:bg-gray-50 disabled:cursor-not-allowed resize-y min-h-[80px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-50 text-red-700 px-4 py-3 rounded-lg border-l-4 border-red-500 text-sm">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-6 py-3.5 text-base font-semibold rounded-lg transition-all mt-2.5 hover:-translate-y-0.5 hover:shadow-[0_6px_20px_rgba(102,126,234,0.4)] active:translate-y-0 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? 'Submitting...' : 'Request Demo'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-5 text-center">
|
||||||
|
<p className="text-xs text-gray-500 leading-relaxed">
|
||||||
|
We respect your privacy. Your information will only be used to contact you about Aptiva AI.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DemoRequest;
|
||||||
@ -38,7 +38,7 @@ export default function HomePage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500 mt-4">
|
<p className="text-sm text-gray-500 mt-4">
|
||||||
Free forever · No credit card required · Full access to career planning tools
|
No credit card required · Full access to career planning tools
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -201,7 +201,7 @@ export default function HomePage() {
|
|||||||
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>AI Career Coach powered by GPT-4o for personalized milestone recommendations</span>
|
<span>AI Career Coach Agent for personalized milestone recommendations and automated milestone/task/impact creation</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start">
|
<li className="flex items-start">
|
||||||
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
@ -213,7 +213,7 @@ export default function HomePage() {
|
|||||||
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Resume optimizer tailored to specific job descriptions (2-5/week)</span>
|
<span>Resume optimizer tailored to specific job descriptions (10/week)</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -311,13 +311,13 @@ export default function HomePage() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="bg-aptiva/10 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
<div className="bg-aptiva/10 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
||||||
<svg className="w-8 h-8 text-aptiva" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-8 h-8 text-aptiva" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">Affordable</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-2">Privacy-First</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Premium features start at just $4.99/month—less than a coffee per week.
|
We never ask for your birthdate or age. Plan your career and retirement
|
||||||
Free tier includes comprehensive career exploration tools.
|
without giving up personal data. Your privacy is protected.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -439,7 +439,7 @@ export default function HomePage() {
|
|||||||
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>AI Resume optimizer (2 per week)</span>
|
<span>AI Resume optimizer (10 per week)</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start">
|
<li className="flex items-start">
|
||||||
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-5 h-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
|||||||
@ -118,7 +118,7 @@ export default function Paywall() {
|
|||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-green-600 font-bold mt-0.5">✓</span>
|
<span className="text-green-600 font-bold mt-0.5">✓</span>
|
||||||
<span><strong>Resume Optimizer</strong> – 3 AI-enhanced optimizations per week</span>
|
<span><strong>Resume Optimizer</strong> – 10 AI-enhanced optimizations per week</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-green-600 font-bold mt-0.5">✓</span>
|
<span className="text-green-600 font-bold mt-0.5">✓</span>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user