Files
ave-cms/adminx/modules/Customers/view/index.twig
T
2026-07-27 12:58:44 +03:00

252 lines
50 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends '@adminx/main.twig' %}
{% block title %}Пользователи сайта{% endblock %}
{% block content %}
<nav class="breadcrumbs" aria-label="Хлебные крошки"><a href="{{ ADMINX_BASE }}/">Главная</a><i class="ti ti-chevron-right"></i><span>Пользователи сайта</span></nav>
<div class="page-header"><div><h1>Пользователи сайта</h1><p class="text-secondary">Публичные аккаунты, профили и способы входа</p></div></div>
<div class="ax-summary customers-summary">
<div class="card stat-tile"><div class="stat-ico"><i class="ti ti-users"></i></div><div><div class="stat-val">{{ stats.total }}</div><div class="stat-label">Всего</div></div></div>
<div class="card stat-tile"><div class="stat-ico is-green"><i class="ti ti-user-check"></i></div><div><div class="stat-val">{{ stats.active }}</div><div class="stat-label">Активные</div></div></div>
<div class="card stat-tile"><div class="stat-ico is-blue"><i class="ti ti-mail-check"></i></div><div><div class="stat-val">{{ stats.verified }}</div><div class="stat-label">Email подтверждён</div></div></div>
<div class="card stat-tile"><div class="stat-ico is-violet"><i class="ti ti-forms"></i></div><div><div class="stat-val">{{ stats.fields }}</div><div class="stat-label">Доп. полей</div></div></div>
</div>
<nav class="tabs customers-tabs">
<a class="tab{{ tab=='customers'?' active':'' }}" href="{{ ADMINX_BASE }}/system/customers?tab=customers"><i class="ti ti-users"></i>Пользователи</a>
<a class="tab{{ tab=='fields'?' active':'' }}" href="{{ ADMINX_BASE }}/system/customers?tab=fields"><i class="ti ti-forms"></i>Поля профиля</a>
<a class="tab{{ tab=='auth'?' active':'' }}" href="{{ ADMINX_BASE }}/system/customers?tab=auth"><i class="ti ti-user-cog"></i>Регистрация</a>
<a class="tab{{ tab=='pages'?' active':'' }}" href="{{ ADMINX_BASE }}/system/customers?tab=pages"><i class="ti ti-browser"></i>Страницы входа</a>
</nav>
{% if tab=='customers' %}
<div class="section-header ax-panel-header"><div class="section-icon"><i class="ti ti-user-search"></i></div><div><div class="section-eyebrow">Аккаунты сайта</div><h2>Пользователи</h2></div><div class="section-header-right"><form class="customers-filter"><input type="hidden" name="tab" value="customers"><div class="input-wrap"><i class="ti ti-search"></i><input class="input" type="search" name="q" value="{{ q|e }}" placeholder="Имя, email, телефон"></div><button class="btn btn-secondary"><i class="ti ti-filter"></i>Найти</button></form></div></div>
<div class="card ax-list-card">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--green-100);--tile-fg:var(--green-600)"><i class="ti ti-users-group"></i></span><div><h2>Публичные аккаунты</h2><p class="text-secondary">Посетители, зарегистрированные на сайте, их контакты и доступ к личному кабинету.</p></div></div><span class="badge badge-blue">{{ customers|length }}</span></div>
<div class="table-scroll"><table class="table customers-table"><thead><tr><th>ID</th><th>Пользователь</th><th>Контакты</th><th>Компания</th><th>Регистрация</th><th>Статус</th>{% if can_manage %}<th class="customers-actions-col">Действия</th>{% endif %}</tr></thead><tbody>
{% for customer in customers %}<tr data-customer-row data-id="{{ customer.id }}"><td class="mono text-muted">{{ customer.id }}</td><td data-customer-name><b>{{ (customer.firstname ~ ' ' ~ customer.lastname)|trim ?: customer.user_name }}</b><small class="mono">{{ customer.user_name }}</small></td><td data-customer-contacts><span>{{ customer.email ?: customer.phone }}</span>{% if customer.email and customer.phone %}<small>{{ customer.phone }}</small>{% endif %}</td><td data-customer-company>{{ customer.company ?: '—' }}</td><td class="mono text-sm">{{ customer.reg_time ? customer.reg_time|date('d.m.Y') : '—' }}</td><td>{% if can_manage %}<label class="switch" data-tooltip="Изменить активность"><input type="checkbox" data-customer-toggle data-url="{{ ADMINX_BASE }}/system/customers/{{ customer.id }}/toggle"{{ customer.status=='1'?' checked':'' }}><span></span></label>{% else %}<span class="badge {{ customer.status=='1'?'badge-green':'badge-gray' }}">{{ customer.status=='1'?'Активен':'Отключён' }}</span>{% endif %}</td>{% if can_manage %}<td><button class="btn btn-icon btn-ghost ax-act ax-act-edit" type="button" data-customer-edit data-url="{{ ADMINX_BASE }}/system/customers/users/{{ customer.id }}" data-tooltip="Редактировать" aria-label="Редактировать"><i class="ti ti-pencil"></i></button></td>{% endif %}</tr>
{% else %}<tr><td colspan="{{ can_manage ? 7 : 6 }}"><div class="empty-state">Пользователи не найдены</div></td></tr>{% endfor %}
</tbody></table></div>
</div>
{% elseif tab=='fields' %}
<div class="section-header ax-panel-header customers-fields-head"><div class="section-icon"><i class="ti ti-forms"></i></div><div><div class="section-eyebrow">Форма профиля</div><h2>Конструктор дополнительных полей</h2><p class="section-desc">Порядок и состояние сохраняются без перезагрузки; справа показана итоговая форма пользователя.</p></div>{% if can_manage %}<div class="section-header-right"><button class="btn btn-primary" data-field-new data-open-drawer="customerFieldDrawer"><i class="ti ti-plus"></i>Добавить поле</button></div>{% endif %}</div>
<div class="customers-field-builder">
<section class="card ax-list-card customers-field-structure">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--violet-100);--tile-fg:var(--violet-600)"><i class="ti ti-list-details"></i></span><div><h2>Структура профиля</h2><p class="text-secondary">Перетаскивайте поля за маркер, чтобы изменить порядок.</p></div></div><span class="badge badge-blue">{{ fields|length }}</span></div>
<div class="customers-field-list" data-fields-sortable data-reorder-url="{{ ADMINX_BASE }}/system/customers/fields/reorder">
{% for field in fields %}
<article class="customers-field-item{{ field.is_active ? '' : ' is-disabled' }}" data-field-id="{{ field.id }}" data-field='{{ field|json_encode|e('html_attr') }}'>
{% if can_manage %}<button class="customers-field-handle" type="button" draggable="true" aria-label="Изменить порядок" data-tooltip-right="Перетащить"><i class="ti ti-grip-vertical"></i></button>{% endif %}
<span class="icon-tile customers-field-icon"><i class="ti {{ field.type_icon }}"></i></span>
<div class="customers-field-copy"><b>{{ field.name }}</b><span><span class="mono">{{ field.code }}</span> · {{ field.type_label }}{% if field.is_required %} · обязательное{% endif %}</span><span class="customers-field-places">{% if field.show_registration %}<span class="badge badge-blue">Регистрация</span>{% endif %}{% if field.show_profile %}<span class="badge badge-green">Профиль</span>{% endif %}{% if not field.show_registration and not field.show_profile %}<span class="badge badge-gray">Не размещено</span>{% endif %}</span></div>
{% if can_manage %}<label class="switch customers-field-toggle" data-tooltip="Показывать поле"><input type="checkbox" data-field-toggle data-url="{{ ADMINX_BASE }}/system/customers/fields/{{ field.id }}/toggle"{{ field.is_active ? ' checked' : '' }}><span></span></label>{% else %}<span class="badge badge-{{ field.is_active ? 'green' : 'gray' }}">{{ field.is_active ? 'Включено' : 'Скрыто' }}</span>{% endif %}
{% if can_manage %}<div class="customers-field-actions"><button class="btn btn-icon btn-ghost ax-act ax-act-edit" data-field-edit data-open-drawer="customerFieldDrawer" data-tooltip="Редактировать" aria-label="Редактировать"><i class="ti ti-pencil"></i></button><button class="btn btn-icon btn-ghost ax-act ax-act-danger" data-field-delete data-url="{{ ADMINX_BASE }}/system/customers/fields/{{ field.id }}/delete" data-tooltip="Удалить" aria-label="Удалить"><i class="ti ti-trash"></i></button></div>{% endif %}
</article>
{% else %}<div class="empty-state"><i class="ti ti-forms"></i><b>Дополнительных полей пока нет</b><span>Добавьте первое поле профиля.</span></div>{% endfor %}
</div>
</section>
<aside class="card customers-profile-preview">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--cyan-100);--tile-fg:var(--cyan-600)"><i class="ti ti-eye"></i></span><div><h2>Предпросмотр формы</h2><p class="text-secondary">Так активные поля увидит пользователь.</p></div></div></div>
<div class="customers-preview-form" data-fields-preview>
{% for field in fields %}
<label class="field" data-preview-field="{{ field.id }}"{{ field.is_active and field.show_profile ? '' : ' hidden' }}><span class="field-label">{{ field.name }}{% if field.is_required %} <span class="text-danger">*</span>{% endif %}</span>
{% if field.type == 'textarea' %}<textarea class="textarea" rows="3" disabled></textarea>
{% elseif field.type == 'select' %}<select class="select" disabled><option>Выберите значение</option></select>
{% elseif field.type == 'checkbox' %}<span class="switch"><input type="checkbox" disabled><span>Да / нет</span></span>
{% else %}<input class="input" type="{{ field.type in ['date','number','email','tel'] ? field.type : 'text' }}" disabled>{% endif %}
</label>
{% else %}<div class="empty-state">Добавьте поля, чтобы увидеть форму</div>{% endfor %}
</div>
</aside>
</div>
{% elseif tab=='auth' %}
<div class="section-header ax-panel-header customers-auth-head"><div class="section-icon"><i class="ti ti-user-cog"></i></div><div><div class="section-eyebrow">Публичный аккаунт</div><h2>Регистрация и личный кабинет</h2><p class="section-desc">Единые правила создания аккаунта пользователя, его активации и восстановления доступа.</p></div>{% if can_manage %}<div class="section-header-right"><button class="btn btn-primary" type="submit" form="customerAuthForm"><i class="ti ti-device-floppy"></i>Сохранить</button></div>{% endif %}</div>
<div class="alert alert-info customers-auth-note"><span class="icon-tile" style="--tile-bg:var(--blue-100);--tile-fg:var(--blue-600)"><i class="ti ti-info-circle"></i></span><div><b>Одна учётная запись для сайта и панели</b><p>Публичная группа определяет возможности на сайте. Этому же пользователю можно разрешить работу в панели управления; отдельная регистрация и второй вход не потребуются.</p></div></div>
<form class="customers-auth-form" id="customerAuthForm" data-customer-auth data-base="{{ ADMINX_BASE }}">
<section class="card customers-auth-section">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--green-100);--tile-fg:var(--green-600)"><i class="ti ti-user-plus"></i></span><div><h2>Создание аккаунта</h2><p class="text-secondary">Кто может зарегистрироваться и когда станет доступен вход.</p></div></div><label class="switch" data-tooltip="Разрешить регистрацию"><input type="checkbox" name="registration_enabled" value="1"{{ auth_settings.registration_enabled ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label></div>
<div class="customers-auth-settings-grid">
<label class="field"><span class="field-label">Активация аккаунта</span><select class="select" name="registration_mode" data-registration-mode{{ can_manage ? '' : ' disabled' }}><option value="email"{{ auth_settings.registration_mode=='email' ? ' selected' : '' }}>После подтверждения email</option><option value="now"{{ auth_settings.registration_mode=='now' ? ' selected' : '' }}>Сразу после регистрации</option><option value="byadmin"{{ auth_settings.registration_mode=='byadmin' ? ' selected' : '' }}>После одобрения администратором</option></select><span class="field-hint">Для режима одобрения включите пользователя в первом табе.</span></label>
<label class="field"><span class="field-label">Группа после регистрации</span><select class="select" name="default_group"{{ can_manage ? '' : ' disabled' }}>{% for group in customer_groups %}<option value="{{ group.id }}"{{ auth_settings.default_group==group.id ? ' selected' : '' }}>#{{ group.id }} · {{ group.name }} · {{ group.permissions_count }} разрешений</option>{% endfor %}</select><span class="field-hint">Вы сами выбираете группу, которая будет назначаться новым пользователям сайта.</span></label>
<div class="field customers-auth-fixed"><span class="field-label">Канал регистрации</span><div class="customers-auth-fixed-value"><i class="ti ti-mail"></i><span><b>Email</b><small>Телефон и SMS подключаются отдельным гейтом.</small></span></div></div>
</div>
</section>
<section class="card customers-auth-section">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--cyan-100);--tile-fg:var(--cyan-600)"><i class="ti ti-forms"></i></span><div><h2>Поля формы регистрации</h2><p class="text-secondary">Базовые данные аккаунта. Дополнительные поля подключаются в соседнем табе.</p></div></div></div>
<div class="customers-auth-fields" data-auth-fields>
<div class="customers-auth-fields-head"><span>Поле</span><span>Показывать</span><span>Обязательное</span></div>
<div class="customers-auth-field-row"><span><i class="ti ti-mail"></i><span><b>Email</b><small>Логин и канал подтверждения</small></span></span><span class="badge badge-blue">Всегда</span><span class="badge badge-blue">Всегда</span></div>
<div class="customers-auth-field-row"><span><i class="ti ti-user"></i><span><b>Имя</b><small>Основное имя пользователя</small></span></span><span class="badge badge-blue">Всегда</span><label class="switch"><input type="checkbox" name="require_firstname" value="1"{{ auth_settings.require_firstname ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label></div>
<div class="customers-auth-field-row" data-auth-field-row><span><i class="ti ti-id"></i><span><b>Фамилия</b><small>Дополняет отображаемое имя</small></span></span><label class="switch"><input type="checkbox" name="show_lastname" value="1" data-auth-show{{ auth_settings.show_lastname ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label><label class="switch"><input type="checkbox" name="require_lastname" value="1" data-auth-required{{ auth_settings.require_lastname ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label></div>
<div class="customers-auth-field-row" data-auth-field-row><span><i class="ti ti-phone"></i><span><b>Телефон</b><small>Контакт для заказов и доставки</small></span></span><label class="switch"><input type="checkbox" name="show_phone" value="1" data-auth-show{{ auth_settings.show_phone ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label><label class="switch"><input type="checkbox" name="require_phone" value="1" data-auth-required{{ auth_settings.require_phone ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label></div>
<div class="customers-auth-field-row" data-auth-field-row><span><i class="ti ti-building"></i><span><b>Компания</b><small>Для корпоративных профилей</small></span></span><label class="switch"><input type="checkbox" name="show_company" value="1" data-auth-show{{ auth_settings.show_company ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label><label class="switch"><input type="checkbox" name="require_company" value="1" data-auth-required{{ auth_settings.require_company ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label></div>
</div>
</section>
<section class="card customers-auth-section">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--amber-100);--tile-fg:var(--amber-600)"><i class="ti ti-shield-lock"></i></span><div><h2>Пароль и подтверждение</h2><p class="text-secondary">Срок действия одноразовых ссылок и требования к паролю.</p></div></div></div>
<div class="customers-auth-settings-grid">
<label class="field"><span class="field-label">Минимальная длина пароля</span><input class="input" type="number" name="password_min_length" min="8" max="72" value="{{ auth_settings.password_min_length }}"{{ can_manage ? '' : ' disabled' }}></label>
<label class="field" data-auth-email-only><span class="field-label">Ссылка подтверждения, часов</span><input class="input" type="number" name="verification_hours" min="1" max="168" value="{{ auth_settings.verification_hours }}"{{ can_manage ? '' : ' disabled' }}></label>
<label class="field"><span class="field-label">Ссылка восстановления, часов</span><input class="input" type="number" name="reset_hours" min="1" max="168" value="{{ auth_settings.reset_hours }}"{{ can_manage ? '' : ' disabled' }}></label>
<label class="customers-switch-card customers-auth-reset"><span><b>Восстановление пароля</b><small>Показывать форму «Забыли пароль» и отправлять ссылку на email.</small></span><span class="switch"><input type="checkbox" name="password_reset_enabled" value="1"{{ auth_settings.password_reset_enabled ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></span></label>
</div>
</section>
<section class="card customers-auth-section customers-checkout-registration">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--violet-100);--tile-fg:var(--violet-600)"><i class="ti ti-shopping-bag-check"></i></span><div><h2>Аккаунт после заказа</h2><p class="text-secondary">Покупатель может создать тот же личный кабинет прямо при оформлении.</p></div></div><label class="switch" data-tooltip="Предлагать регистрацию в корзине"><input type="checkbox" name="checkout_registration_enabled" value="1"{{ auth_settings.checkout_registration_enabled ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label></div>
<div class="customers-checkout-body">
<div class="alert alert-info customers-checkout-note"><i class="ti ti-lock-check"></i><p>Пароль по почте не отправляется. После заказа пользователь сразу входит в аккаунт и получает одноразовую ссылку, чтобы задать пароль самостоятельно.</p></div>
<div class="customers-auth-restrictions">
<label class="field"><span class="field-label">URL политики</span><span class="input-wrap"><i class="ti ti-link"></i><input class="input" name="checkout_policy_url" value="{{ auth_settings.checkout_policy_url }}" placeholder="/privacy-policy"{{ can_manage ? '' : ' disabled' }}></span><span class="field-hint">Только внутренний адрес сайта.</span></label>
<label class="field"><span class="field-label">Текст согласия</span><input class="input" name="checkout_policy_label" value="{{ auth_settings.checkout_policy_label }}" maxlength="300"{{ can_manage ? '' : ' disabled' }}></label>
</div>
<label class="field"><span class="field-label">Тема письма после заказа</span><input class="input" name="checkout_access_subject" value="{{ auth_settings.checkout_access_subject }}" maxlength="190"{{ can_manage ? '' : ' disabled' }}></label>
<div class="customers-checkout-template-head"><div><b>Письмо с доступом</b><small>HTML и Twig. Нажмите тег, чтобы вставить его в позицию курсора.</small></div>{% if can_manage %}<button class="btn btn-icon btn-ghost" type="button" data-checkout-template-default data-template="{{ checkout_access_template_default|e('html_attr') }}" data-tooltip="Вернуть штатный шаблон" aria-label="Вернуть штатный шаблон"><i class="ti ti-restore"></i></button>{% endif %}</div>
<textarea class="textarea mono" name="checkout_access_template" data-code-editor data-mode="htmlmixed" data-height="320"{{ can_manage ? '' : ' disabled' }}>{{ auth_settings.checkout_access_template }}</textarea>
<span class="code-template-tags customers-checkout-template-tags" data-code-editor-tags="checkout_access_template">
{% for value,description in {'{{ user.firstname }}':'Имя пользователя','{{ user.lastname }}':'Фамилия','{{ user.email }}':'Email и логин','{{ order.id }}':'Номер заказа','{{ order.total }}':'Сумма заказа','{{ access_url }}':'Одноразовая ссылка задания пароля','{{ expires_hours }}':'Срок ссылки в часах'} %}<button type="button" data-code-editor-tag="{{ value|e('html_attr') }}" data-tooltip="{{ description }}"><code>{{ value }}</code><span>{{ description }}</span></button>{% endfor %}
</span>
</div>
</section>
<section class="card customers-auth-section">
<div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--red-100);--tile-fg:var(--red-600)"><i class="ti ti-shield-x"></i></span><div><h2>Ограничения регистрации</h2><p class="text-secondary">Один email или домен на строку. Проверка не зависит от регистра букв.</p></div></div></div>
<div class="customers-auth-restrictions"><label class="field"><span class="field-label">Запрещённые домены</span><textarea class="textarea mono" name="deny_domains" rows="5" placeholder="example.org&#10;mail.invalid"{{ can_manage ? '' : ' disabled' }}>{{ auth_settings.deny_domains|e }}</textarea></label><label class="field"><span class="field-label">Запрещённые адреса</span><textarea class="textarea mono" name="deny_emails" rows="5" placeholder="blocked@example.org"{{ can_manage ? '' : ' disabled' }}>{{ auth_settings.deny_emails|e }}</textarea></label></div>
</section>
{% if can_manage %}<div class="customers-auth-actions"><button class="btn btn-primary" type="submit"><i class="ti ti-device-floppy"></i>Сохранить настройки</button></div>{% endif %}
</form>
{% if oauth_modules %}
<div class="section-header customers-social-head">
<div class="section-icon"><i class="ti ti-login-2"></i></div>
<div><div class="section-eyebrow">Авторизация</div><h2>Подключённые способы входа</h2><p class="section-desc">OAuth и вход по одноразовому коду используют одну учётную запись сайта.</p></div>
<div class="section-header-right"><span class="badge badge-blue">{{ oauth_modules|length }} подключено</span></div>
</div>
<div class="customers-social-grid">
{% for provider in oauth_modules %}
<section class="card customers-social-card">
<div class="ax-list-head">
<div class="ax-list-title"><span class="customers-social-mark {{ provider.brand }}">{{ provider.mark }}</span><div><h3>{{ provider.label }}</h3><p class="text-secondary">{{ provider.description }}</p></div></div>
<span class="badge {{ provider.state.badge }}">{{ provider.state.label }}</span>
</div>
<div class="customers-social-status">
<div class="customers-social-checks">
<span class="{{ provider.module_enabled ? 'is-ready' : '' }}"><i class="ti {{ provider.module_enabled ? 'ti-circle-check' : 'ti-circle-x' }}"></i>Модуль включён</span>
<span class="{{ provider.configured ? 'is-ready' : '' }}"><i class="ti {{ provider.configured ? 'ti-key' : 'ti-key-off' }}"></i>Ключи настроены</span>
<span class="{{ provider.allow_registration ? 'is-ready' : '' }}"><i class="ti ti-user-plus"></i>Регистрация {{ provider.allow_registration ? 'разрешена' : 'выключена' }}</span>
</div>
{% if provider.can_open %}<a class="btn btn-secondary btn-sm" href="{{ ADMINX_BASE }}{{ provider.url }}"><i class="ti ti-settings"></i>Настроить</a>{% endif %}
</div>
</section>
{% endfor %}
</div>
{% else %}
<div class="alert alert-info customers-auth-note"><span class="icon-tile" style="--tile-bg:var(--blue-100);--tile-fg:var(--blue-600)"><i class="ti ti-plug-connected"></i></span><div><b>Дополнительные способы входа устанавливаются модулями</b><p>Яндекс, VK ID и SMSC настраиваются отдельно в разделе «Модули». Удаление пакета физически убирает его код и форму настроек.</p></div><a class="btn btn-secondary" href="{{ ADMINX_BASE }}/modules"><i class="ti ti-puzzle"></i>Открыть модули</a></div>
{% endif %}
{% else %}
<div class="section-header ax-panel-header customers-pages-head"><div class="section-icon"><i class="ti ti-browser"></i></div><div><div class="section-eyebrow">Публичный сайт</div><h2>Страницы личного кабинета</h2><p class="section-desc">Каждая страница имеет редактируемый Twig-шаблон и выводится через `[tag:maincontent]` внутри выбранного шаблона сайта.</p></div>{% if can_manage %}<div class="section-header-right"><button class="btn btn-primary" type="submit" form="customerPagesForm"><i class="ti ti-device-floppy"></i>Сохранить</button></div>{% endif %}</div>
<div class="alert alert-info customers-auth-note"><span class="icon-tile" style="--tile-bg:var(--cyan-100);--tile-fg:var(--cyan-600)"><i class="ti ti-route"></i></span><div><b>Старые адреса продолжат работать</b><p>После смены URL системные `/login`, `/register`, `/remember`, `/password/reset` и `/personal` останутся совместимыми алиасами. Новые ссылки в интерфейсе и письмах сразу начнут использовать заданные адреса.</p></div></div>
<form class="customers-pages-form" id="customerPagesForm" data-customer-pages data-base="{{ ADMINX_BASE }}">
<div class="customers-pages-grid">
{% for key in ['login','register','remember','reset','overview','profile','password'] %}
{% set settings = auth_settings.pages[key] %}{% set form_meta = auth_forms[key] %}
<section class="card customers-page-card">
<div class="customers-page-card-head"><span class="icon-tile" style="--tile-bg:var(--{{ form_meta.tile }}-100);--tile-fg:var(--{{ form_meta.tile }}-600)"><i class="ti {{ form_meta.icon }}"></i></span><div class="customers-page-card-copy"><h3>{{ form_meta.label }}</h3><span>{{ form_meta.description }}</span></div><div class="customers-page-card-actions">{% if can_manage %}<button class="btn btn-icon btn-ghost ax-act ax-act-edit" type="button" data-auth-form-edit="{{ key }}" data-tooltip="Редактировать форму" aria-label="Редактировать форму"><i class="ti ti-code"></i></button>{% endif %}<a class="btn btn-icon btn-ghost ax-act ax-act-view" href="{{ settings.path }}?_auth_preview=1" target="_blank" rel="noopener" data-tooltip="Предпросмотр" aria-label="Открыть предпросмотр"><i class="ti ti-external-link"></i></a></div></div>
<div class="customers-page-fields"><label class="field"><span class="field-label">Публичный URL</span><span class="input-wrap"><i class="ti ti-link"></i><input class="input mono" name="pages[{{ key }}][path]" value="{{ settings.path }}" required></span></label><label class="field"><span class="field-label">Шаблон сайта</span><select class="select" name="pages[{{ key }}][template_id]" required>{% for template in page_templates %}<option value="{{ template.id }}"{{ template.id == settings.template_id ? ' selected' : '' }}>#{{ template.id }} · {{ template.title }}</option>{% endfor %}</select></label><label class="field"><span class="field-label">Заголовок</span><input class="input" name="pages[{{ key }}][title]" value="{{ settings.title }}" required></label><label class="field"><span class="field-label">Кнопка</span><input class="input" name="pages[{{ key }}][submit_label]" value="{{ settings.submit_label }}" required></label><label class="field"><span class="field-label">Описание</span><textarea class="textarea" name="pages[{{ key }}][description]" rows="2">{{ settings.description }}</textarea></label></div>
</section>
{% endfor %}
</div>
<section class="card customers-auth-section customers-service-routes"><div class="ax-list-head"><div class="ax-list-title"><span class="icon-tile" style="--tile-bg:var(--gray-100);--tile-fg:var(--gray-700)"><i class="ti ti-code"></i></span><div><h2>Служебные маршруты</h2><p class="text-secondary">Подтверждение email, выход и общий шаблон системных результатов.</p></div></div>{% if can_manage %}<div class="customers-service-actions"><button class="btn btn-secondary btn-sm" type="button" data-auth-form-edit="message"><i class="ti ti-message-circle-check"></i>Сообщения</button></div>{% endif %}</div><div class="customers-auth-restrictions"><label class="field"><span class="field-label">Подтверждение email</span><span class="input-wrap"><i class="ti ti-link"></i><input class="input mono" name="pages[verify][path]" value="{{ auth_settings.pages.verify.path }}" required></span><input type="hidden" name="pages[verify][title]" value=""><input type="hidden" name="pages[verify][description]" value=""><input type="hidden" name="pages[verify][submit_label]" value=""><input type="hidden" name="pages[verify][template_id]" value="1"></label><label class="field"><span class="field-label">Выход</span><span class="input-wrap"><i class="ti ti-link"></i><input class="input mono" name="pages[logout][path]" value="{{ auth_settings.pages.logout.path }}" required></span><input type="hidden" name="pages[logout][title]" value=""><input type="hidden" name="pages[logout][description]" value=""><input type="hidden" name="pages[logout][submit_label]" value=""><input type="hidden" name="pages[logout][template_id]" value="1"></label></div></section>
{% if can_manage %}<div class="customers-auth-actions"><button class="btn btn-primary" type="submit"><i class="ti ti-device-floppy"></i>Сохранить страницы</button></div>{% endif %}
</form>
{% endif %}
{% if can_manage %}
<aside class="drawer drawer-right customer-editor-drawer" id="customerEditorDrawer" role="dialog" aria-modal="true" aria-labelledby="customerEditorTitle" hidden>
<div class="drawer-header"><div><h3 id="customerEditorTitle" data-customer-editor-title>Пользователь сайта</h3><p>Профиль, доступ и данные личного кабинета</p></div><button class="btn btn-icon btn-ghost" type="button" data-close-drawer aria-label="Закрыть"><i class="ti ti-x"></i></button></div>
<form data-customer-editor data-base="{{ ADMINX_BASE }}">
<div class="drawer-body">
<div class="customers-editor-meta"><span><i class="ti ti-id"></i><b data-customer-meta-id>#—</b></span><span><i class="ti ti-user-plus"></i><b data-customer-meta-created>—</b><small>Регистрация</small></span><span><i class="ti ti-clock"></i><b data-customer-meta-visit>—</b><small>Последний вход</small></span></div>
<div class="customers-editor-sections">
<section class="customers-form-section"><div class="customers-form-section-head"><span class="icon-tile" style="--tile-bg:var(--blue-100);--tile-fg:var(--blue-600)"><i class="ti ti-user"></i></span><div><b>Основные данные</b><span>Имя, контакты и данные организации.</span></div></div><div class="form-grid"><label class="field col-6"><span class="field-label">Имя</span><input class="input" name="firstname" maxlength="50"></label><label class="field col-6"><span class="field-label">Фамилия</span><input class="input" name="lastname" maxlength="50"></label><label class="field col-6"><span class="field-label">Email</span><input class="input" type="email" name="email" maxlength="100" required></label><label class="field col-6"><span class="field-label">Логин</span><input class="input mono" name="user_name" maxlength="50" required><span class="field-hint">Можно использовать для входа наравне с email.</span></label><label class="field col-6"><span class="field-label">Телефон</span><input class="input" type="tel" name="phone" maxlength="50"></label><label class="field col-6"><span class="field-label">Компания</span><input class="input" name="company" maxlength="255"></label><label class="field col-4"><span class="field-label">Дата рождения</span><input class="input" type="date" name="birthday"></label><label class="field col-8"><span class="field-label">О пользователе</span><textarea class="textarea" name="description" rows="2"></textarea></label></div></section>
<section class="customers-form-section"><div class="customers-form-section-head"><span class="icon-tile" style="--tile-bg:var(--cyan-100);--tile-fg:var(--cyan-600)"><i class="ti ti-map-pin"></i></span><div><b>Адрес</b><span>Данные для профиля и предзаполнения заказов.</span></div></div><div class="form-grid"><label class="field col-4"><span class="field-label">Город</span><input class="input" name="city"></label><label class="field col-4"><span class="field-label">Улица</span><input class="input" name="street"></label><label class="field col-2"><span class="field-label">Дом</span><input class="input" name="street_nr"></label><label class="field col-2"><span class="field-label">Индекс</span><input class="input" name="zipcode"></label></div></section>
<section class="customers-form-section">
<div class="customers-form-section-head"><span class="icon-tile" style="--tile-bg:var(--green-100);--tile-fg:var(--green-600)"><i class="ti ti-shield-check"></i></span><div><b>Доступ</b><span>Публичная группа, состояние аккаунта и роль в панели управления.</span></div></div>
<div class="form-grid">
<label class="field col-6"><span class="field-label">Группа публичных пользователей</span><select class="select" name="user_group" required>{% for group in customer_groups %}<option value="{{ group.id }}">#{{ group.id }} · {{ group.name }} · {{ group.permissions_count }} разрешений</option>{% endfor %}</select></label>
<label class="field col-6" data-customer-admin-role><span class="field-label">Роль в панели управления</span><select class="select" name="admin_role"{{ can_manage_admin_access ? '' : ' disabled' }}>{% for code, label in admin_roles %}<option value="{{ code }}">{{ label }}</option>{% endfor %}</select><span class="field-hint">Права роли настраиваются в разделе «Роли и права».</span></label>
</div>
<div class="customers-switch-grid customers-editor-access">
<label class="customers-switch-card"><span><b>Аккаунт включён</b><small>Может входить на сайт и в личный кабинет.</small></span><span class="switch"><input type="checkbox" name="status" value="1"><span></span></span></label>
<label class="customers-switch-card"><span><b>Доступ в панель управления</b><small>{{ can_manage_admin_access ? 'Та же учётная запись сможет работать в административном интерфейсе.' : 'Изменение требует права управления системными пользователями.' }}</small></span><span class="switch"><input type="checkbox" name="admin_access" value="1" data-customer-admin-access{{ can_manage_admin_access ? '' : ' disabled' }}><span></span></span></label>
<label class="customers-switch-card"><span><b>Email подтверждён</b><small>Администратор может изменить статус вручную.</small></span><span class="switch"><input type="checkbox" name="email_verified" value="1"><span></span></span></label>
<label class="customers-switch-card"><span><b>Телефон подтверждён</b><small>Подготовлено для будущего SMS-гейта.</small></span><span class="switch"><input type="checkbox" name="phone_verified" value="1"><span></span></span></label>
</div>
</section>
{% if fields %}<section class="customers-form-section"><div class="customers-form-section-head"><span class="icon-tile" style="--tile-bg:var(--violet-100);--tile-fg:var(--violet-600)"><i class="ti ti-forms"></i></span><div><b>Дополнительные поля</b><span>Активные поля из конструктора публичного профиля.</span></div></div><div class="form-grid">{% for field in fields %}{% if field.is_active %}<label class="field col-6"><span class="field-label">{{ field.name }}{% if field.is_required %} <span class="text-danger">*</span>{% endif %}</span>{% if field.type=='textarea' %}<textarea class="textarea" name="extra[{{ field.id }}]" rows="3"{{ field.is_required?' required':'' }}></textarea>{% elseif field.type=='select' %}<select class="select" name="extra[{{ field.id }}]"{{ field.is_required?' required':'' }}><option value="">Выберите значение</option>{% for choice in field.choices %}<option value="{{ choice|e('html_attr') }}">{{ choice }}</option>{% endfor %}</select>{% elseif field.type=='checkbox' %}<span class="customers-editor-checkbox"><span class="switch"><input type="checkbox" name="extra[{{ field.id }}]" value="1"><span></span></span><span>Да / нет</span></span>{% else %}<input class="input" type="{{ field.type in ['date','number','email','tel'] ? field.type : 'text' }}" name="extra[{{ field.id }}]"{{ field.is_required?' required':'' }}>{% endif %}<span class="field-hint mono">{{ field.code }}</span></label>{% endif %}{% endfor %}</div></section>{% endif %}
<section class="customers-form-section"><div class="customers-form-section-head"><span class="icon-tile" style="--tile-bg:var(--red-100);--tile-fg:var(--red-600)"><i class="ti ti-key"></i></span><div><b>Безопасность</b><span>Пароль изменится, только если заполнить поле.</span></div></div><label class="field customers-editor-password"><span class="field-label">Новый пароль</span><input class="input" type="password" name="password" autocomplete="new-password" placeholder="Оставьте пустым, чтобы не менять"><span class="field-hint">При смене пароля будут отозваны remember-сессии и ссылки восстановления.</span></label></section>
</div>
</div>
<footer class="drawer-footer"><button class="btn btn-secondary" type="button" data-close-drawer>Закрыть</button><button class="btn btn-primary" type="submit"><i class="ti ti-device-floppy"></i>Сохранить</button></footer>
</form>
</aside>
<aside class="drawer drawer-right customer-field-drawer" id="customerFieldDrawer" role="dialog" aria-modal="true" hidden>
<div class="drawer-header"><div><h3 data-field-title>Новое поле</h3><p>Настройка поля публичного профиля</p></div><button class="btn btn-icon btn-ghost" data-close-drawer aria-label="Закрыть"><i class="ti ti-x"></i></button></div>
<form data-customer-field data-base="{{ ADMINX_BASE }}">
<div class="drawer-body"><div class="customers-field-form">
<section class="customers-form-section"><div class="customers-form-section-head"><span class="icon-tile"><i class="ti ti-forms"></i></span><div><b>Основные параметры</b><span>Название, системный код и способ ввода.</span></div></div><div class="form-grid"><label class="field col-6"><span class="field-label">Название</span><input class="input" name="name" required></label><label class="field col-6"><span class="field-label">Код</span><input class="input mono" name="code" pattern="[a-z][a-z0-9_]+" required><span class="field-hint">Используется в API и шаблонах.</span></label><label class="field col-6"><span class="field-label">Тип поля</span><select class="select" name="type"><option value="text">Однострочный текст</option><option value="textarea">Многострочный текст</option><option value="select">Список значений</option><option value="checkbox">Переключатель</option><option value="date">Дата</option><option value="number">Число</option><option value="email">Email</option><option value="tel">Телефон</option></select></label><label class="field col-3"><span class="field-label">Порядок</span><input class="input" type="number" name="position" min="0" value="0"></label></div></section>
<section class="customers-form-section" data-field-options-section hidden><div class="customers-form-section-head"><span class="icon-tile" style="--tile-bg:var(--amber-100);--tile-fg:var(--amber-600)"><i class="ti ti-list"></i></span><div><b>Варианты списка</b><span>Каждый вариант отображается отдельной строкой.</span></div><button class="btn btn-secondary btn-sm" type="button" data-field-option-add><i class="ti ti-plus"></i>Вариант</button></div><input type="hidden" name="options"><div class="customers-options" data-field-options></div></section>
<section class="customers-form-section"><div class="customers-form-section-head"><span class="icon-tile" style="--tile-bg:var(--green-100);--tile-fg:var(--green-600)"><i class="ti ti-adjustments"></i></span><div><b>Поведение</b><span>Правила заполнения и области показа поля.</span></div></div><div class="customers-switch-grid"><label class="customers-switch-card"><span><b>Обязательное поле</b><small>Значение потребуется во всех выбранных формах.</small></span><span class="switch"><input type="checkbox" name="is_required" value="1"><span></span></span></label><label class="customers-switch-card"><span><b>Поле включено</b><small>Отключённое поле скрывается во всех формах.</small></span><span class="switch"><input type="checkbox" name="is_active" value="1" checked><span></span></span></label><label class="customers-switch-card"><span><b>Показывать при регистрации</b><small>Собрать значение во время создания аккаунта.</small></span><span class="switch"><input type="checkbox" name="show_registration" value="1"><span></span></span></label><label class="customers-switch-card"><span><b>Показывать в профиле</b><small>Разрешить заполнение и изменение в личном кабинете.</small></span><span class="switch"><input type="checkbox" name="show_profile" value="1" checked><span></span></span></label></div></section>
</div></div>
<footer class="drawer-footer"><button class="btn btn-secondary" type="button" data-close-drawer>Закрыть</button><button class="btn btn-primary" type="submit"><i class="ti ti-device-floppy"></i>Сохранить</button></footer>
</form>
</aside>
<aside class="drawer drawer-right customer-auth-form-drawer" id="customerAuthFormDrawer" role="dialog" aria-modal="true" hidden>
<div class="drawer-header"><div><h3 data-auth-form-title>Шаблон страницы</h3><p data-auth-form-description>Публичная Twig-разметка</p></div><button class="btn btn-icon btn-ghost" type="button" data-close-drawer aria-label="Закрыть"><i class="ti ti-x"></i></button></div>
<form data-auth-form-template data-base="{{ ADMINX_BASE }}">
<input type="hidden" name="key" value="">
<div class="drawer-body customers-auth-template-body">
<div class="customers-auth-template-layout">
<section class="customers-auth-template-editor">
<div class="customers-auth-template-toolbar"><span data-auth-form-state class="badge badge-neutral">Штатный шаблон</span><div><button class="btn btn-secondary btn-sm" type="button" data-auth-form-reset><i class="ti ti-restore"></i>Восстановить штатный</button><button class="btn btn-icon btn-ghost" type="button" data-auth-template-fullscreen data-tooltip="На весь экран" aria-label="Развернуть редактор"><i class="ti ti-arrows-maximize"></i></button></div></div>
<label class="field customers-auth-template-code"><span class="field-label">Twig-шаблон страницы</span><textarea class="textarea mono" name="template" data-code-editor data-mode="htmlmixed" data-height="620" required></textarea><span class="field-error" data-auth-form-error></span></label>
</section>
<aside class="customers-auth-template-help">
<div><span class="icon-tile" style="--tile-bg:var(--cyan-100);--tile-fg:var(--cyan-600)"><i class="ti ti-braces"></i></span><div><b>Доступные данные</b><span>Нажмите, чтобы вставить выражение в редактор.</span></div></div>
<div class="customers-auth-template-tags">
<button type="button" data-auth-template-tag="{{ '{{ page.title }}' }}"><code>{{ '{{ page.title }}' }}</code><span>Заголовок страницы</span></button>
<button type="button" data-auth-template-tag="{{ '{{ page.description }}' }}"><code>{{ '{{ page.description }}' }}</code><span>Описание</span></button>
<button type="button" data-auth-template-tag="{{ '{{ page.submit_label }}' }}"><code>{{ '{{ page.submit_label }}' }}</code><span>Подпись кнопки</span></button>
<button type="button" data-auth-template-tag="{{ '{{ csrf|e }}' }}"><code>{{ '{{ csrf|e }}' }}</code><span>CSRF-токен</span></button>
<button type="button" data-auth-template-tag="{{ '{{ base }}' }}"><code>{{ '{{ base }}' }}</code><span>Базовый URL</span></button>
<button type="button" data-auth-template-tag="{{ '{{ auth_urls.login }}' }}"><code>{{ '{{ auth_urls.login }}' }}</code><span>URL входа</span></button>
<button type="button" data-auth-template-tag="{{ '{% for provider in oauth_providers %}\n\t{{ provider.label }}\n{% endfor %}' }}"><code>oauth_providers</code><span>Активные гейты Яндекс/VK</span></button>
<button type="button" data-auth-template-tag="{{ '{{ errors._form }}' }}"><code>{{ '{{ errors._form }}' }}</code><span>Ошибка формы</span></button>
<button type="button" data-auth-template-tag="{{ '{% for field in fields %}\n\t{{ field.name }}\n{% endfor %}' }}"><code>fields</code><span>Дополнительные поля</span></button>
<button type="button" data-auth-template-tag="{{ '{{ user.firstname }}' }}"><code>{{ '{{ user.firstname }}' }}</code><span>Имя пользователя</span></button>
<button type="button" data-auth-template-tag="{{ '{{ orders_total }}' }}"><code>{{ '{{ orders_total }}' }}</code><span>Всего заказов · обзор</span></button>
<button type="button" data-auth-template-tag="{{ '{{ orders_paid }}' }}"><code>{{ '{{ orders_paid }}' }}</code><span>Оплачено · обзор</span></button>
<button type="button" data-auth-template-tag="{{ '{{ favorites_count }}' }}"><code>{{ '{{ favorites_count }}' }}</code><span>В избранном · обзор</span></button>
<button type="button" data-auth-template-tag="{{ '{% for order in orders %}\n\t{{ order.order_id }} · {{ order.order_total }}\n{% endfor %}' }}"><code>orders</code><span>Последние заказы · обзор</span></button>
</div>
<div class="alert alert-info customers-auth-template-note"><i class="ti ti-shield-check"></i><p>Разметка и CSS-классы редактируются здесь. Маршруты, проверка данных, CSRF и сохранение пользователей остаются в системном обработчике.</p></div>
</aside>
</div>
</div>
<footer class="drawer-footer"><button class="btn btn-secondary" type="button" data-close-drawer>Закрыть</button><button class="btn btn-primary" type="submit" data-code-save-stay><i class="ti ti-device-floppy"></i>Сохранить шаблон</button></footer>
</form>
</aside>
{% endif %}
{% endblock %}