diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -27,16 +27,22 @@ def load_data(): data = json.load(file) if not isinstance(data, dict): logging.warning("Данные не в формате dict, инициализация пустой базы") - return {'posts': [], 'users': {}} + return {'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}, 'user_orders': {}} if 'posts' not in data: data['posts'] = [] if 'users' not in data: data['users'] = {} + if 'pending_organizations' not in data: + data['pending_organizations'] = [] + if 'orders' not in data: + data['orders'] = {} + if 'user_orders' not in data: + data['user_orders'] = {} logging.info("Данные успешно загружены") return data except Exception as e: logging.error(f"Ошибка загрузки данных: {e}") - return {'posts': [], 'users': {}} + return {'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}, 'user_orders': {}} def save_data(data): try: @@ -78,7 +84,7 @@ def download_db_from_hf(): logging.error(f"Ошибка скачивания базы: {e}") if not os.path.exists(DATA_FILE): with open(DATA_FILE, 'w', encoding='utf-8') as f: - json.dump({'posts': [], 'users': {}}, f) + json.dump({'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}, 'user_orders': {}}, f) def periodic_backup(): while True: @@ -99,6 +105,7 @@ BASE_STYLE = ''' --shadow: 0 10px 40px rgba(0, 0, 0, 0.15); --glass-bg: rgba(255, 255, 255, 0.15); --transition: all 0.4s ease; + --cart-btn: #10b981; } * { margin: 0; padding: 0; box-sizing: border-box; } body { @@ -152,6 +159,7 @@ BASE_STYLE = ''' border-radius: 15px; font-size: 1.1em; transition: var(--transition); + position: relative; } body.dark .nav-link { background: var(--card-bg-dark); @@ -169,6 +177,21 @@ BASE_STYLE = ''' .logout-btn:hover { background: #e63970; } + .order-count { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); + background: var(--secondary); + color: white; + border-radius: 50%; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.9em; + } .menu-btn { display: none; font-size: 32px; @@ -206,7 +229,13 @@ BASE_STYLE = ''' transform: scale(1.08); background: #5439cc; } - input, textarea { + .cart-btn { + background: var(--cart-btn); + } + .cart-btn:hover { + background: #0d9f6e; + } + input, textarea, select { width: 100%; padding: 14px; margin: 15px 0; @@ -217,11 +246,11 @@ BASE_STYLE = ''' font-size: 1.1em; transition: var(--transition); } - body.dark input, body.dark textarea { + body.dark input, body.dark textarea, body.dark select { border: 2px solid rgba(255, 255, 255, 0.1); color: var(--text-dark); } - input:focus, textarea:focus { + input:focus, textarea:focus, select:focus { outline: none; border-color: var(--primary); background: rgba(255, 255, 255, 0.2); @@ -258,6 +287,68 @@ BASE_STYLE = ''' box-shadow: var(--shadow); transition: var(--transition); } + .cart-float { + position: fixed; + bottom: 20px; + right: 20px; + background: var(--cart-btn); + color: white; + border-radius: 50%; + width: 60px; + height: 60px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--shadow); + cursor: pointer; + z-index: 1000; + transition: var(--transition); + } + .cart-float:hover { + transform: scale(1.1); + background: #0d9f6e; + } + .cart-modal { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: var(--card-bg-light); + padding: 30px; + border-radius: 25px; + box-shadow: var(--shadow); + z-index: 2000; + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + } + body.dark .cart-modal { + background: var(--card-bg-dark); + } + .cart-modal h2 { + font-size: 1.8em; + margin-bottom: 20px; + background: linear-gradient(45deg, var(--primary), var(--secondary)); + -webkit-background-clip: text; + color: transparent; + } + .cart-item { + background: var(--glass-bg); + padding: 15px; + border-radius: 15px; + margin-bottom: 15px; + } + .cart-item h3 { + font-size: 1.2em; + margin-bottom: 10px; + } + .cart-total { + font-size: 1.2em; + font-weight: 600; + margin-top: 20px; + } @media (max-width: 768px) { .sidebar { transform: translateX(-100%); @@ -277,10 +368,16 @@ BASE_STYLE = ''' top: 80px; right: 15px; } - .btn, input, textarea { + .btn, input, textarea, select { font-size: 1em; padding: 12px; } + .cart-float { + bottom: 15px; + right: 15px; + width: 50px; + height: 50px; + } } ''' @@ -294,6 +391,10 @@ NAV_HTML = ''' {% if is_authenticated %} 👤 Профиль ({{ username }}) ⬆️ Загрузить + {% if user_type == 'seller' and verified %} + 🛒 Заказы {% if order_count > 0 %}{{ order_count }}{% endif %} + {% endif %} + 📦 Мои заказы 🚪 Выйти {% else %} 🔑 Войти @@ -307,19 +408,84 @@ NAV_HTML = ''' @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': + logging.debug(f"Получен POST-запрос: {request.form}") + username = request.form.get('username') password = request.form.get('password') + user_type = request.form.get('user_type') + phone = request.form.get('phone') + address = request.form.get('address') data = load_data() - + + if not username or not password or not phone or (user_type == 'buyer' and not address): + flash('Заполните все обязательные поля!', 'error') + logging.debug("Не все обязательные поля заполнены") + return redirect(url_for('register')) + if username in data['users']: - flash('Пользователь уже существует!') + flash('Пользователь уже существует!', 'error') + logging.debug(f"Пользователь {username} уже существует") return redirect(url_for('register')) - - data['users'][username] = {'password': password, 'bio': '', 'link': '', 'avatar': None} - save_data(data) - flash('Регистрация успешна! Войдите в систему.') - return redirect(url_for('login')) - + + if 'register_seller' in request.form: + logging.debug("Нажата кнопка регистрации продавца") + org_name = request.form.get('org_name') + org_phone = request.form.get('org_phone') + is_online = request.form.get('is_online') == 'on' + org_address = request.form.get('org_address') if not is_online else None + + if not org_name or not org_phone: + flash('Укажите название организации и рабочий номер!', 'error') + logging.debug("Не указаны org_name или org_phone") + return redirect(url_for('register')) + + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'type': 'seller', + 'verified': False, + 'phone': phone, + 'org_phone': org_phone, + 'org_address': org_address, + 'is_online': is_online + } + data['pending_organizations'].append({ + 'username': username, + 'org_name': org_name, + 'org_phone': org_phone, + 'org_address': org_address, + 'is_online': is_online, + 'submitted_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + save_data(data) + flash('Ваша заявка принята, мы с вами свяжемся в течение 2 суток', 'success') + logging.debug(f"Продавец {username} зарегистрирован и отправлен на верификацию") + return redirect(url_for('login')) + + elif 'register_buyer' in request.form: + logging.debug("Нажата кнопка регистрации покупателя") + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'type': 'buyer', + 'verified': True, + 'phone': phone, + 'address': address + } + save_data(data) + flash('Регистрация успешна! Войдите в систему.', 'success') + logging.debug(f"Покупатель {username} зарегистрирован") + return redirect(url_for('login')) + + else: + flash('Неизвестная ошибка при выборе типа пользователя!', 'error') + logging.debug("Ни одна из кнопок регистрации не была нажата") + return redirect(url_for('register')) + is_authenticated = 'username' in session username = session.get('username', None) html = ''' @@ -333,7 +499,7 @@ def register(): @@ -377,17 +559,30 @@ def register():

Регистрация

- {% with messages = get_flashed_messages() %} + {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} - {% for message in messages %} -
{{ message }}
+ {% for category, message in messages %} +
{{ message }}
{% endfor %} {% endif %} {% endwith %} +
+ + +
- + + +
+ + + + +
+ +
@@ -399,6 +594,14 @@ def register(): document.body.classList.toggle('dark'); localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); } + function toggleSellerFields(show) { + document.getElementById('seller-fields').style.display = show ? 'block' : 'none'; + document.getElementById('address_field').required = !show; + } + function toggleAddress() { + const isOnline = document.querySelector('input[name="is_online"]').checked; + document.getElementById('org_address').disabled = isOnline; + } window.onload = () => { if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark'); }; @@ -420,7 +623,7 @@ def login(): session['username'] = username session.permanent = True return redirect(url_for('feed')) - flash('Неверный логин или пароль!') + flash('Неверный логин или пароль!', 'error') return redirect(url_for('login')) is_authenticated = 'username' in session @@ -456,12 +659,17 @@ def login(): color: transparent; } .flash { - color: var(--secondary); text-align: center; margin-bottom: 20px; font-size: 1.1em; font-weight: 500; } + .flash.success { + color: #10b981; + } + .flash.error { + color: var(--secondary); + } .link { text-align: center; margin-top: 25px; @@ -480,10 +688,10 @@ def login():

Вход

- {% with messages = get_flashed_messages() %} + {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} - {% for message in messages %} -
{{ message }}
+ {% for category, message in messages %} +
{{ message }}
{% endfor %} {% endif %} {% endwith %} @@ -524,6 +732,9 @@ def feed(): posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True) is_authenticated = 'username' in session username = session.get('username', None) + user_type = data['users'].get(username, {}).get('type') if username else None + verified = data['users'].get(username, {}).get('verified', False) if username else False + order_count = len(data['orders'].get(username, [])) if user_type == 'seller' and verified else 0 search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else request.args.get('search', '').strip().lower() if search_query: @@ -627,12 +838,29 @@ def feed(): .username-link:hover { text-decoration: underline; } + .price { + font-weight: 600; + color: var(--primary); + } + .quantity-input { + width: 60px; + display: inline-block; + margin-right: 10px; + } ''' + NAV_HTML + ''' + +
+

