diff --git a/.build.hash b/.build.hash index ec7a2d8..0ce7a74 100644 --- a/.build.hash +++ b/.build.hash @@ -1 +1 @@ -ec484f55e8a48bee6d0e6223d06b815335ea5e74-006fe5ea7fd8cb46290bcd0cf210f88f2ec04061-e9eccd451b778829eb2f2c9752c670b707e1268b +7525e7b74f06b3341cb73a157afaea13b4af1f5d-006fe5ea7fd8cb46290bcd0cf210f88f2ec04061-e9eccd451b778829eb2f2c9752c670b707e1268b diff --git a/.dockerignore b/.dockerignore index 2e2c4da..1e8fb70 100644 --- a/.dockerignore +++ b/.dockerignore @@ -37,3 +37,13 @@ test-results/ blob-report/ *.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 + diff --git a/.gitignore b/.gitignore index b371a05..63c9f93 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,13 @@ uploads/.env scan-env.sh .aptiva-test-user.json 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 diff --git a/backend/server2.js b/backend/server2.js index 2a2e412..432c9e0 100755 --- a/backend/server2.js +++ b/backend/server2.js @@ -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: `
${emailBody}`,
+ 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) ----------------- */
/* CREATE thread */
diff --git a/backend/server3.js b/backend/server3.js
index 051435d..9871849 100644
--- a/backend/server3.js
+++ b/backend/server3.js
@@ -5043,7 +5043,7 @@ app.post(
userPlan = 'premium';
}
- const weeklyLimits = { premium: 3, pro: 5 };
+ const weeklyLimits = { premium: 10, pro: 10 };
const userWeeklyLimit = weeklyLimits[userPlan] || 0;
let resetDate = new Date(userProfile.resume_limit_reset);
@@ -5143,7 +5143,7 @@ app.get('/api/premium/resume/remaining', authenticatePremiumUser, async (req, re
userPlan = 'premium';
}
- const weeklyLimits = { basic: 1, premium: 2, pro: 5 };
+ const weeklyLimits = { basic: 0, premium: 10, pro: 10 };
const userWeeklyLimit = weeklyLimits[userPlan] || 0;
let resetDate = new Date(userProfile.resume_limit_reset);
diff --git a/migrate_encrypted_columns.sql b/migrate_encrypted_columns.sql
index 60f72d6..11f82cb 100644
--- a/migrate_encrypted_columns.sql
+++ b/migrate_encrypted_columns.sql
@@ -266,3 +266,16 @@ ALTER TABLE career_profiles
ADD COLUMN resume_filesize INT UNSIGNED NULL AFTER resume_filename,
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;
diff --git a/nginx.conf b/nginx.conf
index 9ee05a9..87aa974 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -191,10 +191,14 @@ http {
proxy_set_header Connection "";
}
- location = /api/user-profile { limit_conn perip 5;
- limit_req zone=reqperip burst=10 nodelay;
+ location = /api/user-profile { limit_conn perip 5;
+ limit_req zone=reqperip burst=10 nodelay;
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
location ^~ /api/ { proxy_pass http://backend5000; }
diff --git a/src/App.js b/src/App.js
index 8d7c7b7..8db0d31 100644
--- a/src/App.js
+++ b/src/App.js
@@ -51,6 +51,7 @@ import { initNetObserver } from './utils/net.js';
import PrivacyPolicy from './components/PrivacyPolicy.js';
import TermsOfService from './components/TermsOfService.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 === '/privacy' ||
location.pathname === '/terms' ||
- location.pathname === '/home'
+ location.pathname === '/home' ||
+ location.pathname === '/demo'
) {
try { localStorage.removeItem('id'); } catch {}
setIsAuthenticated(false);
@@ -269,7 +271,8 @@ if (loggingOut) return;
p === '/paywall' ||
p === '/privacy' ||
p === '/terms' ||
- p === '/home';
+ p === '/home' ||
+ p === '/demo';
if (!onPublic) navigate('/signin?session=expired', { replace: true });
} finally {
if (!cancelled) setIsLoading(false);
@@ -810,6 +813,9 @@ const cancelLogout = () => {
{/* Public Home Page */}
+ We've received your demo request and will reach out soon to schedule a time that works for you. +
+Want to connect sooner?
+ + Email jcoakley@aptivaai.com directly + +Career Planning for the AI Era
++ We respect your privacy. Your information will only be used to contact you about Aptiva AI. +
+- Free forever · No credit card required · Full access to career planning tools + No credit card required · Full access to career planning tools
@@ -201,7 +201,7 @@ export default function HomePage() { - AI Career Coach powered by GPT-4o for personalized milestone recommendations + AI Career Coach Agent for personalized milestone recommendations and automated milestone/task/impact creation- Premium features start at just $4.99/month—less than a coffee per week. - Free tier includes comprehensive career exploration tools. + We never ask for your birthdate or age. Plan your career and retirement + without giving up personal data. Your privacy is protected.