limited ratings so that only one per user and restyled the comment frame for a user

This commit is contained in:
zipben 2025-06-04 14:38:51 +00:00
parent c5ddede56e
commit 111742cf99
3 changed files with 58 additions and 33 deletions

View file

@ -149,7 +149,7 @@
<div class="grow px-2 md:px-10">
<div class="mt-12">
<div class="comments-section mt-4 border-t border-gray-700 pt-4">
<h3 class="text-xl font-semibold mb-4">{{ $strings.LabelComments }}</h3>
<h3 class="text-xl font-semibold mb-4">{{ $strings.LabelRatings }}</h3>
<!-- Average Rating Display -->
<div v-if="comments.length" class="mb-4">
@ -162,7 +162,7 @@
</div>
<!-- Add Comment Form -->
<div class="mb-6">
<div v-if="!hasUserComment" class="mb-6">
<div class="bg-bg border border-gray-700 rounded-lg p-4">
<textarea v-model="newComment" :placeholder="$strings.PlaceholderAddComment" class="w-full p-2 bg-transparent border border-gray-600 rounded resize-none focus:outline-none mb-3" rows="3"></textarea>
@ -174,11 +174,10 @@
<span class="abs-icons icon-star"></span>
</button>
</div>
<button v-if="newRating" class="ml-2 text-sm text-gray-400 hover:text-white" @click="newRating = 0">({{ $strings.ButtonClear }})</button>
</div>
<div class="flex justify-end">
<button class="px-4 py-2 bg-primary text-white rounded hover:bg-opacity-75" @click="postComment">
<button class="px-4 py-2 bg-primary text-white rounded hover:bg-opacity-75 disabled:opacity-50 disabled:cursor-not-allowed" :disabled="!newRating" @click="postComment">
{{ $strings.ButtonPost }}
</button>
</div>
@ -187,10 +186,15 @@
<!-- Comments List -->
<div v-if="comments.length" class="space-y-4">
<div v-for="comment in comments" :key="comment.id" class="bg-bg border border-gray-700 rounded-lg p-4">
<div v-for="comment in sortedComments" :key="comment.id" class="bg-bg border rounded-lg p-4" :class="comment.userId === currentUser.id ? 'border-white border-4' : 'border-gray-700'">
<div class="flex justify-between items-start mb-2">
<div>
<span class="font-semibold">{{ comment.user.username }}</span>
<template v-if="comment.userId === currentUser.id">
<nuxt-link to="/account" class="font-semibold hover:text-white hover:underline">{{ comment.user.username }}</nuxt-link>
</template>
<template v-else>
<span class="font-semibold">{{ comment.user.username }}</span>
</template>
<span class="text-sm text-gray-400 ml-2">
{{ formatDate(comment.createdAt) }}
</span>
@ -224,11 +228,10 @@
<span class="abs-icons icon-star"></span>
</button>
</div>
<button v-if="editRating" class="ml-2 text-sm text-gray-400 hover:text-white" @click="editRating = 0">({{ $strings.ButtonClear }})</button>
</div>
<div class="flex space-x-2">
<button class="px-4 py-2 bg-primary text-white rounded hover:bg-opacity-75" @click="saveEdit(comment)">
<button class="px-4 py-2 bg-primary text-white rounded hover:bg-opacity-75 disabled:opacity-50 disabled:cursor-not-allowed" :disabled="!editRating" @click="saveEdit(comment)">
{{ $strings.ButtonSave }}
</button>
<button class="px-4 py-2 bg-gray-700 text-white rounded hover:bg-opacity-75" @click="cancelEdit">
@ -562,6 +565,18 @@ export default {
if (!ratedComments.length) return 0
const sum = ratedComments.reduce((acc, comment) => acc + comment.rating, 0)
return sum / ratedComments.length
},
hasUserComment() {
return this.comments.some((comment) => comment.userId === this.$store.state.user.user.id)
},
sortedComments() {
return [...this.comments].sort((a, b) => {
// Put current user's comment at the top
if (a.userId === this.currentUser.id) return -1
if (b.userId === this.currentUser.id) return 1
// Sort remaining comments by date, newest first
return new Date(b.createdAt) - new Date(a.createdAt)
})
}
},
methods: {
@ -593,7 +608,7 @@ export default {
.$get(`/api/podcasts/${this.libraryItemId}/clear-queue`)
.then(() => {
this.$toast.success(this.$strings.ToastEpisodeDownloadQueueClearSuccess)
this.episodeDownloadQueued = []
this.episodeDownloadsQueued = []
})
.catch((error) => {
console.error('Failed to clear queue', error)
@ -913,28 +928,22 @@ export default {
if (!this.newComment.trim()) return
try {
console.log('Posting comment:', {
text: this.newComment.trim(),
rating: this.newRating || null
})
const response = await this.$axios.$post(`/api/items/${this.libraryItemId}/comments`, {
text: this.newComment.trim(),
rating: this.newRating || null
rating: this.newRating
})
// Load comments if they haven't been loaded yet
if (!this.comments) {
this.comments = []
}
this.comments.unshift(response)
this.newComment = ''
this.newRating = 0
this.$toast.success(this.$strings.MessageCommentAdded)
} catch (error) {
console.error('Error posting comment:', error)
this.$toast.error(this.$strings.ErrorAddingComment)
if (error.response?.data?.error) {
this.$toast.error(error.response.data.error)
} else {
this.$toast.error(this.$strings.ErrorAddingComment)
}
}
},
startEditing(comment) {
@ -946,7 +955,7 @@ export default {
try {
const response = await this.$axios.$patch(`/api/items/${this.libraryItemId}/comments/${comment.id}`, {
text: this.editCommentText.trim(),
rating: this.editRating || null
rating: this.editRating
})
const index = this.comments.findIndex((c) => c.id === comment.id)

View file

@ -1105,17 +1105,17 @@
"ToastUserPasswordMustChange": "New password cannot match old password",
"ToastUserRootRequireName": "Must enter a root username",
"LabelComments": "Comments",
"PlaceholderAddComment": "Add a comment... (Ctrl+Enter to post)",
"PlaceholderAddComment": "Add a rating... (Ctrl+Enter to post)",
"ButtonPost": "Post",
"MessageNoComments": "No comments yet. Be the first to comment!",
"MessageCommentAdded": "Comment added successfully",
"MessageCommentUpdated": "Comment updated successfully",
"MessageCommentDeleted": "Comment deleted successfully",
"ErrorLoadingComments": "Error loading comments",
"ErrorAddingComment": "Error adding comment",
"ErrorUpdatingComment": "Error updating comment",
"ErrorDeletingComment": "Error deleting comment",
"ConfirmDeleteComment": "Are you sure you want to delete this comment?",
"MessageNoComments": "No rating yet. Be the first to rate!",
"MessageCommentAdded": "Rating added successfully",
"MessageCommentUpdated": "Rating updated successfully",
"MessageCommentDeleted": "Rating deleted successfully",
"ErrorLoadingComments": "Error loading ratings",
"ErrorAddingComment": "Error adding ratings",
"ErrorUpdatingComment": "Error updating ratings",
"ErrorDeletingComment": "Error deleting ratings",
"ConfirmDeleteComment": "Are you sure you want to delete this rating?",
"LabelRating": "Rating",
"LabelStarRating": "{0} stars",
"LabelNoRating": "No rating",

View file

@ -1170,7 +1170,6 @@ class LibraryItemController {
}
/**
*
* @param {RequestWithUser} req
* @param {Response} res
* @param {NextFunction} next
@ -1193,6 +1192,11 @@ class LibraryItemController {
}
}
// Allow comment operations for all authenticated users
if (req.path.includes('/comments')) {
return next()
}
if (req.path.includes('/play')) {
// allow POST requests using /play and /play/:episodeId
} else if (req.method == 'DELETE' && !req.user.canDelete) {
@ -1240,6 +1244,18 @@ class LibraryItemController {
*/
async addComment(req, res) {
try {
// Check if user already has a comment for this item
const existingComment = await Database.commentModel.findOne({
where: {
libraryItemId: req.params.id,
userId: req.user.id
}
})
if (existingComment) {
return res.status(400).json({ error: 'You have already submitted a review for this item' })
}
const comment = await Database.commentModel.create({
text: req.body.text,
rating: req.body.rating,