v1.0.0.1
StocketBase
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/includes/config.php';
- require_once __DIR__ . '/includes/database.php';
- require_once __DIR__ . '/includes/auth.php';
- require_once __DIR__ . '/includes/csrf.php';
- require_once __DIR__ . '/includes/avatar.php';
- require_once __DIR__ . '/includes/activity-log.php';
- require_once __DIR__ . '/includes/two-factor.php';
-
- Auth::boot();
- Auth::requireRoleAtLeast(Auth::ROLE_STORE_OWNER);
-
- $currentUser = Auth::user();
- $currentRole = Auth::role();
- $isSuperAdmin = $currentRole === Auth::ROLE_SUPER_ADMIN;
-
- $assignableRoles = $isSuperAdmin
- ? [
- Auth::ROLE_SUPER_ADMIN,
- Auth::ROLE_STORE_OWNER,
- Auth::ROLE_STORE_MANAGER,
- Auth::ROLE_CATALOG_EDITOR,
- Auth::ROLE_PRODUCT_EDITOR,
- Auth::ROLE_CATALOG_ASSISTANT,
- ]
- : [
- Auth::ROLE_STORE_MANAGER,
- Auth::ROLE_CATALOG_EDITOR,
- Auth::ROLE_PRODUCT_EDITOR,
- Auth::ROLE_CATALOG_ASSISTANT,
- ];
-
- $flashMessage = null;
- $flashType = 'success';
- $db = Database::site();
- $usersDb = Database::users();
- $activeTab = ($_GET['tab'] ?? 'team') === 'customers' ? 'customers' : 'team';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!Csrf::verify($_POST['csrf_token'] ?? null)) {
- $flashMessage = 'Your session expired. Please try again.';
- $flashType = 'error';
- } else {
- $action = $_POST['action'] ?? '';
-
- if ($action === 'suspend_customer') {
- if (!$isSuperAdmin) {
- $flashMessage = 'Only the Super Admin can block customers.';
- $flashType = 'error';
- } else {
- $customerId = (int) ($_POST['customer_id'] ?? 0);
- $update = $usersDb->prepare("UPDATE user_accounts SET status = 'suspended' WHERE id = :id");
- $update->execute(['id' => $customerId]);
- $flashMessage = 'Customer blocked. They can no longer log in.';
- }
- $activeTab = 'customers';
- }
-
- if ($action === 'reactivate_customer') {
- if (!$isSuperAdmin) {
- $flashMessage = 'Only the Super Admin can unblock customers.';
- $flashType = 'error';
- } else {
- $customerId = (int) ($_POST['customer_id'] ?? 0);
- $update = $usersDb->prepare("UPDATE user_accounts SET status = 'active' WHERE id = :id");
- $update->execute(['id' => $customerId]);
- $flashMessage = 'Customer unblocked.';
- }
- $activeTab = 'customers';
- }
-
- if ($action === 'create_account') {
- $username = trim((string) ($_POST['username'] ?? ''));
- $email = trim((string) ($_POST['email'] ?? ''));
- $displayName = trim((string) ($_POST['display_name'] ?? ''));
- $password = (string) ($_POST['password'] ?? '');
- $role = (string) ($_POST['role'] ?? '');
-
- if ($username === '' || $email === '' || $password === '' || !in_array($role, $assignableRoles, true)) {
- $flashMessage = 'Please fill in every field and pick a role you are allowed to give.';
- $flashType = 'error';
- } elseif ($role === Auth::ROLE_SUPER_ADMIN) {
- $flashMessage = 'There can only be one Super Admin.';
- $flashType = 'error';
- } elseif (strlen($password) < 10) {
- $flashMessage = 'The password needs at least 10 characters.';
- $flashType = 'error';
- } else {
- $newAvatar = null;
- try {
- if (Avatar::hasUpload($_FILES['avatar'] ?? null)) {
- [$newAvatar, $avatarError] = Avatar::store($_FILES['avatar']);
- if ($avatarError !== null) {
- throw new InvalidArgumentException($avatarError);
- }
- }
-
- $insert = $db->prepare(
- 'INSERT INTO team_accounts (username, email, password_hash, display_name, role, status, avatar_path) VALUES (:username, :email, :hash, :display_name, :role, :status, :avatar)'
- );
- $insert->execute([
- 'username' => $username,
- 'email' => $email,
- 'hash' => password_hash($password, PASSWORD_DEFAULT),
- 'display_name' => $displayName !== '' ? $displayName : $username,
- 'role' => $role,
- 'status' => 'active',
- 'avatar' => $newAvatar,
- ]);
- $flashMessage = 'Account created. They can log in now.';
- } catch (InvalidArgumentException $exception) {
- $flashMessage = $exception->getMessage();
- $flashType = 'error';
- } catch (Throwable $exception) {
- Avatar::delete($newAvatar);
- $flashMessage = 'That username or email is already taken.';
- $flashType = 'error';
- }
- }
- }
-
- if ($action === 'update_account') {
- $targetId = (int) ($_POST['account_id'] ?? 0);
- $displayName = trim((string) ($_POST['display_name'] ?? ''));
- $role = (string) ($_POST['role'] ?? '');
- $status = ($_POST['status'] ?? 'active') === 'suspended' ? 'suspended' : 'active';
- $newPassword = (string) ($_POST['new_password'] ?? '');
-
- $targetStatement = $db->prepare('SELECT * FROM team_accounts WHERE id = :id LIMIT 1');
- $targetStatement->execute(['id' => $targetId]);
- $targetAccount = $targetStatement->fetch();
-
- $isSelfEdit = $targetAccount && (int) $targetAccount['id'] === (int) $currentUser['id'];
- if ($isSelfEdit) {
- $role = (string) $targetAccount['role'];
- $status = 'active';
- }
-
- if (!$targetAccount) {
- $flashMessage = 'Account not found.';
- $flashType = 'error';
- } elseif (!$isSuperAdmin && !$isSelfEdit && !in_array($targetAccount['role'], $assignableRoles, true)) {
- $flashMessage = 'You can only change people with a lower role than yours.';
- $flashType = 'error';
- } elseif ($targetAccount['role'] === Auth::ROLE_SUPER_ADMIN && (int) $targetAccount['id'] !== (int) $currentUser['id']) {
- $flashMessage = 'Only the Super Admin can change their own account.';
- $flashType = 'error';
- } elseif ($role === Auth::ROLE_SUPER_ADMIN && $targetAccount['role'] !== Auth::ROLE_SUPER_ADMIN) {
- $flashMessage = 'You can\'t make someone else Super Admin.';
- $flashType = 'error';
- } elseif (!$isSelfEdit && !in_array($role, array_merge($assignableRoles, [Auth::ROLE_SUPER_ADMIN]), true)) {
- $flashMessage = 'You can\'t give that role.';
- $flashType = 'error';
- } else {
- $fields = ['display_name = :display_name', 'role = :role', 'status = :status'];
- $params = [
- 'display_name' => $displayName !== '' ? $displayName : $targetAccount['display_name'],
- 'role' => $role,
- 'status' => $targetAccount['role'] === Auth::ROLE_SUPER_ADMIN ? 'active' : $status,
- 'id' => $targetId,
- ];
- $saveError = null;
- $newAvatar = null;
- $removeAvatar = isset($_POST['remove_avatar']);
-
- if ($newPassword !== '') {
- if (strlen($newPassword) < 10) {
- $saveError = 'The new password needs at least 10 characters.';
- } else {
- $fields[] = 'password_hash = :hash';
- $params['hash'] = password_hash($newPassword, PASSWORD_DEFAULT);
- }
- }
-
- if ($saveError === null && Avatar::hasUpload($_FILES['avatar'] ?? null)) {
- [$newAvatar, $saveError] = Avatar::store($_FILES['avatar']);
- }
-
- if ($saveError !== null) {
- $flashMessage = $saveError;
- $flashType = 'error';
- } else {
- if ($newAvatar !== null) {
- $fields[] = 'avatar_path = :avatar';
- $params['avatar'] = $newAvatar;
- } elseif ($removeAvatar) {
- $fields[] = 'avatar_path = NULL';
- }
-
- $resetTwoFactor = isset($_POST['reset_2fa']);
- if ($resetTwoFactor) {
- array_push($fields, 'totp_enabled = 0', 'totp_secret = NULL', 'totp_recovery = NULL', 'totp_last_step = NULL');
- }
-
- $update = $db->prepare('UPDATE team_accounts SET ' . implode(', ', $fields) . ' WHERE id = :id');
- $update->execute($params);
-
- if ($isSelfEdit && isset($params['hash'])) {
- session_regenerate_id(true);
- Auth::refreshPasswordFingerprint($targetId);
- }
-
- if ($resetTwoFactor) {
- ActivityLog::record('security.2fa_reset', 'account', $targetId);
- }
-
- if ($newAvatar !== null || $removeAvatar) {
- Avatar::delete((string) ($targetAccount['avatar_path'] ?? ''));
- }
-
- $flashMessage = 'Changes saved.';
- }
- }
- }
-
- if ($action === 'delete_account') {
- $targetId = (int) ($_POST['account_id'] ?? 0);
-
- $targetStatement = $db->prepare('SELECT * FROM team_accounts WHERE id = :id LIMIT 1');
- $targetStatement->execute(['id' => $targetId]);
- $targetAccount = $targetStatement->fetch();
-
- if (!$targetAccount) {
- $flashMessage = 'Account not found.';
- $flashType = 'error';
- } elseif ($targetAccount['role'] === Auth::ROLE_SUPER_ADMIN) {
- $flashMessage = 'The Super Admin can\'t be deleted.';
- $flashType = 'error';
- } elseif ((int) $targetAccount['id'] === (int) $currentUser['id']) {
- $flashMessage = 'You can\'t delete your own account.';
- $flashType = 'error';
- } elseif (!$isSuperAdmin && !in_array($targetAccount['role'], $assignableRoles, true)) {
- $flashMessage = 'You can only change people with a lower role than yours.';
- $flashType = 'error';
- } else {
- $transferTo = (int) ($_POST['transfer_to'] ?? 0);
- $receiver = null;
- if ($transferTo > 0 && $transferTo !== $targetId) {
- $receiverStatement = $db->prepare('SELECT id, display_name FROM team_accounts WHERE id = :id LIMIT 1');
- $receiverStatement->execute(['id' => $transferTo]);
- $receiver = $receiverStatement->fetch() ?: null;
- }
-
- if ($receiver === null) {
- $flashMessage = 'Pick who should take over this person\'s products, pages, ads and discount codes.';
- $flashType = 'error';
- } else {
- try {
- $db->beginTransaction();
- $transferSql = [
- 'UPDATE products SET created_by = :to WHERE created_by = :from',
- 'UPDATE pages SET author_id = :to WHERE author_id = :from AND is_home = 0',
- 'UPDATE ads SET created_by = :to WHERE created_by = :from',
- 'UPDATE discounts SET created_by = :to WHERE created_by = :from',
- ];
- foreach ($transferSql as $sql) {
- $db->prepare($sql)->execute(['to' => (int) $receiver['id'], 'from' => $targetId]);
- }
- $db->prepare('UPDATE pages SET author_id = NULL WHERE author_id = :from AND is_home = 1')->execute(['from' => $targetId]);
- $delete = $db->prepare('DELETE FROM team_accounts WHERE id = :id');
- $delete->execute(['id' => $targetId]);
- $db->commit();
- Avatar::delete((string) ($targetAccount['avatar_path'] ?? ''));
- $flashMessage = 'Account deleted. Everything they made now belongs to ' . $receiver['display_name'] . '.';
- } catch (Throwable $exception) {
- if ($db->inTransaction()) {
- $db->rollBack();
- }
- error_log('StocketBase: account delete failed: ' . $exception->getMessage());
- $flashMessage = 'Something went wrong, so nothing was deleted. Please try again.';
- $flashType = 'error';
- }
- }
- }
- }
- }
- }
-
- $accounts = $db->query('SELECT * FROM team_accounts ORDER BY FIELD(role, \'super_admin\',\'store_owner\',\'store_manager\',\'catalog_editor\',\'product_editor\',\'catalog_assistant\'), display_name ASC')->fetchAll();
-
- $ownedCounts = [];
- foreach ([
- 'products' => 'SELECT created_by AS owner, COUNT(*) AS c FROM products WHERE created_by IS NOT NULL GROUP BY created_by',
- 'pages' => 'SELECT author_id AS owner, COUNT(*) AS c FROM pages WHERE author_id IS NOT NULL AND is_home = 0 GROUP BY author_id',
- 'ads' => 'SELECT created_by AS owner, COUNT(*) AS c FROM ads WHERE created_by IS NOT NULL GROUP BY created_by',
- 'discounts' => 'SELECT created_by AS owner, COUNT(*) AS c FROM discounts WHERE created_by IS NOT NULL GROUP BY created_by',
- ] as $ownedKey => $ownedSql) {
- try {
- foreach ($db->query($ownedSql)->fetchAll() as $ownedRow) {
- $ownedCounts[(int) $ownedRow['owner']][$ownedKey] = (int) $ownedRow['c'];
- }
- } catch (PDOException $exception) {
- }
- }
-
- $customers = [];
- if ($isSuperAdmin) {
- $customers = $usersDb->query('SELECT * FROM user_accounts ORDER BY created_at DESC')->fetchAll();
- }
-
- $dashActivePage = 'admin';
- $dashPageTitle = 'Team';
-
- require __DIR__ . '/includes/dash-header.php';
-
- ?>
-
- <?php if ($flashMessage !== null): ?>
- <div class="dash-flash dash-flash-<?= htmlspecialchars($flashType) ?>"><?= Icons::icon($flashType === 'error' ? 'x' : 'check', 'icon icon-sm') ?><?= htmlspecialchars($flashMessage) ?></div>
- <?php endif; ?>
-
- <h1 class="dash-title"><?= Icons::icon("users", "icon icon-lg") ?>Accounts</h1>
-
- <?php if ($isSuperAdmin): ?>
- <div class="settings-tabs">
- <a href="admin.php?tab=team" class="<?= $activeTab === 'team' ? 'active' : '' ?>"><?= Icons::icon("team", "icon icon-sm") ?>Your team</a>
- <a href="admin.php?tab=customers" class="<?= $activeTab === 'customers' ? 'active' : '' ?>"><?= Icons::icon("book", "icon icon-sm") ?>Customers</a>
- </div>
- <?php endif; ?>
-
- <?php if ($activeTab === 'customers' && $isSuperAdmin): ?>
-
- <table class="dash-table">
- <thead>
- <tr>
- <th><?= Icons::icon('heading', 'icon icon-sm') ?>Name</th>
- <th><?= Icons::icon('user', 'icon icon-sm') ?>Email</th>
- <th><?= Icons::icon('flag', 'icon icon-sm') ?>Status</th>
- <th><?= Icons::icon('stats', 'icon icon-sm') ?>Registered</th>
- <th><?= Icons::icon('eye', 'icon icon-sm') ?>Last seen</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <?php foreach ($customers as $customer): ?>
- <tr>
- <td><?= htmlspecialchars((string) ($customer['display_name'] ?? 'No name')) ?></td>
- <td><?= htmlspecialchars($customer['email']) ?></td>
- <td><span class="status-pill status-<?= $customer['status'] === 'active' ? 'published' : 'rejected' ?>"><?= htmlspecialchars(StatusLabel::get((string) $customer['status'])) ?></span></td>
- <td><?= htmlspecialchars($customer['created_at']) ?></td>
- <td><?= htmlspecialchars((string) ($customer['last_login_at'] ?? 'Never')) ?></td>
- <td class="dash-table-actions">
- <?php if ($customer['status'] === 'active'): ?>
- <form method="post" style="display:inline;" onsubmit="return confirm('Block this customer? They will be logged out and won\'t be able to log in.');">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="suspend_customer">
- <input type="hidden" name="customer_id" value="<?= (int) $customer['id'] ?>">
- <button type="submit" class="dash-btn-small dash-btn-danger"><?= Icons::icon('lock', 'icon icon-sm') ?>Block</button>
- </form>
- <?php else: ?>
- <form method="post" style="display:inline;">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="reactivate_customer">
- <input type="hidden" name="customer_id" value="<?= (int) $customer['id'] ?>">
- <button type="submit" class="dash-btn-small dash-btn-success"><?= Icons::icon('check', 'icon icon-sm') ?>Unblock</button>
- </form>
- <?php endif; ?>
- </td>
- </tr>
- <?php endforeach; ?>
- <?php if (empty($customers)): ?>
- <tr>
- <td colspan="6">No customers yet.</td>
- </tr>
- <?php endif; ?>
- </tbody>
- </table>
-
- <?php else: ?>
-
- <table class="dash-table">
- <thead>
- <tr>
- <th><?= Icons::icon('heading', 'icon icon-sm') ?>Name</th>
- <th><?= Icons::icon('user', 'icon icon-sm') ?>Username</th>
- <th><?= Icons::icon('hash', 'icon icon-sm') ?>Email</th>
- <th><?= Icons::icon('team', 'icon icon-sm') ?>Role</th>
- <th><?= Icons::icon('flag', 'icon icon-sm') ?>Status</th>
- <th><?= Icons::icon('lock', 'icon icon-sm') ?>2FA</th>
- <th><?= Icons::icon('eye', 'icon icon-sm') ?>Last seen</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <?php foreach ($accounts as $account): ?>
- <tr>
- <td><span class="dash-avatar-inline"><?= Avatar::html($account, 'dash-user-avatar') ?><?= htmlspecialchars($account['display_name']) ?></span></td>
- <td><?= htmlspecialchars($account['username']) ?></td>
- <td><?= htmlspecialchars((string) $account['email']) ?></td>
- <td><?= htmlspecialchars(Auth::roleLabel($account['role'])) ?></td>
- <td><?= htmlspecialchars(StatusLabel::get((string) $account['status'])) ?></td>
- <td><?= TwoFactor::isEnabled($account) ? 'On' : 'Off' ?></td>
- <td><?= htmlspecialchars((string) ($account['last_login_at'] ?? 'Never')) ?></td>
- <td class="dash-table-actions">
- <?php if ($account['role'] !== Auth::ROLE_SUPER_ADMIN || (int) $account['id'] === (int) $currentUser['id']): ?>
- <button type="button" class="dash-btn-small js-edit-account"
- data-id="<?= (int) $account['id'] ?>"
- data-display-name="<?= htmlspecialchars($account['display_name'], ENT_QUOTES) ?>"
- data-avatar="<?= htmlspecialchars(Avatar::isValidPath((string) ($account['avatar_path'] ?? '')) ? (string) $account['avatar_path'] : '', ENT_QUOTES) ?>"
- data-initial="<?= htmlspecialchars(Avatar::initial((string) $account['display_name']), ENT_QUOTES) ?>"
- data-role="<?= htmlspecialchars($account['role'], ENT_QUOTES) ?>"
- data-status="<?= htmlspecialchars($account['status'], ENT_QUOTES) ?>"><?= Icons::icon('edit', 'icon icon-sm') ?>Edit</button>
- <?php endif; ?>
- <?php if ($account['role'] !== Auth::ROLE_SUPER_ADMIN && (int) $account['id'] !== (int) $currentUser['id']): ?>
- <?php $owned = $ownedCounts[(int) $account['id']] ?? []; ?>
- <button type="button" class="dash-btn-small dash-btn-danger js-delete-account"
- data-id="<?= (int) $account['id'] ?>"
- data-name="<?= htmlspecialchars($account['display_name'], ENT_QUOTES) ?>"
- data-products="<?= (int) ($owned['products'] ?? 0) ?>"
- data-pages="<?= (int) ($owned['pages'] ?? 0) ?>"
- data-ads="<?= (int) ($owned['ads'] ?? 0) ?>"
- data-discounts="<?= (int) ($owned['discounts'] ?? 0) ?>"><?= Icons::icon('trash', 'icon icon-sm') ?>Delete</button>
- <?php endif; ?>
- </td>
- </tr>
- <?php endforeach; ?>
- </tbody>
- </table>
-
- <div class="sidebar-box" id="delete-account-box" style="display:none;margin-top:20px;">
- <h2 class="dash-subtitle" style="margin-top:0;"><?= Icons::icon('trash', 'icon icon-sm') ?>Delete account: <span id="delete_account_name"></span></h2>
- <p id="delete_account_summary" style="color:var(--muted);margin:0 0 14px;"></p>
- <form method="post" id="delete-account-form">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="delete_account">
- <input type="hidden" name="account_id" id="delete_account_id" value="">
-
- <label><?= Icons::icon('users', 'icon icon-sm') ?>Who takes over their products, pages, ads and discount codes?</label>
- <select name="transfer_to" id="delete_transfer_to" required>
- <?php foreach ($accounts as $receiverAccount): ?>
- <option value="<?= (int) $receiverAccount['id'] ?>" <?= (int) $receiverAccount['id'] === (int) $currentUser['id'] ? 'selected' : '' ?>><?= htmlspecialchars($receiverAccount['display_name']) ?> (<?= htmlspecialchars(Auth::roleLabel($receiverAccount['role'])) ?>)</option>
- <?php endforeach; ?>
- </select>
-
- <div class="publish-actions">
- <button type="submit" class="dash-btn dash-btn-danger"><?= Icons::icon('trash', 'icon icon-sm') ?>Hand over and delete</button>
- <button type="button" class="dash-btn" onclick="document.getElementById('delete-account-box').style.display='none';"><?= Icons::icon('x', 'icon icon-sm') ?>Cancel</button>
- </div>
- </form>
- </div>
-
- <h2 class="dash-subtitle"><?= Icons::icon('plus', 'icon icon-sm') ?>Add a team member</h2>
- <form method="post" enctype="multipart/form-data">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="create_account">
-
- <label><?= Icons::icon('user', 'icon icon-sm') ?>Username</label>
- <input type="text" name="username" required>
-
- <label><?= Icons::icon('hash', 'icon icon-sm') ?>Email</label>
- <input type="text" name="email" required>
-
- <label><?= Icons::icon('heading', 'icon icon-sm') ?>Name shown in the dashboard</label>
- <input type="text" name="display_name">
-
- <label><?= Icons::icon('lock', 'icon icon-sm') ?>Password</label>
- <input type="password" name="password" minlength="10" required>
-
- <label><?= Icons::icon('user', 'icon icon-sm') ?>Photo (optional, JPG, PNG or WebP up to 2 MB)</label>
- <input type="file" name="avatar" accept="image/jpeg,image/png,image/webp">
-
- <label><?= Icons::icon('team', 'icon icon-sm') ?>Role</label>
- <select name="role" required>
- <?php foreach ($assignableRoles as $role): ?>
- <?php if ($role === Auth::ROLE_SUPER_ADMIN) {
- continue;
- } ?>
- <option value="<?= htmlspecialchars($role) ?>"><?= htmlspecialchars(Auth::roleLabel($role)) ?></option>
- <?php endforeach; ?>
- </select>
-
- <button type="submit" class="dash-btn dash-btn-primary" style="margin-top:16px;"><?= Icons::icon('plus', 'icon icon-sm') ?>Add to team</button>
- </form>
-
- <h2 class="dash-subtitle" id="edit-account-title" style="display:none;"><?= Icons::icon('edit', 'icon icon-sm') ?>Edit team member</h2>
- <form method="post" id="edit-account-form" style="display:none;" enctype="multipart/form-data">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="update_account">
- <input type="hidden" name="account_id" id="edit_account_id">
-
- <label><?= Icons::icon('user', 'icon icon-sm') ?>Photo (JPG, PNG or WebP up to 2 MB)</label>
- <div class="avatar-edit-preview">
- <div class="dash-user-avatar" id="edit_avatar_preview"></div>
- <input type="file" name="avatar" accept="image/jpeg,image/png,image/webp">
- </div>
- <label><input type="checkbox" name="remove_avatar" id="edit_remove_avatar"> Remove the photo (we'll show their first letter instead)</label>
-
- <label><?= Icons::icon('heading', 'icon icon-sm') ?>Name shown in the dashboard</label>
- <input type="text" name="display_name" id="edit_display_name">
-
- <label><?= Icons::icon('team', 'icon icon-sm') ?>Role</label>
- <select name="role" id="edit_role">
- <?php $editableRoleOptions = $isSuperAdmin ? array_merge($assignableRoles, [Auth::ROLE_SUPER_ADMIN]) : $assignableRoles; ?>
- <?php foreach (array_unique($editableRoleOptions) as $role): ?>
- <option value="<?= htmlspecialchars($role) ?>"><?= htmlspecialchars(Auth::roleLabel($role)) ?></option>
- <?php endforeach; ?>
- </select>
-
- <label><?= Icons::icon('flag', 'icon icon-sm') ?>Status</label>
- <select name="status" id="edit_status">
- <option value="active">Active</option>
- <option value="suspended">Blocked</option>
- </select>
-
- <label><?= Icons::icon('lock', 'icon icon-sm') ?>New password (leave empty to keep the old one)</label>
- <input type="password" name="new_password" minlength="10">
-
- <label><input type="checkbox" name="reset_2fa" id="edit_reset_2fa"> Turn off their two-step login (if they lost their phone and backup codes)</label>
-
- <div class="publish-actions">
- <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('check', 'icon icon-sm') ?>Save changes</button>
- <button type="button" class="dash-btn" onclick="document.getElementById('edit-account-form').style.display='none';document.getElementById('edit-account-title').style.display='none';"><?= Icons::icon('x', 'icon icon-sm') ?>Cancel</button>
- </div>
- </form>
-
- <script>
- document.querySelectorAll('.js-delete-account').forEach(function(button) {
- button.addEventListener('click', function() {
- var box = document.getElementById('delete-account-box');
- var select = document.getElementById('delete_transfer_to');
- var targetId = this.dataset.id;
- document.getElementById('delete_account_id').value = targetId;
- document.getElementById('delete_account_name').textContent = this.dataset.name;
- var parts = [];
- [['products', 'product'], ['pages', 'page'], ['ads', 'ad'], ['discounts', 'discount code']].forEach(function(entry) {
- var count = parseInt(button.dataset[entry[0]] || '0', 10);
- if (count > 0) {
- parts.push(count + ' ' + entry[1] + (count === 1 ? '' : 's'));
- }
- });
- document.getElementById('delete_account_summary').textContent = parts.length
- ? 'This person made ' + parts.join(', ') + '. Nothing gets deleted, it all moves to the person you pick below. Orders and customers stay as they are.'
- : 'This person hasn\'t made any products, pages, ads or discount codes. Orders and customers stay as they are.';
- var firstAllowed = null;
- Array.prototype.forEach.call(select.options, function(option) {
- option.disabled = option.value === targetId;
- option.hidden = option.value === targetId;
- if (!option.disabled && firstAllowed === null) {
- firstAllowed = option;
- }
- });
- if (select.selectedOptions.length === 0 || select.selectedOptions[0].disabled) {
- select.value = firstAllowed ? firstAllowed.value : '';
- }
- box.style.display = '';
- box.scrollIntoView({ behavior: 'smooth' });
- });
- });
-
- document.getElementById('delete-account-form').addEventListener('submit', function(event) {
- var select = document.getElementById('delete_transfer_to');
- var receiver = select.selectedOptions.length ? select.selectedOptions[0].textContent : '';
- if (!confirm('Delete ' + document.getElementById('delete_account_name').textContent + ' and give everything they made to ' + receiver + '?')) {
- event.preventDefault();
- }
- });
-
- document.querySelectorAll('.js-edit-account').forEach(function(button) {
- button.addEventListener('click', function() {
- document.getElementById('edit-account-title').style.display = '';
- document.getElementById('edit-account-form').style.display = '';
- document.getElementById('edit_account_id').value = this.dataset.id;
- document.getElementById('edit_display_name').value = this.dataset.displayName;
- document.getElementById('edit_role').value = this.dataset.role;
- document.getElementById('edit_status').value = this.dataset.status;
- document.getElementById('edit_remove_avatar').checked = false;
- document.getElementById('edit_reset_2fa').checked = false;
-
- var preview = document.getElementById('edit_avatar_preview');
- preview.textContent = '';
- preview.classList.toggle('has-image', this.dataset.avatar !== '');
- if (this.dataset.avatar !== '') {
- var image = document.createElement('img');
- image.src = this.dataset.avatar;
- image.alt = '';
- preview.appendChild(image);
- } else {
- preview.textContent = this.dataset.initial;
- }
- document.getElementById('edit-account-form').scrollIntoView({
- behavior: 'smooth'
- });
- });
- });
- </script>
-
- <?php endif; ?>
-
- <?php require __DIR__ . '/includes/dash-footer.php'; ?>