UX: add warning for too long reviews, see #1775

This commit is contained in:
Pieter Vander Vennet 2024-02-12 15:53:37 +01:00
parent f0152ce856
commit fa9aca0685
3 changed files with 77 additions and 50 deletions

View file

@ -676,6 +676,7 @@
"saving_review": "Saving…", "saving_review": "Saving…",
"title": "{count} reviews", "title": "{count} reviews",
"title_singular": "One review", "title_singular": "One review",
"too_long": "At most {max} characters are allowed. Your review has {amount} characters.",
"tos": "If you create a review, you agree to <a href='https://mangrove.reviews/terms' target='_blank'>the TOS and privacy policy of Mangrove.reviews</a>", "tos": "If you create a review, you agree to <a href='https://mangrove.reviews/terms' target='_blank'>the TOS and privacy policy of Mangrove.reviews</a>",
"write_a_comment": "Leave a review…" "write_a_comment": "Leave a review…"
}, },

View file

@ -52,6 +52,10 @@ export class MangroveIdentity {
* Tracks all reviews of a given feature, allows to create a new review * Tracks all reviews of a given feature, allows to create a new review
*/ */
export default class FeatureReviews { export default class FeatureReviews {
/**
* See https://gitlab.com/open-reviews/mangrove/-/blob/master/servers/reviewer/src/review.rs#L269 and https://github.com/pietervdvn/MapComplete/issues/1775
*/
public static readonly REVIEW_OPINION_MAX_LENGTH = 1000
private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {} private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {}
public readonly subjectUri: Store<string> public readonly subjectUri: Store<string>
public readonly average: Store<number | null> public readonly average: Store<number | null>
@ -172,6 +176,9 @@ export default class FeatureReviews {
* The given review is uploaded to mangrove.reviews and added to the list of known reviews * The given review is uploaded to mangrove.reviews and added to the list of known reviews
*/ */
public async createReview(review: Omit<Review, "sub">): Promise<void> { public async createReview(review: Omit<Review, "sub">): Promise<void> {
if(review.opinion.length > FeatureReviews .REVIEW_OPINION_MAX_LENGTH){
throw "Opinion too long, should be at most "+FeatureReviews.REVIEW_OPINION_MAX_LENGTH+" characters long"
}
const r: Review = { const r: Review = {
sub: this.subjectUri.data, sub: this.subjectUri.data,
...review, ...review,

View file

@ -1,60 +1,72 @@
<script lang="ts"> <script lang="ts">
import FeatureReviews from "../../Logic/Web/MangroveReviews" import FeatureReviews from "../../Logic/Web/MangroveReviews"
import StarsBar from "./StarsBar.svelte" import StarsBar from "./StarsBar.svelte"
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte" import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson" import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Checkbox from "../Base/Checkbox.svelte" import Checkbox from "../Base/Checkbox.svelte"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import If from "../Base/If.svelte" import If from "../Base/If.svelte"
import Loading from "../Base/Loading.svelte" import Loading from "../Base/Loading.svelte"
import { Review } from "mangrove-reviews-typescript" import { Review } from "mangrove-reviews-typescript"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { placeholder } from "../../Utils/placeholder" import { placeholder } from "../../Utils/placeholder"
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>> export let tags: UIEventSource<Record<string, string>>
export let feature: Feature export let feature: Feature
export let layer: LayerConfig export let layer: LayerConfig
/** /**
* The form to create a new review. * The form to create a new review.
* This is multi-stepped. * This is multi-stepped.
*/ */
export let reviews: FeatureReviews export let reviews: FeatureReviews
let score = 0 let score = 0
let confirmedScore = undefined let confirmedScore = undefined
let isAffiliated = new UIEventSource(false) let isAffiliated = new UIEventSource(false)
let opinion = new UIEventSource<string>(undefined) let opinion = new UIEventSource<string>(undefined)
const t = Translations.t.reviews const t = Translations.t.reviews
let _state: "ask" | "saving" | "done" = "ask" let _state: "ask" | "saving" | "done" = "ask"
const connection = state.osmConnection const connection = state.osmConnection
async function save() { const hasError: Store<undefined | "too_long"> = opinion.mapD(op => {
_state = "saving" const tooLong = op.length > FeatureReviews.REVIEW_OPINION_MAX_LENGTH
let nickname = undefined if (tooLong) {
if (connection.isLoggedIn.data) { return "too_long"
nickname = connection.userDetails.data.name }
return undefined
})
async function save() {
if (hasError.data) {
return
}
_state = "saving"
let nickname = undefined
if (connection.isLoggedIn.data) {
nickname = connection.userDetails.data.name
}
const review: Omit<Review, "sub"> = {
rating: confirmedScore,
opinion: opinion.data,
metadata: { nickname, is_affiliated: isAffiliated.data },
}
if (state.featureSwitchIsTesting?.data ?? true) {
console.log("Testing - not actually saving review", review)
await Utils.waitFor(1000)
} else {
await reviews.createReview(review)
}
_state = "done"
} }
const review: Omit<Review, "sub"> = {
rating: confirmedScore,
opinion: opinion.data,
metadata: { nickname, is_affiliated: isAffiliated.data },
}
if (state.featureSwitchIsTesting?.data ?? true) {
console.log("Testing - not actually saving review", review)
await Utils.waitFor(1000)
} else {
await reviews.createReview(review)
}
_state = "done"
}
</script> </script>
{#if _state === "done"} {#if _state === "done"}
@ -95,6 +107,12 @@
class="mb-1 w-full" class="mb-1 w-full"
use:placeholder={t.reviewPlaceholder} use:placeholder={t.reviewPlaceholder}
/> />
{#if $hasError === "too_long"}
<div class="alert flex items-center px-2">
<ExclamationTriangle class="w-12 h-12"/>
<Tr t={t.too_long.Subs({max: FeatureReviews.REVIEW_OPINION_MAX_LENGTH, amount: $opinion?.length ?? 0})}> </Tr>
</div>
{/if}
</label> </label>
<Checkbox selected={isAffiliated}> <Checkbox selected={isAffiliated}>
@ -108,7 +126,8 @@
<Tr t={t.reviewing_as.Subs({ nickname: state.osmConnection.userDetails.data.name })} /> <Tr t={t.reviewing_as.Subs({ nickname: state.osmConnection.userDetails.data.name })} />
<Tr slot="else" t={t.reviewing_as_anonymous} /> <Tr slot="else" t={t.reviewing_as_anonymous} />
</If> </If>
<button class="primary" on:click={save}> <button class="primary" class:disabled={$hasError !== undefined}
on:click={save}>
<Tr t={t.save} /> <Tr t={t.save} />
</button> </button>
</div> </div>