Initial commit
This commit is contained in:
369
app/internal/templates/posts.html
Normal file
369
app/internal/templates/posts.html
Normal file
@@ -0,0 +1,369 @@
|
||||
{{ define "content" }}
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
<!-- Header -->
|
||||
<div class="bg-white border-b border-gray-200 sticky top-0 z-10">
|
||||
<div class="max-w-2xl mx-auto px-4 py-4">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Posts</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<!-- Create Post Form -->
|
||||
<div class="bg-white border-b border-gray-200 p-6">
|
||||
<form
|
||||
action="/posts"
|
||||
method="POST"
|
||||
enctype="multipart/form-data"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 bg-blue-500 flex items-center justify-center text-white font-semibold"
|
||||
>
|
||||
U
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<textarea
|
||||
id="content"
|
||||
name="content"
|
||||
placeholder="What's on your mind?"
|
||||
class="w-full px-0 py-2 text-gray-900 placeholder-gray-500 border-0 resize-none focus:outline-none focus:ring-0"
|
||||
rows="3"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between pt-4 border-t border-gray-100"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<label
|
||||
for="image"
|
||||
class="cursor-pointer flex items-center space-x-2 text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium">Photo</span>
|
||||
</label>
|
||||
<input
|
||||
id="image"
|
||||
type="file"
|
||||
name="image"
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 text-sm font-semibold transition-colors disabled:opacity-50"
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Image preview -->
|
||||
<div id="imagePreview" class="hidden">
|
||||
<img
|
||||
id="previewImg"
|
||||
class="w-full h-64 object-cover border"
|
||||
alt="Preview"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="removeImage"
|
||||
class="mt-2 text-red-500 text-sm hover:text-red-600"
|
||||
>
|
||||
Remove image
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Posts Feed -->
|
||||
<div class="space-y-0">
|
||||
{{range .Posts}}
|
||||
<article class="bg-white border-b border-gray-200">
|
||||
<!-- Post Header -->
|
||||
<div class="flex items-center px-6 py-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 bg-blue-500 flex items-center justify-center text-white font-semibold"
|
||||
>
|
||||
{{slice .AuthorName 0 1}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900">{{.AuthorName}}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{.CreatedAt.Format "Jan 2, 2006"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post Image -->
|
||||
{{if .ImageURL}}
|
||||
<div class="w-full">
|
||||
<img
|
||||
src="{{.ImageURL}}"
|
||||
alt="Post image"
|
||||
class="w-full max-h-96 object-cover"
|
||||
onerror="this.parentElement.style.display='none'"
|
||||
/>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Post Actions -->
|
||||
<div class="px-6 py-3">
|
||||
<div class="flex items-center space-x-6">
|
||||
<!-- Like Button -->
|
||||
<button
|
||||
class="reaction-btn flex items-center space-x-2 text-gray-600 hover:text-blue-500 transition-colors"
|
||||
data-post-id="{{.PostID}}"
|
||||
data-reaction="like"
|
||||
>
|
||||
<i class="fa-solid fa-thumbs-up text-lg"></i>
|
||||
<span class="text-sm font-medium like-count">0</span>
|
||||
</button>
|
||||
|
||||
<!-- Dislike Button -->
|
||||
<button
|
||||
class="reaction-btn flex items-center space-x-2 text-gray-600 hover:text-red-500 transition-colors"
|
||||
data-post-id="{{.PostID}}"
|
||||
data-reaction="dislike"
|
||||
>
|
||||
<i class="fa-solid fa-thumbs-down text-lg"></i>
|
||||
<span class="text-sm font-medium dislike-count">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post Content -->
|
||||
{{if .Content}}
|
||||
<div class="px-6 pb-4">
|
||||
<p class="text-gray-900 leading-relaxed">
|
||||
<span class="font-semibold">{{.AuthorName}}</span> {{.Content}}
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
{{else}}
|
||||
<div class="bg-white p-12 text-center">
|
||||
<div class="max-w-sm mx-auto">
|
||||
<svg
|
||||
class="w-16 h-16 mx-auto text-gray-300 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No posts yet</h3>
|
||||
<p class="text-gray-500">
|
||||
Be the first to share something with the community!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Custom styles for Instagram-like feel */
|
||||
.reaction-btn.active {
|
||||
color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
.reaction-btn.active svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.reaction-btn.dislike-active {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
.reaction-btn {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.reaction-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
button:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const fileInput = document.getElementById("image");
|
||||
const imagePreview = document.getElementById("imagePreview");
|
||||
const previewImg = document.getElementById("previewImg");
|
||||
const removeImageBtn = document.getElementById("removeImage");
|
||||
const form = document.querySelector("form");
|
||||
|
||||
// Image upload preview
|
||||
if (fileInput) {
|
||||
fileInput.addEventListener("change", function (e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
console.log(
|
||||
"Selected file:",
|
||||
file.name,
|
||||
"Size:",
|
||||
file.size,
|
||||
"Type:",
|
||||
file.type
|
||||
);
|
||||
|
||||
// Validate file size (10MB max)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert("File is too large. Maximum size is 10MB.");
|
||||
this.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
const allowedTypes = [
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
alert("Invalid file type. Please select a valid image file.");
|
||||
this.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Show preview
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
previewImg.src = e.target.result;
|
||||
imagePreview.classList.remove("hidden");
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove image preview
|
||||
if (removeImageBtn) {
|
||||
removeImageBtn.addEventListener("click", function () {
|
||||
fileInput.value = "";
|
||||
imagePreview.classList.add("hidden");
|
||||
previewImg.src = "";
|
||||
});
|
||||
}
|
||||
|
||||
// Reaction buttons
|
||||
const reactionBtns = document.querySelectorAll(".reaction-btn");
|
||||
reactionBtns.forEach(function (btn) {
|
||||
btn.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
const postId = this.dataset.postId;
|
||||
const reaction = this.dataset.reaction;
|
||||
|
||||
// Toggle active state
|
||||
if (reaction === "like") {
|
||||
this.classList.toggle("active");
|
||||
// Remove dislike active state from sibling
|
||||
const dislikeBtn = this.parentElement.querySelector(
|
||||
'[data-reaction="dislike"]'
|
||||
);
|
||||
dislikeBtn.classList.remove("dislike-active");
|
||||
} else {
|
||||
this.classList.toggle("dislike-active");
|
||||
// Remove like active state from sibling
|
||||
const likeBtn = this.parentElement.querySelector(
|
||||
'[data-reaction="like"]'
|
||||
);
|
||||
likeBtn.classList.remove("active");
|
||||
}
|
||||
|
||||
// Update count (mock implementation)
|
||||
const countSpan = this.querySelector("span");
|
||||
const currentCount = parseInt(countSpan.textContent);
|
||||
const isActive =
|
||||
this.classList.contains("active") ||
|
||||
this.classList.contains("dislike-active");
|
||||
countSpan.textContent = isActive
|
||||
? currentCount + 1
|
||||
: Math.max(0, currentCount - 1);
|
||||
|
||||
console.log(`${reaction} clicked for post ${postId}`);
|
||||
|
||||
// Here you would typically send an AJAX request to update the backend
|
||||
// fetch(`/posts/${postId}/react`, {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ reaction: reaction })
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
||||
// Form submission
|
||||
if (form) {
|
||||
form.addEventListener("submit", function (e) {
|
||||
const content = document.getElementById("content").value.trim();
|
||||
const hasImage = fileInput.files.length > 0;
|
||||
|
||||
if (!content && !hasImage) {
|
||||
e.preventDefault();
|
||||
alert("Please add some content or an image to your post.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Form being submitted...");
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-resize textarea
|
||||
const textarea = document.getElementById("content");
|
||||
if (textarea) {
|
||||
textarea.addEventListener("input", function () {
|
||||
this.style.height = "auto";
|
||||
this.style.height = this.scrollHeight + "px";
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth scroll to top when clicking header
|
||||
const header = document.querySelector("h1");
|
||||
if (header) {
|
||||
header.addEventListener("click", function () {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user