Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
2e20f0abb7 Autosave: 20260305-075707 2026-03-05 07:57:07 +00:00
13 changed files with 912 additions and 519 deletions

105
admin_kyc_list.php Normal file
View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/app.php';
ensure_kyc_table();
$stmt = db()->query('SELECT * FROM kyc_applications ORDER BY created_at DESC LIMIT 50');
$items = $stmt->fetchAll();
$pendingCount = 0;
foreach ($items as $row) {
if (($row['status'] ?? '') === 'pending') {
$pendingCount++;
}
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html>
<html lang="<?= h($lang) ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= h(t('admin_title')) ?> · <?= h(t('site_name')) ?></title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= h($projectDescription) ?>" />
<meta property="og:description" content="<?= h($projectDescription) ?>" />
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h((string) time()) ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light">
<div class="container">
<a class="navbar-brand" href="<?= h(url_with_lang('index.php')) ?>"><?= h(t('site_name')) ?></a>
<div class="ms-auto lang-toggle btn-group" role="group">
<a class="btn btn-sm <?= $lang === 'zh' ? 'active' : '' ?>" href="<?= h(url_with_lang('admin_kyc_list.php', ['lang' => 'zh'])) ?>">中文</a>
<a class="btn btn-sm <?= $lang === 'en' ? 'active' : '' ?>" href="<?= h(url_with_lang('admin_kyc_list.php', ['lang' => 'en'])) ?>">EN</a>
</div>
</div>
</nav>
<main class="container my-5">
<div class="section-card">
<div class="d-flex flex-column flex-lg-row justify-content-between gap-3 mb-3">
<div>
<h1 class="section-title mb-1"><?= h(t('admin_title')) ?></h1>
<p class="text-muted mb-0"><?= h(t('admin_subtitle')) ?></p>
</div>
<div class="section-card py-2 px-3 align-self-start">
<div class="text-muted small">Pending</div>
<div class="fw-semibold"><?= h((string) $pendingCount) ?></div>
</div>
</div>
<?php if (!$items): ?>
<div class="alert alert-warning"><?= h(t('admin_empty')) ?></div>
<?php else: ?>
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Status</th>
<th>Submitted</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $row): ?>
<?php
$statusClass = match ($row['status']) {
'approved' => 'status-approved',
'rejected' => 'status-rejected',
default => 'status-pending',
};
?>
<tr>
<td><?= h((string) $row['id']) ?></td>
<td><?= h($row['full_name']) ?></td>
<td><?= h($row['email']) ?></td>
<td><span class="status-pill <?= h($statusClass) ?>"><?= h($row['status']) ?></span></td>
<td><?= h($row['created_at']) ?></td>
<td><a class="ghost-btn text-decoration-none" href="<?= h(url_with_lang('admin_kyc_view.php', ['id' => $row['id']])) ?>"><?= h(t('admin_view')) ?></a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= h((string) time()) ?>"></script>
</body>
</html>

125
admin_kyc_view.php Normal file
View File

