Store picker in topbar, redesign cabinet, rename to WBfeed.ru
- index.html: store dropdown in topbar (switch without leaving page) - cabinet.html: store-cards with Активировать/Проверить, edit hidden in <details> - Removed 'Используется' button, active shown as green card + label - next=index param to return to main page after store switch - Brand name changed to WBfeed.ru across all templates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1202,6 +1202,8 @@ def cabinet():
|
||||
token_row = db.get_token(int(token_id))
|
||||
if token_row and (user["is_admin"] or token_row["user_id"] == user["id"]):
|
||||
session["token_id"] = int(token_id)
|
||||
if request.form.get("next") == "index":
|
||||
return redirect(url_for("index"))
|
||||
return redirect(url_for("cabinet", status="selected"))
|
||||
error_message = "Не удалось выбрать токен."
|
||||
elif action == "check":
|
||||
@@ -1243,6 +1245,9 @@ def index():
|
||||
status = request.args.get("status")
|
||||
api_cooldown_seconds_left = _get_api_cooldown_seconds_left()
|
||||
_, active_token_name = _get_active_token()
|
||||
active_token_id = session.get("token_id")
|
||||
raw_tokens = db.fetch_tokens_for_user(g.user["id"], bool(g.user["is_admin"]))
|
||||
tokens = [{"id": r["id"], "name": r["name"]} for r in raw_tokens]
|
||||
error_message: Optional[str] = None
|
||||
success_message: Optional[str] = None
|
||||
if status == "reply_sent":
|
||||
@@ -1261,6 +1266,8 @@ def index():
|
||||
error_message=error_message,
|
||||
success_message=success_message,
|
||||
active_token_name=active_token_name,
|
||||
active_token_id=active_token_id,
|
||||
tokens=tokens,
|
||||
auto_reply_enabled=is_auto_reply_enabled(),
|
||||
api_cooldown_seconds_left=api_cooldown_seconds_left,
|
||||
next_fetch_seconds_left=_next_fetch_seconds_left(),
|
||||
|
||||
@@ -1847,3 +1847,146 @@ fieldset:disabled .filter-pill { pointer-events: none; opacity: 0.5; }
|
||||
font-weight: 700;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* ── Store picker (topbar dropdown) ─────────────────────── */
|
||||
.store-picker {
|
||||
position: relative;
|
||||
}
|
||||
.store-picker__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--line);
|
||||
border: 1.5px solid var(--line-strong);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
transition: background var(--t);
|
||||
white-space: nowrap;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.store-picker__btn:hover { background: var(--line-strong); filter: none; transform: none; box-shadow: none; }
|
||||
.store-picker__dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
min-width: 200px;
|
||||
background: #fff;
|
||||
border: 1.5px solid var(--line);
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 300;
|
||||
overflow: hidden;
|
||||
padding: 6px;
|
||||
}
|
||||
.store-picker.open .store-picker__dropdown { display: block; }
|
||||
.store-picker__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 9px 12px;
|
||||
border-radius: var(--r-sm);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
box-shadow: none;
|
||||
transition: background var(--t);
|
||||
text-align: left;
|
||||
}
|
||||
.store-picker__item:hover { background: var(--line); filter: none; transform: none; box-shadow: none; }
|
||||
.store-picker__item--active { color: var(--green); font-weight: 600; }
|
||||
.store-picker__sep { height: 1px; background: var(--line); margin: 4px 0; }
|
||||
|
||||
/* ── Store cards (cabinet) ───────────────────────────────── */
|
||||
.store-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.store-card {
|
||||
border: 2px solid var(--line);
|
||||
border-radius: var(--r);
|
||||
padding: 18px 20px;
|
||||
background: var(--card);
|
||||
transition: border-color var(--t);
|
||||
}
|
||||
.store-card--active {
|
||||
border-color: var(--green-line);
|
||||
background: var(--green-bg);
|
||||
}
|
||||
.store-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
.store-card__icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--line);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
.store-card--active .store-card__icon {
|
||||
background: var(--green-line);
|
||||
color: var(--green);
|
||||
}
|
||||
.store-card__info { flex: 1; min-width: 0; }
|
||||
.store-card__name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.store-card__active-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--green);
|
||||
margin-top: 2px;
|
||||
display: block;
|
||||
}
|
||||
.store-card__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btn-activate {
|
||||
background: linear-gradient(180deg, #22C55E 0%, #16A34A 100%);
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 2px 8px rgba(34,197,94,0.3);
|
||||
}
|
||||
.btn-activate:hover { opacity: 0.88; box-shadow: 0 4px 12px rgba(34,197,94,0.4); filter: none; }
|
||||
.store-card__edit {
|
||||
margin-top: 14px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.store-card__edit summary {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
list-style: none;
|
||||
}
|
||||
.store-card__edit summary:hover { color: var(--text); }
|
||||
.store-card__edit summary::marker,
|
||||
.store-card__edit summary::-webkit-details-marker { display: none; }
|
||||
.store-card__edit summary::before { content: '▸ '; font-size: 11px; }
|
||||
.store-card__edit[open] summary::before { content: '▾ '; }
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="topbar__inner">
|
||||
<a href="{{ url_for('index') }}" class="topbar__brand">
|
||||
<img src="{{ url_for('static', filename='wb3.png') }}" class="topbar__logo-img" alt="WB">
|
||||
<span class="topbar__name">Feedback</span>
|
||||
<span class="topbar__name">WBfeed.ru</span>
|
||||
</a>
|
||||
<div class="topbar__nav">
|
||||
<a href="{{ url_for('index') }}" class="topbar__link">Отзывы</a>
|
||||
|
||||
+32
-24
@@ -12,7 +12,7 @@
|
||||
<div class="topbar__inner">
|
||||
<a href="{{ url_for('index') }}" class="topbar__brand">
|
||||
<img src="{{ url_for('static', filename='wb3.png') }}" class="topbar__logo-img" alt="WB">
|
||||
<span class="topbar__name">Feedback</span>
|
||||
<span class="topbar__name">WBfeed.ru</span>
|
||||
</a>
|
||||
<div class="topbar__nav">
|
||||
<a href="{{ url_for('index') }}" class="topbar__link">Отзывы</a>
|
||||
@@ -51,21 +51,44 @@
|
||||
</div>
|
||||
|
||||
{% if tokens %}
|
||||
<ul class="token-list">
|
||||
<div class="store-list">
|
||||
{% for token in tokens %}
|
||||
<li class="token-item">
|
||||
<div class="token-main">
|
||||
<div class="token-name">
|
||||
<div class="store-card {% if token.id == active_token_id %}store-card--active{% endif %}">
|
||||
<div class="store-card__header">
|
||||
<div class="store-card__icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
</div>
|
||||
<div class="store-card__info">
|
||||
<div class="store-card__name">
|
||||
{{ token.name }}
|
||||
{% if current_user["is_admin"] and token.owner %}
|
||||
<span class="token-owner">({{ token.owner }})</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if token.id == active_token_id %}
|
||||
<span class="badge badge-green">Активен</span>
|
||||
<span class="store-card__active-label">● Активен</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="store-card__actions">
|
||||
{% if token.id != active_token_id %}
|
||||
<form method="post" class="inline-form">
|
||||
<input type="hidden" name="cabinet_action" value="select">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<button type="submit" class="btn-activate">Активировать</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" class="inline-form">
|
||||
<input type="hidden" name="cabinet_action" value="check">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<button type="submit" class="secondary">Проверить токен</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="cabinet-form token-edit-form">
|
||||
<!-- Редактирование (скрытое по умолчанию) -->
|
||||
<details class="store-card__edit">
|
||||
<summary>Изменить данные</summary>
|
||||
<form method="post" class="cabinet-form" style="margin-top:14px">
|
||||
<input type="hidden" name="cabinet_action" value="edit">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<label>
|
||||
@@ -80,25 +103,10 @@
|
||||
<button type="submit">Сохранить изменения</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="token-actions">
|
||||
<form method="post" class="inline-form">
|
||||
<input type="hidden" name="cabinet_action" value="select">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<button type="submit" {% if token.id == active_token_id %}class="secondary"{% endif %}>
|
||||
{{ "Используется" if token.id == active_token_id else "Использовать" }}
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" class="inline-form">
|
||||
<input type="hidden" name="cabinet_action" value="check">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<button type="submit" class="secondary">Проверить</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">Пока нет сохранённых магазинов.</div>
|
||||
{% endif %}
|
||||
|
||||
+30
-3
@@ -12,7 +12,7 @@
|
||||
<div class="topbar__inner">
|
||||
<a href="{{ url_for('index') }}" class="topbar__brand">
|
||||
<img src="{{ url_for('static', filename='wb3.png') }}" class="topbar__logo-img" alt="WB">
|
||||
<span class="topbar__name">Feedback</span>
|
||||
<span class="topbar__name">WBfeed.ru</span>
|
||||
</a>
|
||||
<div class="topbar__nav">
|
||||
<a href="{{ url_for('index') }}" class="topbar__link active">Отзывы</a>
|
||||
@@ -23,8 +23,29 @@
|
||||
</div>
|
||||
<div class="topbar__user">
|
||||
<span class="topbar__username">{{ current_user["username"] }}</span>
|
||||
{% if active_token_name %}
|
||||
<span class="badge">{{ active_token_name }}</span>
|
||||
{% if tokens %}
|
||||
<div class="store-picker">
|
||||
<button type="button" class="store-picker__btn" onclick="this.closest('.store-picker').classList.toggle('open')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
{{ active_token_name or 'Магазин не выбран' }}
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<div class="store-picker__dropdown">
|
||||
{% for t in tokens %}
|
||||
<form method="post" action="{{ url_for('cabinet') }}">
|
||||
<input type="hidden" name="cabinet_action" value="select">
|
||||
<input type="hidden" name="token_id" value="{{ t.id }}">
|
||||
<input type="hidden" name="next" value="index">
|
||||
<button type="submit" class="store-picker__item {% if t.id == active_token_id %}store-picker__item--active{% endif %}">
|
||||
{{ t.name }}
|
||||
{% if t.id == active_token_id %}<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
{% endfor %}
|
||||
<div class="store-picker__sep"></div>
|
||||
<a href="{{ url_for('cabinet') }}" class="store-picker__item">Управление магазинами →</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('logout') }}" class="btn-ghost">Выйти</a>
|
||||
</div>
|
||||
@@ -399,6 +420,12 @@ initPools();
|
||||
poll();
|
||||
setInterval(poll, 10000);
|
||||
})();
|
||||
// ── Store picker close on outside click ───────────────────
|
||||
document.addEventListener('click', e => {
|
||||
if (!e.target.closest('.store-picker')) {
|
||||
document.querySelectorAll('.store-picker.open').forEach(el => el.classList.remove('open'));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user