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…",
"title": "{count} reviews",
"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>",
"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
*/
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> = {}
public readonly subjectUri: Store<string>
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
*/
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 = {
sub: this.subjectUri.data,
...review,

View file

@ -1,60 +1,72 @@
<script lang="ts">
import FeatureReviews from "../../Logic/Web/MangroveReviews"
import StarsBar from "./StarsBar.svelte"
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"
import Checkbox from "../Base/Checkbox.svelte"
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 { Utils } from "../../Utils"
import { placeholder } from "../../Utils/placeholder"
import FeatureReviews from "../../Logic/Web/MangroveReviews"
import StarsBar from "./StarsBar.svelte"
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"
import Checkbox from "../Base/Checkbox.svelte"
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 { Utils } from "../../Utils"
import { placeholder } from "../../Utils/placeholder"
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>
export let feature: Feature
export let layer: LayerConfig
/**
* The form to create a new review.
* This is multi-stepped.
*/
export let reviews: FeatureReviews
export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>
export let feature: Feature
export let layer: LayerConfig
/**
* The form to create a new review.
* This is multi-stepped.
*/
export let reviews: FeatureReviews
let score = 0
let confirmedScore = undefined
let isAffiliated = new UIEventSource(false)
let opinion = new UIEventSource<string>(undefined)
let score = 0
let confirmedScore = undefined
let isAffiliated = new UIEventSource(false)
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() {
_state = "saving"
let nickname = undefined
if (connection.isLoggedIn.data) {
nickname = connection.userDetails.data.name
const hasError: Store<undefined | "too_long"> = opinion.mapD(op => {
const tooLong = op.length > FeatureReviews.REVIEW_OPINION_MAX_LENGTH
if (tooLong) {
return "too_long"
}
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>
{#if _state === "done"}
@ -95,6 +107,12 @@
class="mb-1 w-full"
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>
<Checkbox selected={isAffiliated}>
@ -108,7 +126,8 @@
<Tr t={t.reviewing_as.Subs({ nickname: state.osmConnection.userDetails.data.name })} />
<Tr slot="else" t={t.reviewing_as_anonymous} />
</If>
<button class="primary" on:click={save}>
<button class="primary" class:disabled={$hasError !== undefined}
on:click={save}>
<Tr t={t.save} />
</button>
</div>