@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/app.php';
ensure_kyc_table();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$record = null;
$notice = null;
if ($id > 0) {
$stmt = db()->prepare('SELECT * FROM kyc_applications WHERE id = :id LIMIT 1');
$stmt->execute([':id' => $id]);
$record = $stmt->fetch();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $record) {
$status = $_POST['status'] ?? 'pending';
$allowed = ['pending', 'approved', 'rejected'];
if (!in_array($status, $allowed, true)) {
$status = 'pending';
}
$stmt = db()->prepare('UPDATE kyc_applications SET status = :status WHERE id = :id');
$stmt->execute([':status' => $status, ':id' => $record['id']]);
$notice = 'Status updated.';
$record['status'] = $status;
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html>
<html lang="<?= h($lang) ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= h(t('admin_detail_title')) ?> · <?= h(t('site_name')) ?></title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= h($projectDescription) ?>" />
<meta property="og:description" content="<?= h($projectDescription) ?>" />
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h((string) time()) ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light">
<div class="container">
<a class="navbar-brand" href="<?= h(url_with_lang('index.php')) ?>"><?= h(t('site_name')) ?></a>
<div class="ms-auto lang-toggle btn-group" role="group">
<a class="btn btn-sm <?= $lang === 'zh' ? 'active' : '' ?>" href="<?= h(url_with_lang('admin_kyc_view.php', ['lang' => 'zh', 'id' => $id])) ?>">中文</a>
<a class="btn btn-sm <?= $lang === 'en' ? 'active' : '' ?>" href="<?= h(url_with_lang('admin_kyc_view.php', ['lang' => 'en', 'id' => $id])) ?>">EN</a>
</div>
</div>
</nav>
<main class="container my-5">
<div class="section-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h1 class="section-title mb-1"><?= h(t('admin_detail_title')) ?></h1>
<p class="text-muted mb-0">ID <?= h((string) $id) ?></p>
</div>
<a class="ghost-btn text-decoration-none" href="<?= h(url_with_lang('admin_kyc_list.php')) ?>"><?= h(t('admin_back')) ?></a>
</div>
<?php if ($notice): ?>
<div class="alert alert-success"><?= h($notice) ?></div>
<?php endif; ?>
<?php if (!$record): ?>
<div class="alert alert-warning">Record not found.</div>
<?php else: ?>
<div class="row g-3 mb-4">
<div class="col-md-6">
<div class="text-muted">Name</div>
<div class="fw-semibold"><?= h($record['full_name']) ?></div>
</div>
<div class="col-md-6">
<div class="text-muted">Email</div>
<div class="fw-semibold"><?= h($record['email']) ?></div>
</div>
<div class="col-md-6">
<div class="text-muted">Phone</div>
<div class="fw-semibold"><?= h($record['phone']) ?></div>
</div>
<div class="col-md-6">
<div class="text-muted">ID Number</div>
<div class="fw-semibold"><?= h($record['id_number']) ?></div>
</div>
<div class="col-12">
<div class="text-muted">Address</div>
<div class="fw-semibold"><?= h($record['address']) ?></div>
</div>
<div class="col-12">
<div class="text-muted">Document</div>
<a href="<?= h($record['doc_url']) ?>" target="_blank" rel="noopener noreferrer"><?= h($record['doc_url']) ?></a>
</div>
</div>
<form method="post" class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label"><?= h(t('admin_update')) ?></label>
<select name="status" class="form-select">
<option value="pending" <?= $record['status'] === 'pending' ? 'selected' : '' ?>>pending</option>
<option value="approved" <?= $record['status'] === 'approved' ? 'selected' : '' ?>>approved</option>
<option value="rejected" <?= $record['status'] === 'rejected' ? 'selected' : '' ?>>rejected</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="cta-btn">Save</button>
</div>
</form>
<?php endif; ?>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= h((string) time()) ?>"></script>
</body>
</html>

156
app.php Normal file
View File

@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
@date_default_timezone_set('UTC');
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/db/config.php';
$lang = $_GET['lang'] ?? ($_SESSION['lang'] ?? 'zh');
$lang = in_array($lang, ['zh', 'en'], true) ? $lang : 'zh';
$_SESSION['lang'] = $lang;
$i18n = [
'zh' => [
'site_name' => '金曜黄金交易所',
'nav_home' => '首页',
'nav_kyc' => 'KYC申请',
'nav_status' => '状态查询',
'nav_admin' => '后台管理',
'hero_title' => '合规黄金交易入口,支持真实交易流程',
'hero_subtitle' => '提交KYC、绑定资金账户并完成审核后即可开始交易。首版提供完整申请-审核-状态查询闭环。',
'cta_kyc' => '提交KYC申请',
'cta_status' => '查询申请状态',
'badge' => 'MVP · 真实交易准备流程',
'steps_title' => '三步启动交易',
'step1_title' => '提交KYC信息',
'step1_desc' => '录入身份信息与地址证明,系统生成申请编号。',
'step2_title' => '审核与通知',
'step2_desc' => '运营后台完成审核并更新状态。',
'step3_title' => '资金准备与交易',
'step3_desc' => '审核通过后即可进入资金与下单流程。',
'features_title' => '首版功能',
'feature1' => '双语中英文切换,适配国际客户',
'feature2' => 'KYC申请、状态查询、后台审核闭环',
'feature3' => '后续可扩展行情、下单、资金管理模块',
'cta_admin' => '查看后台审核',
'kyc_title' => 'KYC申请表',
'kyc_subtitle' => '请填写真实信息我们会在1-2个工作日完成审核。',
'form_name' => '姓名(与证件一致)',
'form_email' => '邮箱',
'form_phone' => '手机号',
'form_id' => '身份证/护照号',
'form_address' => '居住地址',
'form_doc' => '证明文件链接(可为云盘地址)',
'form_submit' => '提交申请',
'form_note' => '提交即表示同意平台条款与隐私政策。',
'status_title' => 'KYC状态查询',
'status_subtitle' => '输入申请编号查看最新审核状态。',
'status_label' => '申请编号',
'status_btn' => '查询',
'status_empty' => '未找到该编号,请确认后重试。',
'status_pending' => '等待审核',
'status_approved' => '审核通过',
'status_rejected' => '审核未通过',
'admin_title' => 'KYC审核列表',
'admin_subtitle' => '最新申请列表与审核状态更新。',
'admin_view' => '查看详情',
'admin_detail_title' => 'KYC详情',
'admin_update' => '更新状态',
'admin_back' => '返回列表',
'admin_empty' => '暂无申请记录。',
'alert_success' => '申请已提交,我们已生成申请编号。',
'alert_error' => '请完整填写必填信息。',
'alert_email' => '邮箱格式不正确。',
],
'en' => [
'site_name' => 'Jinyao Gold Exchange',
'nav_home' => 'Home',
'nav_kyc' => 'KYC Apply',
'nav_status' => 'Check Status',
'nav_admin' => 'Admin',
'hero_title' => 'Compliant access to real gold trading',
'hero_subtitle' => 'Submit KYC, prepare funding, and get approved to start trading. This MVP delivers the full apply-review-status loop.',
'cta_kyc' => 'Submit KYC',
'cta_status' => 'Check Status',
'badge' => 'MVP · Real-trading onboarding',
'steps_title' => 'Start in three steps',
'step1_title' => 'Submit KYC',
'step1_desc' => 'Provide identity and address documents and receive an application ID.',
'step2_title' => 'Review & approval',
'step2_desc' => 'Operations reviews and updates your status.',
'step3_title' => 'Funding & trading',
'step3_desc' => 'Approved users move to funding and order placement.',
'features_title' => 'First release features',
'feature1' => 'Bilingual Chinese/English interface',
'feature2' => 'KYC application, status lookup, and admin review loop',
'feature3' => 'Ready to extend with pricing, orders, and wallet modules',
'cta_admin' => 'Open admin review',
'kyc_title' => 'KYC Application',
'kyc_subtitle' => 'Please submit accurate data. Review takes 1-2 business days.',
'form_name' => 'Full legal name',
'form_email' => 'Email',
'form_phone' => 'Phone number',
'form_id' => 'ID / Passport number',
'form_address' => 'Residential address',
'form_doc' => 'Document link (cloud storage URL)',
'form_submit' => 'Submit application',
'form_note' => 'By submitting you agree to our terms and privacy policy.',
'status_title' => 'KYC Status Lookup',
'status_subtitle' => 'Enter your application ID to see the latest status.',
'status_label' => 'Application ID',
'status_btn' => 'Search',
'status_empty' => 'No record found. Please verify the ID.',
'status_pending' => 'Pending review',
'status_approved' => 'Approved',
'status_rejected' => 'Rejected',
'admin_title' => 'KYC Review List',
'admin_subtitle' => 'Latest submissions and status updates.',
'admin_view' => 'View details',
'admin_detail_title' => 'KYC Details',
'admin_update' => 'Update status',
'admin_back' => 'Back to list',
'admin_empty' => 'No applications yet.',
'alert_success' => 'Application submitted. Your application ID is ready.',
'alert_error' => 'Please complete all required fields.',
'alert_email' => 'Email format is invalid.',
],
];
function t(string $key): string
{
global $i18n, $lang;
return $i18n[$lang][$key] ?? $key;
}
function h(?string $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function url_with_lang(string $path, array $params = []): string
{
global $lang;
$params = array_merge(['lang' => $lang], $params);
return $path . '?' . http_build_query($params);
}
function ensure_kyc_table(): void
{
$pdo = db();
$pdo->exec(
"CREATE TABLE IF NOT EXISTS kyc_applications (
id INT AUTO_INCREMENT PRIMARY KEY,
full_name VARCHAR(120) NOT NULL,
email VARCHAR(160) NOT NULL,
phone VARCHAR(60) NOT NULL,
id_number VARCHAR(60) NOT NULL,
address VARCHAR(255) NOT NULL,
doc_url VARCHAR(255) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
}

View File

@ -1,403 +1,175 @@
:root {
--bg: #f6f7f9;
--surface: #ffffff;
--surface-muted: #f1f3f5;
--border: #e5e7eb;
--text: #111827;
--muted: #6b7280;
--primary: #c6a15b;
--primary-strong: #a8853f;
--success: #0f766e;
--danger: #b91c1c;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
}
* {
box-sizing: border-box;
}
body {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
color: #212529;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
margin: 0;
min-height: 100vh;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--text);
font-size: 14px;
}
.main-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
width: 100%;
padding: 20px;
box-sizing: border-box;
position: relative;
z-index: 1;
.navbar {
background: var(--surface);
border-bottom: 1px solid var(--border);
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
.navbar-brand {
font-weight: 700;
letter-spacing: -0.2px;
}
.chat-container {
width: 100%;
max-width: 600px;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
display: flex;
flex-direction: column;
height: 85vh;
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
overflow: hidden;
.nav-link {
color: var(--text);
}
.chat-header {
padding: 1.5rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
background: rgba(255, 255, 255, 0.5);
font-weight: 700;
font-size: 1.1rem;
display: flex;
justify-content: space-between;
align-items: center;
.nav-link:hover,
.nav-link:focus {
color: var(--primary-strong);
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
.lang-toggle .btn {
border-color: var(--border);
color: var(--text);
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
.lang-toggle .btn.active {
background: var(--text);
color: #fff;
border-color: var(--text);
}
::-webkit-scrollbar-track {
background: transparent;
.hero {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 48px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 10px;
.hero h1 {
font-size: 32px;
letter-spacing: -0.6px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
.hero p {
color: var(--muted);
font-size: 15px;
}
.message {
max-width: 85%;
padding: 0.85rem 1.1rem;
border-radius: 16px;
line-height: 1.5;
font-size: 0.95rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
.badge-soft {
background: var(--surface-muted);
color: var(--muted);
border: 1px solid var(--border);
padding: 6px 12px;
border-radius: 999px;
font-size: 12px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px) scale(0.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
.section-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 24px;
height: 100%;
}
.message.visitor {
align-self: flex-end;
background: linear-gradient(135deg, #212529 0%, #343a40 100%);
color: #fff;
border-bottom-right-radius: 4px;
.section-title {
font-size: 20px;
letter-spacing: -0.3px;
margin-bottom: 16px;
}
.message.bot {
align-self: flex-start;
background: #ffffff;
color: #212529;
border-bottom-left-radius: 4px;
.feature-item {
border-bottom: 1px dashed var(--border);
padding: 12px 0;
}
.chat-input-area {
padding: 1.25rem;
background: rgba(255, 255, 255, 0.5);
border-top: 1px solid rgba(0, 0, 0, 0.05);
.feature-item:last-child {
border-bottom: none;
}
.chat-input-area form {
display: flex;
gap: 0.75rem;
.cta-btn {
background: var(--text);
color: #fff;
border-radius: var(--radius-sm);
padding: 10px 18px;
border: 1px solid var(--text);
}
.chat-input-area input {
flex: 1;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
padding: 0.75rem 1rem;
outline: none;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
.cta-btn:hover {
background: #000;
color: #fff;
}
.chat-input-area input:focus {
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
.ghost-btn {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 18px;
color: var(--text);
}
.chat-input-area button {
background: #212529;
color: #fff;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
.form-control,
.form-select {
border-radius: var(--radius-sm);
border-color: var(--border);
font-size: 14px;
}
.chat-input-area button:hover {
background: #000;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
/* Background Animations */
.bg-animations {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
.blob {
position: absolute;
width: 500px;
height: 500px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
filter: blur(80px);
animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
}
.blob-1 {
top: -10%;
left: -10%;
background: rgba(238, 119, 82, 0.4);
}
.blob-2 {
bottom: -10%;
right: -10%;
background: rgba(35, 166, 213, 0.4);
animation-delay: -7s;
width: 600px;
height: 600px;
}
.blob-3 {
top: 40%;
left: 30%;
background: rgba(231, 60, 126, 0.3);
animation-delay: -14s;
width: 450px;
height: 450px;
}
@keyframes move {
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
100% { transform: translate(0, 0) rotate(360deg) scale(1); }
}
.header-link {
font-size: 14px;
color: #fff;
text-decoration: none;
background: rgba(0, 0, 0, 0.2);
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.header-link:hover {
background: rgba(0, 0, 0, 0.4);
text-decoration: none;
}
/* Admin Styles */
.admin-container {
max-width: 900px;
margin: 3rem auto;
padding: 2.5rem;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 24px;
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
border: 1px solid rgba(255, 255, 255, 0.4);
position: relative;
z-index: 1;
}
.admin-container h1 {
margin-top: 0;
color: #212529;
font-weight: 800;
.form-control:focus,
.form-select:focus {
border-color: var(--text);
box-shadow: 0 0 0 0.15rem rgba(17, 24, 39, 0.08);
}
.table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
margin-top: 1.5rem;
font-size: 13px;
}
.table th {
background: transparent;
border: none;
padding: 1rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 1px;
.table thead th {
color: var(--muted);
font-weight: 600;
}
.table td {
background: #fff;
padding: 1rem;
border: none;
.status-pill {
border-radius: 999px;
padding: 4px 10px;
font-size: 12px;
border: 1px solid var(--border);
}
.table tr td:first-child { border-radius: 12px 0 0 12px; }
.table tr td:last-child { border-radius: 0 12px 12px 0; }
.form-group {
margin-bottom: 1.25rem;
.status-pending {
background: #fff7ed;
color: #9a3412;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
.status-approved {
background: #ecfdf3;
color: var(--success);
}
.form-control {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
background: #fff;
transition: all 0.3s ease;
box-sizing: border-box;
.status-rejected {
background: #fef2f2;
color: var(--danger);
}
.form-control:focus {
outline: none;
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
.footer {
color: var(--muted);
font-size: 12px;
padding: 24px 0 48px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-links {
display: flex;
gap: 1rem;
}
.admin-card {
background: rgba(255, 255, 255, 0.6);
padding: 2rem;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.5);
margin-bottom: 2.5rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
}
.admin-card h3 {
margin-top: 0;
margin-bottom: 1.5rem;
font-weight: 700;
}
.btn-delete {
background: #dc3545;
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
}
.btn-add {
background: #212529;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-top: 1rem;
}
.btn-save {
background: #0088cc;
color: white;
border: none;
padding: 0.8rem 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
width: 100%;
transition: all 0.3s ease;
}
.webhook-url {
font-size: 0.85em;
color: #555;
margin-top: 0.5rem;
}
.history-table-container {
overflow-x: auto;
background: rgba(255, 255, 255, 0.4);
padding: 1rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.history-table {
width: 100%;
}
.history-table-time {
width: 15%;
white-space: nowrap;
font-size: 0.85em;
color: #555;
}
.history-table-user {
width: 35%;
background: rgba(255, 255, 255, 0.3);
border-radius: 8px;
padding: 8px;
}
.history-table-ai {
width: 50%;
background: rgba(255, 255, 255, 0.5);
border-radius: 8px;
padding: 8px;
}
.no-messages {
text-align: center;
color: #777;
}

View File

@ -1,39 +1,15 @@
document.addEventListener('DOMContentLoaded', () => {
const chatForm = document.getElementById('chat-form');
const chatInput = document.getElementById('chat-input');
const chatMessages = document.getElementById('chat-messages');
const appendMessage = (text, sender) => {
const msgDiv = document.createElement('div');
msgDiv.classList.add('message', sender);
msgDiv.textContent = text;
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
};
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const message = chatInput.value.trim();
if (!message) return;
appendMessage(message, 'visitor');
chatInput.value = '';
try {
const response = await fetch('api/chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message })
});
const data = await response.json();
// Artificial delay for realism
setTimeout(() => {
appendMessage(data.reply, 'bot');
}, 500);
} catch (error) {
console.error('Error:', error);
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
}
const copyButtons = document.querySelectorAll('[data-copy]');
copyButtons.forEach((btn) => {
btn.addEventListener('click', () => {
const value = btn.getAttribute('data-copy');
if (!value) return;
navigator.clipboard.writeText(value).then(() => {
btn.innerText = btn.getAttribute('data-copied') || 'Copied';
setTimeout(() => {
btn.innerText = btn.getAttribute('data-default') || 'Copy';
}, 1800);
});
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

58
dashboard.php Normal file
View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YLG Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background-color: #f6f7f9; color: #111827; }
.nav-bottom { position: fixed; bottom: 0; width: 100%; background: white; border-top: 1px solid #ddd; display: flex; justify-content: space-around; padding: 10px; z-index: 1000; }
.btn-action { display: flex; flex-direction: column; align-items: center; background: white; border-radius: 10px; padding: 15px; text-decoration: none; color: #111827; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
.hero-banner { width: 100%; height: 150px; background: #ddd; border-radius: 15px; margin-bottom: 20px; display: flex; align-items: center; justify-content: center; }
.product-card { background: white; border-radius: 10px; padding: 10px; font-size: 0.85rem; }
</style>
</head>
<body>
<div class="container pb-5 mb-5">
<header class="d-flex justify-content-between align-items-center py-3">
<div class="fw-bold">YLG BULLION</div>
<button class="btn btn-outline-secondary btn-sm">🌐 简体中文</button>
</header>
<div class="hero-banner">Banner Image</div>
<div class="row g-2 mb-4">
<div class="col-3"><a href="#" class="btn-action">📈 快速入市</a></div>
<div class="col-3"><a href="#" class="btn-action">🌐 切换语言</a></div>
<div class="col-3"><a href="#" class="btn-action">📄 我的订单</a></div>
<div class="col-3"><a href="#" class="btn-action">💳 出款方式</a></div>
<div class="col-3"><a href="#" class="btn-action">🏢 关于我们</a></div>
<div class="col-3"><a href="#" class="btn-action">💬 帮助中心</a></div>
<div class="col-6"><a href="#" class="btn-action flex-row gap-2">🎧 在线客服 <small class="text-muted">竭诚服务</small></a></div>
</div>
<section class="mb-4">
<h6 class="text-center"> 产品推荐 </h6>
<div class="row g-2">
<div class="col-4"><div class="product-card">Gold<br>5379.11<br><span class="text-danger">+0.5%</span></div></div>
<div class="col-4"><div class="product-card">Rare Earth<br>71.60<br><span class="text-success">-4.5%</span></div></div>
<div class="col-4"><div class="product-card">Poly Si<br>14.83<br><span class="text-danger">+1.0%</span></div></div>
</div>
</section>
<section>
<h6 class="text-center"> 市场 </h6>
<div class="card p-3 rounded-4">Gold | 5379.111 | <span class="badge bg-danger">+0.51%</span></div>
</section>
</div>
<nav class="nav-bottom">
<a href="dashboard.php" class="text-decoration-none text-dark">🏠 首页</a>
<a href="#" class="text-decoration-none text-muted">📊 产品</a>
<a href="#" class="text-decoration-none text-muted">📈 记录</a>
<a href="#" class="text-decoration-none text-muted">👤 我的</a>
</nav>
</body>
</html>
EOF

242
index.php
View File

@ -1,150 +1,122 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
require_once __DIR__ . '/app.php';
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
// Mock translation data for the landing page structure
// In a real app, these would be loaded from a config or database
$translations = [
'en' => [
'site_name' => 'GoldExchange Global',
'nav_home' => 'Home',
'nav_market' => 'Market',
'nav_kyc' => 'Trading Account',
'nav_dashboard' => 'Dashboard',
'nav_admin' => 'Admin',
'hero_title' => 'Professional Gold Trading Platform',
'hero_subtitle' => 'Secure, liquid, and accessible gold trading solutions for institutional and retail investors.',
'cta_start' => 'Start Trading',
'market_title' => 'Live Gold Price',
'features_title' => 'Why Choose Us',
'feature1' => 'Institutional Grade Security',
'feature2' => 'Deep Liquidity Pool',
'feature3' => '24/7 Global Trading'
],
'zh' => [
'site_name' => '全球黄金交易所',
'nav_home' => '首页',
'nav_market' => '市场行情',
'nav_kyc' => '交易账户',
'nav_dashboard' => '仪表盘',
'nav_admin' => '管理后台',
'hero_title' => '专业黄金交易平台',
'hero_subtitle' => '为机构及零售投资者提供安全、高流动性且便捷的黄金交易服务。',
'cta_start' => '立即交易',
'market_title' => '黄金实时行情',
'features_title' => '为什么选择我们',
'feature1' => '机构级安全保障',
'feature2' => '深厚流动性池',
'feature3' => '7x24 全球交易'
]
];
function t_custom($key) {
global $lang, $translations;
return $translations[$lang][$key] ?? $key;
}
?>
<!doctype html>
<html lang="en">
<html lang="<?= h($lang) ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
<title><?= h(t_custom('site_name')) ?></title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h((string) time()) ?>">
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<!-- Header -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand fw-bold text-warning" href="index.php"><?= h(t_custom('site_name')) ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMain">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navMain">
<ul class="navbar-nav ms-auto gap-3">
<li class="nav-item"><a class="nav-link" href="index.php"><?= h(t_custom('nav_home')) ?></a></li>
<li class="nav-item"><a class="nav-link" href="#"><?= h(t_custom('nav_market')) ?></a></li>
<li class="nav-item"><a class="nav-link" href="kyc.php"><?= h(t_custom('nav_kyc')) ?></a></li>
<li class="nav-item"><a class="nav-link" href="admin_kyc_list.php"><?= h(t_custom('nav_admin')) ?></a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown"><?= $lang === 'zh' ? '中文' : 'EN' ?></a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="?lang=zh">中文</a></li>
<li><a class="dropdown-item" href="?lang=en">English</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Hero Section -->
<section class="py-5 bg-dark text-white">
<div class="container text-center py-5">
<h1 class="display-4 fw-bold"><?= h(t_custom('hero_title')) ?></h1>
<p class="lead mb-4 text-secondary"><?= h(t_custom('hero_subtitle')) ?></p>
<a href="kyc.php" class="btn btn-warning btn-lg px-4"><?= h(t_custom('cta_start')) ?></a>
</div>
</section>
<!-- Main Content -->
<main class="container my-5">
<div class="row g-4">
<div class="col-md-8">
<div class="section-card">
<h3><?= h(t_custom('market_title')) ?></h3>
<div class="p-4 bg-light rounded text-center">
<h2 class="display-5 fw-bold text-primary">$2,650.00 <span class="text-success fs-5">+1.2%</span></h2>
</div>
</div>
</div>
<div class="col-md-4">
<div class="section-card">
<h3><?= h(t_custom('features_title')) ?></h3>
<ul class="list-unstyled mt-3">
<li class="mb-2"> <?= h(t_custom('feature1')) ?></li>
<li class="mb-2"> <?= h(t_custom('feature2')) ?></li>
<li class="mb-2"> <?= h(t_custom('feature3')) ?></li>
</ul>
</div>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
<footer class="text-center py-4 text-muted border-top">
<div class="container">© <?= date('Y') ?> <?= h(t_custom('site_name')) ?>. All Rights Reserved.</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
</html>

134
kyc.php Normal file
View File

@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/app.php';
ensure_kyc_table();
$errors = [];
$successId = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$fullName = trim((string)($_POST['full_name'] ?? ''));
$email = trim((string)($_POST['email'] ?? ''));
$phone = trim((string)($_POST['phone'] ?? ''));
$idNumber = trim((string)($_POST['id_number'] ?? ''));
$address = trim((string)($_POST['address'] ?? ''));
$docUrl = trim((string)($_POST['doc_url'] ?? ''));
if ($fullName === '' || $email === '' || $phone === '' || $idNumber === '' || $address === '' || $docUrl === '') {
$errors[] = t('alert_error');
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = t('alert_email');
}
if (!$errors) {
$stmt = db()->prepare(
'INSERT INTO kyc_applications (full_name, email, phone, id_number, address, doc_url, status)
VALUES (:full_name, :email, :phone, :id_number, :address, :doc_url, :status)'
);
$stmt->execute([
':full_name' => $fullName,
':email' => $email,
':phone' => $phone,
':id_number' => $idNumber,
':address' => $address,
':doc_url' => $docUrl,
':status' => 'pending',
]);
$successId = (int)db()->lastInsertId();
}
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html>
<html lang="<?= h($lang) ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= h(t('kyc_title')) ?> · <?= h(t('site_name')) ?></title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= h($projectDescription) ?>" />
<meta property="og:description" content="<?= h($projectDescription) ?>" />
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h((string) time()) ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light">
<div class="container">
<a class="navbar-brand" href="<?= h(url_with_lang('index.php')) ?>"><?= h(t('site_name')) ?></a>
<div class="ms-auto lang-toggle btn-group" role="group">
<a class="btn btn-sm <?= $lang === 'zh' ? 'active' : '' ?>" href="<?= h(url_with_lang('kyc.php', ['lang' => 'zh'])) ?>">中文</a>
<a class="btn btn-sm <?= $lang === 'en' ? 'active' : '' ?>" href="<?= h(url_with_lang('kyc.php', ['lang' => 'en'])) ?>">EN</a>
</div>
</div>
</nav>
<main class="container my-5">
<div class="section-card">
<h1 class="section-title mb-1"><?= h(t('kyc_title')) ?></h1>
<p class="text-muted mb-4"><?= h(t('kyc_subtitle')) ?></p>
<?php if ($successId): ?>
<div class="alert alert-success d-flex justify-content-between align-items-center flex-wrap gap-2">
<div>
<strong><?= h(t('alert_success')) ?></strong>
<div class="mt-1">ID: <span class="fw-semibold"><?= h((string) $successId) ?></span></div>
</div>
<div class="d-flex gap-2">
<a class="ghost-btn text-decoration-none" href="<?= h(url_with_lang('kyc_status.php', ['id' => $successId])) ?>"><?= h(t('cta_status')) ?></a>
<button class="cta-btn" data-copy="<?= h((string) $successId) ?>" data-default="Copy ID" data-copied="Copied">Copy ID</button>
</div>
</div>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-danger">
<?= h(implode(' ', $errors)) ?>
</div>
<?php endif; ?>
<form method="post" class="row g-3">
<div class="col-md-6">
<label class="form-label"><?= h(t('form_name')) ?></label>
<input type="text" name="full_name" class="form-control" required value="<?= h($_POST['full_name'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label"><?= h(t('form_email')) ?></label>
<input type="email" name="email" class="form-control" required value="<?= h($_POST['email'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label"><?= h(t('form_phone')) ?></label>
<input type="text" name="phone" class="form-control" required value="<?= h($_POST['phone'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label"><?= h(t('form_id')) ?></label>
<input type="text" name="id_number" class="form-control" required value="<?= h($_POST['id_number'] ?? '') ?>">
</div>
<div class="col-12">
<label class="form-label"><?= h(t('form_address')) ?></label>
<input type="text" name="address" class="form-control" required value="<?= h($_POST['address'] ?? '') ?>">
</div>
<div class="col-12">
<label class="form-label"><?= h(t('form_doc')) ?></label>
<input type="url" name="doc_url" class="form-control" required value="<?= h($_POST['doc_url'] ?? '') ?>">
</div>
<div class="col-12 d-flex flex-wrap justify-content-between align-items-center">
<small class="text-muted"><?= h(t('form_note')) ?></small>
<button type="submit" class="cta-btn"><?= h(t('form_submit')) ?></button>
</div>
</form>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= h((string) time()) ?>"></script>
</body>
</html>

95
kyc_status.php Normal file
View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/app.php';
ensure_kyc_table();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$record = null;
if ($id > 0) {
$stmt = db()->prepare('SELECT * FROM kyc_applications WHERE id = :id LIMIT 1');
$stmt->execute([':id' => $id]);
$record = $stmt->fetch();
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html>
<html lang="<?= h($lang) ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= h(t('status_title')) ?> · <?= h(t('site_name')) ?></title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= h($projectDescription) ?>" />
<meta property="og:description" content="<?= h($projectDescription) ?>" />
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h((string) time()) ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light">
<div class="container">
<a class="navbar-brand" href="<?= h(url_with_lang('index.php')) ?>"><?= h(t('site_name')) ?></a>
<div class="ms-auto lang-toggle btn-group" role="group">
<a class="btn btn-sm <?= $lang === 'zh' ? 'active' : '' ?>" href="<?= h(url_with_lang('kyc_status.php', ['lang' => 'zh', 'id' => $id ?: null])) ?>">中文</a>
<a class="btn btn-sm <?= $lang === 'en' ? 'active' : '' ?>" href="<?= h(url_with_lang('kyc_status.php', ['lang' => 'en', 'id' => $id ?: null])) ?>">EN</a>
</div>
</div>
</nav>
<main class="container my-5">
<div class="section-card">
<h1 class="section-title mb-1"><?= h(t('status_title')) ?></h1>
<p class="text-muted mb-4"><?= h(t('status_subtitle')) ?></p>
<form class="row g-3 mb-4" method="get">
<input type="hidden" name="lang" value="<?= h($lang) ?>">
<div class="col-md-6">
<label class="form-label"><?= h(t('status_label')) ?></label>
<input type="number" name="id" class="form-control" required value="<?= h($id ? (string) $id : '') ?>">
</div>
<div class="col-md-3 align-self-end">
<button type="submit" class="cta-btn"><?= h(t('status_btn')) ?></button>
</div>
</form>
<?php if ($id > 0 && !$record): ?>
<div class="alert alert-warning"><?= h(t('status_empty')) ?></div>
<?php elseif ($record): ?>
<?php
$status = $record['status'] ?? 'pending';
$statusLabel = match ($status) {
'approved' => t('status_approved'),
'rejected' => t('status_rejected'),
default => t('status_pending'),
};
$statusClass = match ($status) {
'approved' => 'status-approved',
'rejected' => 'status-rejected',
default => 'status-pending',
};
?>
<div class="d-flex flex-column flex-lg-row justify-content-between gap-3">
<div>
<div class="text-muted"><?= h(t('status_label')) ?>: <?= h((string) $record['id']) ?></div>
<div class="mt-2">Name: <?= h($record['full_name']) ?></div>
<div class="text-muted mt-1">Submitted: <?= h($record['created_at']) ?></div>
</div>
<div class="status-pill <?= h($statusClass) ?> align-self-start"><?= h($statusLabel) ?></div>
</div>
<?php endif; ?>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= h((string) time()) ?>"></script>
</body>
</html>