Release v1.5.1

This commit is contained in:
AudioCore Bot
2026-04-01 14:12:17 +00:00
parent dc53051358
commit 9deabe1ec9
50 changed files with 10775 additions and 5637 deletions

View File

@@ -1,8 +1,11 @@
<?php
use Core\Services\Plugins;
$pageTitle = $title ?? 'Store Customers';
$customers = $customers ?? [];
$currency = (string)($currency ?? 'GBP');
$q = (string)($q ?? '');
$reportsEnabled = Plugins::isEnabled('advanced-reporting');
ob_start();
?>
<section class="admin-card customers-page">
@@ -20,6 +23,9 @@ ob_start();
<a href="/admin/store/settings" class="btn outline small">Settings</a>
<a href="/admin/store/orders" class="btn outline small">Orders</a>
<a href="/admin/store/customers" class="btn outline small">Customers</a>
<?php if ($reportsEnabled): ?>
<a href="/admin/store/reports" class="btn outline small">Sales Reports</a>
<?php endif; ?>
</div>
<form method="get" action="/admin/store/customers" class="customers-search">
@@ -39,7 +45,9 @@ ob_start();
<tr>
<th>Customer</th>
<th>Orders</th>
<th>Revenue</th>
<th>Before Fees</th>
<th>Fees</th>
<th>After Fees</th>
<th>Latest Order</th>
<th>Last Seen</th>
</tr>
@@ -70,7 +78,9 @@ ob_start();
<?php endif; ?>
</td>
<td class="num"><?= (int)($customer['order_count'] ?? 0) ?></td>
<td class="num"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($customer['revenue'] ?? 0), 2) ?></td>
<td class="num"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($customer['before_fees'] ?? 0), 2) ?></td>
<td class="num"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($customer['paypal_fees'] ?? 0), 2) ?></td>
<td class="num"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($customer['after_fees'] ?? 0), 2) ?></td>
<td>
<?php if ($lastOrderId > 0): ?>
<a href="/admin/store/order?id=<?= $lastOrderId ?>" class="order-link">
@@ -206,8 +216,10 @@ ob_start();
@media (max-width: 980px) {
.customers-table th:nth-child(3),
.customers-table td:nth-child(3),
.customers-table th:nth-child(5),
.customers-table td:nth-child(5) { display:none; }
.customers-table th:nth-child(4),
.customers-table td:nth-child(4),
.customers-table th:nth-child(7),
.customers-table td:nth-child(7) { display:none; }
}
@media (max-width: 700px) {

View File

@@ -1,4 +1,6 @@
<?php
use Core\Services\Plugins;
$pageTitle = $title ?? 'Store';
$tablesReady = (bool)($tables_ready ?? false);
$privateRoot = (string)($private_root ?? '');
@@ -9,8 +11,11 @@ $newCustomers = is_array($new_customers ?? null) ? $new_customers : [];
$currency = (string)($currency ?? 'GBP');
$totalOrders = (int)($stats['total_orders'] ?? 0);
$paidOrders = (int)($stats['paid_orders'] ?? 0);
$totalRevenue = (float)($stats['total_revenue'] ?? 0);
$beforeFees = (float)($stats['before_fees'] ?? 0);
$paypalFees = (float)($stats['paypal_fees'] ?? 0);
$afterFees = (float)($stats['after_fees'] ?? 0);
$totalCustomers = (int)($stats['total_customers'] ?? 0);
$reportsEnabled = Plugins::isEnabled('advanced-reporting');
ob_start();
?>
<section class="admin-card">
@@ -24,6 +29,9 @@ ob_start();
<a href="/admin/store/settings" class="btn outline">Settings</a>
<a href="/admin/store/orders" class="btn outline">Orders</a>
<a href="/admin/store/customers" class="btn outline">Customers</a>
<?php if ($reportsEnabled): ?>
<a href="/admin/store/reports" class="btn outline">Sales Reports</a>
<?php endif; ?>
</div>
</div>
<div style="display:flex; flex-wrap:wrap; gap:8px; margin-top:12px;">
@@ -31,6 +39,9 @@ ob_start();
<a href="/admin/store/settings" class="btn outline small">Settings</a>
<a href="/admin/store/orders" class="btn outline small">Orders</a>
<a href="/admin/store/customers" class="btn outline small">Customers</a>
<?php if ($reportsEnabled): ?>
<a href="/admin/store/reports" class="btn outline small">Sales Reports</a>
<?php endif; ?>
</div>
<?php if (!$tablesReady): ?>
@@ -62,8 +73,14 @@ ob_start();
<div style="font-size:12px; color:var(--muted); margin-top:4px;">Paid: <?= $paidOrders ?></div>
</div>
<div class="admin-card" style="padding:14px;">
<div class="label">Revenue</div>
<div style="font-size:26px; font-weight:700; margin-top:8px;"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($totalRevenue, 2) ?></div>
<div class="label">Before Fees</div>
<div style="font-size:26px; font-weight:700; margin-top:8px;"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($beforeFees, 2) ?></div>
<div style="font-size:12px; color:var(--muted); margin-top:4px;">Gross paid sales</div>
</div>
<div class="admin-card" style="padding:14px;">
<div class="label">After Fees</div>
<div style="font-size:26px; font-weight:700; margin-top:8px;"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($afterFees, 2) ?></div>
<div style="font-size:12px; color:var(--muted); margin-top:4px;">PayPal fees: <?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($paypalFees, 2) ?></div>
</div>
<div class="admin-card" style="padding:14px;">
<div class="label">Total Customers</div>
@@ -86,7 +103,11 @@ ob_start();
</div>
<div style="display:flex; justify-content:space-between; gap:10px; color:var(--muted); font-size:12px; margin-top:4px;">
<span><?= htmlspecialchars((string)($order['email'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
<span><?= htmlspecialchars((string)($order['currency'] ?? $currency), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['total'] ?? 0), 2) ?></span>
<span><?= htmlspecialchars((string)($order['currency'] ?? $currency), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_net'] ?? $order['total'] ?? 0), 2) ?></span>
</div>
<div style="display:flex; justify-content:space-between; gap:10px; color:var(--muted); font-size:12px; margin-top:4px;">
<span>Before fees <?= htmlspecialchars((string)($order['currency'] ?? $currency), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_gross'] ?? $order['total'] ?? 0), 2) ?></span>
<span>Fees <?= htmlspecialchars((string)($order['currency'] ?? $currency), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_fee'] ?? 0), 2) ?></span>
</div>
<div style="margin-top:8px;">
<a href="/admin/store/order?id=<?= (int)($order['id'] ?? 0) ?>" class="btn outline small">View Order</a>

View File

@@ -1,9 +1,12 @@
<?php
use Core\Services\Plugins;
$pageTitle = $title ?? 'Order Detail';
$order = is_array($order ?? null) ? $order : [];
$items = is_array($items ?? null) ? $items : [];
$downloadsByItem = is_array($downloads_by_item ?? null) ? $downloads_by_item : [];
$downloadEvents = is_array($download_events ?? null) ? $download_events : [];
$reportsEnabled = Plugins::isEnabled('advanced-reporting');
ob_start();
?>
<section class="admin-card">
@@ -20,33 +23,45 @@ ob_start();
<a href="/admin/store/settings" class="btn outline small">Settings</a>
<a href="/admin/store/orders" class="btn outline small">Orders</a>
<a href="/admin/store/customers" class="btn outline small">Customers</a>
<?php if ($reportsEnabled): ?>
<a href="/admin/store/reports" class="btn outline small">Sales Reports</a>
<?php endif; ?>
</div>
<div class="admin-card" style="padding:14px; margin-top:16px;">
<div style="display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px;">
<div>
<div class="admin-card order-summary-card" style="margin-top:16px;">
<div class="order-summary-top">
<div class="order-summary-identity">
<div class="label">Order Number</div>
<div style="font-weight:700; margin-top:6px;"><?= htmlspecialchars((string)($order['order_no'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
<div class="order-summary-no"><?= htmlspecialchars((string)($order['order_no'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
<div class="order-summary-meta">Customer <?= htmlspecialchars((string)($order['email'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
</div>
<div>
<div class="label">Status</div>
<div style="font-weight:700; margin-top:6px;"><?= htmlspecialchars((string)($order['status'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
<div class="order-summary-total-cluster">
<div class="order-summary-top-stat">
<div class="label">Status</div>
<div class="pill"><?= htmlspecialchars((string)($order['status'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
</div>
<div class="order-summary-top-stat">
<div class="label">After Fees</div>
<div class="order-summary-total-value"><?= htmlspecialchars((string)($order['payment_currency'] ?? $order['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_net'] ?? $order['total'] ?? 0), 2) ?></div>
</div>
</div>
<div>
<div class="label">Total</div>
<div style="font-weight:700; margin-top:6px;"><?= htmlspecialchars((string)($order['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['total'] ?? 0), 2) ?></div>
</div>
<div class="order-summary-grid">
<div class="order-stat">
<div class="label">Before Fees</div>
<div class="order-stat-value"><?= htmlspecialchars((string)($order['payment_currency'] ?? $order['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_gross'] ?? $order['total'] ?? 0), 2) ?></div>
</div>
<div>
<div class="label">Customer Email</div>
<div style="margin-top:6px;"><?= htmlspecialchars((string)($order['email'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
<div class="order-stat">
<div class="label">PayPal Fees</div>
<div class="order-stat-value"><?= htmlspecialchars((string)($order['payment_currency'] ?? $order['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_fee'] ?? 0), 2) ?></div>
</div>
<div>
<div class="order-stat">
<div class="label">Order IP</div>
<div style="margin-top:6px;"><?= htmlspecialchars((string)($order['customer_ip'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
<div class="order-stat-value order-stat-text"><?= htmlspecialchars((string)($order['customer_ip'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
</div>
<div>
<div class="order-stat">
<div class="label">Created</div>
<div style="margin-top:6px;"><?= htmlspecialchars((string)($order['created_at'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
<div class="order-stat-value order-stat-text"><?= htmlspecialchars((string)($order['created_at'] ?? '-'), ENT_QUOTES, 'UTF-8') ?></div>
</div>
</div>
</div>
@@ -118,6 +133,108 @@ ob_start();
<?php endif; ?>
</div>
</section>
<style>
.order-summary-card {
padding: 18px;
display: grid;
gap: 18px;
}
.order-summary-top {
display: grid;
grid-template-columns: minmax(0, 1.6fr) auto;
gap: 20px;
align-items: start;
padding-bottom: 18px;
border-bottom: 1px solid rgba(255,255,255,.08);
}
.order-summary-identity {
min-width: 0;
}
.order-summary-no {
margin-top: 8px;
font-size: 18px;
font-weight: 700;
line-height: 1.2;
font-family: 'IBM Plex Mono', monospace;
letter-spacing: .02em;
color: #f3f6ff;
word-break: break-all;
}
.order-summary-meta {
margin-top: 10px;
color: var(--muted);
font-size: 13px;
line-height: 1.4;
word-break: break-word;
}
.order-summary-total-cluster {
display: grid;
gap: 10px;
min-width: 220px;
}
.order-summary-top-stat {
display: grid;
gap: 6px;
justify-items: end;
text-align: right;
padding: 12px 14px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,.08);
background: rgba(255,255,255,.025);
}
.order-summary-total-value {
font-size: 28px;
font-weight: 700;
line-height: 1;
white-space: nowrap;
}
.order-summary-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.order-stat {
padding: 14px 16px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,.08);
background: rgba(255,255,255,.025);
min-width: 0;
}
.order-stat-value {
margin-top: 8px;
font-size: 22px;
font-weight: 700;
line-height: 1.1;
}
.order-stat-text {
font-size: 16px;
font-weight: 600;
color: #eef2ff;
word-break: break-word;
}
@media (max-width: 900px) {
.order-summary-top {
grid-template-columns: 1fr;
}
.order-summary-total-cluster {
grid-template-columns: 1fr 1fr;
min-width: 0;
}
.order-summary-top-stat {
justify-items: start;
text-align: left;
}
.order-summary-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.order-summary-total-cluster,
.order-summary-grid {
grid-template-columns: 1fr;
}
}
</style>
<?php
$content = ob_get_clean();
require __DIR__ . '/../../../../modules/admin/views/layout.php';

View File

@@ -1,9 +1,12 @@
<?php
use Core\Services\Plugins;
$pageTitle = $title ?? 'Store Orders';
$orders = is_array($orders ?? null) ? $orders : [];
$q = (string)($q ?? '');
$saved = (string)($saved ?? '');
$error = (string)($error ?? '');
$reportsEnabled = Plugins::isEnabled('advanced-reporting');
ob_start();
?>
<section class="admin-card store-orders">
@@ -21,6 +24,9 @@ ob_start();
<a href="/admin/store/settings" class="btn outline small">Settings</a>
<a href="/admin/store/orders" class="btn small">Orders</a>
<a href="/admin/store/customers" class="btn outline small">Customers</a>
<?php if ($reportsEnabled): ?>
<a href="/admin/store/reports" class="btn outline small">Sales Reports</a>
<?php endif; ?>
</div>
<?php if ($saved !== ''): ?>
@@ -78,7 +84,11 @@ ob_start();
<div class="store-order-amount">
<?= htmlspecialchars((string)($order['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?>
<?= number_format((float)($order['total'] ?? 0), 2) ?>
<?= number_format((float)($order['payment_net'] ?? $order['total'] ?? 0), 2) ?>
<div style="margin-top:6px; color:var(--muted); font-size:12px; font-weight:500;">
Before fees <?= htmlspecialchars((string)($order['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_gross'] ?? $order['total'] ?? 0), 2) ?>
· Fees <?= htmlspecialchars((string)($order['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($order['payment_fee'] ?? 0), 2) ?>
</div>
</div>
<div class="store-order-status pill"><?= htmlspecialchars($status, ENT_QUOTES, 'UTF-8') ?></div>

View File

@@ -1,35 +1,51 @@
<?php
$pageTitle = $title ?? 'Store Settings';
$settings = $settings ?? [];
<?php
use Core\Services\Plugins;
$pageTitle = $title ?? 'Store Settings';
$settings = $settings ?? [];
$gateways = is_array($gateways ?? null) ? $gateways : [];
$error = (string)($error ?? '');
$saved = (string)($saved ?? '');
$tab = (string)($tab ?? 'general');
$tab = in_array($tab, ['general', 'payments', 'emails', 'discounts', 'sales_chart'], true) ? $tab : 'general';
$tab = in_array($tab, ['general', 'payments', 'emails', 'discounts', 'bundles', 'sales_chart'], true) ? $tab : 'general';
$paypalTest = (string)($_GET['paypal_test'] ?? '');
$privateRootReady = (bool)($private_root_ready ?? false);
$discounts = is_array($discounts ?? null) ? $discounts : [];
$bundles = is_array($bundles ?? null) ? $bundles : [];
$bundleReleaseOptions = is_array($bundle_release_options ?? null) ? $bundle_release_options : [];
$chartRows = is_array($chart_rows ?? null) ? $chart_rows : [];
$chartLastRebuildAt = (string)($chart_last_rebuild_at ?? '');
$chartCronUrl = (string)($chart_cron_url ?? '');
$chartCronCmd = (string)($chart_cron_cmd ?? '');
ob_start();
?>
$reportsEnabled = Plugins::isEnabled('advanced-reporting');
ob_start();
?>
<section class="admin-card">
<div class="badge">Store</div>
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
<div>
<h1 style="font-size:28px; margin:0;">Store Settings</h1>
<p style="color: var(--muted); margin-top:6px;">Configure defaults, payments, and transactional emails.</p>
</div>
<a href="/admin/store" class="btn outline">Back</a>
</div>
<div style="display:flex; flex-wrap:wrap; gap:8px; margin-top:12px;">
<a href="/admin/store/settings?tab=general" class="btn <?= $tab === 'general' ? '' : 'outline' ?> small">General</a>
<a href="/admin/store" class="btn outline">Back</a>
</div>
<div style="display:flex; flex-wrap:wrap; gap:8px; margin-top:12px;">
<a href="/admin/store" class="btn outline small">Overview</a>
<a href="/admin/store/settings" class="btn small">Settings</a>
<a href="/admin/store/orders" class="btn outline small">Orders</a>
<a href="/admin/store/customers" class="btn outline small">Customers</a>
<?php if ($reportsEnabled): ?>
<a href="/admin/store/reports" class="btn outline small">Sales Reports</a>
<?php endif; ?>
</div>
<div style="display:flex; flex-wrap:wrap; gap:8px; margin-top:12px;">
<a href="/admin/store/settings?tab=general" class="btn <?= $tab === 'general' ? '' : 'outline' ?> small">General</a>
<a href="/admin/store/settings?tab=payments" class="btn <?= $tab === 'payments' ? '' : 'outline' ?> small">Payments</a>
<a href="/admin/store/settings?tab=emails" class="btn <?= $tab === 'emails' ? '' : 'outline' ?> small">Emails</a>
<a href="/admin/store/settings?tab=discounts" class="btn <?= $tab === 'discounts' ? '' : 'outline' ?> small">Discounts</a>
<a href="/admin/store/settings?tab=bundles" class="btn <?= $tab === 'bundles' ? '' : 'outline' ?> small">Bundles</a>
<a href="/admin/store/settings?tab=sales_chart" class="btn <?= $tab === 'sales_chart' ? '' : 'outline' ?> small">Sales Chart</a>
</div>
@@ -91,10 +107,10 @@ ob_start();
<button class="btn" type="submit">Save General Settings</button>
</div>
</form>
<?php elseif ($tab === 'payments'): ?>
<form method="post" action="/admin/store/settings" style="margin-top:16px; display:grid; gap:16px;">
<input type="hidden" name="tab" value="payments">
<div class="admin-card" style="padding:16px;">
<?php elseif ($tab === 'payments'): ?>
<form method="post" action="/admin/store/settings" style="margin-top:16px; display:grid; gap:16px;">
<input type="hidden" name="tab" value="payments">
<div class="admin-card" style="padding:16px;">
<div class="label" style="margin-bottom:10px;">Payment Mode</div>
<input type="hidden" name="store_test_mode" value="0">
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
@@ -103,22 +119,78 @@ ob_start();
</label>
</div>
<div class="admin-card" style="padding:16px;">
<div class="label" style="margin-bottom:10px;">PayPal</div>
<input type="hidden" name="store_paypal_enabled" value="0">
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
<input type="checkbox" name="store_paypal_enabled" value="1" <?= ((string)($settings['store_paypal_enabled'] ?? '0') === '1') ? 'checked' : '' ?>>
Enable PayPal
</label>
<div class="label" style="margin-top:10px;">PayPal Client ID</div>
<input class="input" name="store_paypal_client_id" value="<?= htmlspecialchars((string)($settings['store_paypal_client_id'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<div class="label" style="margin-top:10px;">PayPal Secret</div>
<input class="input" name="store_paypal_secret" value="<?= htmlspecialchars((string)($settings['store_paypal_secret'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<div style="margin-top:12px; display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn outline small" type="submit" name="paypal_probe_mode" value="live" formaction="/admin/store/settings/test-paypal" formmethod="post">Test PayPal Live</button>
<button class="btn outline small" type="submit" name="paypal_probe_mode" value="sandbox" formaction="/admin/store/settings/test-paypal" formmethod="post">Test PayPal Sandbox</button>
<button class="btn" type="submit">Save Payment Settings</button>
<div class="admin-card" style="padding:16px;">
<div class="label" style="margin-bottom:10px;">PayPal</div>
<input type="hidden" name="store_paypal_enabled" value="0">
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
<input type="checkbox" name="store_paypal_enabled" value="1" <?= ((string)($settings['store_paypal_enabled'] ?? '0') === '1') ? 'checked' : '' ?>>
Enable PayPal
</label>
<div class="label" style="margin-top:10px;">PayPal Client ID</div>
<input class="input" name="store_paypal_client_id" value="<?= htmlspecialchars((string)($settings['store_paypal_client_id'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<div class="label" style="margin-top:10px;">PayPal Secret</div>
<input class="input" name="store_paypal_secret" value="<?= htmlspecialchars((string)($settings['store_paypal_secret'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<div style="display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; margin-top:12px;">
<div>
<div class="label">Merchant Country</div>
<input class="input" name="store_paypal_merchant_country" value="<?= htmlspecialchars((string)($settings['store_paypal_merchant_country'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="GB">
</div>
<div>
<div class="label">Card Button Label</div>
<input class="input" name="store_paypal_card_branding_text" value="<?= htmlspecialchars((string)($settings['store_paypal_card_branding_text'] ?? 'Pay with card'), ENT_QUOTES, 'UTF-8') ?>" placeholder="Pay with card">
</div>
</div>
<div style="display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; margin-top:12px;">
<div>
<div class="label">Card Checkout Mode</div>
<select class="input" name="store_paypal_sdk_mode">
<?php $sdkMode = (string)($settings['store_paypal_sdk_mode'] ?? 'embedded_fields'); ?>
<option value="embedded_fields" <?= $sdkMode === 'embedded_fields' ? 'selected' : '' ?>>Embedded card fields</option>
<option value="paypal_only_fallback" <?= $sdkMode === 'paypal_only_fallback' ? 'selected' : '' ?>>PayPal-only fallback</option>
</select>
</div>
<div style="display:flex; align-items:end;">
<div style="padding:12px 14px; border:1px solid rgba(255,255,255,.08); border-radius:12px; background:rgba(255,255,255,.03); width:100%;">
<input type="hidden" name="store_paypal_cards_enabled" value="0">
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
<input type="checkbox" name="store_paypal_cards_enabled" value="1" <?= ((string)($settings['store_paypal_cards_enabled'] ?? '0') === '1') ? 'checked' : '' ?>>
Enable Credit / Debit Card Checkout
</label>
</div>
</div>
</div>
<?php
$capabilityStatus = (string)($settings['store_paypal_cards_capability_status'] ?? 'unknown');
$capabilityMessage = (string)($settings['store_paypal_cards_capability_message'] ?? 'Run a PayPal credentials test to check card-field support.');
$capabilityCheckedAt = (string)($settings['store_paypal_cards_capability_checked_at'] ?? '');
$capabilityMode = (string)($settings['store_paypal_cards_capability_mode'] ?? '');
$capabilityColor = '#c7cfdf';
if ($capabilityStatus === 'available') {
$capabilityColor = '#9be7c6';
} elseif ($capabilityStatus === 'unavailable') {
$capabilityColor = '#f3b0b0';
}
?>
<div style="margin-top:12px; padding:14px; border-radius:12px; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.03); display:grid; gap:6px;">
<div class="label" style="font-size:10px;">Card Capability</div>
<div style="font-weight:700; color:<?= htmlspecialchars($capabilityColor, ENT_QUOTES, 'UTF-8') ?>; text-transform:uppercase; letter-spacing:.12em;">
<?= htmlspecialchars($capabilityStatus !== '' ? $capabilityStatus : 'unknown', ENT_QUOTES, 'UTF-8') ?>
</div>
<div style="font-size:13px; color:var(--muted);"><?= htmlspecialchars($capabilityMessage, ENT_QUOTES, 'UTF-8') ?></div>
<?php if ($capabilityCheckedAt !== ''): ?>
<div style="font-size:12px; color:var(--muted);">
Last checked: <?= htmlspecialchars($capabilityCheckedAt, ENT_QUOTES, 'UTF-8') ?><?= $capabilityMode !== '' ? ' (' . htmlspecialchars($capabilityMode, ENT_QUOTES, 'UTF-8') . ')' : '' ?>
</div>
<?php endif; ?>
</div>
<div style="margin-top:12px; display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn outline small" type="submit" name="paypal_probe_mode" value="live" formaction="/admin/store/settings/test-paypal" formmethod="post">Test PayPal Live</button>
<button class="btn outline small" type="submit" name="paypal_probe_mode" value="sandbox" formaction="/admin/store/settings/test-paypal" formmethod="post">Test PayPal Sandbox</button>
<button class="btn" type="submit">Save Payment Settings</button>
</div>
</div>
@@ -230,6 +302,89 @@ ob_start();
</div>
<?php endif; ?>
</div>
<?php elseif ($tab === 'bundles'): ?>
<div class="admin-card" style="margin-top:16px; padding:16px;">
<div class="label" style="margin-bottom:10px;">Create Bundle</div>
<form method="post" action="/admin/store/bundles/create" style="display:grid; gap:12px;">
<div style="display:grid; grid-template-columns:1.3fr .8fr .7fr .6fr; gap:10px;">
<div>
<div class="label" style="font-size:10px;">Bundle Name</div>
<input class="input" name="name" placeholder="Hard Dance Essentials" required>
</div>
<div>
<div class="label" style="font-size:10px;">Slug (optional)</div>
<input class="input" name="slug" placeholder="hard-dance-essentials">
</div>
<div>
<div class="label" style="font-size:10px;">Bundle Price</div>
<input class="input" name="bundle_price" value="9.99" required>
</div>
<div>
<div class="label" style="font-size:10px;">Currency</div>
<input class="input" name="currency" value="<?= htmlspecialchars((string)($settings['store_currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?>" maxlength="3">
</div>
</div>
<div style="display:grid; grid-template-columns:1fr auto; gap:10px; align-items:end;">
<div>
<div class="label" style="font-size:10px;">Button Label (optional)</div>
<input class="input" name="purchase_label" placeholder="Buy Discography">
</div>
<label style="display:flex; align-items:center; gap:6px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:.16em; padding-bottom:6px;">
<input type="checkbox" name="is_enabled" value="1" checked> Active
</label>
</div>
<div>
<div class="label" style="font-size:10px;">Releases in Bundle (Ctrl/Cmd-click for multi-select)</div>
<select class="input" name="release_ids[]" multiple size="8" required style="height:auto;">
<?php foreach ($bundleReleaseOptions as $opt): ?>
<option value="<?= (int)($opt['id'] ?? 0) ?>"><?= htmlspecialchars((string)($opt['label'] ?? ''), ENT_QUOTES, 'UTF-8') ?></option>
<?php endforeach; ?>
</select>
</div>
<div style="display:flex; justify-content:flex-end;">
<button class="btn small" type="submit">Save Bundle</button>
</div>
</form>
</div>
<div class="admin-card" style="margin-top:12px; padding:14px;">
<div class="label" style="margin-bottom:10px;">Existing Bundles</div>
<?php if (!$bundles): ?>
<div style="color:var(--muted); font-size:13px;">No bundles yet.</div>
<?php else: ?>
<div style="overflow:auto;">
<table style="width:100%; border-collapse:separate; border-spacing:0 8px;">
<thead>
<tr style="color:var(--muted); font-size:10px; letter-spacing:.14em; text-transform:uppercase; text-align:left;">
<th style="padding:0 10px;">Bundle</th>
<th style="padding:0 10px;">Slug</th>
<th style="padding:0 10px;">Releases</th>
<th style="padding:0 10px;">Price</th>
<th style="padding:0 10px;">Status</th>
<th style="padding:0 10px; text-align:right;">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundles as $b): ?>
<tr style="background:rgba(255,255,255,.02);">
<td style="padding:10px; border:1px solid rgba(255,255,255,.08); border-right:none; border-radius:10px 0 0 10px; font-weight:700;"><?= htmlspecialchars((string)($b['name'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td>
<td style="padding:10px; border-top:1px solid rgba(255,255,255,.08); border-bottom:1px solid rgba(255,255,255,.08); color:var(--muted); font-size:12px;"><?= htmlspecialchars((string)($b['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td>
<td style="padding:10px; border-top:1px solid rgba(255,255,255,.08); border-bottom:1px solid rgba(255,255,255,.08); font-size:12px; color:var(--muted);"><?= (int)($b['release_count'] ?? 0) ?></td>
<td style="padding:10px; border-top:1px solid rgba(255,255,255,.08); border-bottom:1px solid rgba(255,255,255,.08); font-size:12px;"><?= htmlspecialchars((string)($b['currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($b['bundle_price'] ?? 0), 2) ?></td>
<td style="padding:10px; border-top:1px solid rgba(255,255,255,.08); border-bottom:1px solid rgba(255,255,255,.08);"><span class="pill"><?= (int)($b['is_enabled'] ?? 0) === 1 ? 'active' : 'off' ?></span></td>
<td style="padding:10px; border:1px solid rgba(255,255,255,.08); border-left:none; border-radius:0 10px 10px 0; text-align:right;">
<form method="post" action="/admin/store/bundles/delete" onsubmit="return confirm('Delete this bundle?');" style="display:inline-flex;">
<input type="hidden" name="id" value="<?= (int)($b['id'] ?? 0) ?>">
<button class="btn outline small" type="submit" style="border-color:rgba(255,120,120,.45); color:#ffb9b9;">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<?php else: ?>
<form method="post" action="/admin/store/settings" style="margin-top:16px; display:grid; gap:16px;">
<input type="hidden" name="tab" value="sales_chart">