12/28/25 V.18

This commit is contained in:
Flatlogic Bot 2025-12-28 08:49:29 +00:00
parent 7f34b50ab8
commit f87d8bee23
10 changed files with 325 additions and 15 deletions

106
apply.php Normal file
View File

@ -0,0 +1,106 @@
<?php
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/mail/MailService.php';
header('Content-Type: application/json');
$response = ['success' => false, 'message' => 'An unknown error occurred.'];
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$response['message'] = 'Invalid request method.';
echo json_encode($response);
exit;
}
$data = json_decode(file_get_contents('php://input'), true);
if (empty($data)) {
$response['message'] = 'No data received.';
echo json_encode($response);
exit;
}
// Sanitize and validate data
$companyName = htmlspecialchars($data['companyName'] ?? '');
$yourName = htmlspecialchars($data['yourName'] ?? '');
$workEmail = filter_var($data['workEmail'] ?? '', FILTER_SANITIZE_EMAIL);
$role = htmlspecialchars($data['role'] ?? '');
$employees = filter_var($data['employees'] ?? '', FILTER_SANITIZE_NUMBER_INT);
$rolesPerMonth = filter_var($data['rolesPerMonth'] ?? '', FILTER_SANITIZE_NUMBER_INT);
$candidatesPerRole = filter_var($data['candidatesPerRole'] ?? '', FILTER_SANITIZE_NUMBER_INT);
$ats = htmlspecialchars($data['ats'] ?? '');
$scheduling = htmlspecialchars($data['scheduling'] ?? '');
$painPoints = htmlspecialchars($data['painPoints'] ?? '');
$successMetrics = htmlspecialchars($data['successMetrics'] ?? '');
$hiringFocus = htmlspecialchars($data['hiringFocus'] ?? '');
if (!filter_var($workEmail, FILTER_VALIDATE_EMAIL)) {
$response['message'] = 'Invalid email address.';
echo json_encode($response);
exit;
}
if (empty($companyName) || empty($yourName)) {
$response['message'] = 'Please fill out all required fields.';
echo json_encode($response);
exit;
}
// Insert into database
$pdo = db();
$stmt = $pdo->prepare("
INSERT INTO applications (name, company, email, role, employees, roles_per_month, candidates_per_role, ats, scheduling, pain_points, success_metrics, hiring_focus)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
try {
$stmt->execute([
$yourName,
$companyName,
$workEmail,
$role,
$employees,
$rolesPerMonth,
$candidatesPerRole,
$ats,
$scheduling,
$painPoints,
$successMetrics,
$hiringFocus
]);
} catch (PDOException $e) {
error_log('Database Error: ' . $e->getMessage());
$response['message'] = 'There was an error saving your application. Please try again later.';
echo json_encode($response);
exit;
}
$to = getenv('MAIL_TO') ?: 'default-recipient@example.com'; // Fallback recipient
$subject = 'New FinMox Beta Application';
$htmlBody = ""
. "<h1>New FinMox Beta Application</h1>"
. "<p><strong>Company Name:</strong> {$companyName}</p>"
. "<p><strong>Name:</strong> {$yourName}</p>"
. "<p><strong>Email:</strong> {$workEmail}</p>"
. "<p><strong>Role:</strong> {$role}</p>"
. "<p><strong>Employees:</strong> {$employees}</p>"
. "<p><strong>Roles Per Month:</strong> {$rolesPerMonth}</p>"
. "<p><strong>Candidates Per Role:</strong> {$candidatesPerRole}</p>"
. "<p><strong>ATS:</strong> {$ats}</p>"
. "<p><strong>Scheduling:</strong> {$scheduling}</p>"
. "<p><strong>Pain Points:</strong> {$painPoints}</p>"
. "<p><strong>Success Metrics:</strong> {$successMetrics}</p>"
. "<p><strong>Hiring Focus:</strong> {$hiringFocus}</p>";
$res = MailService::sendMail($to, $subject, $htmlBody);
if (!empty($res['success'])) {
$response['success'] = true;
$response['message'] = 'Application submitted successfully!';
} else {
error_log('MailService Error: ' . ($res['error'] ?? 'Unknown error'));
$response['message'] = 'There was an error submitting your application. Please try again later.';
}
echo json_encode($response);

View File

@ -118,4 +118,116 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
}); });
} }
const applyNowBtn = document.getElementById('applyNowBtn');
if (applyNowBtn) {
applyNowBtn.addEventListener('click', applyForBeta);
}
}); });
function signIn(event) {
event.preventDefault();
const emailEl = document.getElementById('signinEmail');
const passwordEl = document.getElementById('signinPassword');
const msgEl = document.getElementById('signinMsg');
if (!emailEl || !passwordEl || !msgEl) {
return;
}
const email = emailEl.value.trim();
const password = passwordEl.value.trim();
msgEl.classList.add('hidden');
msgEl.textContent = '';
if (!email || !password) {
msgEl.textContent = 'Please enter both email and password.';
msgEl.classList.remove('hidden');
return;
}
fetch('login.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify({ email, password }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = 'dashboard.php';
} else {
msgEl.textContent = data.message || 'An unknown error occurred.';
msgEl.classList.remove('hidden');
}
})
.catch(error => {
msgEl.textContent = 'A network error occurred. Please try again.';
msgEl.classList.remove('hidden');
});
}
function applyForBeta(event) {
event.preventDefault();
const form = document.getElementById('applyForm');
const msgEl = document.getElementById('applyMsg');
if (!form || !msgEl) {
return;
}
const data = {
companyName: document.getElementById('companyName').value,
yourName: document.getElementById('yourName').value,
workEmail: document.getElementById('workEmail').value,
role: document.getElementById('role').value,
employees: document.getElementById('employees').value,
rolesPerMonth: document.getElementById('rolesPerMonth').value,
candidatesPerRole: document.getElementById('candidatesPerRole').value,
ats: document.getElementById('ats').value,
scheduling: document.getElementById('scheduling').value,
painPoints: document.getElementById('painPoints').value,
successMetrics: document.getElementById('successMetrics').value,
hiringFocus: document.getElementById('hiringFocus').value,
};
msgEl.classList.add('hidden');
msgEl.textContent = '';
// Basic validation
if (!data.workEmail || !data.companyName || !data.yourName) {
msgEl.textContent = 'Please fill out all required fields.';
msgEl.classList.remove('hidden');
return;
}
fetch('apply.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify(data),
})
.then(response => response.json())
.then(result => {
if (result.success) {
msgEl.textContent = 'Thank you for your application! We will be in touch shortly.';
msgEl.classList.remove('hidden');
msgEl.classList.remove('text-red-600');
msgEl.classList.add('text-green-600');
form.reset();
} else {
msgEl.textContent = result.message || 'An unknown error occurred.';
msgEl.classList.remove('hidden');
}
})
.catch(error => {
msgEl.textContent = 'A network error occurred. Please try again.';
msgEl.classList.remove('hidden');
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

7
check_auth.php Normal file
View File

@ -0,0 +1,7 @@
<?php
session_start();
if (!isset($_SESSION['user'])) {
header('Location: index.php#signin');
exit();
}

2
dashboard.php Normal file

File diff suppressed because one or more lines are too long

31
db/migrate.php Normal file
View File

@ -0,0 +1,31 @@
<?php
require_once __DIR__ . '/config.php';
function run_migrations() {
$pdo = db();
$pdo->exec("CREATE TABLE IF NOT EXISTS `migrations` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `migration` VARCHAR(255) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP );");
$ran_migrations = $pdo->query("SELECT migration FROM migrations")->fetchAll(PDO::FETCH_COLUMN);
$migration_files = glob(__DIR__ . '/migrations/*.sql');
sort($migration_files);
foreach ($migration_files as $file) {
$migration_name = basename($file);
if (!in_array($migration_name, $ran_migrations)) {
$sql = file_get_contents($file);
$pdo->exec($sql);
$stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)");
$stmt->execute([$migration_name]);
echo "Migration run: " . $migration_name . "\n";
} else {
echo "Migration already run: " . $migration_name . "\n";
}
}
}
run_migrations();

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS `migrations` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`migration` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,9 @@
ALTER TABLE `applications`
ADD COLUMN `employees` INT NULL,
ADD COLUMN `roles_per_month` INT NULL,
ADD COLUMN `candidates_per_role` INT NULL,
ADD COLUMN `ats` VARCHAR(255) NULL,
ADD COLUMN `scheduling` VARCHAR(255) NULL,
ADD COLUMN `pain_points` TEXT NULL,
ADD COLUMN `success_metrics` TEXT NULL,
ADD COLUMN `hiring_focus` VARCHAR(255) NULL;

View File

@ -1,3 +1,4 @@
<?php session_start(); ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -635,7 +636,14 @@
<section class="max-w-7xl mx-auto px-6 py-24 text-center"> <h2 class="text-3xl font-semibold mb-4"> Still have questions? </h2> <p class="text-gray-600 mb-8"> Wed rather answer them directly than hide behind marketing. </p> <div class="flex justify-center gap-4"> <a href="/apply.php" class="bg-black text-white px-8 py-4 rounded-xl"> Apply for access </a> <a href="/trust.php" class="border px-8 py-4 rounded-xl"> Security & Trust </a> </div> </section> <section class="max-w-7xl mx-auto px-6 py-24 text-center"> <h2 class="text-3xl font-semibold mb-4"> Still have questions? </h2> <p class="text-gray-600 mb-8"> Wed rather answer them directly than hide behind marketing. </p> <div class="flex justify-center gap-4"> <a href="/apply.php" class="bg-black text-white px-8 py-4 rounded-xl"> Apply for access </a> <a href="/trust.php" class="border px-8 py-4 rounded-xl"> Security & Trust </a> </div> </section>
</section> </section>
<!-- SIGNIN PAGE (PART 1/2) --> <section id="page-signin" class="page fade hidden"> <section class="panel-strong p-8 lg:p-10"> <div class="grid lg:grid-cols-12 gap-10 items-start"> <!-- Left: Trust / context --> <div class="lg:col-span-5"> <div class="chip inline-flex px-3 py-1 text-xs">Account</div> <h1 class="mt-5 text-4xl font-extrabold tracking-tight"> Sign in to FinMox </h1> <p class="mt-5 text-gray-700"> Access your workspace to review hiring execution, decision trails, and audit-ready outputs. </p> <div class="mt-7 panel p-6"> <div class="text-sm font-semibold">What you can do inside</div> <ul class="mt-3 text-sm text-gray-700 space-y-2"> <li> Review ranked candidates + structured scoring</li> <li> See interview summaries + recommendations</li> <li> Track movement + SLA nudges</li> <li> Export decision logs for audits</li> </ul> <div class="mt-4 flex flex-wrap gap-2"> <span class="chip px-3 py-1 text-xs">Role-based access</span> <span class="chip px-3 py-1 text-xs">Least privilege</span> <span class="chip px-3 py-1 text-xs">Audit trail</span> </div> </div> <div class="mt-7 panel p-6"> <div class="text-sm font-semibold">Need access?</div> <p class="mt-2 text-sm text-gray-700"> FinMox is onboarding teams through private beta. Apply and well confirm fit. </p> <div class="mt-4 flex flex-wrap gap-3"> <a href="#apply" onclick="openPage('apply'); return false;" class="bg-black text-white px-6 py-3 rounded-2xl text-sm hoverlift"> Apply for Access </a> <a href="#trust" onclick="openPage('trust'); return false;" class="chip px-6 py-3 rounded-2xl text-sm hoverlift"> Security & Trust </a> </div> </div> </div><!-- SIGNIN PAGE (PART 2/2) --> <!-- Right: Sign in card --> <div class="lg:col-span-7"> <div class="panel p-7"> <div class="flex items-center justify-between gap-4"> <div> <div class="text-sm font-semibold">Welcome back</div> <div class="text-sm text-gray-600 mt-1">Sign in to continue.</div> </div> <span class="chip px-3 py-1 text-xs">Private beta</span> </div> <form class="mt-6 grid gap-3" onsubmit="return false;"> <div class="grid md:grid-cols-2 gap-3"> <input type="email" placeholder="Work email" class="panel-strong px-4 py-3 rounded-2xl" autocomplete="email" /> <input type="password" placeholder="Password" class="panel-strong px-4 py-3 rounded-2xl" autocomplete="current-password" /> </div> <div class="flex items-center justify-between gap-4 text-sm"> <label class="flex items-center gap-2 text-gray-700"> <input type="checkbox" class="accent-black" /> Remember me </label> <a href="#forgot" onclick="openPage('forgot'); return false;" class="underline text-sm text-gray-700"> Forgot access? </a> </div> <button type="button" class="bg-black text-white py-3 rounded-2xl hoverlift" > Sign In </button> <div class="text-xs text-gray-600"> Demo placeholder. Wire this to your auth later (AppWizzy auth, Supabase, Clerk, etc.). </div> </form> <div class="mt-6 grid md:grid-cols-3 gap-3"> <div class="panel-strong p-5"> <div class="text-xs text-gray-500">Access control</div> <div class="mt-2 text-sm text-gray-700">Role-based permissions supported.</div> </div> <div class="panel-strong p-5"> <div class="text-xs text-gray-500">Session security</div> <div class="mt-2 text-sm text-gray-700">Least-privilege by default.</div> </div> <div class="panel-strong p-5"> <div class="text-xs text-gray-500">Auditability</div> <div class="mt-2 text-sm text-gray-700">Actions map to decision trail.</div> </div> </div> <div class="mt-6 panel-strong p-6"> <div class="text-sm font-semibold">New here?</div> <div class="mt-2 text-sm text-gray-700"> Apply for beta access and well onboard your team with a short process.</div></div></div></div></section></section> <!-- SIGNIN PAGE (PART 1/2) --> <section id="page-signin" class="page fade hidden"> <section class="panel-strong p-8 lg:p-10"> <div class="grid lg:grid-cols-12 gap-10 items-start"> <!-- Left: Trust / context --> <div class="lg:col-span-5"> <div class="chip inline-flex px-3 py-1 text-xs">Account</div> <h1 class="mt-5 text-4xl font-extrabold tracking-tight"> Sign in to FinMox </h1> <p class="mt-5 text-gray-700"> Access your workspace to review hiring execution, decision trails, and audit-ready outputs. </p> <div class="mt-7 panel p-6"> <div class="text-sm font-semibold">What you can do inside</div> <ul class="mt-3 text-sm text-gray-700 space-y-2"> <li> Review ranked candidates + structured scoring</li> <li> See interview summaries + recommendations</li> <li> Track movement + SLA nudges</li> <li> Export decision logs for audits</li> </ul> <div class="mt-4 flex flex-wrap gap-2"> <span class="chip px-3 py-1 text-xs">Role-based access</span> <span class="chip px-3 py-1 text-xs">Least privilege</span> <span class="chip px-3 py-1 text-xs">Audit trail</span> </div> </div> <div class="mt-7 panel p-6"> <div class="text-sm font-semibold">Need access?</div> <p class="mt-2 text-sm text-gray-700"> FinMox is onboarding teams through private beta. Apply and well confirm fit. </p> <div class="mt-4 flex flex-wrap gap-3"> <a href="#apply" onclick="openPage('apply'); return false;" class="bg-black text-white px-6 py-3 rounded-2xl text-sm hoverlift"> Apply for Access </a> <a href="#trust" onclick="openPage('trust'); return false;" class="chip px-6 py-3 rounded-2xl text-sm hoverlift"> Security & Trust </a> </div> </div> </div><!-- SIGNIN PAGE (PART 2/2) --> <!-- Right: Sign in card --> <div class="lg:col-span-7"> <div class="panel p-7"> <div class="flex items-center justify-between gap-4"> <div> <div class="text-sm font-semibold">Welcome back</div> <div class="text-sm text-gray-600 mt-1">Sign in to continue.</div> </div> <span class="chip px-3 py-1 text-xs">Private beta</span> </div> <form class="mt-6 grid gap-3">
<input type="email" id="signinEmail" placeholder="Work email" class="panel-strong px-4 py-3 rounded-2xl" />
<input type="password" id="signinPassword" placeholder="Password" class="panel-strong px-4 py-3 rounded-2xl" />
<button type="button" onclick="signIn(event)" class="bg-black text-white py-3 rounded-2xl hoverlift">
Sign in
</button>
<div id="signinMsg" class="hidden text-sm text-red-600"></div>
</form> <div class="mt-6 grid md:grid-cols-3 gap-3"> <div class="panel-strong p-5"> <div class="text-xs text-gray-500">Access control</div> <div class="mt-2 text-sm text-gray-700">Role-based permissions supported.</div> </div> <div class="panel-strong p-5"> <div class="text-xs text-gray-500">Session security</div> <div class="mt-2 text-sm text-gray-700">Least-privilege by default.</div> </div> <div class="panel-strong p-5"> <div class="text-xs text-gray-500">Auditability</div> <div class="mt-2 text-sm text-gray-700">Actions map to decision trail.</div> </div> </div> <div class="mt-6 panel-strong p-6"> <div class="text-sm font-semibold">New here?</div> <div class="mt-2 text-sm text-gray-700"> Apply for beta access and well onboard your team with a short process.</div></div></div></div></section></section>
<!-- APPLY PAGE (PART 1/3) --> <!-- APPLY PAGE (PART 1/3) -->
<section id="page-apply" class="page fade hidden"> <section id="page-apply" class="page fade hidden">
@ -692,35 +700,36 @@
</div> </div>
<span class="chip px-3 py-1 text-xs">Pilot-first</span> <span class="chip px-3 py-1 text-xs">Pilot-first</span>
</div> </div>
<form class="mt-6 grid gap-3" onsubmit="return false;"> <form id="applyForm" class="mt-6 grid gap-3" onsubmit="return false;">
<div class="grid md:grid-cols-2 gap-3"> <div class="grid md:grid-cols-2 gap-3">
<input type="text" placeholder="Company name" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="companyName" type="text" placeholder="Company name" class="panel-strong px-4 py-3 rounded-2xl" />
<input type="text" placeholder="Your name" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="yourName" type="text" placeholder="Your name" class="panel-strong px-4 py-3 rounded-2xl" />
</div> </div>
<div class="grid md:grid-cols-2 gap-3"> <div class="grid md:grid-cols-2 gap-3">
<input type="email" placeholder="Work email" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="workEmail" type="email" placeholder="Work email" class="panel-strong px-4 py-3 rounded-2xl" />
<input type="text" placeholder="Role / title" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="role" type="text" placeholder="Role / title" class="panel-strong px-4 py-3 rounded-2xl" />
</div> </div>
<div class="grid md:grid-cols-3 gap-3"> <div class="grid md:grid-cols-3 gap-3">
<input type="number" placeholder="Employees" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="employees" type="number" placeholder="Employees" class="panel-strong px-4 py-3 rounded-2xl" />
<input type="number" placeholder="Roles per month" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="rolesPerMonth" type="number" placeholder="Roles per month" class="panel-strong px-4 py-3 rounded-2xl" />
<input type="number" placeholder="Candidates per role" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="candidatesPerRole" type="number" placeholder="Candidates per role" class="panel-strong px-4 py-3 rounded-2xl" />
</div> </div>
<div class="grid md:grid-cols-2 gap-3"> <div class="grid md:grid-cols-2 gap-3">
<input type="text" placeholder="ATS (Greenhouse, Lever, etc.)" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="ats" type="text" placeholder="ATS (Greenhouse, Lever, etc.)" class="panel-strong px-4 py-3 rounded-2xl" />
<input type="text" placeholder="Scheduling (Calendly, Google, etc.)" class="panel-strong px-4 py-3 rounded-2xl" /> <input id="scheduling" type="text" placeholder="Scheduling (Calendly, Google, etc.)" class="panel-strong px-4 py-3 rounded-2xl" />
</div> </div>
<textarea rows="4" placeholder="Where does hiring execution break today? (drift, delays, inconsistency, documentation)" class="panel-strong px-4 py-3 rounded-2xl"></textarea> <textarea id="painPoints" rows="4" placeholder="Where does hiring execution break today? (drift, delays, inconsistency, documentation)" class="panel-strong px-4 py-3 rounded-2xl"></textarea>
<textarea rows="3" placeholder="What does success look like in 30 days? (faster decisions, less chasing, audit trail, etc.)" class="panel-strong px-4 py-3 rounded-2xl"></textarea> <textarea id="successMetrics" rows="3" placeholder="What does success look like in 30 days? (faster decisions, less chasing, audit trail, etc.)" class="panel-strong px-4 py-3 rounded-2xl"></textarea>
<div id="applyMsg" class="hidden text-sm text-red-600"></div>
<div class="grid md:grid-cols-2 gap-3"> <div class="grid md:grid-cols-2 gap-3">
<select class="panel-strong px-4 py-3 rounded-2xl"> <select id="hiringFocus" class="panel-strong px-4 py-3 rounded-2xl">
<option>Primary hiring focus</option> <option>Primary hiring focus</option>
<option>Engineering</option> <option>Engineering</option>
<option>Sales</option> <option>Sales</option>
<option>Marketing</option> <option>Marketing</option>
<option>Other</option> <option>Other</option>
</select> </select>
<button type="button" class="bg-black text-white py-3 rounded-2xl hoverlift" > Apply Now </button> <button type="button" id="applyNowBtn" class="bg-black text-white py-3 rounded-2xl hoverlift" > Apply Now </button>
</div> </div>
</form> </form>
</div> </div>

29
login.php Normal file
View File

@ -0,0 +1,29 @@
<?php
session_start();
// Hardcoded credentials for demonstration.
// In a real application, you should fetch user from a database and verify the password hash.
$valid_email = 'test@example.com';
$valid_password = 'password';
$response = ['success' => false, 'message' => ''];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$email = $data['email'] ?? null;
$password = $data['password'] ?? null;
if (empty($email) || empty($password)) {
$response['message'] = 'Please enter both email and password.';
} elseif ($email === $valid_email && $password === $valid_password) {
$_SESSION['user'] = ['email' => $email];
$response['success'] = true;
} else {
$response['message'] = 'Invalid credentials.';
}
} else {
$response['message'] = 'Invalid request method.';
}
header('Content-Type: application/json');
echo json_encode($response);