feat(Moderation): enhance moderation reporting and user feedback
- Added user blocking checks in authentication and reporting processes, returning appropriate error responses. - Expanded moderation report functionality to include new target types and optional fields for reports. - Implemented a new API endpoint to retrieve the count of open moderation reports. - Enhanced frontend components to allow users to report profiles, images, and guestbook entries, with corresponding UI updates. - Updated internationalization files to include new strings for reporting features in both German and English.
This commit is contained in:
@@ -17,6 +17,11 @@
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" v-if="activeTab === 'general'">
|
||||
<div class="profile-report-row">
|
||||
<button type="button" class="report-btn" @click="reportProfile">
|
||||
{{ $t('socialnetwork.profile.reportProfile') }}
|
||||
</button>
|
||||
</div>
|
||||
<table>
|
||||
<tr v-for="(value, key) in userProfile.params" :key="key">
|
||||
<td>{{ $t(`socialnetwork.profile.${key}`) }}</td>
|
||||
@@ -44,6 +49,9 @@
|
||||
<li v-for="image in images" :key="image.id" @click="openImageDialog(image)">
|
||||
<img :src="image.url || image.placeholder" :alt="$t('socialnetwork.gallery.imageLoadingAlt')" />
|
||||
<p>{{ image.title }}</p>
|
||||
<button type="button" class="report-btn" @click.stop="reportGalleryImage(image)">
|
||||
{{ $t('socialnetwork.gallery.reportImage') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -82,6 +90,19 @@
|
||||
<span @click="openProfile(entry.senderUsername)">{{ entry.sender }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="entry-actions">
|
||||
<button type="button" class="report-btn" @click="reportGuestbookEntry(entry)">
|
||||
{{ $t('socialnetwork.profile.guestbook.reportEntry') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="canDeleteGuestbookEntry(entry)"
|
||||
type="button"
|
||||
class="delete-btn"
|
||||
@click="deleteGuestbookEntry(entry)"
|
||||
>
|
||||
{{ $t('socialnetwork.profile.guestbook.deleteEntry') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<button @click="loadGuestbookEntries(currentPage - 1)" :disabled="currentPage === 1">{{
|
||||
@@ -357,6 +378,75 @@ export default {
|
||||
console.error('Error fetching image:', error);
|
||||
}
|
||||
},
|
||||
canDeleteGuestbookEntry(entry) {
|
||||
const ownUsername = String(this.user?.username || '').toLowerCase();
|
||||
const profileUsername = String(this.userProfile?.username || '').toLowerCase();
|
||||
const sender = String(entry?.sender || '').toLowerCase();
|
||||
return ownUsername && (ownUsername === sender || ownUsername === profileUsername);
|
||||
},
|
||||
async deleteGuestbookEntry(entry) {
|
||||
const ok = window.confirm(this.$t('socialnetwork.profile.guestbook.confirmDeleteEntry'));
|
||||
if (!ok) return;
|
||||
try {
|
||||
await apiClient.delete(`/api/socialnetwork/guestbook/entries/${entry.id}`);
|
||||
await this.loadGuestbookEntries(this.currentPage);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Gästebucheintrags:', error);
|
||||
}
|
||||
},
|
||||
async reportModerationTarget(payload, successKey, errorKey) {
|
||||
const reason = window.prompt(this.$t('socialnetwork.reporting.reasonPrompt'));
|
||||
if (reason == null) return;
|
||||
const trimmed = String(reason || '').trim();
|
||||
if (trimmed.length < 3) {
|
||||
this.$root.$refs.messageDialog?.open(this.$t('socialnetwork.reporting.reasonTooShort'), this.$t('error.title'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await apiClient.post('/api/moderation/reports', {
|
||||
...payload,
|
||||
reason: trimmed,
|
||||
details: payload.details || ''
|
||||
});
|
||||
this.$root.$refs.messageDialog?.open(this.$t(successKey), this.$t('message.title'));
|
||||
} catch (error) {
|
||||
console.error('Error creating moderation report:', error);
|
||||
this.$root.$refs.messageDialog?.open(this.$t(errorKey), this.$t('error.title'));
|
||||
}
|
||||
},
|
||||
async reportProfile() {
|
||||
await this.reportModerationTarget(
|
||||
{
|
||||
targetType: 'user_profile',
|
||||
targetRef: this.userId,
|
||||
details: `username=${this.userProfile?.username || ''}`
|
||||
},
|
||||
'socialnetwork.reporting.profileReported',
|
||||
'socialnetwork.reporting.reportError'
|
||||
);
|
||||
},
|
||||
async reportGalleryImage(image) {
|
||||
await this.reportModerationTarget(
|
||||
{
|
||||
targetType: 'gallery_image',
|
||||
targetId: image.id,
|
||||
details: `profile=${this.userProfile?.username || ''}; imageTitle=${image?.title || ''}`
|
||||
},
|
||||
'socialnetwork.reporting.imageReported',
|
||||
'socialnetwork.reporting.reportError'
|
||||
);
|
||||
},
|
||||
async reportGuestbookEntry(entry) {
|
||||
await this.reportModerationTarget(
|
||||
{
|
||||
targetType: 'guestbook_entry',
|
||||
targetId: entry.id,
|
||||
details: `profile=${this.userProfile?.username || ''}; sender=${entry?.sender || ''}`
|
||||
},
|
||||
'socialnetwork.reporting.guestbookReported',
|
||||
'socialnetwork.reporting.reportError'
|
||||
);
|
||||
},
|
||||
async handleFriendship() {
|
||||
console.log(this.friendshipState);
|
||||
if (['none', 'withdrawn'].includes(this.friendshipState)) {
|
||||
@@ -502,6 +592,10 @@ export default {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image-list > li > .report-btn {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.folder-name-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -541,6 +635,26 @@ export default {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.entry-actions {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.report-btn,
|
||||
.delete-btn {
|
||||
min-height: auto;
|
||||
padding: 4px 10px;
|
||||
font-size: 0.75rem;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.profile-report-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
Reference in New Issue
Block a user