This commit is contained in:
2025-08-07 18:18:36 -04:00
parent 8df728530e
commit 918fc91604
28 changed files with 1127 additions and 243 deletions

134
app/templates/base.html Normal file
View File

@@ -0,0 +1,134 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}Pets of Powerwashing{% endblock %}</title>
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
rel="stylesheet"
/>
<style>
.modal {
display: none;
}
.modal.active {
display: flex;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<nav class="bg-white">
<div class="max-w-6xl mx-auto px-4">
<div class="flex justify-between items-center py-4">
<div class="text-xl font-semibold text-gray-800">
<a href="{{ url_for('main.index') }}">Pets of Powerwashing</a>
</div>
<div class="space-x-4">
<a
href="{{ url_for('main.index') }}"
class="text-gray-600 hover:text-gray-800"
>Gallery</a
>
{% if session.logged_in %}
<a
href="{{ url_for('pictures.upload') }}"
class="text-gray-600 hover:text-gray-800"
>Upload New</a
>
<a
href="{{ url_for('auth.logout') }}"
class="text-gray-600 hover:text-gray-800"
>Logout</a
>
{% else %}
<a
href="{{ url_for('auth.login') }}"
class="text-gray-600 hover:text-gray-800"
>Login</a
>
{% endif %}
</div>
</div>
</div>
</nav>
<main class="max-w-6xl mx-auto px-4 py-8">
{% with messages = get_flashed_messages() %} {% if messages %} {% for
message in messages %}
<div
class="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 mb-4"
role="alert"
>
{{ message }}
</div>
{% endfor %} {% endif %} {% endwith %} {% block content %}{% endblock %}
</main>
<!-- Modal for fullscreen image view -->
<div id="imageModal" class="modal fixed inset-0 bg-black bg-opacity-75 items-center justify-center z-50" onclick="closeModal()">
<div class="relative max-w-full max-h-full p-4" onclick="event.stopPropagation()">
<button onclick="closeModal()" class="absolute top-4 right-4 text-white text-4xl hover:text-gray-300 z-10">&times;</button>
<img id="modalImage" src="" alt="" class="max-w-full max-h-full object-contain">
<div id="modalInfo" class="absolute bottom-4 left-4 bg-black bg-opacity-50 text-white p-3 rounded">
<h3 id="modalTitle" class="text-lg font-semibold"></h3>
<p id="modalDescription" class="text-sm"></p>
<p id="modalDate" class="text-xs opacity-75"></p>
</div>
</div>
</div>
<script>
function openModal(imageSrc, title, description, date) {
document.getElementById('modalImage').src = imageSrc;
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalDescription').textContent = description;
document.getElementById('modalDate').textContent = date;
document.getElementById('imageModal').classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeModal() {
document.getElementById('imageModal').classList.remove('active');
document.body.style.overflow = 'auto';
}
// Close modal with Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeModal();
}
});
// Like functionality
function likePicture(pictureId) {
fetch('/like/' + pictureId, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
const likeBtn = document.getElementById('like-btn-' + pictureId);
const likeCount = document.getElementById('like-count-' + pictureId);
// Update like count
likeCount.textContent = data.like_count;
// Update button appearance and heart
if (data.liked) {
likeBtn.className = 'flex-1 flex items-center justify-center px-4 py-2 rounded transition-colors bg-red-500 text-white hover:bg-red-600';
likeBtn.querySelector('span').textContent = '❤️';
} else {
likeBtn.className = 'flex-1 flex items-center justify-center px-4 py-2 rounded transition-colors bg-gray-200 text-gray-700 hover:bg-gray-300';
likeBtn.querySelector('span').textContent = '🤍';
}
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Page Not Found - Pets of Powerwashing{% endblock %}
{% block content %}
<div class="text-center py-12">
<h1 class="text-6xl font-bold text-gray-400 mb-4">404</h1>
<h2 class="text-2xl font-semibold text-gray-800 mb-4">Page Not Found</h2>
<p class="text-gray-600 mb-6">Sorry, the page you're looking for doesn't exist.</p>
<a href="{{ url_for('main.index') }}" class="bg-blue-500 text-white px-6 py-3 rounded hover:bg-blue-600 transition-colors">
Back to Gallery
</a>
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}File Too Large - Pets of Powerwashing{% endblock %}
{% block content %}
<div class="text-center py-12">
<h1 class="text-6xl font-bold text-gray-400 mb-4">413</h1>
<h2 class="text-2xl font-semibold text-gray-800 mb-4">File Too Large</h2>
<p class="text-gray-600 mb-6">The file you're trying to upload is too large. Please choose a smaller file.</p>
<a href="{{ url_for('pictures.upload') }}" class="bg-blue-500 text-white px-6 py-3 rounded hover:bg-blue-600 transition-colors">
Try Again
</a>
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Server Error - Pets of Powerwashing{% endblock %}
{% block content %}
<div class="text-center py-12">
<h1 class="text-6xl font-bold text-gray-400 mb-4">500</h1>
<h2 class="text-2xl font-semibold text-gray-800 mb-4">Internal Server Error</h2>
<p class="text-gray-600 mb-6">Something went wrong on our end. Please try again later.</p>
<a href="{{ url_for('main.index') }}" class="bg-blue-500 text-white px-6 py-3 rounded hover:bg-blue-600 transition-colors">
Back to Gallery
</a>
</div>
{% endblock %}

92
app/templates/index.html Normal file
View File

@@ -0,0 +1,92 @@
{% extends "base.html" %} {% block title %}Pet Pictures - Pets of Powerwashing{%
endblock %} {% block content %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for picture in pictures %}
<div
class="bg-white rounded-lg shadow-md overflow-hidden {% if picture.posted and not public_view %}opacity-50{% endif %}"
>
<div class="relative group">
<a
href="javascript:void(0)"
onclick="openModal('{{ url_for('static', filename='uploads/' + picture.filename) }}', 'From: {{ picture.subscriber_name }}', '{{ picture.description or '' }}', 'Uploaded: {{ picture.uploaded_at }}')"
class="block transform transition-transform hover:scale-105"
>
<img
src="{{ url_for('static', filename='uploads/' + picture.filename) }}"
alt="Pet picture from {{ picture.subscriber_name }}"
class="w-full h-64 object-cover transition-transform duration-300"
/>
<div
class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-opacity duration-300 flex items-center justify-center"
>
<span
class="text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300"
>
Click to view fullscreen
</span>
</div>
</a>
</div>
<div class="p-4">
<h3 class="text-lg font-semibold text-gray-800">
From: {{ picture.subscriber_name }}
</h3>
{% if picture.description %}
<p class="mt-2 text-gray-600">{{ picture.description }}</p>
{% endif %}
<p class="text-sm text-gray-600">Uploaded: {{ picture.uploaded_at }}</p>
<div class="mt-4 space-y-2">
<div class="flex space-x-2 mb-2">
<button
onclick="likePicture({{ picture.id }})"
id="like-btn-{{ picture.id }}"
class="flex-1 flex items-center justify-center px-4 py-2 rounded transition-colors {% if picture.user_liked %}bg-red-500 text-white hover:bg-red-600{% else %}bg-gray-200 text-gray-700 hover:bg-gray-300{% endif %}"
>
<span class="mr-1">{% if picture.user_liked %}❤️{% else %}🤍{% endif %}</span>
<span id="like-count-{{ picture.id }}">{{ picture.likes or 0 }}</span>
</button>
</div>
<a
href="{{ url_for('pictures.download_file', filename=picture.filename) }}"
class="block w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors text-center"
>
Download Original
</a>
{% if not public_view %}
{% if not picture.posted %}
<form
action="{{ url_for('pictures.mark_posted', picture_id=picture.id) }}"
method="POST"
>
<button
type="submit"
class="w-full bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 transition-colors"
>
Mark as Posted
</button>
</form>
{% else %}
<div class="text-center text-green-600 font-semibold">✓ Posted</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% else %}
<div class="col-span-full text-center py-12">
{% if public_view %}
<p class="text-gray-600 text-lg">No published pet pictures yet.</p>
<p class="text-gray-500 text-sm mt-2">Pictures will appear here once they are marked as posted by an admin.</p>
{% else %}
<p class="text-gray-600 text-lg">No pet pictures uploaded yet.</p>
<a
href="{{ url_for('pictures.upload') }}"
class="mt-4 inline-block bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600 transition-colors"
>
Upload Your First Picture
</a>
{% endif %}
</div>
{% endfor %}
</div>
{% endblock %}

