2026-04-01 14:12:17 +00:00
|
|
|
<?php
|
|
|
|
|
use Core\Services\Plugins;
|
|
|
|
|
|
|
|
|
|
$pageTitle = $title ?? 'Store Settings';
|
|
|
|
|
$settings = $settings ?? [];
|
2026-03-04 20:46:11 +00:00
|
|
|
$gateways = is_array($gateways ?? null) ? $gateways : [];
|
|
|
|
|
$error = (string)($error ?? '');
|
|
|
|
|
$saved = (string)($saved ?? '');
|
|
|
|
|
$tab = (string)($tab ?? 'general');
|
2026-04-01 14:12:17 +00:00
|
|
|
$tab = in_array($tab, ['general', 'payments', 'emails', 'discounts', 'bundles', 'sales_chart'], true) ? $tab : 'general';
|
2026-03-04 20:46:11 +00:00
|
|
|
$paypalTest = (string)($_GET['paypal_test'] ?? '');
|
|
|
|
|
$privateRootReady = (bool)($private_root_ready ?? false);
|
|
|
|
|
$discounts = is_array($discounts ?? null) ? $discounts : [];
|
2026-04-01 14:12:17 +00:00
|
|
|
$bundles = is_array($bundles ?? null) ? $bundles : [];
|
|
|
|
|
$bundleReleaseOptions = is_array($bundle_release_options ?? null) ? $bundle_release_options : [];
|
2026-03-04 20:46:11 +00:00
|
|
|
$chartRows = is_array($chart_rows ?? null) ? $chart_rows : [];
|
|
|
|
|
$chartLastRebuildAt = (string)($chart_last_rebuild_at ?? '');
|
|
|
|
|
$chartCronUrl = (string)($chart_cron_url ?? '');
|
|
|
|
|
$chartCronCmd = (string)($chart_cron_cmd ?? '');
|
2026-04-01 14:12:17 +00:00
|
|
|
$reportsEnabled = Plugins::isEnabled('advanced-reporting');
|
|
|
|
|
ob_start();
|
|
|
|
|
?>
|
2026-03-04 20:46:11 +00:00
|
|
|
<section class="admin-card">
|
|
|
|
|
<div class="badge">Store</div>
|
2026-04-01 14:12:17 +00:00
|
|
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
|
2026-03-04 20:46:11 +00:00
|
|
|
<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>
|
2026-04-01 14:12:17 +00:00
|
|
|
<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>
|
2026-03-04 20:46:11 +00:00
|
|
|
<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>
|
2026-04-01 14:12:17 +00:00
|
|
|
<a href="/admin/store/settings?tab=bundles" class="btn <?= $tab === 'bundles' ? '' : 'outline' ?> small">Bundles</a>
|
2026-03-04 20:46:11 +00:00
|
|
|
<a href="/admin/store/settings?tab=sales_chart" class="btn <?= $tab === 'sales_chart' ? '' : 'outline' ?> small">Sales Chart</a>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<?php if ($error !== ''): ?>
|
|
|
|
|
<div style="margin-top:12px; color:#f3b0b0; font-size:13px;"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<?php if ($saved !== ''): ?>
|
|
|
|
|
<div style="margin-top:12px; color:#9be7c6; font-size:13px;">Settings saved.</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<?php if ($paypalTest === 'live' || $paypalTest === 'sandbox'): ?>
|
|
|
|
|
<div style="margin-top:12px; color:#9be7c6; font-size:13px;">PayPal <?= htmlspecialchars($paypalTest, ENT_QUOTES, 'UTF-8') ?> credentials are valid.</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
|
|
<?php if ($tab === 'general'): ?>
|
|
|
|
|
<form method="post" action="/admin/store/settings" style="margin-top:16px; display:grid; gap:16px;">
|
|
|
|
|
<input type="hidden" name="tab" value="general">
|
|
|
|
|
<div class="admin-card" style="padding:16px;">
|
|
|
|
|
<div class="label">Currency</div>
|
|
|
|
|
<input class="input" name="store_currency" value="<?= htmlspecialchars((string)($settings['store_currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?>" placeholder="GBP">
|
|
|
|
|
|
|
|
|
|
<div class="label" style="margin-top:12px;">Private Download Root (outside public_html)</div>
|
|
|
|
|
<input class="input" name="store_private_root" value="<?= htmlspecialchars((string)($settings['store_private_root'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="/home/audiocore.site/private_downloads">
|
|
|
|
|
<div style="margin-top:8px; font-size:12px; color: <?= $privateRootReady ? '#9be7c6' : '#f3b0b0' ?>;">
|
|
|
|
|
<?= $privateRootReady ? 'Path is writable' : 'Path missing or not writable' ?>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:12px;">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label">Download Limit</div>
|
|
|
|
|
<input class="input" name="store_download_limit" value="<?= htmlspecialchars((string)($settings['store_download_limit'] ?? '5'), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label">Expiry Days</div>
|
|
|
|
|
<input class="input" name="store_download_expiry_days" value="<?= htmlspecialchars((string)($settings['store_download_expiry_days'] ?? '30'), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-05 15:27:58 +00:00
|
|
|
<div class="label" style="margin-top:12px;">Order Number Prefix</div>
|
|
|
|
|
<input class="input" name="store_order_prefix" value="<?= htmlspecialchars((string)($settings['store_order_prefix'] ?? 'AC-ORD'), ENT_QUOTES, 'UTF-8') ?>" placeholder="AC-ORD">
|
|
|
|
|
|
|
|
|
|
<div class="label" style="margin-top:12px;">Store Timezone</div>
|
|
|
|
|
<input class="input" name="store_timezone" list="store-timezone-options" value="<?= htmlspecialchars((string)($settings['store_timezone'] ?? 'UTC'), ENT_QUOTES, 'UTF-8') ?>" placeholder="UTC">
|
|
|
|
|
<datalist id="store-timezone-options">
|
|
|
|
|
<option value="UTC"></option>
|
|
|
|
|
<option value="Europe/London"></option>
|
|
|
|
|
<option value="Europe/Berlin"></option>
|
|
|
|
|
<option value="America/New_York"></option>
|
|
|
|
|
<option value="America/Chicago"></option>
|
|
|
|
|
<option value="America/Los_Angeles"></option>
|
|
|
|
|
<option value="Australia/Sydney"></option>
|
|
|
|
|
<option value="Asia/Tokyo"></option>
|
|
|
|
|
</datalist>
|
|
|
|
|
<div style="margin-top:8px; font-size:12px; color:var(--muted);">
|
|
|
|
|
Used for order numbers, store timestamps, and expiry calculations. Invalid values fall back to UTC.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-04 20:46:11 +00:00
|
|
|
|
|
|
|
|
<div style="display:flex; justify-content:flex-end;">
|
|
|
|
|
<button class="btn" type="submit">Save General Settings</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
2026-04-01 14:12:17 +00:00
|
|
|
<?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;">
|
2026-03-04 20:46:11 +00:00
|
|
|
<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;">
|
|
|
|
|
<input type="checkbox" name="store_test_mode" value="1" <?= ((string)($settings['store_test_mode'] ?? '1') === '1') ? 'checked' : '' ?>>
|
|
|
|
|
Test Mode (Sandbox)
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-01 14:12:17 +00:00
|
|
|
<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>
|
2026-03-04 20:46:11 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</form>
|
|
|
|
|
<?php elseif ($tab === 'emails'): ?>
|
|
|
|
|
<form method="post" action="/admin/store/settings" style="margin-top:16px; display:grid; gap:16px;">
|
|
|
|
|
<input type="hidden" name="tab" value="emails">
|
|
|
|
|
<div class="admin-card" style="padding:16px;">
|
|
|
|
|
<div class="label" style="margin-bottom:10px;">Order Email Template</div>
|
|
|
|
|
<div class="label">Email Logo URL</div>
|
|
|
|
|
<input class="input" name="store_email_logo_url" value="<?= htmlspecialchars((string)($settings['store_email_logo_url'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="https://example.com/logo.png">
|
|
|
|
|
|
|
|
|
|
<div class="label" style="margin-top:10px;">Subject</div>
|
|
|
|
|
<input class="input" name="store_order_email_subject" value="<?= htmlspecialchars((string)($settings['store_order_email_subject'] ?? 'Your AudioCore order {{order_no}}'), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
|
|
|
|
|
<div class="label" style="margin-top:10px;">HTML Body</div>
|
|
|
|
|
<textarea class="input" name="store_order_email_html" rows="10" style="resize:vertical; font-family:'IBM Plex Mono',monospace; font-size:12px;"><?= htmlspecialchars((string)($settings['store_order_email_html'] ?? ''), ENT_QUOTES, 'UTF-8') ?></textarea>
|
|
|
|
|
|
|
|
|
|
<div style="margin-top:8px; color:var(--muted); font-size:12px;">
|
|
|
|
|
Placeholders: <code>{{site_name}}</code>, <code>{{order_no}}</code>, <code>{{customer_email}}</code>, <code>{{currency}}</code>, <code>{{total}}</code>, <code>{{status}}</code>, <code>{{logo_url}}</code>, <code>{{logo_html}}</code>, <code>{{items_html}}</code>, <code>{{download_links_html}}</code>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style="margin-top:14px; display:grid; gap:10px; max-width:460px;">
|
|
|
|
|
<div class="label">Send Test Email To</div>
|
|
|
|
|
<input class="input" type="email" name="test_email_to" placeholder="you@example.com">
|
|
|
|
|
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
|
|
|
|
<button class="btn outline small" type="submit" formaction="/admin/store/settings/test-email" formmethod="post">Send Test Email</button>
|
|
|
|
|
<button class="btn" type="submit">Save Email Settings</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
<?php elseif ($tab === 'discounts'): ?>
|
|
|
|
|
<div class="admin-card" style="margin-top:16px; padding:16px;">
|
|
|
|
|
<div class="label" style="margin-bottom:10px;">Create Discount Code</div>
|
|
|
|
|
<form method="post" action="/admin/store/discounts/create" style="display:grid; gap:12px;">
|
|
|
|
|
<div style="display:grid; grid-template-columns:1.2fr 1fr 0.8fr 0.9fr 1.2fr; gap:10px;">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Code</div>
|
|
|
|
|
<input class="input" name="code" placeholder="SAVE10" maxlength="32" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Discount Type</div>
|
|
|
|
|
<select class="input" name="discount_type">
|
|
|
|
|
<option value="percent">Percent</option>
|
|
|
|
|
<option value="fixed">Fixed Amount</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Value</div>
|
|
|
|
|
<input class="input" name="discount_value" value="10" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Max Uses</div>
|
|
|
|
|
<input class="input" name="max_uses" value="0" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Expires At</div>
|
|
|
|
|
<input class="input" type="datetime-local" name="expires_at">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
|
|
|
|
<label style="display:flex; align-items:center; gap:6px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:.16em;">
|
|
|
|
|
<input type="checkbox" name="is_active" value="1" checked> Active
|
|
|
|
|
</label>
|
|
|
|
|
<button class="btn small" type="submit">Save Code</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
<div style="margin-top:8px; color:var(--muted); font-size:12px;">Max uses: <strong>0</strong> means unlimited. Leave expiry blank for no expiry.</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="admin-card" style="margin-top:12px; padding:14px;">
|
|
|
|
|
<div class="label" style="margin-bottom:10px;">Existing Codes</div>
|
|
|
|
|
<?php if (!$discounts): ?>
|
|
|
|
|
<div style="color:var(--muted); font-size:13px;">No discount codes 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;">Code</th>
|
|
|
|
|
<th style="padding:0 10px;">Type</th>
|
|
|
|
|
<th style="padding:0 10px;">Value</th>
|
|
|
|
|
<th style="padding:0 10px;">Usage</th>
|
|
|
|
|
<th style="padding:0 10px;">Expires</th>
|
|
|
|
|
<th style="padding:0 10px;">Status</th>
|
|
|
|
|
<th style="padding:0 10px; text-align:right;">Action</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php foreach ($discounts as $d): ?>
|
|
|
|
|
<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)($d['code'] ?? ''), 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)($d['discount_type'] ?? ''), 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;"><?= number_format((float)($d['discount_value'] ?? 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); font-size:12px; color:var(--muted);"><?= (int)($d['used_count'] ?? 0) ?>/<?= (int)($d['max_uses'] ?? 0) === 0 ? 'INF' : (int)($d['max_uses'] ?? 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; color:var(--muted);"><?= htmlspecialchars((string)($d['expires_at'] ?? 'No expiry'), 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);"><span class="pill"><?= (int)($d['is_active'] ?? 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/discounts/delete" onsubmit="return confirm('Delete this discount code?');" style="display:inline-flex;">
|
|
|
|
|
<input type="hidden" name="id" value="<?= (int)($d['id'] ?? 0) ?>">
|
|
|
|
|
<button class="btn outline small" type="submit" style="border-color:rgba(255,120,120,.45); color:#ffb9b9;">Delete</button>
|
2026-04-01 14:12:17 +00:00
|
|
|
</form>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</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>
|
2026-03-04 20:46:11 +00:00
|
|
|
</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">
|
|
|
|
|
<div class="admin-card" style="padding:16px; display:grid; gap:12px;">
|
|
|
|
|
<div class="label" style="margin-bottom:6px;">Sales Chart Defaults</div>
|
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:10px;">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Default Type</div>
|
|
|
|
|
<select class="input" name="store_sales_chart_default_scope">
|
|
|
|
|
<option value="tracks" <?= ((string)($settings['store_sales_chart_default_scope'] ?? 'tracks') === 'tracks') ? 'selected' : '' ?>>Tracks</option>
|
|
|
|
|
<option value="releases" <?= ((string)($settings['store_sales_chart_default_scope'] ?? 'tracks') === 'releases') ? 'selected' : '' ?>>Releases</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Default Window</div>
|
|
|
|
|
<select class="input" name="store_sales_chart_default_window">
|
|
|
|
|
<option value="latest" <?= ((string)($settings['store_sales_chart_default_window'] ?? 'latest') === 'latest') ? 'selected' : '' ?>>Latest (rolling)</option>
|
|
|
|
|
<option value="weekly" <?= ((string)($settings['store_sales_chart_default_window'] ?? 'latest') === 'weekly') ? 'selected' : '' ?>>Weekly</option>
|
|
|
|
|
<option value="all_time" <?= ((string)($settings['store_sales_chart_default_window'] ?? 'latest') === 'all_time') ? 'selected' : '' ?>>All time</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Default Limit</div>
|
|
|
|
|
<input class="input" name="store_sales_chart_limit" value="<?= htmlspecialchars((string)($settings['store_sales_chart_limit'] ?? '10'), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Latest Window Hours</div>
|
|
|
|
|
<input class="input" name="store_sales_chart_latest_hours" value="<?= htmlspecialchars((string)($settings['store_sales_chart_latest_hours'] ?? '24'), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Cron Refresh Minutes</div>
|
|
|
|
|
<input class="input" name="store_sales_chart_refresh_minutes" value="<?= htmlspecialchars((string)($settings['store_sales_chart_refresh_minutes'] ?? '180'), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Cron Key</div>
|
|
|
|
|
<div style="display:grid; grid-template-columns:1fr auto; gap:8px;">
|
|
|
|
|
<input class="input" value="<?= htmlspecialchars((string)($settings['store_sales_chart_cron_key'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" readonly>
|
|
|
|
|
<button class="btn outline small" type="submit" name="store_sales_chart_regen_key" value="1">Regenerate</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="label" style="font-size:10px;">Cron URL (dynamic)</div>
|
|
|
|
|
<input class="input" value="<?= htmlspecialchars($chartCronUrl, ENT_QUOTES, 'UTF-8') ?>" readonly>
|
|
|
|
|
<div class="label" style="font-size:10px;">Crontab Line</div>
|
|
|
|
|
<textarea class="input" rows="2" style="resize:vertical; font-family:'IBM Plex Mono',monospace; font-size:12px;" readonly><?= htmlspecialchars($chartCronCmd, ENT_QUOTES, 'UTF-8') ?></textarea>
|
|
|
|
|
<div style="display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end;">
|
|
|
|
|
<button class="btn outline small" type="submit" formaction="/admin/store/settings/rebuild-sales-chart" formmethod="post">Rebuild Now</button>
|
|
|
|
|
<button class="btn" type="submit">Save Sales Chart Settings</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<div class="admin-card" style="margin-top:12px; padding:14px;">
|
|
|
|
|
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:10px;">
|
|
|
|
|
<div class="label">Current Chart Snapshot</div>
|
|
|
|
|
<div style="font-size:12px; color:var(--muted);">
|
|
|
|
|
Last rebuild: <?= $chartLastRebuildAt !== '' ? htmlspecialchars($chartLastRebuildAt, ENT_QUOTES, 'UTF-8') : 'Never' ?>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php if (!$chartRows): ?>
|
|
|
|
|
<div style="color:var(--muted); font-size:13px;">No chart rows yet. Run rebuild once.</div>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<div style="overflow:auto;">
|
|
|
|
|
<table style="width:100%; border-collapse:separate; border-spacing:0 6px;">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr style="color:var(--muted); font-size:10px; letter-spacing:.14em; text-transform:uppercase; text-align:left;">
|
|
|
|
|
<th style="padding:0 10px;">#</th>
|
|
|
|
|
<th style="padding:0 10px;">Item</th>
|
|
|
|
|
<th style="padding:0 10px;">Units</th>
|
|
|
|
|
<th style="padding:0 10px;">Revenue</th>
|
|
|
|
|
<th style="padding:0 10px;">Window</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php foreach ($chartRows as $row): ?>
|
|
|
|
|
<tr style="background:rgba(255,255,255,.02);">
|
|
|
|
|
<td style="padding:9px 10px; border:1px solid rgba(255,255,255,.08); border-right:none; border-radius:10px 0 0 10px; font-family:'IBM Plex Mono',monospace; font-size:12px;"><?= (int)($row['rank_no'] ?? 0) ?></td>
|
|
|
|
|
<td style="padding:9px 10px; border-top:1px solid rgba(255,255,255,.08); border-bottom:1px solid rgba(255,255,255,.08);"><?= htmlspecialchars((string)($row['item_label'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td>
|
|
|
|
|
<td style="padding:9px 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;"><?= (int)($row['units'] ?? 0) ?></td>
|
|
|
|
|
<td style="padding:9px 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)($settings['store_currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?> <?= number_format((float)($row['revenue'] ?? 0), 2) ?></td>
|
|
|
|
|
<td style="padding:9px 10px; border:1px solid rgba(255,255,255,.08); border-left:none; border-radius:0 10px 10px 0; color:var(--muted); font-size:12px;">
|
|
|
|
|
<?= htmlspecialchars((string)($row['snapshot_from'] ?? 'all'), ENT_QUOTES, 'UTF-8') ?> -> <?= htmlspecialchars((string)($row['snapshot_to'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</section>
|
|
|
|
|
<?php
|
|
|
|
|
$content = ob_get_clean();
|
|
|
|
|
require __DIR__ . '/../../../../modules/admin/views/layout.php';
|