main features

This commit is contained in:
2025-08-10 17:28:38 +09:00
parent 3e4a21d5b1
commit 95bef94c53
30 changed files with 4246 additions and 1066 deletions

View File

@@ -1,4 +1,4 @@
{% load static %}
{% load static ui_extras %}
<!DOCTYPE html>
<html lang="ru">
<head>
@@ -17,42 +17,37 @@
.card { background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:18px; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
.grid { display:grid; gap:16px; }
.grid-2 { grid-template-columns: 1fr 1fr; }
dl { display:grid; grid-template-columns: 200px 1fr; gap:8px 14px; margin: 0; }
dt { font-weight:600; color:#374151; }
dd { margin:0; color:#111827; }
.pill { display:inline-block; padding:4px 10px; border-radius:999px; background:#eef2ff; color:#3730a3; font-size:12px; margin:2px 6px 2px 0; }
.row { display:flex; gap:18px; align-items:center; }
.avatar { width:96px; height:96px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:700; font-size:32px; background:#e5e7eb; color:#374151; }
.avatar img { width:96px; height:96px; object-fit:cover; border-radius:50%; display:block; }
.form { display:grid; gap:14px; }
.form label { font-weight:600; font-size:14px; }
.form input[type="text"], .form select, .form textarea {
width:100%; border:1px solid #d1d5db; border-radius:8px; padding:10px 12px; font:inherit; background:#fff;
}
.form small { color:#6b7280; }
.form input[type="text"], .form select, .form textarea { width:100%; border:1px solid #d1d5db; border-radius:8px; padding:10px 12px; font:inherit; background:#fff; }
.btnrow { display:flex; gap:10px; margin-top:8px; }
.btn { display:inline-block; padding:10px 14px; border-radius:10px; border:1px solid transparent; font-weight:600; cursor:pointer; }
.btn-primary { background:#2563eb; color:#fff; }
.btn { display:inline-block; padding:10px 14px; border-radius:10px; border:1px solid #d1d5db; background:#fff; cursor:pointer; font-weight:600; color:#111; }
.btn-primary { background:#2563eb; color:#fff; border-color:#2563eb; }
.btn-outline { background:#fff; color:#111; border-color:#d1d5db; }
.messages { list-style:none; padding:0; margin:0 0 16px; }
.messages li { padding:10px 12px; margin-bottom:8px; border-radius:10px; }
.messages li.success { background:#ecfdf5; color:#065f46; border:1px solid #a7f3d0; }
.messages li.error { background:#fef2f2; color:#991b1b; border:1px solid #fecaca; }
.messages li.info { background:#eff6ff; color:#1e40af; border:1px solid #bfdbfe; }
dl { display:grid; grid-template-columns: 200px 1fr; gap:8px 14px; margin: 0; }
dt { font-weight:600; color:#374151; }
dd { margin:0; color:#111827; }
.pill { display:inline-block; padding:4px 10px; border-radius:999px; background:#eef2ff; color:#3730a3; font-size:12px; margin:2px 6px 2px 0; }
details summary { cursor:pointer; }
code, pre { background:#111827; color:#e5e7eb; padding:10px 12px; border-radius:10px; display:block; overflow:auto; }
</style>
</head>
<body>
<header class="topbar">
<div style="flex:1 1 auto;">
{% with header_name=header_name|default:request.session.user_full_name|default:request.session.user_email %}
Здравствуйте, <strong>{{ header_name }}</strong>!
{% endwith %}
Здравствуйте, <strong>{{ request.session.user_full_name|default:request.session.user_email }}</strong>!
</div>
<nav style="display:flex; gap:14px;">
<a href="{% url 'index' %}">Главная</a>
<a href="{% url 'cabinet' %}">Кабинет</a>
<a href="{% url 'profiles' %}">Каталог</a>
<a href="{% url 'logout' %}">Выход</a>
<a href="{% url 'ui:index' %}">Главная</a>
<a href="{% url 'ui:cabinet' %}">Кабинет</a>
<a href="{% url 'ui:profiles' %}">Каталог</a>
<a href="{% url 'ui:logout' %}">Выход</a>
</nav>
</header>
@@ -75,118 +70,129 @@
{% endif %}
</div>
<!-- Две колонки: Аккаунт + Профиль -->
<div class="grid grid-2">
<!-- ======= ДАННЫЕ АККАУНТА ======= -->
<!-- Аккаунт -->
<section class="card">
<h2 class="muted" style="margin-top:0;">Данные аккаунта</h2>
<dl>
<dt>Имя</dt>
<dd>{{ request.session.user_full_name|default:"—" }}</dd>
<div class="row" style="margin-bottom:14px;">
<div class="avatar">
{% if request.session.user_email %}
<img src="{{ request.session.user_email|gravatar_url:96 }}" alt="">
{% else %}
{{ request.session.user_full_name|default:request.session.user_email|initial }}
{% endif %}
</div>
<div>
<div style="font-weight:700;">{{ request.session.user_full_name|default:"Без имени" }}</div>
<div class="muted">{{ request.session.user_email }}</div>
<div class="pill" style="margin-top:6px;">{{ request.session.user_role|default:"CLIENT" }}</div>
</div>
</div>
<dt>Email</dt>
<dd>{{ request.session.user_email|default:"—" }}</dd>
<dt>Роль</dt>
<dd><span class="pill">{{ request.session.user_role|default:"—" }}</span></dd>
<dt>ID пользователя</dt>
<dd><code>{{ request.session.user_id|default:"—" }}</code></dd>
</dl>
<form class="form" method="post" action="{% url 'ui:cabinet' %}">
{% csrf_token %}
<input type="hidden" name="action" value="update_name">
<label for="full_name">Имя / ФИО</label>
<input id="full_name" name="full_name" type="text" value="{{ request.session.user_full_name|default_if_none:'' }}" placeholder="Ваше имя">
<div class="btnrow">
<button class="btn btn-primary" type="submit">Сохранить имя</button>
</div>
</form>
</section>
<!-- ======= ДАННЫЕ ПРОФИЛЯ ======= -->
<section class="card">
<h2 class="muted" style="margin-top:0;">Данные профиля</h2>
{% if has_profile and profile %}
<dl>
<dt>Пол</dt>
<dd>{{ profile.gender|default:"—" }}</dd>
<dt>Город</dt>
<dd>{{ profile.city|default:"—" }}</dd>
<dt>Языки</dt>
<dd>
{% if profile.languages %}
{% for lang in profile.languages %}<span class="pill">{{ lang }}</span>{% endfor %}
{% else %} — {% endif %}
</dd>
<dt>Интересы</dt>
<dd>
{% if profile.interests %}
{% for it in profile.interests %}<span class="pill">{{ it }}</span>{% endfor %}
{% else %} — {% endif %}
</dd>
<dt>ID профиля</dt>
<dd><code>{{ profile.id }}</code></dd>
<dt>ID пользователя (в профиле)</dt>
<dd><code>{{ profile.user_id }}</code></dd>
</dl>
<details style="margin-top:12px;">
<summary class="muted">Показать сырой JSON профиля</summary>
<pre>{{ profile|safe }}</pre>
</details>
{% else %}
<p class="muted">Профиль ещё не создан. Заполните форму ниже.</p>
{% endif %}
</section>
</div>
{% if has_profile and profile %}
<section class="card" style="margin-top:16px;">
<h2 class="muted" style="margin-top:0;">Фото профиля</h2>
<div style="display:flex; gap:16px; align-items:center;">
<div style="width:120px; height:120px; border-radius:12px; overflow:hidden; background:#e5e7eb;">
{% if profile.photo_url %}
<img src="{{ profile.photo_url }}" alt="" style="width:100%; height:100%; object-fit:cover;">
{% elif profile.photo %}
<img src="{{ profile.photo }}" alt="" style="width:100%; height:100%; object-fit:cover;">
{% elif request.session.user_email %}
<img src="{{ request.session.user_email|gravatar_url:240 }}" alt="" style="width:100%; height:100%; object-fit:cover;">
{% else %}
<div style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:#374151;font-weight:700;"></div>
{% endif %}
</div>
<form method="post" action="{% url 'ui:cabinet_upload_photo' %}" enctype="multipart/form-data" style="display:flex; gap:10px; align-items:center;">
{% csrf_token %}
<input type="file" name="file" accept="image/*" required>
<button class="btn btn-primary" type="submit">Загрузить фото</button>
</form>
</div>
<p class="muted" style="margin-top:10px;">Поддерживается multipart загрузка изображения в поле <code>file</code>. Эндпоинт: <code>/profiles/v1/profiles/me/photo</code>. :contentReference[oaicite:4]{index=4}</p>
</section>
{% endif %}
{% if not has_profile or not profile %}
<!-- ======= ФОРМА СОЗДАНИЯ ПРОФИЛЯ ======= -->
<section class="card" style="margin-top:16px;" aria-labelledby="section-create">
<h2 id="section-create" class="muted" style="margin-top:0;">Создать профиль</h2>
<form class="form" method="post" action="{% url 'cabinet' %}">
<form class="form" method="post" action="{% url 'ui:cabinet' %}">
{% csrf_token %}
<div class="grid grid-2">
<input type="hidden" name="action" value="create_profile">
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:16px;">
<div>
<label for="gender">Пол</label>
<select id="gender" name="gender" required>
<option value="">— выберите —</option>
<option value="male" {% if request.POST.gender == "male" %}selected{% endif %}>Мужской</option>
<option value="female" {% if request.POST.gender == "female" %}selected{% endif %}>Женский</option>
<option value="other" {% if request.POST.gender == "other" %}selected{% endif %}>Другое</option>
<option value="male">Мужской</option>
<option value="female">Женский</option>
<option value="other">Другое</option>
</select>
</div>
<div>
<label for="city">Город</label>
<input id="city" name="city" type="text" required
value="{{ request.POST.city|default_if_none:'' }}" placeholder="Москва">
<input id="city" name="city" type="text" required placeholder="Москва">
</div>
</div>
<div>
<label for="languages">Языки</label>
<input id="languages" name="languages" type="text"
value="{{ request.POST.languages|default_if_none:'' }}" placeholder="ru,en">
<small>Несколько — через запятую: <code>ru,en</code></small>
<input id="languages" name="languages" type="text" placeholder="ru,en">
<small class="muted">Несколько — через запятую</small>
</div>
<div>
<label for="interests">Интересы</label>
<input id="interests" name="interests" type="text"
value="{{ request.POST.interests|default_if_none:'' }}" placeholder="music,travel">
<small>Несколько — через запятую: <code>music,travel</code></small>
<input id="interests" name="interests" type="text" placeholder="music,travel">
<small class="muted">Несколько — через запятую</small>
</div>
<div class="btnrow">
<button class="btn btn-primary" type="submit">Создать профиль</button>
<a class="btn btn-outline" href="{% url 'cabinet' %}">Сбросить</a>
<a class="btn btn-outline" href="{% url 'ui:cabinet' %}">Сбросить</a>
</div>
</form>
</section>
{% else %}
<section class="card" style="margin-top:16px;">
<h2 class="muted" style="margin-top:0;">Данные профиля</h2>
<dl>
<dt>Пол</dt><dd>{{ profile.gender|default:"—" }}</dd>
<dt>Город</dt><dd>{{ profile.city|default:"—" }}</dd>
<dt>Языки</dt>
<dd>
{% if profile.languages %}
{% for lang in profile.languages %}<span class="pill">{{ lang }}</span>{% endfor %}
{% else %} — {% endif %}
</dd>
<dt>Интересы</dt>
<dd>
{% if profile.interests %}
{% for it in profile.interests %}<span class="pill">{{ it }}</span>{% endfor %}
{% else %} — {% endif %}
</dd>
<dt>ID профиля</dt><dd><code>{{ profile.id }}</code></dd>
<dt>ID пользователя</dt><dd><code>{{ profile.user_id }}</code></dd>
</dl>
<p class="muted" style="margin-top:8px;">Редактирование полей профиля появится, как только сервер добавит PATCH /profiles/v1/profiles/me.</p>
</section>
{% endif %}
</main>
</body>
</html>

View File

@@ -1,17 +1,5 @@
{# expects: profile_id, liked: bool #}
<form method="post"
hx-post="{% url 'like_profile' profile_id %}"
hx-target="this"
hx-swap="outerHTML"
aria-label="Добавить в избранное">
{% csrf_token %}
{% if liked %}
<button type="submit" class="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm hover:bg-white">
<span aria-hidden="true">❤️</span> В избранном
</button>
{% else %}
<button type="submit" class="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm hover:bg-white">
<span aria-hidden="true">🤍</span> В избранное
</button>
{% endif %}
</form>
{% if liked %}
<button class="btn" type="submit">В избранном</button>
{% else %}
<button class="btn" type="submit">В избранное</button>
{% endif %}

View File

@@ -1,3 +1 @@
<a href="/login/?next={{ request.path }}" class="inline-flex items-center gap-1 rounded-md bg-indigo-600 text-white px-3 py-1.5 text-sm hover:bg-indigo-700">
Войти, чтобы добавить
</a>
<button class="btn" disabled>В избранное (войдите)</button>

View File

@@ -1,31 +1,36 @@
{% extends 'base.html' %}
{% block title %}Главная — MatchAgency{% endblock %}
{% block content %}
<section class="grid md:grid-cols-2 gap-8 items-center">
<div>
<h1 class="text-3xl md:text-5xl font-semibold leading-tight">Подбор идеальных пар под ключ</h1>
<p class="mt-4 text-gray-600 text-lg">Фронтенд полностью на API: ни одной локальной таблицы.</p>
<div class="mt-6 flex gap-3">
<a href="/profiles/" class="inline-flex items-center rounded-md bg-rose-600 px-4 py-2 text-white font-medium hover:bg-rose-700">Смотреть анкеты</a>
{% if not api_user %}
<a href="/login/" class="inline-flex items-center rounded-md border px-4 py-2 font-medium hover:bg-white">Войти</a>
{% endif %}
</div>
</div>
<div class="rounded-xl bg-white/70 backdrop-blur p-4 md:p-6 shadow">
<form action="/profiles/" method="get" class="grid sm:grid-cols-2 gap-4">
<input name="q" placeholder="Ключевые слова (хобби, имя, город)"
class="w-full rounded-md border px-3 py-2" />
<select name="gender" class="w-full rounded-md border px-3 py-2">
<option value="">Пол (любой)</option>
<option value="female">Женщины</option>
<option value="male">Мужчины</option>
</select>
<input name="age_min" type="number" min="18" max="100" placeholder="От, лет" class="w-full rounded-md border px-3 py-2"/>
<input name="age_max" type="number" min="18" max="100" placeholder="До, лет" class="w-full rounded-md border px-3 py-2"/>
<input name="city" placeholder="Город" class="w-full rounded-md border px-3 py-2"/>
<button class="sm:col-span-2 rounded-md bg-indigo-600 text-white px-4 py-2 hover:bg-indigo-700">Найти</button>
</form>
</div>
</section>
{% endblock %}
{% load static %}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Главная</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="{% static 'style.css' %}" rel="stylesheet">
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, "Noto Sans", sans-serif; margin:0; background:#f7f7fb; color:#111; }
header { background:#111827; color:#fff; padding:14px 18px; display:flex; gap:14px; }
header a { color:#cfe3ff; text-decoration:none; }
.container { max-width:900px; margin:24px auto; padding:0 16px; }
.btn { padding:10px 14px; border-radius:10px; background:#2563eb; color:#fff; border:none; text-decoration:none; }
</style>
</head>
<body>
<header>
<div style="flex:1 1 auto;">Agency Frontend</div>
<nav>
<a href="{% url 'ui:index' %}">Главная</a>
<a href="{% url 'ui:cabinet' %}">Кабинет</a>
<a href="{% url 'ui:profiles' %}">Каталог</a>
<a href="{% url 'ui:login' %}">Войти</a>
</nav>
</header>
<main class="container">
<h1>Добро пожаловать</h1>
<p>Это фронтенд для API брачного агентства.</p>
<p>
<a class="btn" href="{% url 'ui:login' %}">Войти</a>
<a class="btn" style="background:#10b981;" href="{% url 'ui:register' %}">Регистрация</a>
</p>
</main>
</body>
</html>

View File

@@ -1,27 +1,43 @@
{% extends 'base.html' %}
{% block title %}Вход — MatchAgency{% endblock %}
{% block content %}
<div class="max-w-md mx-auto rounded-xl bg-white/80 backdrop-blur border shadow p-6">
<h1 class="text-xl font-semibold mb-4">Вход</h1>
<form action="" method="post" class="space-y-3">
{% csrf_token %}
<div>
<label class="block text-sm mb-1">Email</label>
<input type="email" name="email" required class="w-full rounded-md border px-3 py-2"/>
</div>
<div>
<label class="block text-sm mb-1">Пароль</label>
<input type="password" name="password" required class="w-full rounded-md border px-3 py-2"/>
</div>
<div class="flex items-center justify-between">
<label class="block text-sm mb-1">Запомнить меня</label>
<input type="checkbox" name="remember_me" class="rounded border px-2 py-1"/>
</div>
{% if error_message %}
<p class="text-red-500 text-sm">{{ error_message }}</p>
{% load static %}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Вход</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="{% static 'style.css' %}" rel="stylesheet">
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, "Noto Sans", sans-serif; margin:0; background:#f7f7fb; color:#111; }
.wrap { max-width:420px; margin:60px auto; background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:18px; }
.form { display:grid; gap:12px; }
.form input { border:1px solid #d1d5db; border-radius:8px; padding:10px 12px; font:inherit; }
.btn { padding:10px 14px; border-radius:10px; background:#2563eb; color:#fff; border:none; cursor:pointer; font-weight:600; }
.muted { color:#6b7280; }
a { color:#2563eb; text-decoration:none; }
</style>
</head>
<body>
<div class="wrap">
<h1>Вход</h1>
{% if messages %}
<ul>
{% for message in messages %}
<li class="{{ message.tags }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<p class="text-sm">Нет аккаунта? <a class="text-indigo-700 hover:underline" href="{% url 'register' %}">Зарегистрироваться</a></p>
<button class="w-full rounded-md bg-indigo-600 text-white px-4 py-2 hover:bg-indigo-700">Войти</button>
</form>
</div>
{% endblock %}
<form class="form" method="post" action="{% url 'ui:login' %}">
{% csrf_token %}
<input type="email" name="email" placeholder="Email">
<input type="password" name="password" placeholder="Пароль">
<button class="btn" type="submit">Войти</button>
</form>
<p class="muted" style="margin-top:10px;">Нет аккаунта?
<a href="{% url 'ui:register' %}">Зарегистрируйтесь</a>
</p>
</div>
</body>
</html>

View File

@@ -1,4 +1,4 @@
{% load static %}
{% load static ui_extras %}
<!DOCTYPE html>
<html lang="ru">
<head>
@@ -10,18 +10,24 @@
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, "Noto Sans", sans-serif; margin:0; background:#f7f7fb; color:#111; }
.topbar { display:flex; gap:16px; align-items:center; padding:14px 18px; background:#111827; color:#fff; }
.topbar a { color:#cfe3ff; text-decoration:none; }
.container { max-width:900px; margin:24px auto; padding:0 16px; }
.container { max-width:960px; margin:24px auto; padding:0 16px; }
.card { background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:18px; }
.row { display:flex; gap:18px; align-items:center; }
.grow { flex:1 1 auto; }
.muted { color:#6b7280; font-size:14px; }
.pill { display:inline-block; padding:4px 10px; border-radius:999px; background:#eef2ff; color:#3730a3; font-size:12px; margin:2px 6px 2px 0; }
.avatar { width:96px; height:96px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:700; font-size:32px; background:#e5e7eb; color:#374151; }
.avatar img { width:96px; height:96px; object-fit:cover; border-radius:50%; display:block; }
.avatar { width:96px; height:96px; border-radius:50%; overflow:hidden; background:#e5e7eb; display:flex; align-items:center; justify-content:center; font-weight:700; color:#374151; }
.avatar img { width:96px; height:96px; object-fit:cover; display:block; }
.btn { display:inline-block; padding:9px 12px; border-radius:10px; border:1px solid #d1d5db; background:#fff; cursor:pointer; font-weight:600; text-decoration:none; color:#111; }
/* Детальные списки */
dl { display:grid; grid-template-columns: 220px 1fr; gap:8px 14px; margin:0; }
dt { font-weight:600; color:#374151; }
dd { margin:0; color:#111827; }
.btn { display:inline-block; padding:9px 12px; border-radius:10px; border:1px solid #d1d5db; background:#fff; cursor:pointer; font-weight:600; text-decoration:none; color:#111; }
.pill-wrap { display:flex; flex-wrap:wrap; gap:6px; }
.grid { display:grid; gap:16px; }
.grid-2 { grid-template-columns: 1fr 1fr; }
@media (max-width: 800px) { .grid-2 { grid-template-columns: 1fr; } }
</style>
</head>
<body>
@@ -29,29 +35,40 @@
<header class="topbar">
<div style="flex:1 1 auto;">Карточка пользователя (ADMIN)</div>
<nav style="display:flex; gap:14px;">
<a href="{% url 'profiles' %}">← Каталог</a>
<a href="{% url 'cabinet' %}">Кабинет</a>
<a href="{% url 'logout' %}">Выход</a>
<a href="{% url 'ui:profiles' %}">← Каталог</a>
<a href="{% url 'ui:cabinet' %}">Кабинет</a>
<a href="{% url 'ui:logout' %}">Выход</a>
</nav>
</header>
<main class="container">
<div class="card">
<!-- Шапка с аватаром, именем и лайком -->
<div class="card" style="margin-bottom:16px;">
<div class="row">
<div class="avatar">
{% if profile.photo %}
{% if profile.photo_url %}
<img src="{{ profile.photo_url }}" alt="">
{% elif profile.photo %}
<img src="{{ profile.photo }}" alt="">
{% elif profile.email %}
<img src="{{ profile.email|gravatar_url:96 }}" alt="">
{% else %}
{{ profile.name|first|upper }}
{{ profile.name|initial }}
{% endif %}
</div>
<div class="grow">
<div style="font-weight:700; font-size:20px;">{{ profile.name }}</div>
<div class="muted" style="margin-top:4px;">ID пользователя: {{ profile.id }}</div>
{% if profile.email %}
<div class="muted">Email: {{ profile.email }}</div>
{% endif %}
{% if profile.role %}
<div class="muted">Роль: <span class="pill">{{ profile.role }}</span></div>
{% endif %}
</div>
<div>
<form method="post" action="{% url 'like_profile' profile.id %}">
<form method="post" action="{% url 'ui:like_profile' profile.id %}">
{% csrf_token %}
{% include "ui/components/like_button.html" with profile_id=profile.id liked=liked %}
</form>
@@ -59,24 +76,57 @@
</div>
</div>
<div class="card" style="margin-top:16px;">
<h2 class="muted" style="margin-top:0;">Профиль</h2>
<dl>
<dt>Пол</dt><dd>{{ profile.gender|default:"—" }}</dd>
<dt>Город</dt><dd>{{ profile.city|default:"—" }}</dd>
<dt>Языки</dt>
<dd>
{% if profile.languages %}
{% for lang in profile.languages %}<span class="pill">{{ lang }}</span>{% endfor %}
{% else %} — {% endif %}
</dd>
<dt>Интересы</dt>
<dd>
{% if profile.interests %}
{% for it in profile.interests %}<span class="pill">{{ it }}</span>{% endfor %}
{% else %} — {% endif %}
</dd>
</dl>
<!-- Полные данные -->
<div class="grid grid-2">
<!-- Данные аккаунта (из UserRead) -->
<section class="card">
<h2 class="muted" style="margin-top:0;">Данные аккаунта</h2>
<dl>
<dt>Имя</dt><dd>{{ profile.name|default:"—" }}</dd>
<dt>Email</dt><dd>{{ profile.email|default:"—" }}</dd>
<dt>Роль</dt><dd>{{ profile.role|default:"—" }}</dd>
<dt>Статус</dt><dd>{% if profile.verified %}<span class="pill">ACTIVE</span>{% else %}<span class="pill">INACTIVE</span>{% endif %}</dd>
<dt>ID пользователя</dt><dd><code>{{ profile.id|default:"—" }}</code></dd>
</dl>
</section>
<!-- Данные профиля (ProfileOut) -->
<section class="card">
<h2 class="muted" style="margin-top:0;">Данные профиля</h2>
<dl>
<dt>Пол</dt><dd>{{ profile.gender|default:"—" }}</dd>
<dt>Город</dt><dd>{{ profile.city|default:"—" }}</dd>
<dt>Языки</dt>
<dd>
{% if profile.languages %}
<div class="pill-wrap">
{% for lang in profile.languages %}<span class="pill">{{ lang }}</span>{% endfor %}
</div>
{% else %} — {% endif %}
</dd>
<dt>Интересы</dt>
<dd>
{% if profile.interests %}
<div class="pill-wrap">
{% for it in profile.interests %}<span class="pill">{{ it }}</span>{% endfor %}
</div>
{% else %} — {% endif %}
</dd>
<dt>О себе</dt><dd>{{ profile.about|default:"—" }}</dd>
<dt>Возраст</dt><dd>{{ profile.age|default:"—" }}</dd>
<dt>ID профиля</dt><dd><code>{{ profile.profile_id|default:profile.id|default:"—" }}</code></dd>
<dt>ID пользователя (в профиле)</dt><dd><code>{{ profile.user_id|default:"—" }}</code></dd>
<dt>Фото (URL)</dt><dd>
{% if profile.photo_url %}<code>{{ profile.photo_url }}</code>
{% elif profile.photo %}<code>{{ profile.photo }}</code>
{% else %} — {% endif %}
</dd>
</dl>
<p class="muted" style="margin-top:8px;">
Поля профиля основаны на контракте <code>ProfileOut</code> (gender, city, languages, interests, id, user_id).
Если это чужой пользователь, сервер может не возвращать профиль, поэтому часть значений будет пустой. :contentReference[oaicite:1]{index=1}
</p>
</section>
</div>
</main>

View File

@@ -1,4 +1,4 @@
{% load static %}
{% load static ui_extras %}
<!DOCTYPE html>
<html lang="ru">
<head>
@@ -11,6 +11,7 @@
.topbar { display:flex; gap:16px; align-items:center; padding:14px 18px; background:#111827; color:#fff; }
.topbar a { color:#cfe3ff; text-decoration:none; }
.container { max-width:1100px; margin:24px auto; padding:0 16px; }
.messages { list-style:none; padding:0; margin:0 0 16px; }
.messages li { padding:10px 12px; margin-bottom:8px; border-radius:10px; }
.messages li.success { background:#ecfdf5; color:#065f46; border:1px solid #a7f3d0; }
@@ -18,43 +19,37 @@
.messages li.info { background:#eff6ff; color:#1e40af; border:1px solid #bfdbfe; }
.filters { display:grid; grid-template-columns: repeat(8, 1fr); gap:10px; background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:14px; }
.filters .full { grid-column: 1 / -1; }
.filters input[type="text"], .filters select {
width:100%; border:1px solid #d1d5db; border-radius:8px; padding:8px 10px; font:inherit; background:#fff;
.filters input[type="text"], .filters select { width:100%; border:1px solid #d1d5db; border-radius:8px; padding:8px 10px; font:inherit; background:#fff; }
/* Фото‑карточки */
.list { margin-top:16px; display:grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap:14px; }
.card-photo { position:relative; display:block; border-radius:14px; overflow:hidden; border:1px solid #e5e7eb; background:#e5e7eb; }
.card-photo img { width:100%; height:280px; object-fit:cover; display:block; }
.card-photo__overlay {
position:absolute; left:0; right:0; bottom:0;
padding:12px 14px;
background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.65) 90%);
color:#fff;
}
.btn { display:inline-block; padding:9px 12px; border-radius:10px; border:1px solid #d1d5db; background:#fff; cursor:pointer; font-weight:600; }
.btn-primary { background:#2563eb; color:#fff; border-color:#2563eb; }
.list { margin-top:16px; display:grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap:14px; }
.card { background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:14px; }
.row { display:flex; align-items:center; gap:12px; }
.grow { flex:1 1 auto; }
.muted { color:#6b7280; font-size:14px; }
.pill { display:inline-block; padding:4px 10px; border-radius:999px; background:#eef2ff; color:#3730a3; font-size:12px; margin:2px 6px 2px 0; }
.meta { margin-top:8px; display:flex; flex-wrap:wrap; gap:10px 18px; }
.meta .k { color:#6b7280; }
.meta .v { color:#111827; font-weight:600; }
.card-photo__title { font-weight:700; font-size:18px; text-shadow:0 1px 2px rgba(0,0,0,.4); }
.pagination { display:flex; gap:10px; margin:16px 0; align-items:center; }
.pagination a, .pagination span {
padding:8px 12px; border-radius:10px; border:1px solid #d1d5db; text-decoration:none; color:#111;
background:#fff;
}
.pagination a, .pagination span { padding:8px 12px; border-radius:10px; border:1px solid #d1d5db; text-decoration:none; color:#111; background:#fff; }
.pagination .disabled { opacity:.5; pointer-events:none; }
.btn { padding:9px 12px; border-radius:10px; border:1px solid #d1d5db; background:#fff; cursor:pointer; font-weight:600; }
.muted { color:#6b7280; font-size:14px; }
.pill { display:inline-block; padding:4px 10px; border-radius:999px; background:#eef2ff; color:#3730a3; font-size:12px; margin:2px 6px 2px 0; }
</style>
</head>
<body>
<header class="topbar">
<div style="flex:1 1 auto;">
Каталог анкет (ADMIN)
</div>
<div style="flex:1 1 auto;">Каталог анкет (ADMIN)</div>
<nav style="display:flex; gap:14px;">
<a href="{% url 'index' %}">Главная</a>
<a href="{% url 'cabinet' %}">Кабинет</a>
<a href="{% url 'profiles' %}">Каталог</a>
<a href="{% url 'logout' %}">Выход</a>
<a href="{% url 'ui:index' %}">Главная</a>
<a href="{% url 'ui:cabinet' %}">Кабинет</a>
<a href="{% url 'ui:profiles' %}">Каталог</a>
<a href="{% url 'ui:logout' %}">Выход</a>
</nav>
</header>
@@ -68,7 +63,8 @@
</ul>
{% endif %}
<form class="filters" method="get" action="{% url 'profiles' %}">
<!-- Фильтры оставил: админ всё ещё может искать/сортировать -->
<form class="filters" method="get" action="{% url 'ui:profiles' %}">
<div>
<label class="muted">Поиск</label>
<input type="text" name="q" value="{{ filters.q }}" placeholder="имя или email">
@@ -81,25 +77,12 @@
<option value="ADMIN" {% if filters.role == "ADMIN" %}selected{% endif %}>ADMIN</option>
</select>
</div>
<div>
<label class="muted">Активность</label>
<select name="active">
<option value="">Любая</option>
<option value="1" {% if filters.active == "1" %}selected{% endif %}>Активные</option>
<option value="0" {% if filters.active == "0" %}selected{% endif %}>Неактивные</option>
</select>
</div>
<div>
<label class="muted">Домен</label>
<input type="text" name="domain" value="{{ filters.domain }}" placeholder="example.com">
</div>
<div>
<label class="muted">Сортировка</label>
<select name="sort">
<option value="name" {% if filters.sort == "name" %}selected{% endif %}>Имя ↑</option>
<option value="name_desc" {% if filters.sort == "name_desc" %}selected{% endif %}>Имя ↓</option>
<option value="email" {% if filters.sort == "email" %}selected{% endif %}>Email ↑</option>
<option value="email_desc" {% if filters.sort == "email_desc" %}selected{% endif %}>Email ↓</option>
</select>
</div>
<div>
@@ -115,7 +98,7 @@
<input type="text" name="page" value="{{ filters.page }}" style="width:90px;">
</div>
<div style="display:flex; align-items:flex-end;">
<button class="btn btn-primary" type="submit">Применить</button>
<button class="btn" type="submit">Применить</button>
</div>
</form>
@@ -139,33 +122,25 @@
<div class="messages"><li class="error">{{ error }}</li></div>
{% endif %}
<!-- Фото‑плитка карточек -->
<div class="list">
{% for p in profiles %}
<div class="card">
<div class="row">
<div class="grow">
<div style="font-weight:700; font-size:16px;">{{ p.name }}</div>
</div>
<div>
<form method="post" action="{% url 'like_profile' p.id %}">
{% csrf_token %}
{% include "ui/components/like_button.html" with profile_id=p.id liked=p.liked %}
</form>
<a class="card-photo" href="{% url 'ui:profile_detail' p.id %}" aria-label="Открыть {{ p.name }}">
{% if p.photo_url %}
<img src="{{ p.photo_url }}" alt="{{ p.name }}">
{% elif p.photo %}
<img src="{{ p.photo }}" alt="{{ p.name }}">
{% elif p.email %}
<img src="{{ p.email|gravatar_url:600 }}" alt="{{ p.name }}">
{% else %}
<img src="{% static 'img/profile_placeholder.jpg' %}" alt="{{ p.name }}">
{% endif %}
<div class="card-photo__overlay">
<div class="card-photo__title">
{{ p.name }}{% if p.age %}, {{ p.age }}{% endif %}
</div>
</div>
<div class="meta">
<div><span class="pill">{{ p.verified|yesno:"ACTIVE,INACTIVE" }}</span></div>
<div class="pill">{{ p.role|default:"USER" }}</div>
</div>
<div class="row" style="margin-top:10px;">
<div class="grow"></div>
<a class="btn" href="{% url 'profile_detail' p.id %}">Открыть</a>
</div>
</div>
</a>
{% empty %}
<div class="card">
<div class="muted">Ничего не найдено. Попробуйте изменить фильтры.</div>
@@ -190,6 +165,5 @@
</div>
</main>
</body>
</html>

View File

@@ -1,24 +1,44 @@
{% extends 'base.html' %}
{% block title %}Регистрация — MatchAgency{% endblock %}
{% block content %}
<div class="max-w-md mx-auto rounded-xl bg-white/80 backdrop-blur border shadow p-6">
<h1 class="text-xl font-semibold mb-4">Регистрация</h1>
<form action="" method="post" class="space-y-3">
{% csrf_token %}
<div>
<label class="block text-sm mb-1">Email</label>
<input type="email" name="email" required class="w-full rounded-md border px-3 py-2"/>
</div>
<div>
<label class="block text-sm mb-1">Пароль</label>
<input type="password" name="password" required class="w-full rounded-md border px-3 py-2"/>
</div>
<div>
<label class="block text-sm mb-1">Полное имя (необязательно)</label>
<input type="text" name="full_name" class="w-full rounded-md border px-3 py-2"/>
</div>
<button class="w-full rounded-md bg-indigo-600 text-white px-4 py-2 hover:bg-indigo-700">Зарегистрироваться</button>
</form>
<p class="mt-3 text-sm">Уже есть аккаунт? <a class="text-indigo-700 hover:underline" href="{% url 'login' %}">Войти</a></p>
</div>
{% endblock %}
{% load static %}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Регистрация</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="{% static 'style.css' %}" rel="stylesheet">
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, "Noto Sans", sans-serif; margin:0; background:#f7f7fb; color:#111; }
.wrap { max-width:520px; margin:60px auto; background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:18px; }
.form { display:grid; gap:12px; }
.form input { border:1px solid #d1d5db; border-radius:8px; padding:10px 12px; font:inherit; }
.btn { padding:10px 14px; border-radius:10px; background:#2563eb; color:#fff; border:none; cursor:pointer; font-weight:600; }
.muted { color:#6b7280; }
a { color:#2563eb; text-decoration:none; }
</style>
</head>
<body>
<div class="wrap">
<h1>Регистрация</h1>
{% if messages %}
<ul>
{% for message in messages %}
<li class="{{ message.tags }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<form class="form" method="post" action="{% url 'ui:register' %}">
{% csrf_token %}
<input type="text" name="full_name" placeholder="Имя / ФИО">
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Пароль" required>
<button class="btn" type="submit">Создать аккаунт</button>
</form>
<p class="muted" style="margin-top:10px;">Уже есть аккаунт?
<a href="{% url 'ui:login' %}">Войти</a>
</p>
</div>
</body>
</html>