From 06ff00f7f765dd7f3cf8291f79cfbf27da8e07bf Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 11 Jul 2025 01:22:08 +0200 Subject: [PATCH] Feature(reviews): support deleting and editing reviews, fix #2129 --- langs/en.json | 5 ++ package-lock.json | 9 ++- package.json | 2 +- src/Logic/Web/MangroveReviews.ts | 15 +++- src/UI/Reviews/AllReviews.svelte | 12 ++- src/UI/Reviews/ReviewForm.svelte | 54 +++++++++---- src/UI/Reviews/ReviewPrivacyShield.svelte | 3 +- src/UI/Reviews/SingleReview.svelte | 94 ++++++++++++++++++++++- 8 files changed, 165 insertions(+), 29 deletions(-) diff --git a/langs/en.json b/langs/en.json index 7fafac790d..220bc4806c 100644 --- a/langs/en.json +++ b/langs/en.json @@ -802,6 +802,11 @@ "affiliated_reviewer_warning": "(Affiliated review)", "attribution": "By Mangrove.reviews", "averageRating": "Average rating of {n} stars", + "delete": "Delete review", + "deleteConfirm": "Permantently delete this review", + "deleteText": "This cannot be undone", + "deleteTitle": "Delete this review?", + "edit": "Edit review", "i_am_affiliated": "I am affiliated with this object", "i_am_affiliated_explanation": "Check if you are an owner, creator, employee, …", "no_reviews_yet": "There are no reviews yet. Be the first one!", diff --git a/package-lock.json b/package-lock.json index 75e452cee2..4faa2a6a0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "jspdf": "^2.5.2", "latlon2country": "^1.2.7", "libphonenumber-js": "^1.11.19", - "mangrove-reviews-typescript": "^1.3.1", + "mangrove-reviews-typescript": "^1.4.6", "maplibre-gl": "^5.1.0", "marked": "^12.0.2", "monaco-editor": "^0.46.0", @@ -19525,9 +19525,10 @@ "license": "ISC" }, "node_modules/mangrove-reviews-typescript": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mangrove-reviews-typescript/-/mangrove-reviews-typescript-1.3.1.tgz", - "integrity": "sha512-xgU4uvJ0zQTRMoA4exje509FS19jN3u/buGBSUGh1TcTQBKWBGSmaT0cnHzcH9lhg6J8LCTsgf3Mv07Y87O9/w==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/mangrove-reviews-typescript/-/mangrove-reviews-typescript-1.4.6.tgz", + "integrity": "sha512-ZUM6Dath7OQEBz2oL8MKC+kMAEjIsoY0mPgAzXBcDLdKafFFOx2j6YCrMVu5RAsBhZLT77isFmbv1Ty5jKxMag==", + "license": "GPL-2.0-only", "dependencies": { "axios": "^1.2.3", "jose": "^4.11.2", diff --git a/package.json b/package.json index 5becb26272..7acf47372f 100644 --- a/package.json +++ b/package.json @@ -225,7 +225,7 @@ "jspdf": "^2.5.2", "latlon2country": "^1.2.7", "libphonenumber-js": "^1.11.19", - "mangrove-reviews-typescript": "^1.3.1", + "mangrove-reviews-typescript": "^1.4.6", "maplibre-gl": "^5.1.0", "marked": "^12.0.2", "monaco-editor": "^0.46.0", diff --git a/src/Logic/Web/MangroveReviews.ts b/src/Logic/Web/MangroveReviews.ts index f06ce23028..b1063c4c54 100644 --- a/src/Logic/Web/MangroveReviews.ts +++ b/src/Logic/Web/MangroveReviews.ts @@ -150,7 +150,7 @@ export default class FeatureReviews { private readonly _reviews: UIEventSource< (Review & { kid: string; signature: string; madeByLoggedInUser: Store })[] > = new UIEventSource([]) - public readonly reviews: Store<(Review & { madeByLoggedInUser: Store })[]> = + public readonly reviews: Store<(Review & { signature: string, madeByLoggedInUser: Store })[]> = this._reviews private readonly _lat: number private readonly _lon: number @@ -361,9 +361,10 @@ export default class FeatureReviews { const keypair: CryptoKeyPair = await this._identity.getKeypair() const jwt = await MangroveReviews.signReview(keypair, r) const kid = await MangroveReviews.publicToPem(keypair.publicKey) + if (!this._testmode.data) { try { - await MangroveReviews.submitReview(jwt) + await MangroveReviews.signAndSubmitReview(keypair, r) } catch (e) { await this._reportError( e, @@ -375,10 +376,11 @@ export default class FeatureReviews { console.log("Testmode enabled - not uploading review") await Utils.waitFor(1000) } + console.log("JWT IS", jwt) const reviewWithKid = { ...r, kid, - signature: jwt, + signature: "", madeByLoggedInUser: new ImmutableStore(true), } this._reviews.data.push(reviewWithKid) @@ -448,6 +450,13 @@ export default class FeatureReviews { } } + public async deleteReview(review: Review & {signature: string}){ + await MangroveReviews.deleteReview(await this._identity.getKeypair(), review) + this._reviews.set( + this._reviews.data.filter(r => r !== review) + ) + } + /** * Gets an URI which represents the item in a mangrove-compatible way * diff --git a/src/UI/Reviews/AllReviews.svelte b/src/UI/Reviews/AllReviews.svelte index 920523e552..54fe943a64 100644 --- a/src/UI/Reviews/AllReviews.svelte +++ b/src/UI/Reviews/AllReviews.svelte @@ -8,15 +8,21 @@ import Tr from "../Base/Tr.svelte" import ReviewPrivacyShield from "./ReviewPrivacyShield.svelte" import ThemeViewState from "../../Models/ThemeViewState" - import type { Store } from "../../Logic/UIEventSource" + import { Store, UIEventSource } from "../../Logic/UIEventSource" + import type { Feature } from "geojson" + import LayerConfig from "../../Models/ThemeConfig/LayerConfig" /** * An element showing all reviews */ export let reviews: FeatureReviews export let state: ThemeViewState + export let tags: UIEventSource> = undefined + export let feature: Feature = undefined + export let layer: LayerConfig =undefined let average = reviews.average let allReviews: Store< (Review & { + signature: string, madeByLoggedInUser: Store })[] > = reviews.reviews.map((r) => Utils.NoNull(r)) @@ -26,13 +32,13 @@ -
+
{#if $allReviews.length > 1} {/if} {#if $allReviews.length > 0} {#each $allReviews as review} - + {/each} {:else}
diff --git a/src/UI/Reviews/ReviewForm.svelte b/src/UI/Reviews/ReviewForm.svelte index 3f7061142a..4502285545 100644 --- a/src/UI/Reviews/ReviewForm.svelte +++ b/src/UI/Reviews/ReviewForm.svelte @@ -2,7 +2,7 @@ import FeatureReviews from "../../Logic/Web/MangroveReviews" import StarsBar from "./StarsBar.svelte" import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte" - import { Store, UIEventSource } from "../../Logic/UIEventSource" + import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import type { Feature } from "geojson" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import Translations from "../i18n/Translations" @@ -10,7 +10,7 @@ import Tr from "../Base/Tr.svelte" import If from "../Base/If.svelte" import Loading from "../Base/Loading.svelte" - import { Review } from "mangrove-reviews-typescript" + import { MangroveReviews, Review } from "mangrove-reviews-typescript" import { placeholder } from "../../Utils/placeholder" import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle" import ReviewPrivacyShield from "./ReviewPrivacyShield.svelte" @@ -26,11 +26,12 @@ * This is multi-stepped. */ export let reviews: FeatureReviews - - let score = 0 - let confirmedScore = undefined - let isAffiliated = new UIEventSource(false) - let opinion = new UIEventSource(undefined) + export let editReview: Review & { signature: string } = undefined + let subject: Store = editReview !== undefined ? new ImmutableStore(editReview.sub) : reviews.subjectUri + let score = editReview?.rating ?? 0 + let confirmedScore = editReview?.rating + let isAffiliated = new UIEventSource(editReview?.metadata?.is_affiliated ?? false) + let opinion = new UIEventSource(editReview?.opinion) const t = Translations.t.reviews @@ -47,6 +48,7 @@ }) let uploadFailed: string = undefined + let isTesting = state?.featureSwitchIsTesting async function save() { if (hasError.data) { @@ -62,18 +64,42 @@ opinion: opinion.data, metadata: { nickname, is_affiliated: isAffiliated.data }, } - try { - await reviews.createReview(review) - } catch (e) { - console.error("Could not create review due to", e) - uploadFailed = "" + e + if (!isTesting?.data) { + try { + if (editReview) { + const payload = { + opinion: opinion.data, + rating: confirmedScore, + iat: Math.floor(new Date().getTime()), + sub: editReview.sub, + metadata: { + nickname, + }, + } + if (editReview.images) { + review.images = editReview.images + } + if (isAffiliated.data) { + review.metadata.is_affiliated = isAffiliated.data + } + await reviews.createReview(payload) + await reviews.deleteReview(editReview) + } else { + await reviews.createReview(review) + } + } catch (e) { + console.error("Could not create review due to", e) + uploadFailed = "" + e + } + } else { + console.log("Not actually saving, testing mode is on") } + _state = "done" } let test = state.featureSwitches.featureSwitchIsTesting let debug = state.featureSwitches.featureSwitchIsDebugging - let subject = reviews.subjectUri @@ -156,7 +182,7 @@
diff --git a/src/UI/Reviews/ReviewPrivacyShield.svelte b/src/UI/Reviews/ReviewPrivacyShield.svelte index fe026a2776..7b725b10b7 100644 --- a/src/UI/Reviews/ReviewPrivacyShield.svelte +++ b/src/UI/Reviews/ReviewPrivacyShield.svelte @@ -4,11 +4,12 @@ */ import FeatureReviews from "../../Logic/Web/MangroveReviews" import { MenuState } from "../../Models/MenuState" + import { ImmutableStore } from "../../Logic/UIEventSource" export let guistate: MenuState export let reviews: FeatureReviews export let hiddenIfNotAllowed: boolean = false - let allowed = reviews.loadingAllowed + let allowed = reviews?.loadingAllowed ?? new ImmutableStore(true) {#if $allowed} diff --git a/src/UI/Reviews/SingleReview.svelte b/src/UI/Reviews/SingleReview.svelte index 47f224f1d9..00a793f1bb 100644 --- a/src/UI/Reviews/SingleReview.svelte +++ b/src/UI/Reviews/SingleReview.svelte @@ -1,6 +1,6 @@ + + + + + + + + deleteR()}> + + + + + + + + + + + + + +
+ {#if review.signature} +
+ + {#if byLoggedInUser} + + + {/if} + +
+ {/if}
{#if review.opinion} -