Updated pricing, financial disclaimer in retirement planner

This commit is contained in:
Josh 2025-10-15 18:15:06 +00:00
parent 00cedaf0dc
commit 6e45a4deca
11 changed files with 1147 additions and 45 deletions

View File

@ -1 +1 @@
960faad1696b81c0f004065ea713edaef64ab816-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b 506c4d11372627a92e39ee44d85a809d000a0840-006fe5ea7fd8cb46290bcd0cf210f88f2ec04061-e9eccd451b778829eb2f2c9752c670b707e1268b

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ uploads/.env
.env.* .env.*
scan-env.sh scan-env.sh
.aptiva-test-user.json .aptiva-test-user.json
APTIVA_AI_FEATURES_DOCUMENTATION.md

View File

@ -1 +1 @@
98f674eca26e366aee0b41f250978982060105f0 ad5628b638213a971abbea4f95362eca8672be7b

View File

@ -1 +1 @@
98f674eca26e366aee0b41f250978982060105f0 ad5628b638213a971abbea4f95362eca8672be7b

View File

@ -1747,7 +1747,7 @@ app.post('/api/chat/threads', authenticateUser, async (req, res) => {
); );
// Seed a first assistant message so the drawer never appears blank // Seed a first assistant message so the drawer never appears blank
const intro = const intro =
'Hi — Aptiva Support here. I can help with CareerExplorer, account/billing, or technical issues. What do you need?'; 'Hi — Aptiva Support here. I can help with how to use AptivaAI, account/billing, or technical issues. What do you need?';
await pool.query( await pool.query(
'INSERT INTO ai_chat_messages (thread_id,user_id,role,content) VALUES (?,?, "assistant", ?)', 'INSERT INTO ai_chat_messages (thread_id,user_id,role,content) VALUES (?,?, "assistant", ?)',
[id, userId, intro] [id, userId, intro]

View File

@ -94,7 +94,8 @@ http {
return 302 /signin$is_args$args; return 302 /signin$is_args$args;
} }
location / { location / {
try_files $uri $uri/ /index.html; # First try the exact file, then directory with index.html, then directory/, then fallback
try_files $uri $uri/index.html $uri/ /index.html;
} }
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ { location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ {
expires 6M; expires 6M;

1043
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -73,10 +73,29 @@
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"start": "react-scripts start --host 0.0.0.0", "start": "react-scripts start --host 0.0.0.0",
"build": "react-scripts build", "build": "react-scripts build && react-snap",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"reactSnap": {
"include": [
"/signin",
"/signup",
"/forgot-password",
"/privacy",
"/terms"
],
"skipThirdPartyRequests": true,
"cacheAjaxRequests": false,
"puppeteerArgs": [
"--no-sandbox",
"--disable-setuid-sandbox"
],
"minifyHtml": {
"collapseWhitespace": true,
"removeComments": true
}
},
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
"react-app", "react-app",
@ -117,6 +136,7 @@
"glob": "^11.0.3", "glob": "^11.0.3",
"globals": "^16.3.0", "globals": "^16.3.0",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"react-snap": "^1.23.0",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"typescript-eslint": "^8.38.0" "typescript-eslint": "^8.38.0"
} }

View File

@ -90,45 +90,69 @@ export default function Paywall() {
// No active sub => pricing // No active sub => pricing
return ( return (
<div className="max-w-lg mx-auto p-6 space-y-8"> <div className="max-w-lg mx-auto p-6 space-y-6">
<header className="text-center"> <header className="text-center space-y-2">
<h2 className="text-2xl font-semibold">Upgrade to AptivaAI</h2> <h2 className="text-2xl font-semibold">Upgrade to Premium</h2>
<p className="text-sm text-gray-600">Choose the plan that fits your needs cancel anytime.</p> <p className="text-sm text-gray-600">Unlock AI-enhanced career planning and financial projections</p>
</header> </header>
{/* Premium */} {/* Single Premium Tier */}
<section className="border rounded-lg p-4 space-y-4"> <section className="border-2 border-aptiva rounded-xl p-6 space-y-4 bg-gradient-to-br from-white to-blue-50">
<h3 className="text-lg font-medium">Premium</h3> <div className="text-center">
<ul className="text-sm list-disc list-inside space-y-1"> <h3 className="text-xl font-semibold text-aptiva">AptivaAI Premium</h3>
<li>Career milestone planning</li> <p className="text-xs text-gray-600 mt-1">Everything you need for career planning</p>
<li>Financial projections &amp; benchmarks</li> </div>
<li>2 × resume optimizations / week</li>
<ul className="text-sm space-y-2">
<li className="flex items-start gap-2">
<span className="text-green-600 font-bold mt-0.5"></span>
<span><strong>AI Career Coach</strong> Unlimited conversations with personalized guidance</span>
</li>
<li className="flex items-start gap-2">
<span className="text-green-600 font-bold mt-0.5"></span>
<span><strong>Career Roadmap</strong> Milestone tracking with financial impact modeling</span>
</li>
<li className="flex items-start gap-2">
<span className="text-green-600 font-bold mt-0.5"></span>
<span><strong>20-Year Financial Projections</strong> Model multiple career scenarios</span>
</li>
<li className="flex items-start gap-2">
<span className="text-green-600 font-bold mt-0.5"></span>
<span><strong>Resume Optimizer</strong> 3 AI-enhanced optimizations per week</span>
</li>
<li className="flex items-start gap-2">
<span className="text-green-600 font-bold mt-0.5"></span>
<span><strong>Retirement Planner</strong> Beta access to retirement scenario modeling</span>
</li>
</ul> </ul>
<div className="grid grid-cols-2 gap-3"> <div className="pt-4 space-y-3 flex flex-col items-center">
<Button onClick={() => checkout('premium', 'monthly')}>$4.99&nbsp;/ mo</Button> <Button
<Button onClick={() => checkout('premium', 'annual')}>$49&nbsp;/ yr</Button> onClick={() => checkout('premium', 'monthly')}
</div> className="w-full max-w-xs h-12 text-base"
</section> size="lg"
>
{/* Pro */} $24 / month
<section className="border rounded-lg p-4 space-y-4">
<h3 className="text-lg font-medium">Pro Premium</h3>
<ul className="text-sm list-disc list-inside space-y-1">
<li>Everything in Premium</li>
<li>Priority GPT-4o usage &amp; higher rate limits</li>
<li>5 × resume optimizations / week</li>
</ul>
<div className="grid grid-cols-2 gap-3">
<Button onClick={() => checkout('pro', 'monthly')}>$7.99&nbsp;/ mo</Button>
<Button onClick={() => checkout('pro', 'annual')}>$79&nbsp;/ yr</Button>
</div>
</section>
<Button variant="secondary" onClick={() => nav(-1)} className="w-full">
Cancel / Go back
</Button> </Button>
<Button
onClick={() => checkout('premium', 'annual')}
variant="outline"
className="w-full max-w-xs h-14 text-base border-2 border-aptiva text-aptiva hover:bg-aptiva hover:text-white flex flex-col items-center justify-center"
size="lg"
>
<span>$199 / year</span>
<span className="text-xs font-normal mt-0.5">Save $89 Best Value!</span>
</Button>
</div>
<p className="text-xs text-center text-gray-500 pt-2">Cancel anytime No long-term commitment</p>
</section>
<div className="flex justify-center">
<Button variant="secondary" onClick={() => nav(-1)} className="w-full max-w-xs">
Maybe later
</Button>
</div>
</div> </div>
); );
} }

