447 lines
16 KiB
HTML
447 lines
16 KiB
HTML
{% extends "base.html" %}
|
||
{% import "/components/header.html" as header %}
|
||
{% import "/components/footer.html" as footer %}
|
||
{% import "profile/competition/select.html" as card %}
|
||
{% import "components/profile_card.html" as profile_card %}
|
||
{% import "components/profile_nav.html" as profile_nav %}
|
||
{% import "components/checkbox.html" as checkbox %}
|
||
|
||
{% block title %}Отправка заявки на конкурс{% endblock %}
|
||
|
||
|
||
{% block content %}
|
||
{{ header.section(user) }}
|
||
<section class="c_dg3k4">
|
||
<div class="c_gtoe9">
|
||
{{ profile_card.profile_card(user) }}
|
||
<div class="c_ru432">
|
||
{{ profile_nav.profile_nav(user, 'competitions') }}
|
||
<div class="c_3ihvi" id="main"><h2>{{competition.title}}</h2><p>{{competition.topic}}</p><p>Минимальное кол-во картин: {{competition.min_paintings}}. Максимальное кол-во картин: {{competition.max_paintings}}</p>
|
||
<a class="c_ctfod b_289i1" href="{% if competition.get('rules_link') %}{{competition.rules_link}}{% else %}/b/competition/{{ competition.rules_file_id }}{% endif %}" rel="noopener noreferrer"><span class="c_d2l0b b_al8e7">Условия конкурса</span></a>
|
||
<a class="c_ctfod b_289i1" href="/form/post?next=/profile/competition/{{ competition._id }}"><span class="c_d2l0b b_al8e7">Загрузить картину в список</span></a>
|
||
</div>
|
||
<div class="c_3ihvi"><h3>Выбрано картин: <span id="selected_span">0</span></h3>
|
||
</div>
|
||
<div class="c_3ihvi">
|
||
|
||
<label class="offer-label">
|
||
<input type="checkbox" id="acceptOffer" class="offer-checkbox">
|
||
<span class="offer-text">Принять условия конкурса</span>
|
||
</label>
|
||
{% if competition.user._id == '681684390000000000000000' %}
|
||
<a class="c_ctfod b_289i1" href="https://vk.ru/im?sel=25126640" rel="noopener noreferrer">
|
||
<span class="c_d2l0b b_al8e7">Задать вопрос в VK</span>
|
||
</a>
|
||
{% endif %}
|
||
<button class="c_ctfod b_289i1" id="sendForm">
|
||
<span class="c_d2l0b b_al8e7">Отправить заявку</span>
|
||
</button>
|
||
|
||
</div>
|
||
<div class="c_3ihvi">
|
||
{% if posts|length > 5 %}
|
||
<div class="inptS">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M19.6 21L13.3 14.7C12.8 15.1 12.225 15.4167 11.575 15.65C10.925 15.8833 10.2333 16 9.5 16C7.68333 16 6.14583 15.3708 4.8875 14.1125C3.62917 12.8542 3 11.3167 3 9.5C3 7.68333 3.62917 6.14583 4.8875 4.8875C6.14583 3.62917 7.68333 3 9.5 3C11.3167 3 12.8542 3.62917 14.1125 4.8875C15.3708 6.14583 16 7.68333 16 9.5C16 10.2333 15.8833 10.925 15.65 11.575C15.4167 12.225 15.1 12.8 14.7 13.3L21 19.6L19.6 21ZM9.5 14C10.75 14 11.8125 13.5625 12.6875 12.6875C13.5625 11.8125 14 10.75 14 9.5C14 8.25 13.5625 7.1875 12.6875 6.3125C11.8125 5.4375 10.75 5 9.5 5C8.25 5 7.1875 5.4375 6.3125 6.3125C5.4375 7.1875 5 8.25 5 9.5C5 10.75 5.4375 11.8125 6.3125 12.6875C7.1875 13.5625 8.25 14 9.5 14Z" fill="#1F1F1F"></path>
|
||
</svg>
|
||
<input id="memberSearch" list="membersList" type="search" placeholder="Поиск ..." aria-label="Поиск" />
|
||
<datalist id="membersList">
|
||
{% for post in posts %}
|
||
<option value="{{ post.title or '' }}"></option>
|
||
{% endfor %}
|
||
</datalist>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
|
||
|
||
<div class="c_tihtk">
|
||
{% for post in posts %}
|
||
{{ card.section(post, competition.get('nominations',[])) }}
|
||
{% endfor %}
|
||
|
||
</div>
|
||
<script>
|
||
(function(){
|
||
const input = document.getElementById('memberSearch');
|
||
const container = document.querySelector('.c_tihtk');
|
||
if (!input || !container) return;
|
||
|
||
const itemsSelector = '.c_tihtk > figure';
|
||
const nameSelector = '.c_n5g0e';
|
||
const norm = s => (s || '').toString().trim().toLowerCase();
|
||
|
||
function filter() {
|
||
const q = norm(input.value);
|
||
const items = container.querySelectorAll(itemsSelector);
|
||
items.forEach(a => {
|
||
const nameEl = a.querySelector(nameSelector);
|
||
const name = norm(nameEl ? nameEl.textContent : '');
|
||
a.style.display = (q === '' || name.indexOf(q) !== -1) ? '' : 'none';
|
||
});
|
||
}
|
||
|
||
input.addEventListener('input', filter);
|
||
input.addEventListener('change', filter);
|
||
input.addEventListener('keydown', (e) => { if (e.key === 'Escape') { input.value = ''; filter(); } });
|
||
|
||
// initial: show all
|
||
filter();
|
||
})();
|
||
</script>
|
||
<script>
|
||
document.querySelectorAll('.post').forEach(postEl => {
|
||
const fg = postEl.querySelector('.c_nb6gy.floating-group');
|
||
if (!fg) return;
|
||
|
||
// ensure paragraph exists immediately before the floating-group
|
||
let prev = fg.previousElementSibling;
|
||
let p;
|
||
if (prev && prev.classList && prev.classList.contains('post-description')) {
|
||
p = prev;
|
||
} else {
|
||
p = document.createElement('p');
|
||
p.className = 'post-description c_t5wnp b_mjekb';
|
||
p.textContent = ''; // initially empty
|
||
fg.parentNode.insertBefore(p, fg);
|
||
}
|
||
|
||
const textarea = fg.querySelector('textarea[name="description"]');
|
||
const button = postEl.querySelector('.saves[data-post_id]');
|
||
|
||
if (!button) return;
|
||
|
||
button.addEventListener('click', async () => {
|
||
const postId = button.getAttribute('data-post_id');
|
||
const text = textarea ? textarea.value.trim() : '';
|
||
|
||
try {
|
||
p.textContent = text;
|
||
fg.style.display = 'none';
|
||
button.style.display = 'none';
|
||
const res = await fetch('/api/post/description', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ post_id: postId, description: text })
|
||
});
|
||
if (!res.ok) throw new Error('Network response was not ok');
|
||
|
||
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<script defer>
|
||
const min_paintings = parseInt('{{competition.min_paintings}}')
|
||
const max_paintings = parseInt('{{competition.max_paintings}}')
|
||
const competition_id = '{{competition._id}}'
|
||
|
||
let selectedPosts = [];
|
||
function selectNom(el) {
|
||
console.log(el)
|
||
el.parentElement.classList.remove('error');
|
||
}
|
||
function saveCompetitionIds(postId) {
|
||
const key = 'competition_ids'+competition_id;
|
||
|
||
const stored = localStorage.getItem(key);
|
||
const ids = stored ? JSON.parse(stored) : [];
|
||
|
||
if (!ids.includes(postId)) {
|
||
ids.push(postId);
|
||
localStorage.setItem(key, JSON.stringify(ids));
|
||
}
|
||
}
|
||
function removeCompetitionId(postId) {
|
||
const key = 'competition_ids' + competition_id;
|
||
|
||
const stored = localStorage.getItem(key);
|
||
// If nothing is stored yet, there’s nothing to remove
|
||
if (!stored) return;
|
||
|
||
const ids = JSON.parse(stored);
|
||
const index = ids.indexOf(postId);
|
||
|
||
// Only act if the ID is actually present
|
||
if (index !== -1) {
|
||
ids.splice(index, 1); // delete the element
|
||
localStorage.setItem(key, JSON.stringify(ids));
|
||
}
|
||
}
|
||
function loadAndClick() {
|
||
const key = 'competition_ids'+competition_id;
|
||
const stored = localStorage.getItem(key);
|
||
if (!stored) return;
|
||
|
||
const ids = JSON.parse(stored);
|
||
const container = document.querySelector('.c_tihtk');
|
||
|
||
ids.forEach(postId => {
|
||
const figure = container.querySelector(`figure[data-post-id="${postId}"]`);
|
||
if (!figure) return;
|
||
|
||
const button = figure.querySelector('button.c_kg401');
|
||
if (button) button.click();
|
||
});
|
||
}
|
||
|
||
|
||
const container = document.querySelector('.c_tihtk');
|
||
const sendFormBtn = document.getElementById('sendForm');
|
||
|
||
|
||
function checkAllDescriptions() {
|
||
for (let postId of selectedPosts) {
|
||
const figure = container.querySelector(`figure[data-post-id="${postId}"]`);
|
||
if (!figure) continue;
|
||
const select = figure.querySelector('select');
|
||
const textarea = figure.querySelector('textarea');
|
||
if (select && !select.value.trim()) {
|
||
const floatingGroup = select.parentElement;
|
||
if (floatingGroup) floatingGroup.classList.add('error');
|
||
|
||
select.focus();
|
||
|
||
showToast({message: 'Пожалуйста, выберите номинацию!', status: 'error'});
|
||
return false;
|
||
}
|
||
if (textarea && !textarea.value.trim()) {
|
||
// Подсвечиваем ошибку
|
||
const floatingGroup = figure.querySelector('.floating-group');
|
||
if (floatingGroup) floatingGroup.classList.add('error');
|
||
|
||
textarea.focus();
|
||
|
||
showToast({message: 'Пожалуйста, заполните описание для всех выбранных постов!', status: 'error'});
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
container.addEventListener('click', (e) => {
|
||
const btn = e.target.closest('button.c_kg401');
|
||
if (!btn) return;
|
||
|
||
const figure = btn.closest('figure.c_45o96');
|
||
if (!figure) return;
|
||
|
||
const postId = figure.dataset.postId;
|
||
if (!postId) return;
|
||
|
||
const textarea = figure.querySelector('textarea');
|
||
const select = figure.querySelector('select');
|
||
const floatingGroup = figure.querySelector('.floating-group');
|
||
|
||
|
||
// === Если карточка уже выбрана — отменяем выбор ===
|
||
if (selectedPosts.includes(postId)) {
|
||
// Снимаем выбор
|
||
selectedPosts = selectedPosts.filter(id => id !== postId);
|
||
selected_span.textContent = selectedPosts.length
|
||
removeCompetitionId(postId)
|
||
|
||
// Возвращаем кнопку в исходное состояние
|
||
btn.style.backgroundColor = '';
|
||
btn.style.border = '1px solid #5F1798';
|
||
btn.querySelector('svg').querySelectorAll('rect')[0].style.fill='#5F1798'
|
||
btn.querySelector('svg').querySelectorAll('rect')[1].style.stroke='#5F1798'
|
||
|
||
const span = btn.querySelector('span.c_shz86');
|
||
if (span) span.textContent = 'Выбрать';
|
||
if (textarea) {textarea.parentNode.classList.remove('error');}
|
||
if (select) {select.parentNode.classList.remove('error');}
|
||
|
||
return;
|
||
}
|
||
|
||
// === Новая выборка ===
|
||
if (textarea) {
|
||
if (!textarea.value.trim()) {
|
||
floatingGroup.classList.add('error');
|
||
textarea.focus();
|
||
} else {
|
||
floatingGroup.classList.remove('error');
|
||
}
|
||
}
|
||
if (select) {
|
||
if (!select.value.trim()) {
|
||
select.parentNode.classList.add('error');
|
||
// select.focus();
|
||
} else {
|
||
select.parentNode.classList.remove('error');
|
||
}
|
||
}
|
||
if (selectedPosts.length === max_paintings) {
|
||
showToast({message: `Максимальное кол-во картин: ${max_paintings}`, status: 'error'});
|
||
return;
|
||
}
|
||
|
||
selectedPosts.push(postId);
|
||
selected_span.textContent = selectedPosts.length
|
||
|
||
// Меняем кнопку
|
||
btn.style.backgroundColor = 'rgba(14, 214, 70, 0.10)';
|
||
btn.style.border = '1px solid #0ED646';
|
||
btn.querySelector('svg').querySelectorAll('rect')[0].style.fill='#0ED646'
|
||
btn.querySelector('svg').querySelectorAll('rect')[1].style.stroke='#0ED646'
|
||
saveCompetitionIds(postId);
|
||
|
||
const span = btn.querySelector('span.c_shz86');
|
||
if (span) span.textContent = 'Выбрано';
|
||
|
||
});
|
||
|
||
// === Отправка формы ===
|
||
if (sendFormBtn) {
|
||
sendFormBtn.addEventListener('click', () => {
|
||
if (selectedPosts.length < min_paintings) {
|
||
showToast({message: `Минимальное кол-во картин: ${min_paintings}`, status: 'error'});
|
||
return;
|
||
}
|
||
|
||
const offerAccepted = document.getElementById('acceptOffer')?.checked;
|
||
if (!offerAccepted) {
|
||
showToast({message: 'Необходимо принять оферту', status: 'error'});
|
||
return;
|
||
}
|
||
if (!checkAllDescriptions()) {
|
||
return;
|
||
}
|
||
|
||
const postsToSend = selectedPosts.map(postId => {
|
||
const nominationEl = document.querySelector(`#nom_${postId}`);
|
||
const nom_name = nominationEl?.value?.trim() || null;
|
||
|
||
const textarea = container.querySelector(`figure[data-post-id="${postId}"] textarea`);
|
||
|
||
const doc = {
|
||
postId: postId,
|
||
description: textarea ? textarea.value.trim() : null,
|
||
};
|
||
|
||
if (nom_name) doc.nomination = nom_name;
|
||
|
||
return doc;
|
||
});
|
||
|
||
// Отправляем на сервер
|
||
fetch('/api/send_competition_request/'+competition_id, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
posts: postsToSend
|
||
})
|
||
})
|
||
.then(res => {
|
||
if (res.ok) {
|
||
window.location.href='/competition/{{competition._id}}#{{user._id}}'
|
||
sendFormBtn.disabled = true;
|
||
} else {
|
||
showToast({message: 'Ошибка при отправке заявки', status: 'error'});
|
||
}
|
||
})
|
||
.catch(() => {
|
||
showToast({message: 'Ошибка соединения с сервером', status: 'error'});
|
||
});
|
||
});
|
||
}
|
||
|
||
// Дополнительно: обновляем состояние кнопки при вводе текста в любое textarea
|
||
container.addEventListener('input', (e) => {
|
||
if (e.target.tagName === 'TEXTAREA') {
|
||
const floatingGroup = e.target.closest('.floating-group');
|
||
if (floatingGroup) {
|
||
if (e.target.value.trim()) {
|
||
floatingGroup.classList.remove('error');
|
||
}
|
||
}
|
||
updateSendButtonState();
|
||
}
|
||
});
|
||
|
||
</script>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<script defer>
|
||
loadAndClick();
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const avatarInput = document.getElementById('avatar');
|
||
const preview = document.getElementById('preview');
|
||
|
||
avatarInput.addEventListener('change', async () => {
|
||
if (avatarInput.files.length === 0) return;
|
||
|
||
const file = avatarInput.files[0];
|
||
preview.src = URL.createObjectURL(file);
|
||
|
||
const form = new FormData();
|
||
form.append('avatar', file);
|
||
|
||
try {
|
||
const resp = await fetch('/api/avatar', {
|
||
method: 'POST',
|
||
body: form
|
||
});
|
||
|
||
if (!resp.ok) {
|
||
const text = await resp.text();
|
||
throw new Error(`Upload failed: ${resp.status} ${text}`);
|
||
}
|
||
|
||
const data = await resp.json();
|
||
if (data.url) preview.src = data.url;
|
||
console.log('Upload success', data);
|
||
} catch (err) {
|
||
console.error(err);
|
||
} finally {
|
||
URL.revokeObjectURL(preview.src);
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
{{ footer.section(user) }}
|
||
{% endblock %}
|
||
|
||
{% block head %}
|
||
{{ header.style() }}
|
||
{{ card.style() }}
|
||
{{ profile_nav.style() }}
|
||
{{ checkbox.style() }}
|
||
<style>
|
||
.inptS{height: 48px;
|
||
display: flex; width: 335px;
|
||
align-items: center;gap: 10px; border-bottom: 1px solid #5F1798;}
|
||
|
||
/* Mobile */
|
||
@media (max-width: 699px) {
|
||
.c_ctfod{width: 100%;
|
||
box-sizing: border-box;}
|
||
.c_mrl59 { align-items: center; background: #5F1798; border-radius: 25px; bottom: -15px; display: flex; height: 50px; justify-content: center; position: absolute; right: -15px; width: 50px; }
|
||
|
||
|
||
|
||
}
|
||
|
||
/* Tablet */
|
||
@media (min-width: 700px) and (max-width: 900px) {
|
||
.c_ctfod{width: 335px;
|
||
box-sizing: border-box;}.c_nnsh6>a>img{display: none;}
|
||
.c_iiehk { align-items: center; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 335px; }
|
||
|
||
}
|
||
|
||
/* Desktop */
|
||
@media (min-width: 901px) {
|
||
.c_ctfod{width: 335px;
|
||
box-sizing: border-box;}.c_nnsh6>a>img{display: none;}
|
||
.c_iiehk { align-items: flex-end; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 310px; }
|
||
|
||
}</style>
|
||
{{ footer.style() }}
|
||
{% endblock %} |