Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e20f0abb7 |
105
admin_kyc_list.php
Normal file
105
admin_kyc_list.php
Normal 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
125
admin_kyc_view.php
Normal 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
156
app.php
Normal 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"
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
BIN
assets/pasted-20260305-071949-1606bc87.png
Normal file
BIN
assets/pasted-20260305-071949-1606bc87.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
assets/pasted-20260305-072206-2e58b397.png
Normal file
BIN
assets/pasted-20260305-072206-2e58b397.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
assets/pasted-20260305-072413-d3649698.png
Normal file
BIN
assets/pasted-20260305-072413-d3649698.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
BIN
assets/pasted-20260305-075412-2d71d619.png
Normal file
BIN
assets/pasted-20260305-075412-2d71d619.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
58
dashboard.php
Normal file
58
dashboard.php
Normal 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
242
index.php
@ -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
134
kyc.php
Normal 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
95
kyc_status.php
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user