Корзина

+
+

Итого: 0

+ + +

Лента публикаций

@@ -643,19 +871,22 @@ def feed():
{% for post in posts %} - - {% if post['type'] == 'video' %} +
+ - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

+

{{ post['title'] }}

+

{{ post['description'] }}

+

Цена: {{ post['price'] }} {{ post['currency'] }}

Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}

Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}

- + {% if is_authenticated and user_type == 'buyer' and verified %} + + + {% endif %} +
{% endfor %}
@@ -682,22 +913,93 @@ def feed(): document.getElementById('imageModal').style.display = 'none'; } } + function toggleCart() { + const cartModal = document.getElementById('cartModal'); + cartModal.style.display = cartModal.style.display === 'block' ? 'none' : 'block'; + renderCart(); + } + function addToCart(postId, title, price, currency, uploader) { + let cart = JSON.parse(localStorage.getItem('cart') || '[]'); + const quantity = parseInt(document.getElementById('quantity_' + postId).value) || 1; + const existingItem = cart.find(item => item.postId === postId); + if (existingItem) { + existingItem.quantity += quantity; + } else { + cart.push({ postId, title, price: parseFloat(price), currency, uploader, quantity }); + } + localStorage.setItem('cart', JSON.stringify(cart)); + document.getElementById('cartFloat').style.display = 'block'; + alert('Добавлено в корзину!'); + } + function renderCart() { + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + const cartItems = document.getElementById('cartItems'); + const cartTotal = document.getElementById('cartTotal'); + cartItems.innerHTML = ''; + let total = 0; + cart.forEach(item => { + const div = document.createElement('div'); + div.className = 'cart-item'; + const itemTotal = item.price * item.quantity; + total += itemTotal; + div.innerHTML = ` +

${item.title}

+

Цена: ${item.price} ${item.currency} x ${item.quantity} = ${itemTotal.toFixed(2)} ${item.currency}

+

Продавец: ${item.uploader}

+ + `; + cartItems.appendChild(div); + }); + const totalCurrency = cart.length > 0 ? cart[0].currency : ''; + cartTotal.textContent = `Итого: ${total.toFixed(2)} ${totalCurrency}`; + } + function removeFromCart(postId) { + let cart = JSON.parse(localStorage.getItem('cart') || '[]'); + cart = cart.filter(item => item.postId !== postId); + localStorage.setItem('cart', JSON.stringify(cart)); + if (cart.length === 0) document.getElementById('cartFloat').style.display = 'none'; + renderCart(); + } + function checkout() { + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + if (cart.length === 0) { + alert('Корзина пуста!'); + return; + } + const form = document.createElement('form'); + form.method = 'POST'; + form.action = '/profile'; + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'checkout'; + input.value = 'true'; + const cartData = document.createElement('input'); + cartData.type = 'hidden'; + cartData.name = 'cart_data'; + cartData.value = JSON.stringify(cart); + form.appendChild(input); + form.appendChild(cartData); + document.body.appendChild(form); + form.submit(); + localStorage.removeItem('cart'); + document.getElementById('cartFloat').style.display = 'none'; + } window.onload = () => { if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark'); + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + if (cart.length > 0) document.getElementById('cartFloat').style.display = 'block'; const videos = document.querySelectorAll('.post-preview'); videos.forEach(video => { - if (video.tagName === 'VIDEO') { - video.addEventListener('loadedmetadata', () => { - video.currentTime = Math.random() * video.duration; - }); - } + video.addEventListener('loadedmetadata', () => { + video.currentTime = Math.random() * video.duration; + }); }); }; ''' - return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query) + return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query, user_type=user_type, verified=verified, order_count=order_count) # Страница публикации @app.route('/post/', methods=['GET', 'POST']) @@ -709,6 +1011,9 @@ def post_page(post_id): is_authenticated = 'username' in session username = session.get('username', None) + user_type = data['users'].get(username, {}).get('type') if username else None + verified = data['users'].get(username, {}).get('verified', False) if username else False + order_count = len(data['orders'].get(username, [])) if user_type == 'seller' and verified else 0 post['views'] = post.get('views', 0) + 1 save_data(data) @@ -719,6 +1024,7 @@ def post_page(post_id): else: post['likes'] = [user for user in post.get('likes', []) if user != username] save_data(data) + elif 'comment' in request.form: comment_text = request.form.get('comment') if comment_text: @@ -754,7 +1060,7 @@ def post_page(post_id): -webkit-background-clip: text; color: transparent; } - video, img { + video { width: 100%; max-height: 500px; object-fit: cover; @@ -790,22 +1096,36 @@ def post_page(post_id): .username-link:hover { text-decoration: underline; } + .price { + font-weight: 600; + color: var(--primary); + } + .quantity-input { + width: 60px; + display: inline-block; + margin-right: 10px; + } ''' + NAV_HTML + ''' + +
+

