Добавлен веб-личный кабинет: посты, контент-план, медиа-библиотека и генерация картинок

This commit is contained in:
apuc committed 2026-08-07 09:35:44 +03:00
1 parent d8c086d3b4
commit 81625b2e6f
35 files changed
+2220 -64

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Личный кабинет{% endblock %}</title>
<link rel="stylesheet" href="/static/style.css?v=2">
<script>
(function () {
function isDark() {
return document.documentElement.getAttribute('data-theme') === 'dark';
}
function setIcon(btn) {
if (btn) btn.textContent = isDark() ? '☀️' : '🌙';
}
try {
var saved = localStorage.getItem('theme');
// Тёмная тема по умолчанию: dark включается всегда, кроме явного выбора 'light'
if (saved !== 'light') {
document.documentElement.setAttribute('data-theme', 'dark');
}
} catch (e) {}
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-theme-toggle]');
if (!btn) return;
if (isDark()) {
document.documentElement.removeAttribute('data-theme');
try { localStorage.setItem('theme', 'light'); } catch (e) {}
} else {
document.documentElement.setAttribute('data-theme', 'dark');
try { localStorage.setItem('theme', 'dark'); } catch (e) {}
}
setIcon(btn);
});
document.addEventListener('DOMContentLoaded', function () {
setIcon(document.querySelector('[data-theme-toggle]'));
});
})();
</script>
</head>
<body>
<header class="topbar">
<a class="brand" href="{{ '/dashboard' if user else '/login' }}">🗂 Личный кабинет</a>
<nav>
<button type="button" class="theme-toggle" data-theme-toggle aria-label="Переключить тему">🌙</button>
{% if user %}
<a href="/dashboard">Главная</a>
<a href="/generate">Пост</a>
<a href="/plan">Контент-план</a>
<a href="/history">История</a>
<a href="/images">Картинки</a>
<a href="/stats">Статистика</a>
<a href="/profile/edit">Профиль</a>
<form action="/logout" method="post" class="inline-form">
<button type="submit" class="link-button">Выйти</button>
</form>
</nav>
{% else %}
<a href="/login">Вход</a>
<a href="/register">Регистрация</a>
</nav>
{% endif %}
</header>
<main class="container">
{% if flash_message %}
<div class="flash">{{ flash_message }}</div>
{% endif %}
{% if errors %}
<div class="errors">
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% block content %}{% endblock %}
</main>
<script>
function copyText(text, btn) {
function done(ok) {
if (!btn) return;
var old = btn.textContent;
btn.textContent = ok ? '✅ Скопировано' : '❌ Ошибка';
setTimeout(function () { btn.textContent = old; }, 1500);
}
function fallback() {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
var ok = false;
try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
document.body.removeChild(ta);
done(ok);
}
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(function () { done(true); }, fallback);
} else {
fallback();
}
}
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-copy-target]');
if (!btn) return;
var el = document.getElementById(btn.getAttribute('data-copy-target'));
if (el) copyText(el.textContent, btn);
});
</script>
</body>
</html>
+20
View File
@@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block title %}Главная — Личный кабинет{% endblock %}
{% block content %}
<h1>Привет, {{ user.name }}!</h1>
<div class="card">
<p><strong>Телефон:</strong> {{ user.phone }}</p>
<p><strong>Вид бизнеса:</strong> {{ business_name }}</p>
<p><strong>Описание:</strong> {{ user.description }}</p>
<p><strong>В кабинете с:</strong> {{ user.created_at }}</p>
</div>
<div class="grid">
<a class="tile" href="/generate">💡 Сгенерировать пост</a>
<a class="tile" href="/plan">📅 Контент-план</a>
<a class="tile" href="/history">📜 История постов</a>
<a class="tile" href="/images">🖼 Медиа-библиотека</a>
<a class="tile" href="/stats">📈 Статистика</a>
<a class="tile" href="/profile/edit">✏️ Редактировать профиль</a>
<a class="tile tile-danger" href="/delete-account">🗑 Удалить аккаунт</a>
</div>
{% endblock %}
@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}Удаление аккаунта — Личный кабинет{% endblock %}
{% block content %}
<h1>Удалить аккаунт?</h1>
<p>Это безвозвратно удалит твой профиль и всю историю постов.</p>
<form method="post" action="/delete-account" class="form">
<input type="hidden" name="confirm" value="yes">
<button type="submit" class="button-danger">Да, удалить аккаунт</button>
</form>
<p><a href="/dashboard">Отмена, вернуться в кабинет</a></p>
{% endblock %}
+26
View File
@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block title %}Сгенерировать пост — Личный кабинет{% endblock %}
{% block content %}
<h1>Пост в своём стиле</h1>
<form method="post" action="/generate" class="form">
<label>Платформа
<select name="platform" required>
{% for key, label in platforms %}
<option value="{{ key }}" {% if form and form.platform == key %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<label>Стиль (например: дружелюбный, с юмором, экспертный)
<input type="text" name="style" value="{{ form.style if form else '' }}" required>
</label>
<label>Пожелания к посту
<textarea name="wishes" rows="3">{{ form.wishes if form else '' }}</textarea>
</label>
<button type="submit">Сгенерировать</button>
</form>
{% if result %}
<h2>Готовый пост</h2>
<div class="card result">{{ result }}</div>
{% endif %}
<p><a href="/dashboard">← Назад в кабинет</a></p>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block title %}История постов — Личный кабинет{% endblock %}
{% block content %}
<h1>История постов</h1>
{% if posts %}
<ul class="post-list">
{% for post in posts %}
<li class="card">
<div class="post-meta">{{ platform_label(post.platform) }} · {{ post.created_at }}</div>
<div class="post-content" id="post-content-{{ post.id }}">{{ post.content }}</div>
<div class="post-actions">
<button type="button" class="copy-btn" data-copy-target="post-content-{{ post.id }}">📋 Копировать</button>
<a class="button-link" href="/posts/{{ post.id }}">👁 Открыть</a>
<a class="button-link" href="/posts/{{ post.id }}/edit">✏️ Изменить</a>
<form method="post" action="/posts/{{ post.id }}/delete" class="inline-form"
onsubmit="return confirm('Удалить этот пост? Действие необратимо.');">
<button type="submit" class="button-link button-link-danger">🗑 Удалить</button>
</form>
</div>
</li>
{% endfor %}
</ul>
{% else %}
<div class="card">
<p>Постов пока нет.</p>
<p><a href="/generate">💡 Сгенерировать первый пост</a></p>
</div>
{% endif %}
<p><a href="/dashboard">← Назад в кабинет</a></p>
{% endblock %}
@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}Сгенерировать картинку — Личный кабинет{% endblock %}
{% block content %}
<h1>Генерация картинки</h1>
<form method="post" action="/images/generate" class="form">
<label>Что нарисовать?
<textarea name="prompt" rows="4" required placeholder="Например: современный кофейный стакан на белом фоне, логотип для кофейни">{{ form.prompt if form else '' }}</textarea>
</label>
<label>Размер
<select name="size" required>
{% for key, (value, label) in sizes.items() %}
<option value="{{ key }}" {% if form and form.size == key %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<label>Стиль
<select name="style">
{% for key, (label, suffix) in styles.items() %}
<option value="{{ key }}" {% if form and form.style == key %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<button type="submit">🎨 Сгенерировать</button>
<p class="form-hint">Генерация занимает 10–60 секунд. Картинка сохранится в медиа-библиотеку.</p>
</form>
<p><a href="/images">← Назад в библиотеку</a></p>
{% endblock %}
+34
View File
@@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block title %}Картинки — Личный кабинет{% endblock %}
{% block content %}
<h1>Медиа-библиотека</h1>
<p><a class="copy-btn" href="/images/generate">🎨 Сгенерировать картинку</a></p>
{% if images %}
<div class="media-grid">
{% for img in images %}
<div class="media-card">
<a href="/media/{{ img.filename }}" target="_blank" title="Открыть в полном размере">
<img class="media-thumb" src="/media/{{ img.filename }}" alt="{{ img.prompt }}" loading="lazy">
</a>
<div class="media-body">
<div class="media-prompt" title="{{ img.prompt }}">{{ img.prompt }}</div>
<div class="post-meta">{{ img.created_at }}</div>
<div class="post-actions">
<a class="copy-btn" href="/media/{{ img.filename }}" download>⬇ Скачать</a>
<form method="post" action="/images/{{ img.id }}/delete" class="inline-form"
onsubmit="return confirm('Удалить эту картинку?');">
<button type="submit" class="button-link button-link-danger">🗑 Удалить</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="card">
<p>Картинок пока нет.</p>
<p><a href="/images/generate">🎨 Сгенерировать первую картинку</a></p>
</div>
{% endif %}
<p><a href="/dashboard">← Назад в кабинет</a></p>
{% endblock %}
+15
View File
@@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block title %}Вход — Личный кабинет{% endblock %}
{% block content %}
<h1>Вход</h1>
<form method="post" action="/login" class="form">
<label>Email
<input type="email" name="email" value="{{ form.email if form else '' }}" required>
</label>
<label>Пароль
<input type="password" name="password" required>
</label>
<button type="submit">Войти</button>
</form>
<p>Ещё нет аккаунта? <a href="/register">Зарегистрироваться</a></p>
{% endblock %}
+58
View File
@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}Контент-план — Личный кабинет{% endblock %}
{% block content %}
<h1>Контент-план</h1>
{% if plan %}
<div class="chips">
<span class="chip chip-planned">📝 В плане: {{ status_counts.get('planned', 0) }}</span>
<span class="chip chip-published">✅ Опубликовано: {{ status_counts.get('published', 0) }}</span>
<span class="chip chip-postponed">🕓 Отложено: {{ status_counts.get('postponed', 0) }}</span>
</div>
<div class="plan-list">
{% for item in plan %}
<div class="card plan-item status-{{ item.status }}">
<div class="plan-item-head">
<span class="plan-day">День {{ item.day }}</span>
<span class="plan-platform">{{ platform_label(item.platform) }}</span>
<span class="status-badge status-{{ item.status }}">
{% if item.status == 'published' %}✅ Опубликовано
{% elif item.status == 'postponed' %}🕓 Отложено
{% else %}📝 В плане{% endif %}
</span>
</div>
<div class="plan-idea">{{ item.idea }}</div>
<div class="post-actions">
<form method="post" action="/plan/{{ item.id }}/status" class="inline-form">
<select name="status" class="status-select">
<option value="planned" {% if item.status == 'planned' %}selected{% endif %}>📝 В плане</option>
<option value="published" {% if item.status == 'published' %}selected{% endif %}>✅ Опубликовано</option>
<option value="postponed" {% if item.status == 'postponed' %}selected{% endif %}>🕓 Отложено</option>
</select>
<button type="submit" class="copy-btn">Сохранить</button>
</form>
<form method="post" action="/plan/{{ item.id }}/delete" class="inline-form"
onsubmit="return confirm('Удалить этот пункт плана?');">
<button type="submit" class="button-link button-link-danger">🗑 Удалить</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="card">
<p>Контент-плана пока нет. Составь его на 1–14 дней — он будет сохранён, и ты сможешь отмечать опубликованные и отложенные посты.</p>
</div>
{% endif %}
<h2>{% if plan %}Пересоставить план{% else %}Составить план{% endif %}</h2>
<form method="post" action="/plan" class="form"
onsubmit="{% if plan %}return confirm('Новый план заменит текущий (отметки статусов сбросятся). Продолжить?');{% endif %}">
<label>На сколько дней (1-14)
<input type="number" name="days" min="1" max="14" value="7" required>
</label>
<button type="submit">📅 Составить план</button>
</form>
<p><a href="/dashboard">← Назад в кабинет</a></p>
{% endblock %}
@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block title %}Пост — Личный кабинет{% endblock %}
{% block content %}
<h1>Пост</h1>
<div class="card">
<div class="post-meta">
{{ platform_label(post.platform) }} · {{ post.created_at }}
{% if post.style %} · Стиль: {{ post.style }}{% endif %}
</div>
<div class="post-content" id="post-content-{{ post.id }}">{{ post.content }}</div>
{% if post.wishes %}
<div class="post-notes"><strong>Пожелания:</strong> {{ post.wishes }}</div>
{% endif %}
</div>
<div class="post-actions">
<button type="button" class="copy-btn" data-copy-target="post-content-{{ post.id }}">📋 Копировать</button>
<a class="button-link" href="/posts/{{ post.id }}/edit">✏️ Изменить</a>
<form method="post" action="/posts/{{ post.id }}/delete" class="inline-form"
onsubmit="return confirm('Удалить этот пост? Действие необратимо.');">
<button type="submit" class="button-link button-link-danger">🗑 Удалить</button>
</form>
</div>
<p><a href="/history">← Назад к истории</a></p>
{% endblock %}
+18
View File
@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}Изменить пост — Личный кабинет{% endblock %}
{% block content %}
<h1>Изменить пост</h1>
<div class="card">
<p class="post-meta">{{ platform_label(post.platform) }} · {{ post.created_at }}</p>
<form method="post" action="/posts/{{ post.id }}/edit" class="form">
<label>Текст поста
<textarea name="content" rows="12" required>{{ post.content }}</textarea>
</label>
<div class="form-actions">
<button type="submit">💾 Сохранить</button>
<a class="button-link" href="/posts/{{ post.id }}">Отмена</a>
</div>
</form>
</div>
<p><a href="/history">← Назад к истории</a></p>
{% endblock %}
@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}Профиль — Личный кабинет{% endblock %}
{% block content %}
<h1>Редактировать профиль</h1>
<form method="post" action="/profile/edit" class="form">
<label>Имя
<input type="text" name="name" value="{{ user.name }}" required>
</label>
<label>Телефон
<input type="text" name="phone" value="{{ user.phone }}" required>
</label>
<label>Вид бизнеса
<select name="business_type" required>
{% for key, value in business_types.items() %}
<option value="{{ key }}" {% if key == user.business_type|string %}selected{% endif %}>{{ value.name }}</option>
{% endfor %}
</select>
</label>
<label>Описание бизнеса
<textarea name="description" rows="4" required>{{ user.description }}</textarea>
</label>
<button type="submit">Сохранить</button>
</form>
<p><a href="/dashboard">← Назад в кабинет</a></p>
{% endblock %}
+35
View File
@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block title %}Регистрация — Личный кабинет{% endblock %}
{% block content %}
<h1>Регистрация</h1>
<form method="post" action="/register" class="form">
<label>Имя
<input type="text" name="name" value="{{ form.name if form else '' }}" required>
</label>
<label>Телефон
<input type="text" name="phone" value="{{ form.phone if form else '' }}" required>
</label>
<label>Вид бизнеса
<select name="business_type" required>
<option value="" disabled {% if not form %}selected{% endif %}>Выбери вид бизнеса</option>
{% for key, value in business_types.items() %}
<option value="{{ key }}" {% if form and form.business_type == key %}selected{% endif %}>{{ value.name }}</option>
{% endfor %}
</select>
</label>
<label>Описание бизнеса
<textarea name="description" rows="4" required>{{ form.description if form else '' }}</textarea>
</label>
<label>Email
<input type="email" name="email" value="{{ form.email if form else '' }}" required>
</label>
<label>Пароль
<input type="password" name="password" minlength="8" required>
</label>
<label>Повтори пароль
<input type="password" name="password_confirm" minlength="8" required>
</label>
<button type="submit">Зарегистрироваться</button>
</form>
<p>Уже есть аккаунт? <a href="/login">Войти</a></p>
{% endblock %}
+20
View File
@@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block title %}Статистика — Личный кабинет{% endblock %}
{% block content %}
<h1>Статистика использования</h1>
<div class="card">
<p><strong>В кабинете с:</strong> {{ stats.created_at }}</p>
<p><strong>Всего постов:</strong> {{ stats.total_posts }}</p>
</div>
<h2>По платформам</h2>
{% if stats.by_platform %}
<ul>
{% for platform, count in stats.by_platform.items() %}
<li>{{ platform }}: {{ count }}</li>
{% endfor %}
</ul>
{% else %}
<p>Пока нет постов.</p>
{% endif %}
<p><a href="/dashboard">← Назад в кабинет</a></p>
{% endblock %}