50
app/templates/login.html Normal file
View File

@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block title %}Login - Pets of Powerwashing{% endblock %}
{% block content %}
<div class="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-6 text-center">Admin Login</h2>
<form method="POST" class="space-y-4">
<div>
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">
Username
</label>
<input
type="text"
id="username"
name="username"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Enter username"
/>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<input
type="password"
id="password"
name="password"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Enter password"
/>
</div>
<button
type="submit"
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
>
Login
</button>
</form>
<div class="mt-6 text-center">
<a href="{{ url_for('main.index') }}" class="text-blue-500 hover:text-blue-600 text-sm">
Back to Gallery
</a>
</div>
</div>
{% endblock %}

95
app/templates/upload.html Normal file
View File

@@ -0,0 +1,95 @@
{% extends "base.html" %} {% block title %}Upload Pet Picture - Pets of
Powerwashing{% endblock %} {% block content %}
<div class="max-w-2xl mx-auto">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-6">Upload Pet Picture</h2>
<form
action="{{ url_for('pictures.upload') }}"
method="POST"
enctype="multipart/form-data"
class="space-y-6"
>
<div>
<label
for="subscriber_name"
class="block text-sm font-medium text-gray-700"
>Subscriber Name</label
>
<input
type="text"
name="subscriber_name"
id="subscriber_name"
required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700"
>Description (Optional)</label
>
<textarea
name="description"
id="description"
rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
placeholder="Add a description for the pet picture..."
></textarea>
</div>
<div>
<label for="picture" class="block text-sm font-medium text-gray-700"
>Pet Picture</label
>
<div
class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"
>
<div class="space-y-1 text-center">
<svg
class="mx-auto h-12 w-12 text-gray-400"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<div class="flex text-sm text-gray-600">
<label
for="picture"
class="relative cursor-pointer bg-white rounded-md font-medium text-blue-600 hover:text-blue-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-blue-500"
>
<span>Upload a file</span>
<input
id="picture"
name="picture"
type="file"
class="sr-only"
accept="image/*"
required
/>
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs text-gray-500">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</div>
<div>
<button
type="submit"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Upload Picture
</button>
</div>
</form>
</div>
</div>
{% endblock %}