Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2d6252a41 | ||
|
|
a13b34f98c | ||
|
|
be2c47faae | ||
|
|
dc1fef6309 | ||
|
|
b61ed836a6 | ||
|
|
70d8c64caa | ||
|
|
596121b8dd | ||
|
|
98bb56cb79 | ||
|
|
f800e10a76 | ||
|
|
d532c98853 | ||
|
|
08816375b1 | ||
|
|
d9d90d51f1 | ||
|
|
7af97cfb6c | ||
|
|
bf88d26075 | ||
|
|
17f25edfba | ||
|
|
9e956531ac | ||
|
|
da7e44a74e | ||
|
|
5c4ffc3cf4 |
65
apply.php
Normal file
65
apply.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'mail/MailService.php';
|
||||||
|
|
||||||
|
// 1. Validation
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$company = trim($_POST['company'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$role = trim($_POST['role'] ?? '');
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if (empty($name) || empty($company) || empty($email) || empty($role)) {
|
||||||
|
$error = 'All fields are required.';
|
||||||
|
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$error = 'Please enter a valid email address.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
header('Location: index.php?error=' . urlencode($error) . '#apply');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Database Insert
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'INSERT INTO applications (name, company, email, role) VALUES (?, ?, ?, ?)'
|
||||||
|
);
|
||||||
|
$stmt->execute([$name, $company, $email, $role]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// In a real app, log this error. For now, redirect with a generic error.
|
||||||
|
error_log('DB Error: ' . $e->getMessage());
|
||||||
|
header('Location: index.php?error=' . urlencode('A database error occurred.') . '#apply');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Email Notification
|
||||||
|
$to = getenv('MAIL_TO') ?: null; // Use admin email from .env
|
||||||
|
$subject = 'New FinMox Founding Access Application';
|
||||||
|
$htmlBody = "
|
||||||
|
<h1>New Application Received</h1>
|
||||||
|
<p>A new application has been submitted for FinMox founding access:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Name:</strong> " . htmlspecialchars($name) . "</li>
|
||||||
|
<li><strong>Company:</strong> " . htmlspecialchars($company) . "</li>
|
||||||
|
<li><strong>Email:</strong> " . htmlspecialchars($email) . "</li>
|
||||||
|
<li><strong>Role:</strong> " . htmlspecialchars($role) . "</li>
|
||||||
|
</ul>
|
||||||
|
";
|
||||||
|
$textBody = "New Application:\nName: $name\nCompany: $company\nEmail: $email\nRole: $role";
|
||||||
|
|
||||||
|
// Use MailService, but don't block the user if it fails
|
||||||
|
try {
|
||||||
|
MailService::sendMail($to, $subject, $htmlBody, $textBody);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Log email error but don't prevent user from seeing success
|
||||||
|
error_log('MailService Error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 4. Redirect to Success
|
||||||
|
header('Location: index.php?status=applied#apply');
|
||||||
|
exit;
|
||||||
|
|
||||||
53
assets/css/custom.css
Normal file
53
assets/css/custom.css
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
:root{
|
||||||
|
--ink:#0b0b0c;
|
||||||
|
--paper:#fbfaf7;
|
||||||
|
--warm:#f2efe7;
|
||||||
|
--warm2:#ebe6da;
|
||||||
|
--line: rgba(11,11,12,.08);
|
||||||
|
--shadow: 0 18px 48px rgba(11,11,12,.10);
|
||||||
|
--shadow2: 0 10px 26px rgba(11,11,12,.10);
|
||||||
|
--radius: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body { height: 100%; }
|
||||||
|
body { font-family: 'Inter', sans-serif; background: var(--paper); color: var(--ink); }
|
||||||
|
|
||||||
|
/* page routing */
|
||||||
|
.page.hidden { display: none; }
|
||||||
|
|
||||||
|
/* warm glass cards */
|
||||||
|
.panel { border: 1px solid var(--line); border-radius: var(--radius); background: rgba(255,255,255,.70); box-shadow: var(--shadow2); backdrop-filter: blur(10px); }
|
||||||
|
.panel-strong { border: 1px solid var(--line); border-radius: var(--radius); background: rgba(255,255,255,.92); box-shadow: var(--shadow); }
|
||||||
|
.chip { border: 1px solid var(--line); border-radius: 999px; background: rgba(255,255,255,.75); }
|
||||||
|
.softline { border-color: var(--line); }
|
||||||
|
|
||||||
|
/* hero background */
|
||||||
|
.bg-warm {
|
||||||
|
background:
|
||||||
|
radial-gradient(1200px 520px at 20% 15%, rgba(243,236,223,.95), rgba(251,250,247,0) 60%),
|
||||||
|
radial-gradient(900px 520px at 85% 20%, rgba(236,232,219,.9), rgba(251,250,247,0) 55%),
|
||||||
|
radial-gradient(900px 520px at 55% 90%, rgba(241,238,230,.9), rgba(251,250,247,0) 60%),
|
||||||
|
var(--paper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* subtle motion */
|
||||||
|
.fade { transition: opacity .18s ease, transform .18s ease; }
|
||||||
|
.hoverlift { transition: transform .18s ease, box-shadow .18s ease; }
|
||||||
|
.hoverlift:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
|
||||||
|
|
||||||
|
/* nav active state */
|
||||||
|
.navlink.active { font-weight: 700; }
|
||||||
|
|
||||||
|
/* mobile menu */
|
||||||
|
.drawer.hidden { display:none; }
|
||||||
|
|
||||||
|
/* details */
|
||||||
|
details[open] summary { font-weight: 700; }
|
||||||
|
summary { cursor: pointer; }
|
||||||
|
summary::-webkit-details-marker { display:none; }
|
||||||
|
|
||||||
|
/* input focus */
|
||||||
|
.chip:focus-within {
|
||||||
|
outline: 2px solid var(--ink);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
121
assets/js/main.js
Normal file
121
assets/js/main.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
const pages = ['home','problem','why','how','roi','pricing','who','trust','roadmap','faq','signin','apply'];
|
||||||
|
|
||||||
|
|
||||||
|
function openPage(pageId) {
|
||||||
|
if (!pages.includes(pageId)) {
|
||||||
|
console.warn(`Attempted to navigate to a non-existent page: ${pageId}`);
|
||||||
|
return; // Do not proceed if the page is not in the allowed list
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide all pages
|
||||||
|
document.querySelectorAll('.page').forEach(p => {
|
||||||
|
p.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the active page
|
||||||
|
const activePage = document.getElementById('page-' + pageId);
|
||||||
|
if (activePage) {
|
||||||
|
activePage.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
// If no specific page content, default to home
|
||||||
|
document.getElementById('page-home').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update active link styling
|
||||||
|
document.querySelectorAll('.navlink').forEach(link => {
|
||||||
|
if (link.dataset.page === pageId) {
|
||||||
|
link.classList.add('active');
|
||||||
|
} else {
|
||||||
|
link.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Smooth scroll to top
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const navLinksContainer = document.getElementById('nav-links');
|
||||||
|
const mobileMenuContainer = document.getElementById('mobile-menu');
|
||||||
|
const footerLinksContainer = document.getElementById('footer-links');
|
||||||
|
const menuButton = document.getElementById('menu-button');
|
||||||
|
|
||||||
|
// Generate navigation links
|
||||||
|
let navHTML = '';
|
||||||
|
pages.forEach(page => {
|
||||||
|
let link = `#${page}`;
|
||||||
|
if (page === 'faq' || page === 'trust') {
|
||||||
|
link = `#${page}`;
|
||||||
|
}
|
||||||
|
navHTML += `<a href="${link}" class="navlink px-3 py-2 rounded-full hover:bg-gray-100" data-page="${page}">${page.charAt(0).toUpperCase() + page.slice(1)}</a>`;
|
||||||
|
});
|
||||||
|
navLinksContainer.innerHTML = navHTML;
|
||||||
|
mobileMenuContainer.innerHTML = navHTML.replace(/class="/g, 'class="block w-full text-left ');
|
||||||
|
|
||||||
|
// Generate footer links
|
||||||
|
if (footerLinksContainer) {
|
||||||
|
let footerHTML = '';
|
||||||
|
const footerPages = ['apply', 'signin', 'trust', 'faq'];
|
||||||
|
footerPages.forEach((page, index) => {
|
||||||
|
let link = `#${page}`;
|
||||||
|
if (page === 'faq' || page === 'trust') {
|
||||||
|
link = `#${page}`;
|
||||||
|
}
|
||||||
|
footerHTML += `<a href="${link}" class="underline navlink" data-page="${page}">${page.charAt(0).toUpperCase() + page.slice(1)}</a>`;
|
||||||
|
if (index < footerPages.length - 1) {
|
||||||
|
footerHTML += `<span class="opacity-50">·</span>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
footerLinksContainer.innerHTML = footerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle mobile menu
|
||||||
|
menuButton.addEventListener('click', () => {
|
||||||
|
mobileMenuContainer.classList.toggle('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (document.getElementById('page-home')) {
|
||||||
|
// Handle navigation clicks for SPA
|
||||||
|
document.querySelectorAll('.navlink').forEach(link => {
|
||||||
|
link.addEventListener('click', (e) => {
|
||||||
|
const page = link.dataset.page;
|
||||||
|
e.preventDefault();
|
||||||
|
openPage(link.dataset.page);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open page based on hash or default to 'home'
|
||||||
|
const initialPage = window.location.hash.substring(1) || 'home';
|
||||||
|
openPage(initialPage);
|
||||||
|
|
||||||
|
// Tab functionality for 'Why FinMox' page
|
||||||
|
const whyTabs = document.querySelectorAll('.why-tab');
|
||||||
|
const whyContents = document.querySelectorAll('.why-content');
|
||||||
|
|
||||||
|
whyTabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
const tabId = tab.dataset.tab;
|
||||||
|
|
||||||
|
// Update tab styles
|
||||||
|
whyTabs.forEach(t => {
|
||||||
|
if (t.dataset.tab === tabId) {
|
||||||
|
t.classList.add('bg-white', 'text-gray-900');
|
||||||
|
t.classList.remove('bg-gray-200', 'text-gray-500');
|
||||||
|
} else {
|
||||||
|
t.classList.remove('bg-white', 'text-gray-900');
|
||||||
|
t.classList.add('bg-gray-200', 'text-gray-500');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update content visibility
|
||||||
|
whyContents.forEach(c => {
|
||||||
|
if (c.dataset.content === tabId) {
|
||||||
|
c.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
c.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
1
db/migration_ran.flag
Normal file
1
db/migration_ran.flag
Normal file
@ -0,0 +1 @@
|
|||||||
|
ran
|
||||||
8
db/migrations/01_create_applications_table.sql
Normal file
8
db/migrations/01_create_applications_table.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `applications` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`name` VARCHAR(255) NOT NULL,
|
||||||
|
`company` VARCHAR(255) NOT NULL,
|
||||||
|
`email` VARCHAR(255) NOT NULL,
|
||||||
|
`role` VARCHAR(255) NOT NULL,
|
||||||
|
`applied_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
Loading…
x
Reference in New Issue
Block a user