Корзина

+
+

Итого: 0

+ + +

{{ post['title'] }}

- {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} +

{{ post['description'] }}

+

Цена: {{ post['price'] }} {{ post['currency'] }}

Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}

Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}

{% if is_authenticated %} @@ -814,6 +1134,10 @@ def post_page(post_id): {% if username in post['likes'] %}Убрать лайк{% else %}Лайк{% endif %} + {% if user_type == 'buyer' and verified %} + + + {% endif %}
@@ -830,9 +1154,6 @@ def post_page(post_id):

Войдите, чтобы ставить лайки и комментировать.

{% endif %}
- ''' - return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username) + return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username, user_type=user_type, verified=verified, order_count=order_count) # Профиль пользователя @app.route('/profile', methods=['GET', 'POST']) def profile(): if 'username' not in session: - flash('Войдите, чтобы просмотреть профиль!') + flash('Войдите, чтобы просмотреть профиль!', 'error') return redirect(url_for('login')) data = load_data() @@ -879,6 +1262,14 @@ def profile(): bio = user_data.get('bio', '') link = user_data.get('link', '') avatar = user_data.get('avatar', None) + user_type = user_data.get('type', 'buyer') + verified = user_data.get('verified', False) + phone = user_data.get('phone', '') + address = user_data.get('address', '') + org_phone = user_data.get('org_phone', '') + org_address = user_data.get('org_address', '') + is_online = user_data.get('is_online', False) + order_count = len(data['orders'].get(username, [])) if user_type == 'seller' and verified else 0 if request.method == 'POST': if 'delete_post' in request.form: @@ -890,6 +1281,10 @@ def profile(): elif 'update_profile' in request.form: bio = request.form.get('bio', '') link = request.form.get('link', '') + phone = request.form.get('phone', '') + address = request.form.get('address', '') if user_type == 'buyer' else address + org_phone = request.form.get('org_phone', '') if user_type == 'seller' else org_phone + org_address = request.form.get('org_address', '') if user_type == 'seller' and not is_online else None avatar_file = request.files.get('avatar') if avatar_file and avatar_file.filename: filename = secure_filename(avatar_file.filename) @@ -911,8 +1306,45 @@ def profile(): os.remove(temp_path) data['users'][username]['bio'] = bio data['users'][username]['link'] = link + data['users'][username]['phone'] = phone + if user_type == 'buyer': + data['users'][username]['address'] = address + if user_type == 'seller' and verified: + data['users'][username]['org_phone'] = org_phone + data['users'][username]['org_address'] = org_address save_data(data) return redirect(url_for('profile')) + elif 'checkout' in request.form: + cart = json.loads(request.form.get('cart_data', '[]')) + if cart: + if not phone or (user_type == 'buyer' and not address): + flash('Укажите номер телефона и адрес доставки в профиле перед оформлением заказа!', 'error') + return redirect(url_for('profile')) + for item in cart: + seller = item['uploader'] + order_id = f"order_{int(time.time())}_{random.randint(1, 1000)}" + order_data = { + 'order_id': order_id, + 'buyer': username, + 'post_id': item['postId'], + 'title': item['title'], + 'price': item['price'], + 'currency': item['currency'], + 'quantity': item['quantity'], + 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'status': 'pending', + 'buyer_phone': phone, + 'buyer_address': address if user_type == 'buyer' else None + } + if seller not in data['orders']: + data['orders'][seller] = [] + data['orders'][seller].append(order_data) + if username not in data['user_orders']: + data['user_orders'][username] = [] + data['user_orders'][username].append(order_data) + save_data(data) + flash('Заказ успешно оформлен!', 'success') + return redirect(url_for('profile')) html = ''' @@ -960,12 +1392,12 @@ def profile(): font-size: 1.2em; margin-bottom: 15px; } - .post-grid { + .post-grid, .cart-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 30px; } - .post-item { + .post-item, .cart-item { background: var(--card-bg-light); backdrop-filter: blur(20px); padding: 25px; @@ -973,10 +1405,10 @@ def profile(): box-shadow: var(--shadow); transition: var(--transition); } - body.dark .post-item { + body.dark .post-item, body.dark .cart-item { background: var(--card-bg-dark); } - .post-item:hover { + .post-item:hover, .cart-item:hover { transform: translateY(-15px); } .post-preview { @@ -986,7 +1418,7 @@ def profile(): border-radius: 15px; margin-bottom: 20px; } - .post-item h3 { + .post-item h3, .cart-item h3 { font-size: 1.5em; margin-bottom: 15px; } @@ -1009,12 +1441,30 @@ def profile(): -webkit-background-clip: text; color: transparent; } + .price { + font-weight: 600; + color: var(--primary); + } + .remove-cart-btn { + background: #ef4444; + } + .remove-cart-btn:hover { + background: #dc2626; + } ''' + NAV_HTML + ''' + +
+

Корзина

+
+

Итого: 0

+ + +
{% if avatar %} @@ -1026,6 +1476,15 @@ def profile(): {% if link %}

{{ link }}

{% endif %} +

Тип: {{ 'Продавец' if user_type == 'seller' else 'Покупатель' }} {% if user_type == 'seller' and not verified %}(На проверке){% endif %}

+

Телефон: {{ phone }}

+ {% if user_type == 'buyer' %} +

Адрес доставки: {{ address }}

+ {% endif %} + {% if user_type == 'seller' and verified %} +

Рабочий номер: {{ org_phone }}

+

Адрес организации: {{ org_address if org_address else 'Онлайн' }}

+ {% endif %}

Просмотров: {{ total_views }} | Лайков: {{ total_likes }}

@@ -1034,26 +1493,46 @@ def profile(): + + {% if user_type == 'buyer' %} + + {% endif %} + {% if user_type == 'seller' and verified %} + + {% if not is_online %} + + {% endif %} + {% endif %} - Добавить публикацию -

Ваши публикации

+ {% if user_type == 'seller' and verified %} + Добавить видео + {% endif %} +

Корзина

+
+

Итого: 0

+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} +

Ваши видео

{% if user_posts %} {% for post in user_posts %}
- {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} +

{{ post['title'] }}

{{ post['description'] }}

+

Цена: {{ post['price'] }} {{ post['currency'] }}

{{ post['upload_date'] }}

@@ -1062,13 +1541,10 @@ def profile():
{% endfor %} {% else %} -

Вы пока не загрузили ни одной публикации.

+

Вы пока не загрузили ни одного видео.

{% endif %}
- ''' - return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated) + return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated, user_type=user_type, verified=verified, phone=phone, address=address, org_phone=org_phone, org_address=org_address, is_online=is_online, order_count=order_count) # Профиль другого пользователя @app.route('/profile/') @@ -1120,12 +1648,21 @@ def user_profile(username): user_posts = sorted([p for p in data['posts'] if p['uploader'] == username], key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True) is_authenticated = 'username' in session + current_user = session.get('username') total_views = sum(post.get('views', 0) for post in user_posts) total_likes = sum(len(post.get('likes', [])) for post in user_posts) user_data = data['users'].get(username, {}) bio = user_data.get('bio', '') link = user_data.get('link', '') avatar = user_data.get('avatar', None) + user_type = user_data.get('type', 'buyer') + verified = user_data.get('verified', False) + phone = user_data.get('phone', '') + address = user_data.get('address', '') + org_phone = user_data.get('org_phone', '') + org_address = user_data.get('org_address', '') + is_online = user_data.get('is_online', False) + order_count = len(data['orders'].get(current_user, [])) if data['users'].get(current_user, {}).get('type') == 'seller' and data['users'].get(current_user, {}).get('verified', False) else 0 html = ''' @@ -1216,12 +1753,24 @@ def user_profile(username): -webkit-background-clip: text; color: transparent; } + .price { + font-weight: 600; + color: var(--primary); + } ''' + NAV_HTML + ''' + +
+

Корзина

+
+

Итого: 0

+ + +
{% if avatar %} @@ -1233,37 +1782,40 @@ def user_profile(username): {% if link %}

{{ link }}

{% endif %} +

Тип: {{ 'Продавец' if user_type == 'seller' else 'Покупатель' }} {% if user_type == 'seller' and not verified %}(На проверке){% endif %}

+

Телефон: {{ phone }}

+ {% if user_type == 'buyer' %} +

Адрес доставки: {{ address }}

+ {% endif %} + {% if user_type == 'seller' and verified %} +

Рабочий номер: {{ org_phone }}

+

Адрес организации: {{ org_address if org_address else 'Онлайн' }}

+ {% endif %}

Просмотров: {{ total_views }} | Лайков: {{ total_likes }}

-

Публикации

+

Видео

{% if user_posts %} {% for post in user_posts %}
- {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} +

{{ post['title'] }}

{{ post['description'] }}

+

Цена: {{ post['price'] }} {{ post['currency'] }}

{{ post['upload_date'] }}

{% endfor %} {% else %} -

Этот пользователь пока не загрузил ни одной публикации.

+

Этот пользователь пока не загрузил ни одного видео.

{% endif %}
- ''' - return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated) + return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated, user_type=user_type, verified=verified, phone=phone, address=address, org_phone=org_phone, org_address=org_address, is_online=is_online, order_count=order_count) # Загрузка контента @app.route('/upload', methods=['GET', 'POST']) def upload(): if 'username' not in session: - flash('Войдите, чтобы загрузить контент!') + flash('Войдите, чтобы загрузить контент!', 'error') return redirect(url_for('login')) + data = load_data() + username = session['username'] + user_data = data['users'].get(username, {}) + if user_data.get('type') != 'seller' or not user_data.get('verified'): + flash('Только проверенные продавцы могут загружать видео!', 'error') + return redirect(url_for('profile')) + if request.method == 'POST': title = request.form.get('title') description = request.form.get('description') + price = request.form.get('price') + currency = request.form.get('currency') file = request.files.get('file') uploader = session['username'] - if not title or not file: - return "Укажите название и выберите файл", 400 + if not title or not price or not currency or not file: + flash('Укажите название, цену, валюту и выберите видео!', 'error') + return redirect(url_for('upload')) + + if not file.filename.lower().endswith(('.mp4', '.mov', '.avi')): + flash('Загружайте только видео файлы (mp4, mov, avi)!', 'error') + return redirect(url_for('upload')) filename = secure_filename(file.filename) temp_path = os.path.join('uploads', filename) os.makedirs('uploads', exist_ok=True) file.save(temp_path) - file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo' api = HfApi() api.upload_file( path_or_fileobj=temp_path, - path_in_repo=f"{file_type}s/{filename}", + path_in_repo=f"videos/{filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, - commit_message=f"Добавлена публикация: {title} пользователем {uploader}" + commit_message=f"Добавлено видео: {title} пользователем {uploader}" ) data = load_data() @@ -1347,12 +1958,14 @@ def upload(): 'title': title, 'description': description, 'filename': filename, - 'type': file_type, + 'type': 'video', 'uploader': uploader, 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'views': 0, 'likes': [], - 'comments': [] + 'comments': [], + 'price': price, + 'currency': currency }) save_data(data) @@ -1362,13 +1975,16 @@ def upload(): is_authenticated = 'username' in session username = session.get('username', None) + user_type = user_data.get('type') + verified = user_data.get('verified', False) + order_count = len(data['orders'].get(username, [])) html = ''' - Загрузка контента + Загрузка видео ''' + NAV_HTML + ''' + +
+

Корзина

+
+

Итого: 0

+ + +
-

Загрузить контент

+

Загрузить видео

+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} - + + +
@@ -1434,6 +2087,64 @@ def upload(): document.body.classList.toggle('dark'); localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); } + function toggleCart() { + const cartModal = document.getElementById('cartModal'); + cartModal.style.display = cartModal.style.display === 'block' ? 'none' : 'block'; + renderCart(); + } + function renderCart() { + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + const cartItems = document.getElementById('cartItems'); + const cartTotal = document.getElementById('cartTotal'); + cartItems.innerHTML = ''; + let total = 0; + cart.forEach(item => { + const itemTotal = item.price * item.quantity; + total += itemTotal; + const div = document.createElement('div'); + div.className = 'cart-item'; + div.innerHTML = ` +

${item.title}

+

Цена: ${item.price} ${item.currency} x ${item.quantity} = ${itemTotal.toFixed(2)} ${item.currency}

+

Продавец: ${item.uploader}

+ + `; + cartItems.appendChild(div); + }); + const totalCurrency = cart.length > 0 ? cart[0].currency : ''; + cartTotal.textContent = `Итого: ${total.toFixed(2)} ${totalCurrency}`; + } + function removeFromCart(postId) { + let cart = JSON.parse(localStorage.getItem('cart') || '[]'); + cart = cart.filter(item => item.postId !== postId); + localStorage.setItem('cart', JSON.stringify(cart)); + if (cart.length === 0) document.getElementById('cartFloat').style.display = 'none'; + renderCart(); + } + function checkout() { + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + if (cart.length === 0) { + alert('Корзина пуста!'); + return; + } + const form = document.createElement('form'); + form.method = 'POST'; + form.action = '/profile'; + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'checkout'; + input.value = 'true'; + const cartData = document.createElement('input'); + cartData.type = 'hidden'; + cartData.name = 'cart_data'; + cartData.value = JSON.stringify(cart); + form.appendChild(input); + form.appendChild(cartData); + document.body.appendChild(form); + form.submit(); + localStorage.removeItem('cart'); + document.getElementById('cartFloat').style.display = 'none'; + } document.getElementById('upload-form').onsubmit = async function(e) { e.preventDefault(); const formData = new FormData(this); @@ -1457,32 +2168,457 @@ def upload(): }; window.onload = () => { if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark'); + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + if (cart.length > 0) document.getElementById('cartFloat').style.display = 'block'; }; ''' - return render_template_string(html, username=username, is_authenticated=is_authenticated) + return render_template_string(html, username=username, is_authenticated=is_authenticated, user_type=user_type, verified=verified, order_count=order_count) + +# Заказы продавца +@app.route('/seller_orders', methods=['GET', 'POST']) +def seller_orders(): + if 'username' not in session: + flash('Войдите, чтобы просмотреть заказы!', 'error') + return redirect(url_for('login')) + + data = load_data() + username = session['username'] + user_data = data['users'].get(username, {}) + if user_data.get('type') != 'seller' or not user_data.get('verified'): + flash('Только проверенные продавцы могут просматривать заказы!', 'error') + return redirect(url_for('profile')) + + orders = data['orders'].get(username, []) + is_authenticated = 'username' in session + user_type = user_data.get('type') + verified = user_data.get('verified', False) + order_count = len(orders) + + if request.method == 'POST': + if 'update_status' in request.form: + order_id = request.form.get('order_id') + new_status = request.form.get('status') + for order in orders: + if order['order_id'] == order_id: + order['status'] = new_status + # Обновляем статус в заказах пользователя + for user_order in data['user_orders'].get(order['buyer'], []): + if user_order['order_id'] == order_id: + user_order['status'] = new_status + break + break + save_data(data) + return redirect(url_for('seller_orders')) + + html = ''' + + + + + + Заказы - {{ username }} + + + + + + ''' + NAV_HTML + ''' + + +
+

Корзина

+
+

Итого: 0

+ + +
+
+

Ваши заказы

+
+ {% if orders %} + {% for order in orders %} +
+

{{ order['title'] }}

+

Цена: {{ order['price'] }} {{ order['currency'] }} x {{ order['quantity'] }} = {{ (order['price']|float * order['quantity'])|round(2) }} {{ order['currency'] }}

+

Покупатель: {{ order['buyer'] }}

+

Телефон покупателя: {{ order['buyer_phone'] }}

+ {% if order['buyer_address'] %} +

Адрес доставки: {{ order['buyer_address'] }}

+ {% endif %} +

Дата: {{ order['date'] }}

+
+ + + +
+
+ {% endfor %} + {% else %} +

У вас пока нет заказов.

+ {% endif %} +
+
+ + + + ''' + return render_template_string(html, username=username, orders=orders, is_authenticated=is_authenticated, user_type=user_type, verified=verified, order_count=order_count) + +# Мои заказы пользователя +@app.route('/user_orders', methods=['GET']) +def user_orders(): + if 'username' not in session: + flash('Войдите, чтобы просмотреть свои заказы!', 'error') + return redirect(url_for('login')) + + data = load_data() + username = session['username'] + user_data = data['users'].get(username, {}) + orders = data['user_orders'].get(username, []) + is_authenticated = 'username' in session + user_type = user_data.get('type') + verified = user_data.get('verified', False) + order_count = len(data['orders'].get(username, [])) if user_type == 'seller' and verified else 0 + + html = ''' + + + + + + Мои заказы - {{ username }} + + + + + + ''' + NAV_HTML + ''' + + +
+

Корзина

+
+

Итого: 0

+ + +
+
+

Мои заказы

+
+ {% if orders %} + {% for order in orders %} +
+

{{ order['title'] }}

+

Цена: {{ order['price'] }} {{ order['currency'] }} x {{ order['quantity'] }} = {{ (order['price']|float * order['quantity'])|round(2) }} {{ order['currency'] }}

+

Продавец: {{ order['uploader'] }}

+

Дата: {{ order['date'] }}

+

Статус: {{ 'В ожидании' if order['status'] == 'pending' else 'В обработке' if order['status'] == 'processing' else 'Завершено' }}

+
+ {% endfor %} + {% else %} +

У вас пока нет заказов.

+ {% endif %} +
+
+ + + + ''' + return render_template_string(html, username=username, orders=orders, is_authenticated=is_authenticated, user_type=user_type, verified=verified, order_count=order_count) # Админ-панель @app.route('/admhosto', methods=['GET', 'POST']) def admin_panel(): data = load_data() posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True) + pending_orgs = data.get('pending_organizations', []) is_authenticated = 'username' in session username = session.get('username', None) + user_type = data['users'].get(username, {}).get('type') if username else None + verified = data['users'].get(username, {}).get('verified', False) if username else False + order_count = len(data['orders'].get(username, [])) if user_type == 'seller' and verified else 0 - search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' and 'search' in request.form else request.args.get('search', '').strip().lower() + if request.method == 'POST': + if 'delete' in request.form: + post_id = request.form.get('post_id') + if post_id: + data['posts'] = [p for p in data['posts'] if p['id'] != post_id] + save_data(data) + return redirect(url_for('admin_panel')) + elif 'approve_org' in request.form: + org_username = request.form.get('username') + if org_username: + data['users'][org_username]['verified'] = True + data['pending_organizations'] = [org for org in pending_orgs if org['username'] != org_username] + save_data(data) + return redirect(url_for('admin_panel')) + elif 'reject_org' in request.form: + org_username = request.form.get('username') + if org_username: + data['pending_organizations'] = [org for org in pending_orgs if org['username'] != org_username] + save_data(data) + return redirect(url_for('admin_panel')) + + search_query = request.args.get('search', '').strip().lower() if search_query: posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()] - if request.method == 'POST' and 'delete' in request.form: - post_id = request.form.get('post_id') - if post_id: - data['posts'] = [p for p in data['posts'] if p['id'] != post_id] - save_data(data) - return redirect(url_for('admin_panel')) - html = ''' @@ -1493,7 +2629,7 @@ def admin_panel(): ''' + NAV_HTML + ''' + +
+

Корзина

+
+

Итого: 0

+ + +
-

Админ-панель: Все посты

+

Админ-панель

+

Организации на проверке

+
+ {% if pending_orgs %} + {% for org in pending_orgs %} +
+

{{ org['org_name'] }}

+

Пользователь: {{ org['username'] }}

+

Телефон: {{ org['org_phone'] }}

+

Адрес: {{ org['org_address'] if org['org_address'] else 'Онлайн' }}

+

Дата подачи: {{ org['submitted_at'] }}

+
+ + + +
+
+ {% endfor %} + {% else %} +

Нет организаций на проверке.

+ {% endif %} +
+

Все видео

-
+
@@ -1596,16 +2782,13 @@ def admin_panel(): {% for post in posts %}
- {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

+ +

{{ post['title'] }}

{{ post['description'] }}

+

Цена: {{ post['price'] }} {{ post['currency'] }}

Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}

@@ -1614,7 +2797,7 @@ def admin_panel():
{% endfor %} {% else %} -

Посты не найдены.

+

Видео не найдены.

{% endif %}
@@ -1626,24 +2809,82 @@ def admin_panel(): document.body.classList.toggle('dark'); localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); } + function toggleCart() { + const cartModal = document.getElementById('cartModal'); + cartModal.style.display = cartModal.style.display === 'block' ? 'none' : 'block'; + renderCart(); + } + function renderCart() { + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + const cartItems = document.getElementById('cartItems'); + const cartTotal = document.getElementById('cartTotal'); + cartItems.innerHTML = ''; + let total = 0; + cart.forEach(item => { + const itemTotal = item.price * item.quantity; + total += itemTotal; + const div = document.createElement('div'); + div.className = 'cart-item'; + div.innerHTML = ` +

${item.title}

+

Цена: ${item.price} ${item.currency} x ${item.quantity} = ${itemTotal.toFixed(2)} ${item.currency}

+

Продавец: ${item.uploader}

+ + `; + cartItems.appendChild(div); + }); + const totalCurrency = cart.length > 0 ? cart[0].currency : ''; + cartTotal.textContent = `Итого: ${total.toFixed(2)} ${totalCurrency}`; + } + function removeFromCart(postId) { + let cart = JSON.parse(localStorage.getItem('cart') || '[]'); + cart = cart.filter(item => item.postId !== postId); + localStorage.setItem('cart', JSON.stringify(cart)); + if (cart.length === 0) document.getElementById('cartFloat').style.display = 'none'; + renderCart(); + } + function checkout() { + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + if (cart.length === 0) { + alert('Корзина пуста!'); + return; + } + const form = document.createElement('form'); + form.method = 'POST'; + form.action = '/profile'; + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'checkout'; + input.value = 'true'; + const cartData = document.createElement('input'); + cartData.type = 'hidden'; + cartData.name = 'cart_data'; + cartData.value = JSON.stringify(cart); + form.appendChild(input); + form.appendChild(cartData); + document.body.appendChild(form); + form.submit(); + localStorage.removeItem('cart'); + document.getElementById('cartFloat').style.display = 'none'; + } window.onload = () => { if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark'); + const cart = JSON.parse(localStorage.getItem('cart') || '[]'); + if (cart.length > 0) document.getElementById('cartFloat').style.display = 'block'; const videos = document.querySelectorAll('.post-preview'); videos.forEach(video => { - if (video.tagName === 'VIDEO') { - video.addEventListener('loadedmetadata', () => { - video.currentTime = Math.random() * video.duration; - }); - } + video.addEventListener('loadedmetadata', () => { + video.currentTime = Math.random() * video.duration; + }); }); }; ''' - return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query) + return render_template_string(html, posts=posts, pending_orgs=pending_orgs, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query, user_type=user_type, verified=verified, order_count=order_count) if __name__ == '__main__': backup_thread = threading.Thread(target=periodic_backup, daemon=True) backup_thread.start() - app.run(debug=True, host='0.0.0.0', port=7860) \ No newline at end of file + app.run(debug=True, host='0.0.0.0', port=7860) \ No newline at end of file