Feature(reviews): fix #2457, add report functionality with reason

This commit is contained in:
Pieter Vander Vennet 2025-07-13 15:58:05 +02:00
parent dc62db7dcf
commit 7ce320075d
3 changed files with 89 additions and 10 deletions

View file

@ -817,6 +817,9 @@
"question_opinion": "How was your experience?",
"rate": "Rate {n} stars",
"rated": "Rated {n} stars",
"reportReason": "Why should this report be reported?",
"reportReview": "Report this review as inappropriate",
"reportReviewTitle": "Report this review as inappropriate?",
"reviewPlaceholder": "Describe your experience…",
"reviewing_as": "Reviewing as {nickname}",
"reviewing_as_anonymous": "Reviewing as anonymous",
@ -942,4 +945,4 @@
"startsWithQ": "A wikidata identifier starts with Q and is followed by a number"
}
}
}
}

View file

@ -452,9 +452,14 @@ export default class FeatureReviews {
public async deleteReview(review: Review & {signature: string}){
await MangroveReviews.deleteReview(await this._identity.getKeypair(), review)
this.removeReviewLocally(review)
}
public removeReviewLocally(review: Review){
this._reviews.set(
this._reviews.data.filter(r => r !== review)
)
}
/**

View file

@ -1,5 +1,6 @@
<script lang="ts">
import { MangroveReviews, Review } from "mangrove-reviews-typescript"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import StarsBar from "./StarsBar.svelte"
import Translations from "../i18n/Translations"
@ -7,7 +8,6 @@
import { ariaLabel } from "../../Utils/ariaLabel"
import ThemeViewState from "../../Models/ThemeViewState"
import Markdown from "../Base/Markdown.svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
import AttributedImage from "../Image/AttributedImage.svelte"
import DotMenu from "../Base/DotMenu.svelte"
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
@ -17,7 +17,11 @@
import ReviewForm from "./ReviewForm.svelte"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import FeatureReviews from "../../Logic/Web/MangroveReviews"
import FeatureReviews, { MangroveIdentity } from "../../Logic/Web/MangroveReviews"
import ShieldExclamation from "@babeard/svelte-heroicons/solid/ShieldExclamation"
import Delete_icon from "../../assets/svg/Delete_icon.svelte"
import ValidatedInput from "../InputElement/ValidatedInput.svelte"
export let state: ThemeViewState = undefined
export let tags: UIEventSource<Record<string, string>> = undefined
@ -53,21 +57,39 @@
let isTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
function report() {
MangroveReviews.reportAbuseReview(identity, review, "This review was reported by a MapComplete user")
async function report() {
let reason = reportReason.data
if (!reason) {
reason = "This review was reported by a MapComplete user"
}
const identity: CryptoKeyPair = await state?.userRelatedState?.mangroveIdentity.getKeypair()
if (!isTesting.data) {
try {
await MangroveReviews.reportAbuseReview(identity, review, reason)
} catch (e) {
console.error("Could not report review due to", e)
await state.reportError(e)
}
} else {
console.log("Not actually reporting, test mode")
}
showReport.set(false)
reviews.removeReviewLocally(review)
}
let deleted = new UIEventSource(false)
async function deleteR() {
const identity: MangroveIdentity = await state?.userRelatedState?.mangroveIdentity.getKeypair()
const identity: CryptoKeyPair = await state?.userRelatedState?.mangroveIdentity.getKeypair()
console.log("Deleting review...", identity)
if (!isTesting.data) {
try {
reviews.deleteReview(review)
await reviews.deleteReview(review)
} catch (e) {
console.log("Could not delete review... ", e)
await state.reportError(e)
}
} else {
console.log("Not really deleting the review, testing")
@ -76,8 +98,10 @@
deleted.setData(true)
}
let reportReason = new UIEventSource("")
let showDelete = new UIEventSource(false)
let showEdit = new UIEventSource(false)
let showReport = new UIEventSource(false)
let isDebugging = state?.featureSwitches?.featureSwitchIsDebugging
const t = Translations.t.reviews
</script>
@ -87,20 +111,62 @@
<Tr t={t.deleteTitle} />
</svelte:fragment>
<div class="flex gap-x-4 items-center m-8">
<Delete_icon class="w-16" />
<div class="p-4 low-interaction border-interactive">
<StarsBar starSize="w-4 h-4 md:w-6 md:h-6" readonly={true} score={review.rating} />
<div class="disable-links">
<Markdown src={review.opinion} />
</div>
</div>
</div>
<Tr t={t.deleteText} />
<NextButton clss="primary" on:click={() => deleteR()}>
<NextButton clss="primary float-right" on:click={() => deleteR()}>
<TrashIcon class="w-12 text-red-600" />
<Tr t={t.deleteConfirm} />
</NextButton>
</Popup>
<Popup shown={showReport}>
<svelte:fragment slot="header">
<Tr t={t.reportReviewTitle} />
</svelte:fragment>
<div class="flex gap-x-4 items-center m-8">
<ShieldExclamation class="w-16" />
<div class="p-4 low-interaction border-interactive">
<StarsBar starSize="w-4 h-4 md:w-6 md:h-6" readonly={true} score={review.rating} />
<div class="disable-links">
<Markdown src={review.opinion} />
</div>
</div>
</div>
<h3>
<Tr t={t.reportReason} />
</h3>
<div class="w-full">
<ValidatedInput type="text" value={reportReason} />
</div>
<Tr t={t.deleteText} />
<NextButton clss="primary float-right" on:click={() => {report()}}>
<TrashIcon class="w-12 text-red-600" />
<Tr t={t.reportReview} />
</NextButton>
</Popup>
<Popup shown={showEdit}>
<svelte:fragment slot="header">
<Tr t={t.edit} />
</svelte:fragment>
<ReviewForm {state} editReview={review} {reviews} />
<ReviewForm {tags} {feature} {layer} {state} editReview={review} {reviews} />
</Popup>
@ -149,7 +215,7 @@
{#if review.signature}
<div class="self-start">
<DotMenu>
{#if byLoggedInUser}
{#if $byLoggedInUser}
<button on:click={() => showEdit.set(true)}>
<PencilIcon />
<Tr t={t.edit} />
@ -158,6 +224,11 @@
<TrashIcon />
<Tr t={t.delete} />
</button>
{:else}
<button on:click={() => showReport.set(true)}>
<ShieldExclamation />
<Tr t={t.reportReview} />
</button>
{/if}
</DotMenu>
</div>