mirror of
https://github.com/Michatec/MiniFaceBook.git
synced 2026-04-01 07:56:28 +02:00
Files
This commit is contained in:
20
templates/403.html
Normal file
20
templates/403.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Home') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center align-items-center mt-5">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-lg p-4 mb-5" style="border-radius: 1.5rem;">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="mb-3" style="font-size:2.5rem; font-weight:700; color:#2563eb;">
|
||||
<i class="bi bi-people-fill me-2"></i>MiniFacebook 403
|
||||
</h1>
|
||||
<p class="lead mb-4" style="font-size:1.2rem;">
|
||||
{{ _('This page is not accessible to you, you do not have the permissions to view it.') }}<br>
|
||||
<span class="text-muted">{{ _('You can go back from the page.') }}</span>
|
||||
</p>
|
||||
<a href="{{ url_for('post.feed') }}" class="btn btn-success m-2 px-4 py-2"><i class="bi bi-house-door me-1"></i>{{ _('Go to Feed') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
372
templates/admin.html
Normal file
372
templates/admin.html
Normal file
@@ -0,0 +1,372 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Admin') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-shield-lock me-2"></i>{{ _('Admin Panel') }}</h2>
|
||||
|
||||
<ul class="nav nav-tabs mb-3" id="adminTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="users-tab" data-bs-toggle="tab" data-bs-target="#users" type="button" role="tab">{{ _('Users') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="posts-tab" data-bs-toggle="tab" data-bs-target="#posts" type="button" role="tab">{{ _('Posts') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="friendships-tab" data-bs-toggle="tab" data-bs-target="#friendships" type="button" role="tab">{{ _('Friendships') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="comments-tab" data-bs-toggle="tab" data-bs-target="#comments" type="button" role="tab">{{ _('Comments') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="uploads-tab" data-bs-toggle="tab" data-bs-target="#uploads" type="button" role="tab">{{ _('Uploads') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="notifications-tab" data-bs-toggle="tab" data-bs-target="#notifications" type="button" role="tab">{{ _('Notifications') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="events-tab" data-bs-toggle="tab" data-bs-target="#events" type="button" role="tab">{{ _('Events') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="shop-items-tab" data-bs-toggle="tab" data-bs-target="#shop-items" type="button" role="tab">{{ _('Shop Orders') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="shop-orders-tab" data-bs-toggle="tab" data-bs-target="#shop-orders" type="button" role="tab">{{ _('Reward Points') }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="adminTabContent">
|
||||
<!-- Users Tab -->
|
||||
<div class="tab-pane fade show active" id="users" role="tabpanel">
|
||||
<h4><i class="bi bi-people me-2"></i>{{ _('Users') }}</h4>
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Email') }}</th>
|
||||
<th>{{ _('Admin') }}</th>
|
||||
<th>{{ _('Owner') }}</th>
|
||||
<th>{{ _('Profile Pic') }}</th>
|
||||
<th>{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{% if user.profile_pic and user.profile_pic != 'default.png' %}<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" width="32" class="rounded me-1">{% endif %} {{ user.username }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{% if user.is_admin %}<i class="bi bi-check-lg text-success"></i>{% endif %}</td>
|
||||
<td>{% if user.is_owner %}<i class="bi bi-star-fill text-warning"></i>{% endif %}</td>
|
||||
<td>
|
||||
{% if user.profile_pic and user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" width="32" class="rounded me-1">
|
||||
<form action="{{ url_for('admin.admin_delete_pic', user_id=user.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" title="{{ _('Delete Picture') }}"><i class="bi bi-image"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not user.is_admin %}
|
||||
<form action="{{ url_for('admin.make_admin', user_id=user.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-success btn-sm" title="{{ _('Make Admin') }}"><i class="bi bi-person-gear"></i></button>
|
||||
</form>
|
||||
{% elif not user.is_owner %}
|
||||
<form action="{{ url_for('admin.remove_admin', user_id=user.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-warning btn-sm" title="{{ _('Remove Admin') }}"><i class="bi bi-person-dash"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if not user.is_owner %}
|
||||
<form action="{{ url_for('admin.admin_delete_user', user_id=user.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" title="{{ _('Delete User') }}"><i class="bi bi-person-x"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Posts Tab -->
|
||||
<div class="tab-pane fade" id="posts" role="tabpanel">
|
||||
<h4><i class="bi bi-file-earmark-text me-2"></i>{{ _('All Posts') }}</h4>
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Content') }}</th>
|
||||
<th>{{ _('Visibility') }}</th>
|
||||
<th>{{ _('Created') }}</th>
|
||||
<th>{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for post in posts %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if post.user.profile_pic and post.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" alt="{{ post.user.username }}" class="rounded me-1" width="32">{{ post.user.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ post.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ post.content|truncate(50) }}</td>
|
||||
<td>
|
||||
{% if post.visibility == 'public' %}
|
||||
<span class="badge bg-info"><i class="bi bi-globe"></i> {{ _('Public') }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary"><i class="bi bi-people"></i> {{ _('Friends') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<form action="{{ url_for('admin.admin_delete_post', post_id=post.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" title="{{ _('Delete Post') }}"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Friendships Tab -->
|
||||
<div class="tab-pane fade" id="friendships" role="tabpanel">
|
||||
<h4><i class="bi bi-people-arrows me-2"></i>{{ _('Friendships') }}</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User 1') }}</th>
|
||||
<th>{{ _('User 2') }}</th>
|
||||
<th>{{ _('Status') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for f in friendships %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if f.requester.profile_pic and f.requester.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ f.requester.profile_pic) }}" alt="{{ f.requester.username }}" class="rounded me-1" width="32">{{ f.requester.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ f.requester.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if f.receiver.profile_pic and f.receiver.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ f.receiver.profile_pic) }}" alt="{{ f.receiver.username }}" class="rounded me-1" width="32">{{ f.receiver.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ f.receiver.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if f.status == 'accepted' %}
|
||||
<span class="badge bg-success">{{ _('Accepted') }}</span>
|
||||
{% elif f.status == 'pending' %}
|
||||
<span class="badge bg-warning text-dark">{{ _('Pending') }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">{{ _('Rejected') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Comments Tab -->
|
||||
<div class="tab-pane fade" id="comments" role="tabpanel">
|
||||
<h4><i class="bi bi-chat-left-text me-2"></i>{{ _('Comments') }}</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Post') }}</th>
|
||||
<th>{{ _('Content') }}</th>
|
||||
<th>{{ _('Created') }}</th>
|
||||
<th>{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for comment in comments %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32">{{ comment.user.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ comment.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ comment.post.id }}</td>
|
||||
<td>{{ comment.content|truncate(50) }}</td>
|
||||
<td>{{ comment.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<form action="{{ url_for('post.delete_comment', comment_id=comment.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" title="{{ _('Delete Comment') }}"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Uploads Tab -->
|
||||
<div class="tab-pane fade" id="uploads" role="tabpanel">
|
||||
<h4><i class="bi bi-upload me-2"></i>{{ _('Uploads') }}
|
||||
<form action="{{ url_for('admin.admin_delete_all_uploads') }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" title="{{ _('Delete All Uploads') }}"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Filename') }}</th>
|
||||
<th>{{ _('Type') }}</th>
|
||||
<th>{{ _('Uploaded') }}</th>
|
||||
<th>{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for upload in uploads %}
|
||||
<tr>
|
||||
<td>{{ upload.user.username }}</td>
|
||||
<td>{{ upload.filename }}</td>
|
||||
<td>{{ upload.filetype }}</td>
|
||||
<td>{{ upload.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download class="btn btn-outline-primary btn-sm" title="{{ _('Download') }}"><i class="bi bi-download"></i></a>
|
||||
<form action="{{ url_for('admin.admin_delete_upload', upload_id=upload.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" title="{{ _('Delete Upload') }}"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Notifications Tab -->
|
||||
<div class="tab-pane fade" id="notifications" role="tabpanel">
|
||||
<h4>
|
||||
<i class="bi bi-bell me-2"></i>{{ _('Notifications') }}
|
||||
<form action="{{ url_for('admin.admin_delete_all_notifications') }}" method="post" onsubmit="return confirm('{{ _('Are you sure you want to delete all notifications?') }}');">
|
||||
<button class="btn btn-danger mb-2 float-end"><i class="bi bi-bell-slash"></i> {{ _('Delete All Notifications') }}</button>
|
||||
</form>
|
||||
</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Message') }}</th>
|
||||
<th>{{ _('Created') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for notif in all_notifications %}
|
||||
<tr>
|
||||
<td>{{ notif.user_id }}</td>
|
||||
<td>{{ notif.message }}</td>
|
||||
<td>{{ notif.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Events Tab -->
|
||||
<div class="tab-pane fade" id="events" role="tabpanel">
|
||||
<h4>
|
||||
<i class="bi bi-clock-history me-2"></i>{{ _('Recent Events') }}
|
||||
<form action="{{ url_for('admin.admin_delete_all_events') }}" method="post" onsubmit="return confirm('{{ _('Are you sure you want to delete all events?') }}');">
|
||||
<button class="btn btn-danger mb-2 float-end"><i class="bi bi-calendar-x"></i> {{ _('Delete All Events') }}</button>
|
||||
</form>
|
||||
</h4>
|
||||
<table class="table table-sm">
|
||||
<tbody id="events-tbody">
|
||||
{% for event in events %}
|
||||
<tr>
|
||||
<td>{{ event.timestamp.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>{{ event.message }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- User Shop Items -->
|
||||
<div class="tab-pane fade" id="shop-items" role="tabpanel">
|
||||
<h4><i class="bi bi-shop me-2"></i>{{ _('Shop Orders') }}</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Item') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for usi in user_shop_items %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if usi.user.profile_pic and usi.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ usi.user.profile_pic) }}" class="rounded me-2" width="32">{{ usi.user.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ usi.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ usi.item.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Reward Points -->
|
||||
<div class="tab-pane fade" id="shop-orders" role="tabpanel">
|
||||
<h4><i class="bi bi-cart-check me-2"></i>{{ _('Reward Points') }}</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Points') }}</th>
|
||||
<th>{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if user.profile_pic and user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="rounded me-2" width="32">{{ user.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.reward_points() }}</td>
|
||||
<td>
|
||||
<form action="{{ url_for('admin.admin_points', user_id=user.id) }}" method="post" style="display:inline;">
|
||||
<input id="points" name="points" type="number" step="1" inputmode="decimal" required>
|
||||
<button class="btn btn-success btn-sm" name="action" value="add" title="{{ _('Add Points') }}">
|
||||
<i class="bi bi-plus-circle"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" name="action" value="remove" title="{{ _('Remove Points') }}">
|
||||
<i class="bi bi-dash-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ url_for('static', filename='js/adstop.js') }}"></script>
|
||||
<script>
|
||||
var triggerTabList = [].slice.call(document.querySelectorAll('#adminTab button'))
|
||||
triggerTabList.forEach(function (triggerEl) {
|
||||
var tabTrigger = new bootstrap.Tab(triggerEl)
|
||||
triggerEl.addEventListener('click', function (event) {
|
||||
event.preventDefault()
|
||||
tabTrigger.show()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
12
templates/admin_set_password.html
Normal file
12
templates/admin_set_password.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Set New Password') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-key me-2"></i>{{ _('Set New Password for %(username)s', username=user.username) }}</h2>
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock-fill me-1"></i>{{ _('New Password') }}</label>
|
||||
<input type="password" name="new_password" class="form-control" required>
|
||||
</div>
|
||||
<button class="btn btn-success" type="submit"><i class="bi bi-check-circle me-1"></i>{{ _('Set Password') }}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
113
templates/base.html
Normal file
113
templates/base.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ get_locale() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}MiniFacebook{% endblock %}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/theme.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/translate.js') }}"></script>
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
<meta name="theme-color" content="#2563eb">
|
||||
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/icon-192.png') }}">
|
||||
<link rel="icon" href="{{ url_for('static', filename='icons/icon-192.png') }}">
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('{{ url_for('static', filename='js/sw.js') }}');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100 {{ theme_class }}">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<i class="bi bi-people-fill me-2"></i>MiniFacebook
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
{% if current_user.is_authenticated %}
|
||||
{% if user.is_admin %}
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.admin') }}"><i class="bi bi-shield-lock me-1"></i>{{ _('Admin Panel') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.reset_requests') }}"><i class="bi bi-arrow-clockwise me-1"></i>{{ _('Reset Requests') }}</a></li>
|
||||
{% endif %}
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('post.feed') }}"><i class="bi bi-house-door me-1"></i>{{ _('Feed') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('user.users') }}"><i class="bi bi-people me-1"></i>{{ _('Users') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('friend.friends') }}"><i class="bi bi-person-check me-1"></i>{{ _('Friends') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('profil.my_posts') }}"><i class="bi bi-file-earmark-text me-1"></i>{{ _('My Posts') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('notif.notifications') }}"><i class="bi bi-bell me-1"></i>{{ _('Notifications') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('profil.profile') }}"><i class="bi bi-person-circle me-1"></i>{{ _('Profile') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('log.logout') }}"><i class="bi bi-box-arrow-right me-1"></i>{{ _('Logout') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('support.support') }}"><i class="bi bi-question-circle me-1"></i>{{ _('Support') }}</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('post.feed') }}"><i class="bi bi-house-door me-1"></i>{{ _('Feed') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('log.login') }}"><i class="bi bi-box-arrow-in-right me-1"></i>{{ _('Login') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('log.register') }}"><i class="bi bi-person-plus me-1"></i>{{ _('Register') }}</a></li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<button id="toggle-theme" class="btn btn-outline-secondary btn-sm ms-2" type="button">
|
||||
<span id="theme-icon" class="bi"></span> <span id="theme-label">{{ _('Theme') }}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item dropdown ms-2">
|
||||
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" id="langDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<span id="lang-label">{% if get_locale() == 'de' %}Deutsch{% else %}English{% endif %}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="langDropdown">
|
||||
<li><a class="dropdown-item lang-select" href="#" data-lang="de">Deutsch</a></li>
|
||||
<li><a class="dropdown-item lang-select" href="#" data-lang="en">English</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container flex-grow-1">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} mt-2">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
{% if user.profile_pic and user.profile_pic != 'default.png' %}
|
||||
{% if SHOPITEM_ID_GOLDRAHMEN in user.shop_items | map(attribute='item_id') | list %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ user.profile_pic) }}" class="profile-pic me-2">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<h1>{{ _('Welcome, %(username)s!', username=user.username) }}</h1>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if user.is_admin %}
|
||||
<p class="text-success">{{ _('You are logged in as an admin.') }}</p>
|
||||
{% endif %}
|
||||
{% if SHOPITEM_ID_PREMIUM in user.shop_items | map(attribute='item_id') | list %}
|
||||
<span class="badge bg-warning text-dark"><i class="bi bi-star"></i> Premium</span>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<footer class="footer mt-auto py-3">
|
||||
<div class="container text-center">
|
||||
<span class="text-muted">
|
||||
© 2025 MiniFacebook ·
|
||||
<a href="https://github.com/Michatec/MiniFaceBook" target="_blank" rel="noopener" class="text-decoration-none"><i class="bi bi-github m-1"></i>GitHub</a>
|
||||
· <a href="{{ url_for('index') }}" class="text-decoration-none">{{ _('Home') }}</a>
|
||||
· <a href="{{ url_for('credit.privacy_policy') }}" class="text-decoration-none">{{ _('Privacy Policy') }}</a>
|
||||
· <a href="{{ url_for('credit.credits') }}" class="text-decoration-none">{{ _('Credits') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
20
templates/credits.html
Normal file
20
templates/credits.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Credits') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ _('Credits') }}</h1>
|
||||
<p>{{ _('This project was developed by') }} Michatec. {{ _('Special thanks to all contributors and supporters.') }}</p>
|
||||
<p>{{ _('Translators:') }}</p>
|
||||
<ul>
|
||||
<li>{{ _('German:') }} Michatec</li>
|
||||
<li>{{ _('English:') }} Michatec</li>
|
||||
</ul>
|
||||
<p>{{ _('Special thanks to the open-source community for their invaluable resources and tools.') }}</p>
|
||||
<p>{{ _('Design inspired by various open-source projects and communities.') }}</p>
|
||||
<p>{{ _('Backend powered by Flask and SQLAlchemy.') }}</p>
|
||||
<p>{{ _('Frontend built with Bootstrap and jQuery.') }}</p>
|
||||
<p>{{ _('Icons by FontAwesome and other open-source resources.') }}</p>
|
||||
<p>{{ _('Hosted on a secure and reliable platform.') }}</p>
|
||||
<p>{{ _('If you would like to contribute, please reach out to us.') }}</p>
|
||||
<a href="https://github.com/Michatec/MiniFaceBook" target="_blank">{{ _('GitHub Repository') }}</a>
|
||||
<p>{{ _('Thank you for using our application!') }}</p>
|
||||
{% endblock %}
|
||||
22
templates/discord_register.html
Normal file
22
templates/discord_register.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Discord Registration') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2>{{ _('Complete Registration') }}</h2>
|
||||
<h3>{{ _('Welcome,') }} <strong>{{ username }}</strong>!</h3>
|
||||
<p>{{ _('Please set a password for your account:') }}</p>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="username" value="{{ username }}">
|
||||
<input type="hidden" name="email" value="{{ email }}">
|
||||
<input type="hidden" name="discord_id" value="{{ discord_id }}">
|
||||
<div class="form-group">
|
||||
<label for="password">{{ _('Password') }}</label>
|
||||
<input type="password" name="password" class="form-control" required minlength="8">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">{{ _('Confirm Password') }}</label>
|
||||
<input type="password" name="confirm_password" class="form-control" required minlength="8">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">{{ _('Create Account') }}</button>
|
||||
<a href="{{ url_for('log.login') }}" class="btn btn-secondary">{{ _('Cancel') }}</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
53
templates/edit_post.html
Normal file
53
templates/edit_post.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Edit Post') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2>{{ _('Edit Post') }}</h2>
|
||||
<form action="{{ url_for('post.update_post', post_id=post.id) }}" method="post" enctype="multipart/form-data" class="mb-3">
|
||||
<div class="mb-3">
|
||||
<label for="content" class="form-label">{{ _('Content') }}</label>
|
||||
{% if not SHOPITEM_ID_EXTRA_TYPES in current_user.shop_items | map(attribute='item_id') | list %}
|
||||
<p class="form-text" style="text-align: right;">{{ _('Limit: 250') }}</p>
|
||||
{% else %}
|
||||
<p class="form-text" style="text-align: right;">{{ _('Limit: 500') }}</p>
|
||||
{% endif %}
|
||||
<textarea name="content" id="content" class="form-control" required>{{ post.content }}</textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="visibility" class="form-label">{{ _('Visibility') }}</label>
|
||||
<select name="visibility" id="visibility" class="form-select">
|
||||
<option value="public" {% if post.visibility == 'public' %}selected{% endif %}>🌍 {{ _('Public') }}</option>
|
||||
<option value="friends" {% if post.visibility == 'friends' %}selected{% endif %}>👥 {{ _('Friends only') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="upload" class="form-label">{{ _('Upload Files') }}</label>
|
||||
<input type="file" name="upload" id="upload" class="form-control">
|
||||
{% if SHOPITEM_ID_EXTRA_UPLOAD in current_user.shop_items | map(attribute='item_id') | list %}
|
||||
<input type="file" name="upload2" id="upload2" class="form-control">
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">{{ _('You can upload images, videos, audio files, or documents.') }}</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">{{ _('Update Post') }}</button>
|
||||
<a href="{{ url_for('post.feed') }}" class="btn btn-link">{{ _('Cancel') }}</a>
|
||||
</form>
|
||||
{% if post.uploads %}
|
||||
<div class="mb-3">
|
||||
<h4>{{ _('Current Uploads') }}</h4>
|
||||
{% for upload in post.uploads %}
|
||||
<div class="mb-2">
|
||||
{% if upload.filetype.startswith('image') %}
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" class="img-thumbnail" style="max-width: 200px;">
|
||||
{% elif upload.filetype.startswith('video') %}
|
||||
<video src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" controls width="320"></video>
|
||||
{% elif upload.filetype.startswith('audio') %}
|
||||
<audio src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" controls></audio>
|
||||
{% else %}
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download>{{ upload.filename }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<h2>{{ _('No uploads found for this post.') }}</h2>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
24
templates/edit_profile.html
Normal file
24
templates/edit_profile.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Edit Profile') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-pencil-square me-2"></i>{{ _('Edit Profile') }}</h2>
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-person-circle me-1"></i>{{ _('Username') }}</label>
|
||||
<input type="text" name="username" class="form-control" value="{{ user.username }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-envelope-at me-1"></i>{{ _('Email') }}</label>
|
||||
<input type="email" name="email" class="form-control" value="{{ user.email }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock-fill me-1"></i>{{ _('New Password (optional)') }}</label>
|
||||
<input type="password" name="password" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock me-1"></i>{{ _('Confirm New Password') }}</label>
|
||||
<input type="password" name="confirm_password" class="form-control">
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit"><i class="bi bi-save me-1"></i>{{ _('Save') }}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
122
templates/feed.html
Normal file
122
templates/feed.html
Normal file
@@ -0,0 +1,122 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Feed') }}{% endblock %}
|
||||
{% block content %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<form action="{{ url_for('post.create_post') }}" method="post" enctype="multipart/form-data" class="mb-4">
|
||||
{% if not SHOPITEM_ID_EXTRA_TYPES in current_user.shop_items | map(attribute='item_id') | list %}
|
||||
<p class="form-text" style="text-align: right;">{{ _('Limit: 250') }}</p>
|
||||
{% else %}
|
||||
<p class="form-text" style="text-align: right;">{{ _('Limit: 500') }}</p>
|
||||
{% endif %}
|
||||
<textarea name="content" class="form-control" placeholder="{{ _('What\'s on your mind?') }}" required></textarea>
|
||||
<input type="file" name="file">
|
||||
{% if SHOPITEM_ID_EXTRA_UPLOAD in current_user.shop_items | map(attribute='item_id') | list %}
|
||||
<input type="file" name="file2">
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">{{ _('You can upload images, videos, audio files, or documents.') }}</small>
|
||||
<select name="visibility" class="form-select mt-2" style="max-width:200px;display:inline-block;">
|
||||
<option value="public">🌍 {{ _('Public') }}</option>
|
||||
<option value="friends">👥 {{ _('Friends only') }}</option>
|
||||
</select>
|
||||
<button class="btn btn-primary mt-2" type="submit"><i class="bi bi-send me-1"></i>{{ _('Post') }}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ _('Please') }} <a href="{{ url_for('log.login') }}">{{ _('log in') }}</a> {{ _('to create a post.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for post in posts %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
{% if post.user.profile_pic and post.user.profile_pic != 'default.png' %}
|
||||
{% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-2"></i>
|
||||
{% endif %}
|
||||
<strong>{{ post.user.username }}</strong>
|
||||
</div>
|
||||
<p class="card-text">{{ post.content }}</p>
|
||||
{% for upload in post.uploads %}
|
||||
{% if upload.filetype.startswith('image') %}
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}"
|
||||
class="img-fluid mb-2"
|
||||
style="max-width: 200px; cursor: pointer;"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#imgModal{{ upload.id }}">
|
||||
<div class="modal fade" id="imgModal{{ upload.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content bg-transparent border-0">
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" class="w-100 rounded shadow">
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download class="btn btn-light position-absolute top-0 end-0 m-3"><i class="bi bi-download"></i> {{ _('Download') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% elif upload.filetype.startswith('video') %}
|
||||
<video src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" controls width="320" class="mb-2"></video>
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download class="btn btn-sm btn-outline-secondary ms-2"><i class="bi bi-download"></i> {{ _('Download Video') }}</a>
|
||||
{% elif upload.filetype.startswith('audio') %}
|
||||
<audio src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" controls class="mb-2"></audio>
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download class="btn btn-sm btn-outline-secondary ms-2"><i class="bi bi-download"></i> {{ _('Download Audio') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download><i class="bi bi-paperclip"></i> {{ upload.filename }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<form action="{{ url_for('like.like_post', post_id=post.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-link" type="submit"><i class="bi bi-hand-thumbs-up"></i> {{ _('Like') }} ({{ post.likes.count() }})</button>
|
||||
</form>
|
||||
<form action="{{ url_for('like.unlike_post', post_id=post.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-link" type="submit"><i class="bi bi-hand-thumbs-down"></i> {{ _('Unlike') }}</button>
|
||||
</form>
|
||||
{% if post.user_id == current_user.id %}
|
||||
<form action="{{ url_for('post.delete_post', post_id=post.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" type="submit"><i class="bi bi-trash"></i> {{ _('Delete') }}</button>
|
||||
</form>
|
||||
<form action="{{ url_for('post.edit_post', post_id=post.id) }}" method="get" style="display:inline;">
|
||||
<button class="btn btn-secondary btn-sm" type="submit"><i class="bi bi-pencil-square"></i> {{ _('Edit') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if post.visibility == 'friends' %}
|
||||
<span class="badge bg-secondary ms">{{ _('Friends only') }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success ms">{{ _('Public') }}</span>
|
||||
{% endif %}
|
||||
<small class="text-muted"><i class="bi bi-clock me-1"></i>{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</small>
|
||||
<div class="mt-2">
|
||||
<form action="{{ url_for('post.comment_post', post_id=post.id) }}" method="post">
|
||||
<input name="comment" class="form-control" placeholder="{{ _('Comment...') }}"
|
||||
{% if not current_user.is_authenticated %}disabled{% endif %} required>
|
||||
</form>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<small class="text-muted">{{ _('Please login to comment.') }}</small>
|
||||
{% endif %}
|
||||
{% for comment in post.comments %}
|
||||
<div class="mt-1">
|
||||
{% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32"><b>{{ comment.user.username }}</b>:
|
||||
{% else %}
|
||||
<i class="bi bi-person me-1"></i><b>{{ comment.user.username }}</b>:
|
||||
{% endif %}
|
||||
{{ comment.content }}
|
||||
{% if comment.user_id == current_user.id %}
|
||||
<form action="{{ url_for('post.delete_comment', comment_id=comment.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-link btn-sm text-danger"><i class="bi bi-trash"></i> {{ _('Delete') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not posts %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ _('No posts available.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<script src="{{ url_for('static', filename='js/feed.js') }}"></script>
|
||||
{% endblock %}
|
||||
47
templates/friends.html
Normal file
47
templates/friends.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Friends') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-people-fill me-2"></i>{{ _('Your Friends') }}</h2>
|
||||
<ul class="list-group mb-4">
|
||||
{% for friend in friends %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
{% if friend.profile_pic and friend.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' + friend.profile_pic) }}" alt="{{ friend.username }}" class="profile-pic me-2">{{ friend.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ friend.username }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<form action="{{ url_for('friend.remove_friend', user_id=friend.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-warning btn-sm"><i class="bi bi-person-dash"></i> {{ _('Remove Friend') }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="list-group-item">{{ _('No friends yet.') }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h4><i class="bi bi-person-plus me-2"></i>{{ _('Friend Requests') }}</h4>
|
||||
<ul class="list-group mb-3">
|
||||
{% for req in requests %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
{% if req.requester.profile_pic and req.requester.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' + req.requester.profile_pic) }}" alt="{{ req.requester.username }}" class="profile-pic me-2">{{ req.requester.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ req.requester.username }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<div>
|
||||
<form action="{{ url_for('friend.accept_friend', friendship_id=req.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-success btn-sm"><i class="bi bi-check"></i> {{ _('Accept') }}</button>
|
||||
</form>
|
||||
<form action="{{ url_for('friend.reject_friend', friendship_id=req.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm"><i class="bi bi-x"></i> {{ _('Reject') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="list-group-item">{{ _('No new requests') }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
43
templates/index.html
Normal file
43
templates/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Home') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center align-items-center mt-5">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-lg p-4 mb-5" style="border-radius: 1.5rem;">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="mb-3" style="font-size:2.5rem; font-weight:700; color:#2563eb;">
|
||||
<i class="bi bi-people-fill me-2"></i>MiniFacebook
|
||||
</h1>
|
||||
<p class="lead mb-4" style="font-size:1.2rem;">
|
||||
{{ _('MiniFacebook is a minimalist social network for sharing posts, images, and messages with friends.') }}<br>
|
||||
<span class="text-muted">{{ _('Fast, simple, data-saving - and with') }} <i class="bi bi-moon-stars"></i> {{ _('Dark Mode!') }}</span>
|
||||
</p>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<a href="{{ url_for('log.login') }}" class="btn btn-primary m-2 px-4 py-2"><i class="bi bi-box-arrow-in-right me-1"></i>{{ _('Login') }}</a>
|
||||
<a href="{{ url_for('log.register') }}" class="btn btn-success m-2 px-4 py-2"><i class="bi bi-person-plus me-1"></i>{{ _('Register') }}</a>
|
||||
<a href="{{ url_for('post.feed') }}" class="btn btn-outline-info m-2 px-4 py-2"><i class="bi bi-house-door me-1"></i>{{ _('Go to Feed') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('post.feed') }}" class="btn btn-success m-2 px-4 py-2"><i class="bi bi-house-door me-1"></i>{{ _('Go to Feed') }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card shadow-sm p-4" style="border-radius: 1.5rem; background: var(--about-bg, #f8fafc);">
|
||||
<div class="card-body">
|
||||
<h4 class="mb-3" style="color:#2563eb;">
|
||||
<i class="bi bi-info-circle"></i> {{ _('About MiniFacebook') }}
|
||||
</h4>
|
||||
<ul class="list-unstyled text-start mx-auto" style="max-width:420px;">
|
||||
<li class="mb-2"><i class="bi bi-check-circle text-success"></i> {{ _('Share posts, images & videos') }}</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle text-success"></i> {{ _('Friendships & notifications') }}</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle text-success"></i> {{ _('Modern') }} <i class="bi bi-moon-stars"></i> {{ _('Dark/Light Mode') }}</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle text-success"></i> {{ _('Open Source & privacy-friendly') }}</li>
|
||||
</ul>
|
||||
<div class="mt-4 text-muted" style="font-size:0.95rem;">
|
||||
<p><b>MiniFacebook</b> {{ _('is a private, data-saving network for you and your friends. No ads, no data sharing - just fun in sharing and communicating.') }}</p>
|
||||
<p>{{ _('Developed with love,') }} <i class="bi bi-bootstrap"></i> Bootstrap, <i class="bi bi-moon-stars"></i> {{ _('Dark Mode!') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
33
templates/login.html
Normal file
33
templates/login.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Login') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-5">
|
||||
<div class="card shadow p-4 mt-4" style="border-radius:1rem;">
|
||||
<h2 class="mb-4 text-center"><i class="bi bi-box-arrow-in-right me-2"></i>{{ _('Login') }}</h2>
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-person-circle me-1"></i>{{ _('Username') }}</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock-fill me-1"></i>{{ _('Password') }}</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-primary" type="submit"><i class="bi bi-box-arrow-in-right me-1"></i>{{ _('Login') }}</button>
|
||||
</div>
|
||||
<div class="mt-3 text-center">
|
||||
<a href="{{ url_for('log.register') }}" class="btn btn-link"><i class="bi bi-person-plus"></i> {{ _('Register') }}</a>
|
||||
<a href="{{ url_for('log.reset_password') }}" class="btn btn-link"><i class="bi bi-key"></i> {{ _('Forgot password?') }}</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-4 text-center">
|
||||
<a href="{{ url_for('discord.login_discord') }}" class="btn btn-primary">
|
||||
<i class="bi bi-discord"></i> {{ _('Login with Discord') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
91
templates/my_posts.html
Normal file
91
templates/my_posts.html
Normal file
@@ -0,0 +1,91 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('My Posts') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-file-earmark-text me-2"></i>{{ _('My Posts') }}</h2>
|
||||
{% for post in posts %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
{% if post.user.profile_pic and post.user.profile_pic != 'default.png' %}
|
||||
{% if SHOPITEM_ID_GOLDRAHMEN in post.user.shop_items | map(attribute='item_id') | list %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2" style="border-color: gold;">
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ post.user.profile_pic) }}" class="profile-pic me-2">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>
|
||||
{% endif %}
|
||||
<strong>{{ post.user.username }}</strong>
|
||||
</div>
|
||||
<p class="card-text">{{ post.content }}</p>
|
||||
{% for upload in post.uploads %}
|
||||
{% if upload.filetype.startswith('image') %}
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}"
|
||||
class="img-fluid mb-2"
|
||||
style="max-width: 200px; cursor: pointer;"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#imgModal{{ upload.id }}">
|
||||
<div class="modal fade" id="imgModal{{ upload.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content bg-transparent border-0">
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" class="w-100 rounded shadow">
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download class="btn btn-light position-absolute top-0 end-0 m-3"><i class="bi bi-download"></i> {{ _('Download') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% elif upload.filetype.startswith('video') %}
|
||||
<video src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" controls width="320" class="mb-2"></video>
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download class="btn btn-sm btn-outline-secondary ms-2"><i class="bi bi-download"></i> {{ _('Download Video') }}</a>
|
||||
{% elif upload.filetype.startswith('audio') %}
|
||||
<audio src="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" controls class="mb-2"></audio>
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download class="btn btn-sm btn-outline-secondary ms-2"><i class="bi bi-download"></i> {{ _('Download Audio') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('static', filename='uploads/' ~ upload.filename) }}" download><i class="bi bi-paperclip"></i> {{ upload.filename }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<form action="{{ url_for('like.like_post', post_id=post.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-link" type="submit"><i class="bi bi-hand-thumbs-up"></i> {{ _('Like') }} ({{ post.likes.count() }})</button>
|
||||
</form>
|
||||
<form action="{{ url_for('like.unlike_post', post_id=post.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-link" type="submit"><i class="bi bi-hand-thumbs-down"></i> {{ _('Unlike') }}</button>
|
||||
</form>
|
||||
<form action="{{ url_for('post.delete_post', post_id=post.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" type="submit"><i class="bi bi-trash"></i> {{ _('Delete') }}</button>
|
||||
</form>
|
||||
<form action="{{ url_for('post.edit_post', post_id=post.id) }}" method="get" style="display:inline;">
|
||||
<button class="btn btn-secondary btn-sm" type="submit"><i class="bi bi-pencil-square"></i> {{ _('Edit') }}</button>
|
||||
</form>
|
||||
{% if post.visibility == 'friends' %}
|
||||
<span class="badge bg-secondary ms">{{ _('Friends only') }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success ms">{{ _('Public') }}</span>
|
||||
{% endif %}
|
||||
<small class="text-muted"><i class="bi bi-clock me-1"></i>{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</small>
|
||||
<div class="mt-2">
|
||||
<form action="{{ url_for('post.comment_post', post_id=post.id) }}" method="post">
|
||||
<input name="comment" class="form-control" placeholder="{{ _('Comment...') }}" required>
|
||||
</form>
|
||||
{% for comment in post.comments %}
|
||||
<div class="mt-1">
|
||||
{% if comment.user.profile_pic and comment.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ comment.user.profile_pic) }}" class="rounded me-2" width="32"><b>{{ comment.user.username }}</b>:
|
||||
{% else %}
|
||||
<i class="bi bi-person me-1"></i><b>{{ comment.user.username }}</b>:
|
||||
{% endif %}
|
||||
{% if comment.user_id == current_user.id %}
|
||||
<form action="{{ url_for('post.delete_comment', comment_id=comment.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-link btn-sm text-danger"><i class="bi bi-trash"></i> {{ _('Delete') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not posts %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ _('No posts available.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
32
templates/notifications.html
Normal file
32
templates/notifications.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Notifications') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h3>
|
||||
<i class="bi bi-bell me-2"></i>
|
||||
{{ _('Notifications') }}
|
||||
<span class="badge bg-secondary">{{ notifications|length }}</span>
|
||||
{% if notifications %}
|
||||
<form action="{{ url_for('notif.delete_all_notifications') }}" method="post" style="display:inline;">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger float-end">
|
||||
<i class="bi bi-trash"></i> {{ _('Delete all') }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</h3>
|
||||
<ul class="list-group">
|
||||
{% for notif in notifications %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<i class="bi bi-info-circle text-info me-1"></i>
|
||||
{{ notif.message }}
|
||||
<span class="text-muted ms-2"><i class="bi bi-clock"></i> {{ notif.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
</span>
|
||||
<form action="{{ url_for('notif.delete_notification', notif_id=notif.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i> {{ _('Delete') }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="list-group-item">{{ _('No notifications') }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
16
templates/privacy_policy.html
Normal file
16
templates/privacy_policy.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Privacy Policy') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ _('Privacy Policy') }}</h1>
|
||||
<p>{{ _('Your privacy is important to us. This privacy policy explains how we collect, use, and protect your information when you use our application.') }}</p>
|
||||
<p>{{ _('We collect personal information that you provide to us, such as your name, email address, and any other information you choose to share.') }}</p>
|
||||
<p>{{ _('We use this information to provide and improve our services, communicate with you, and personalize your experience.') }}</p>
|
||||
<p>{{ _('We do not share your personal information with third parties without your consent, except as required by law or to protect our rights.') }}</p>
|
||||
<p>{{ _('We implement security measures to protect your information from unauthorized access, alteration, disclosure, or destruction.') }}</p>
|
||||
<p>{{ _('You have the right to access, correct, or delete your personal information at any time. Please contact us if you wish to exercise these rights.') }}</p>
|
||||
<p>{{ _('We may update this privacy policy from time to time. We will notify you of any changes by posting the new privacy policy on this page.') }}</p>
|
||||
<p>{{ _('By using our application, you agree to the terms of this privacy policy. If you do not agree, please do not use our application.') }}</p>
|
||||
<p>{{ _('If you have any questions or concerns about this privacy policy, please contact us.') }}</p>
|
||||
<a href="https://github.com/Michatec/MiniFaceBook" target="_blank">{{ _('GitHub Repository') }}</a>
|
||||
<p>{{ _('Thank you for using our application!') }}</p>
|
||||
{% endblock %}
|
||||
32
templates/profile.html
Normal file
32
templates/profile.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Profile') }}{% endblock %}
|
||||
{% block content %}
|
||||
<p><i class="bi bi-envelope-at me-1"></i>{{ _('Email') }}: {{ user.email }}</p>
|
||||
|
||||
<form action="{{ url_for('user.upload_pic') }}" method="post" enctype="multipart/form-data" class="mb-3">
|
||||
<input type="file" name="profile_pic" required>
|
||||
<button class="btn btn-secondary btn-sm" type="submit"><i class="bi bi-upload"></i> {{ _('Upload Picture') }}</button>
|
||||
</form>
|
||||
{% if user.profile_pic and user.profile_pic != 'default.png' %}
|
||||
<form action="{{ url_for('user.delete_pic') }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm"><i class="bi bi-trash"></i> {{ _('Delete Picture') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('shop') }}" class="btn btn-secondary"><i class="bi bi-shop"></i> {{ _('Shop') }}</a>
|
||||
<a href="{{ url_for('profil.edit_profile') }}" class="btn btn-secondary"><i class="bi bi-pencil-square"></i> {{ _('Edit Profile') }}</a>
|
||||
<form action="{{ url_for('user.delete_account') }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger"><i class="bi bi-person-x"></i> {{ _('Delete Account') }}</button>
|
||||
</form>
|
||||
{% if not user.discord_linked %}
|
||||
<a href="{{ url_for('discord.link_discord') }}" class="btn btn-primary">
|
||||
<i class="bi bi-discord"></i> {{ _('Link Discord Account') }}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="badge bg-success"><i class="bi bi-discord"></i> {{ _('Discord Linked') }}</span>
|
||||
<form action="{{ url_for('discord.unlink_discord') }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-warning btn-sm" type="submit">
|
||||
<i class="bi bi-x-circle"></i> {{ _('Unlink Discord') }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
41
templates/register.html
Normal file
41
templates/register.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Register') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-lg-5">
|
||||
<div class="card shadow p-4 mt-4" style="border-radius:1rem;">
|
||||
<h2 class="mb-4 text-center"><i class="bi bi-person-plus me-2"></i>{{ _('Register') }}</h2>
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-person-circle me-1"></i>{{ _('Username') }}</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-envelope-at me-1"></i>{{ _('Email') }}</label>
|
||||
<input type="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock-fill me-1"></i>{{ _('Password') }}</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock me-1"></i>{{ _('Confirm Password') }}</label>
|
||||
<input type="password" name="confirm_password" class="form-control" required>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-success" type="submit"><i class="bi bi-person-plus me-1"></i>{{ _('Register') }}</button>
|
||||
</div>
|
||||
<div class="mt-3 text-center">
|
||||
<span>{{ _('Already have an account?') }}</span>
|
||||
<a href="{{ url_for('log.login') }}" class="btn btn-link"><i class="bi bi-box-arrow-in-right"></i> {{ _('Login') }}</a>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<a href="{{ url_for('discord.login_discord') }}" class="btn btn-primary">
|
||||
<i class="bi bi-discord"></i> {{ _('Login with Discord') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
23
templates/reset_password.html
Normal file
23
templates/reset_password.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Reset Password') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-5">
|
||||
<div class="card shadow p-4 mt-4" style="border-radius:1rem;">
|
||||
<h2 class="mb-4 text-center"><i class="bi bi-key me-2"></i>{{ _('Reset Password') }}</h2>
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="username"><i class="bi bi-person-circle me-1"></i>{{ _('Username') }}</label>
|
||||
<input type="text" name="username" id="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-primary" type="submit"><i class="bi bi-arrow-clockwise me-1"></i>{{ _('Request Reset') }}</button>
|
||||
</div>
|
||||
<div class="mt-3 text-center">
|
||||
<a href="{{ url_for('log.login') }}" class="btn btn-link"><i class="bi bi-box-arrow-in-right"></i> {{ _('Login') }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
90
templates/reset_requests.html
Normal file
90
templates/reset_requests.html
Normal file
@@ -0,0 +1,90 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Password Reset Requests') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2>
|
||||
<i class="bi bi-arrow-clockwise me-2"></i>{{ _('Password Reset Requests') }}
|
||||
<a href="{{ url_for('admin.admin_delete_all_reset_requests') }}" class="btn btn-danger btn-sm float-end"><i class="bi bi-trash"></i> {{ _('Delete All') }}</a>
|
||||
</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="bi bi-person-circle"></i> {{ _('User') }}</th>
|
||||
<th><i class="bi bi-clock"></i> {{ _('Requested At') }}</th>
|
||||
<th><i class="bi bi-gear"></i> {{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in requests %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if req.user.profile_pic and req.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" class="rounded me-2" width="32">{{ req.user.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ req.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('admin.admin_reset_password', req_id=req.id) }}" class="btn btn-success btn-sm"><i class="bi bi-key"></i> {{ _('Set Password') }}</a>
|
||||
<form action="{{ url_for('admin.reject_reset_request', req_id=req.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" type="submit"><i class="bi bi-x"></i> {{ _('Reject') }}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if requests_done %}
|
||||
<h3><i class="bi bi-check-circle me-2"></i>{{ _('Completed Requests') }}</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="bi bi-person-circle"></i> {{ _('User') }}</th>
|
||||
<th><i class="bi bi-check-circle"></i> {{ _('Requested At') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in requests_done %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if req.user.profile_pic and req.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" class="rounded me-2" width="32">{{ req.user.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ req.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if requests_rejected %}
|
||||
<h3><i class="bi bi-x-circle me-2"></i>{{ _('Rejected Requests') }}</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="bi bi-person-circle"></i> {{ _('User') }}</th>
|
||||
<th><i class="bi bi-x-circle"></i> {{ _('Requested At') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in requests_rejected %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if req.user.profile_pic and req.user.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' ~ req.user.profile_pic) }}" class="rounded me-2" width="32">{{ req.user.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ req.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ req.requested_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if not requests %}
|
||||
<div class="alert alert-info"><i class="bi bi-info-circle"></i> {{ _('No open reset requests.') }}</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
294
templates/secret.html
Normal file
294
templates/secret.html
Normal file
@@ -0,0 +1,294 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}{{ _('Secret') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Secret</h1>
|
||||
<canvas width="320" height="640" id="game"></canvas>
|
||||
<script>
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
// generate a new tetromino sequence
|
||||
// @see https://tetris.fandom.com/wiki/Random_Generator
|
||||
function generateSequence() {
|
||||
const sequence = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'];
|
||||
|
||||
while (sequence.length) {
|
||||
const rand = getRandomInt(0, sequence.length - 1);
|
||||
const name = sequence.splice(rand, 1)[0];
|
||||
tetrominoSequence.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
// get the next tetromino in the sequence
|
||||
function getNextTetromino() {
|
||||
if (tetrominoSequence.length === 0) {
|
||||
generateSequence();
|
||||
}
|
||||
|
||||
const name = tetrominoSequence.pop();
|
||||
const matrix = tetrominos[name];
|
||||
|
||||
// I and O start centered, all others start in left-middle
|
||||
const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2);
|
||||
|
||||
// I starts on row 21 (-1), all others start on row 22 (-2)
|
||||
const row = name === 'I' ? -1 : -2;
|
||||
|
||||
return {
|
||||
name: name, // name of the piece (L, O, etc.)
|
||||
matrix: matrix, // the current rotation matrix
|
||||
row: row, // current row (starts offscreen)
|
||||
col: col // current col
|
||||
};
|
||||
}
|
||||
|
||||
// rotate an NxN matrix 90deg
|
||||
// @see https://codereview.stackexchange.com/a/186834
|
||||
function rotate(matrix) {
|
||||
const N = matrix.length - 1;
|
||||
const result = matrix.map((row, i) =>
|
||||
row.map((val, j) => matrix[N - j][i])
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// check to see if the new matrix/row/col is valid
|
||||
function isValidMove(matrix, cellRow, cellCol) {
|
||||
for (let row = 0; row < matrix.length; row++) {
|
||||
for (let col = 0; col < matrix[row].length; col++) {
|
||||
if (matrix[row][col] && (
|
||||
// outside the game bounds
|
||||
cellCol + col < 0 ||
|
||||
cellCol + col >= playfield[0].length ||
|
||||
cellRow + row >= playfield.length ||
|
||||
// collides with another piece
|
||||
playfield[cellRow + row][cellCol + col])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// place the tetromino on the playfield
|
||||
function placeTetromino() {
|
||||
for (let row = 0; row < tetromino.matrix.length; row++) {
|
||||
for (let col = 0; col < tetromino.matrix[row].length; col++) {
|
||||
if (tetromino.matrix[row][col]) {
|
||||
|
||||
// game over if piece has any part offscreen
|
||||
if (tetromino.row + row < 0) {
|
||||
return showGameOver();
|
||||
}
|
||||
|
||||
playfield[tetromino.row + row][tetromino.col + col] = tetromino.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for line clears starting from the bottom and working our way up
|
||||
for (let row = playfield.length - 1; row >= 0; ) {
|
||||
if (playfield[row].every(cell => !!cell)) {
|
||||
|
||||
// drop every row above this one
|
||||
for (let r = row; r >= 0; r--) {
|
||||
for (let c = 0; c < playfield[r].length; c++) {
|
||||
playfield[r][c] = playfield[r-1][c];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
row--;
|
||||
}
|
||||
}
|
||||
|
||||
tetromino = getNextTetromino();
|
||||
}
|
||||
|
||||
// show the game over screen
|
||||
function showGameOver() {
|
||||
cancelAnimationFrame(rAF);
|
||||
gameOver = true;
|
||||
|
||||
context.fillStyle = 'black';
|
||||
context.globalAlpha = 0.75;
|
||||
context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60);
|
||||
|
||||
context.globalAlpha = 1;
|
||||
context.fillStyle = 'white';
|
||||
context.font = '36px monospace';
|
||||
context.textAlign = 'center';
|
||||
context.textBaseline = 'middle';
|
||||
context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2);
|
||||
}
|
||||
|
||||
const canvas = document.getElementById('game');
|
||||
const context = canvas.getContext('2d');
|
||||
const grid = 32;
|
||||
const tetrominoSequence = [];
|
||||
|
||||
// keep track of what is in every cell of the game using a 2d array
|
||||
// tetris playfield is 10x20, with a few rows offscreen
|
||||
const playfield = [];
|
||||
|
||||
// populate the empty state
|
||||
for (let row = -2; row < 20; row++) {
|
||||
playfield[row] = [];
|
||||
|
||||
for (let col = 0; col < 10; col++) {
|
||||
playfield[row][col] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// how to draw each tetromino
|
||||
// @see https://tetris.fandom.com/wiki/SRS
|
||||
const tetrominos = {
|
||||
'I': [
|
||||
[0,0,0,0],
|
||||
[1,1,1,1],
|
||||
[0,0,0,0],
|
||||
[0,0,0,0]
|
||||
],
|
||||
'J': [
|
||||
[1,0,0],
|
||||
[1,1,1],
|
||||
[0,0,0],
|
||||
],
|
||||
'L': [
|
||||
[0,0,1],
|
||||
[1,1,1],
|
||||
[0,0,0],
|
||||
],
|
||||
'O': [
|
||||
[1,1],
|
||||
[1,1],
|
||||
],
|
||||
'S': [
|
||||
[0,1,1],
|
||||
[1,1,0],
|
||||
[0,0,0],
|
||||
],
|
||||
'Z': [
|
||||
[1,1,0],
|
||||
[0,1,1],
|
||||
[0,0,0],
|
||||
],
|
||||
'T': [
|
||||
[0,1,0],
|
||||
[1,1,1],
|
||||
[0,0,0],
|
||||
]
|
||||
};
|
||||
|
||||
// color of each tetromino
|
||||
const colors = {
|
||||
'I': 'cyan',
|
||||
'O': 'yellow',
|
||||
'T': 'purple',
|
||||
'S': 'green',
|
||||
'Z': 'red',
|
||||
'J': 'blue',
|
||||
'L': 'orange'
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
let tetromino = getNextTetromino();
|
||||
let rAF = null; // keep track of the animation frame so we can cancel it
|
||||
let gameOver = false;
|
||||
|
||||
// game loop
|
||||
function loop() {
|
||||
rAF = requestAnimationFrame(loop);
|
||||
context.clearRect(0,0,canvas.width,canvas.height);
|
||||
|
||||
// draw the playfield
|
||||
for (let row = 0; row < 20; row++) {
|
||||
for (let col = 0; col < 10; col++) {
|
||||
if (playfield[row][col]) {
|
||||
const name = playfield[row][col];
|
||||
context.fillStyle = colors[name];
|
||||
|
||||
// drawing 1 px smaller than the grid creates a grid effect
|
||||
context.fillRect(col * grid, row * grid, grid-1, grid-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the active tetromino
|
||||
if (tetromino) {
|
||||
|
||||
// tetromino falls every 35 frames
|
||||
if (++count > 35) {
|
||||
tetromino.row++;
|
||||
count = 0;
|
||||
|
||||
// place piece if it runs into anything
|
||||
if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) {
|
||||
tetromino.row--;
|
||||
placeTetromino();
|
||||
}
|
||||
}
|
||||
|
||||
context.fillStyle = colors[tetromino.name];
|
||||
|
||||
for (let row = 0; row < tetromino.matrix.length; row++) {
|
||||
for (let col = 0; col < tetromino.matrix[row].length; col++) {
|
||||
if (tetromino.matrix[row][col]) {
|
||||
|
||||
// drawing 1 px smaller than the grid creates a grid effect
|
||||
context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid-1, grid-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// listen to keyboard events to move the active tetromino
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (gameOver) return;
|
||||
|
||||
// left and right arrow keys (move)
|
||||
if (e.which === 37 || e.which === 39) {
|
||||
const col = e.which === 37
|
||||
? tetromino.col - 1
|
||||
: tetromino.col + 1;
|
||||
|
||||
if (isValidMove(tetromino.matrix, tetromino.row, col)) {
|
||||
tetromino.col = col;
|
||||
}
|
||||
}
|
||||
|
||||
// up arrow key (rotate)
|
||||
if (e.which === 38) {
|
||||
const matrix = rotate(tetromino.matrix);
|
||||
if (isValidMove(matrix, tetromino.row, tetromino.col)) {
|
||||
tetromino.matrix = matrix;
|
||||
}
|
||||
}
|
||||
|
||||
// down arrow key (drop)
|
||||
if(e.which === 40) {
|
||||
const row = tetromino.row + 1;
|
||||
|
||||
if (!isValidMove(tetromino.matrix, row, tetromino.col)) {
|
||||
tetromino.row = row - 1;
|
||||
|
||||
placeTetromino();
|
||||
return;
|
||||
}
|
||||
|
||||
tetromino.row = row;
|
||||
}
|
||||
});
|
||||
|
||||
// start the game
|
||||
rAF = requestAnimationFrame(loop);
|
||||
</script>
|
||||
{% endblock %}
|
||||
24
templates/setup.html
Normal file
24
templates/setup.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Admin Setup') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-person-gear me-2"></i>{{ _('Admin Account Setup') }}</h2>
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-person-circle me-1"></i>{{ _('Username') }}</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-envelope-at me-1"></i>{{ _('Email') }}</label>
|
||||
<input type="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock-fill me-1"></i>{{ _('Password') }}</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="bi bi-lock me-1"></i>{{ _('Confirm Password') }}</label>
|
||||
<input type="password" name="confirm_password" class="form-control" required>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit"><i class="bi bi-person-gear me-1"></i>{{ _('Create Admin') }}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
33
templates/shop.html
Normal file
33
templates/shop.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Shop') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-shop me-2"></i>{{ _('Shop') }}</h2>
|
||||
<p>{{ _('Deine Reward-Punkte:') }} <b>{{ current_user.reward_points() }}</b></p>
|
||||
{% if message %}
|
||||
<div class="alert alert-info">{{ message }}</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
{% for item in items %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi {{ item.icon }} display-4 mb-2"></i>
|
||||
<h5>{{ item.name }}</h5>
|
||||
<p>{{ item.description }}</p>
|
||||
<p><b>{{ item.price }}</b> {{ _('Points') }}</p>
|
||||
{% if item.id in owned_ids %}
|
||||
<button class="btn btn-secondary" disabled>{{ _('Bought') }}</button>
|
||||
{% else %}
|
||||
<form method="post">
|
||||
<input type="hidden" name="item_id" value="{{ item.id }}">
|
||||
<button class="btn btn-success" {% if current_user.reward_points() < item.price %}disabled{% endif %}>
|
||||
<i class="bi bi-cart"></i> {{ _('Buy') }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
49
templates/support.html
Normal file
49
templates/support.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Support') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<p>{{ _('If you have any questions or need assistance, please contact us at:') }}</p>
|
||||
<p><strong>{{ _('Github:') }}</strong> <a href="https://github.com/Michatec/MiniFaceBook/issues" target="_blank">https://github.com/Michatec/MiniFaceBook/issues</a></p>
|
||||
</div>
|
||||
{% if user.is_owner %}
|
||||
<form action="{{ url_for('admin.wipe_server') }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" type="submit">
|
||||
<i class="bi bi-x-circle"></i> {{ _('Wipe Server') }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<h1><i class="bi bi-question-circle me-2"></i>{{ _('Support') }}</h1>
|
||||
<form method="POST" class="mb-4">
|
||||
<input type="text" name="title" placeholder="{{ _('Title') }}" required class="form-control mb-2">
|
||||
<textarea name="description" placeholder="{{ _('Describe your issue...') }}" required class="form-control mb-2"></textarea>
|
||||
<button class="btn btn-primary">{{ _('Create Ticket') }}</button>
|
||||
</form>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('Title') }}</th>
|
||||
<th>{{ _('Status') }}</th>
|
||||
<th>{{ _('Created') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ticket in support_requests %}
|
||||
<tr>
|
||||
<td>{{ ticket.title }}</td>
|
||||
<td>
|
||||
{% if ticket.status == 'open' %}
|
||||
<span class="badge bg-success">{{ _('Open') }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ _('Closed') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ ticket.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('support.support_thread', request_id=ticket.id) }}" class="btn btn-sm btn-info">{{ _('View') }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
40
templates/support_thread.html
Normal file
40
templates/support_thread.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ ticket.title }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2>{{ ticket.title }}</h2>
|
||||
<p><strong>{{ _('Status') }}:</strong>
|
||||
{% if ticket.status == 'open' %}
|
||||
<span class="badge bg-success">{{ _('Open') }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ _('Closed') }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
{% for comment in comments %}
|
||||
<div class="border rounded p-2 mb-2">
|
||||
<strong>{{ comment.user.username }}</strong>
|
||||
<span class="text-muted">{{ comment.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
<p>{{ comment.message }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if ticket.status == 'open' %}
|
||||
<form method="POST">
|
||||
<textarea name="message" class="form-control mb-2" placeholder="{{ _('Write a message...') }}" required></textarea>
|
||||
<button class="btn btn-primary">{{ _('Send') }}</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('support.support_close', request_id=ticket.id) }}">
|
||||
<button class="btn btn-warning mt-2">{{ _('Close Ticket') }}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="alert alert-info">{{ _('This ticket is closed.') }}</div>
|
||||
{% endif %}
|
||||
{% if user.is_admin %}
|
||||
<form method="POST" action="{{ url_for('support.support_delete', request_id=ticket.id) }}" style="display:inline;">
|
||||
<button class="btn btn-danger btn-sm" onclick="return confirm('Delete this ticket?')">
|
||||
<i class="bi bi-trash"></i> {{ _('Delete') }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('support.support') }}" class="btn btn-secondary mt-3">{{ _('Back to Support') }}</a>
|
||||
{% endblock %}
|
||||
46
templates/users.html
Normal file
46
templates/users.html
Normal file
@@ -0,0 +1,46 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Users') }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-people me-2"></i>{{ _('All Users') }}</h2>
|
||||
<ul class="list-group">
|
||||
{% for user_item in users %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
{% if user_item.profile_pic and user_item.profile_pic != 'default.png' %}
|
||||
<img src="{{ url_for('static', filename='profile_pics/' + user_item.profile_pic) }}" alt="{{ user_item.username }}" class="profile-pic me-2">{{ user_item.username }}
|
||||
{% else %}
|
||||
<i class="bi bi-person-circle me-1"></i>{{ user_item.username }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<div>
|
||||
{% if user_item.id != user.id %}
|
||||
{% set friendship = (user.friendships_sent | selectattr('receiver_id', 'equalto', user_item.id) | list) %}
|
||||
{% set reverse_friendship = (user.friendships_received | selectattr('requester_id', 'equalto', user_item.id) | list) %}
|
||||
{% if friendship and friendship[0].status == 'pending' %}
|
||||
<span class="badge bg-warning"><i class="bi bi-hourglass-split me-1"></i>{{ _('Request sent') }}</span>
|
||||
{% elif reverse_friendship and reverse_friendship[0].status == 'pending' %}
|
||||
<span class="badge bg-info"><i class="bi bi-envelope-open me-1"></i>{{ _('Request received') }}</span>
|
||||
{% elif (friendship and friendship[0].status == 'accepted') or (reverse_friendship and reverse_friendship[0].status == 'accepted') %}
|
||||
<span class="badge bg-success"><i class="bi bi-person-check me-1"></i>{{ _('Friend') }}</span>
|
||||
{% else %}
|
||||
<form action="{{ url_for('friend.add_friend', user_id=user_item.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-sm btn-primary"><i class="bi bi-person-plus"></i> {{ _('Add Friend') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if user.is_admin %}
|
||||
{% if not user_item.is_admin %}
|
||||
<form action="{{ url_for('admin.make_admin', user_id=user_item.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-sm btn-success"><i class="bi bi-person-gear"></i> {{ _('Make Admin') }}</button>
|
||||
</form>
|
||||
{% elif not user_item.is_owner %}
|
||||
<form action="{{ url_for('admin.remove_admin', user_id=user_item.id) }}" method="post" style="display:inline;">
|
||||
<button class="btn btn-sm btn-warning"><i class="bi bi-person-dash"></i> {{ _('Remove Admin') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user