View File

@ -6,6 +6,7 @@ import { Button } from './ui/button.js';
import RetirementChatBar from './RetirementChatBar.js'; import RetirementChatBar from './RetirementChatBar.js';
import ScenarioDiffDrawer from './ScenarioDiffDrawer.js'; import ScenarioDiffDrawer from './ScenarioDiffDrawer.js';
import ChatCtx from '../contexts/ChatCtx.js'; import ChatCtx from '../contexts/ChatCtx.js';
import FinancialDisclaimer from './FinancialDisclaimer.js';
/* ------------------------------------------------------------------ /* ------------------------------------------------------------------
* tiny classname helper * tiny classname helper
@ -133,9 +134,14 @@ export default function RetirementPlanner () {
const baselineYears = simYearsMap[baselineId] ?? null; // renamed const baselineYears = simYearsMap[baselineId] ?? null; // renamed
return ( return (
<div className="flex flex-col md:flex-row h-full relative"> <div className="flex flex-col md:flex-row h-full relative">
{/* ================= MAIN COLUMN =========================== */} {/* ================= MAIN COLUMN =========================== */}
<main className="flex-1 p-4 overflow-y-auto"> <main className="flex-1 p-4 overflow-y-auto">
{/* Disclaimer spans above both columns */}
<div className="mx-auto max-w-screen-lg mb-3">
<FinancialDisclaimer />
</div>
{/* desktop add */} {/* desktop add */}
{!isMobile && ( {!isMobile && (
<Button onClick={handleAddScenario} className="mb-4"> <Button onClick={handleAddScenario} className="mb-4">

View File

@ -9,8 +9,8 @@ import { installStorageGuard } from './utils/storageGuard.js';
installStorageGuard(); // Initialize storage guard installStorageGuard(); // Initialize storage guard
const root = ReactDOM.createRoot(document.getElementById('root')); const rootElement = document.getElementById('root');
root.render( const appContent = (
<BrowserRouter> <BrowserRouter>
<PageFlagsProvider> <PageFlagsProvider>
<App /> <App />
@ -18,6 +18,15 @@ root.render(
</BrowserRouter> </BrowserRouter>
); );
// Use hydration if there's pre-rendered content (from react-snap)
// Otherwise use normal rendering (development mode)
if (rootElement.hasChildNodes()) {
ReactDOM.hydrateRoot(rootElement, appContent);
} else {
const root = ReactDOM.createRoot(rootElement);
root.render(appContent);
}
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log)) // to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals