feat(frontend): report profile page

This commit is contained in:
sam 2024-12-18 21:26:17 +01:00
parent 05913a3b2f
commit bd21eeebcf
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
9 changed files with 268 additions and 12 deletions

View file

@ -8,6 +8,7 @@
import { Icon } from "@sveltestrap/sveltestrap";
import Paginator from "$components/Paginator.svelte";
import MemberCard from "$components/profile/user/MemberCard.svelte";
import ProfileButtons from "$components/profile/ProfileButtons.svelte";
type Props = { data: PageData };
let { data }: Props = $props();
@ -28,6 +29,13 @@
<ProfileHeader name="@{data.user.username}" profile={data.user} offset={data.user.utc_offset} />
<ProfileFields profile={data.user} {allPreferences} />
<ProfileButtons
meUser={data.meUser}
user={data.user.username}
sid={data.user.sid}
reportUrl="/report/{data.user.id}"
/>
{#if data.members.length > 0}
<hr />
<h2>

View file

@ -6,6 +6,7 @@
import ProfileFields from "$components/profile/ProfileFields.svelte";
import { Icon } from "@sveltestrap/sveltestrap";
import { t } from "$lib/i18n";
import ProfileButtons from "$components/profile/ProfileButtons.svelte";
type Props = { data: PageData };
let { data }: Props = $props();
@ -37,4 +38,12 @@
<ProfileHeader name="{data.member.name} (@{data.member.user.username})" profile={data.member} />
<ProfileFields profile={data.member} {allPreferences} />
<ProfileButtons
meUser={data.meUser}
user={data.member.user.username}
member={data.member.name}
sid={data.member.sid}
reportUrl="/report/{data.member.user.id}?member={data.member.id}"
/>
</div>

View file

@ -0,0 +1,60 @@
import { apiRequest, fastRequest } from "$api";
import ApiError from "$api/error.js";
import type { Member } from "$api/models/member.js";
import { type CreateReportRequest, ReportReason } from "$api/models/moderation.js";
import type { PartialUser, User } from "$api/models/user.js";
import log from "$lib/log.js";
import { redirect } from "@sveltejs/kit";
export const load = async ({ parent, params, fetch, cookies, url }) => {
const { meUser } = await parent();
if (!meUser) redirect(303, "/");
let user: PartialUser;
let member: Member | null = null;
if (url.searchParams.has("member")) {
const resp = await apiRequest<Member>(
"GET",
`/users/${params.id}/members/${url.searchParams.get("member")}`,
{ fetch, cookies },
);
user = resp.user;
member = resp;
} else {
user = await apiRequest<User>("GET", `/users/${params.id}`, { fetch, cookies });
}
if (meUser.id === user.id) redirect(303, "/");
return { user, member };
};
export const actions = {
default: async ({ request, fetch, cookies }) => {
const body = await request.formData();
const targetIsMember = body.get("target-type") === "member";
const target = body.get("target-id") as string;
const reason = body.get("reason") as ReportReason;
const context = body.get("context") as string | null;
const url = targetIsMember
? `/moderation/report-member/${target}`
: `/moderation/report-user/${target}`;
try {
await fastRequest<CreateReportRequest>("POST", url, {
body: { reason, context },
fetch,
cookies,
});
return { ok: true, error: null };
} catch (e) {
if (e instanceof ApiError) return { ok: false, error: e.obj };
log.error("error reporting user or member %s:", target, e);
throw e;
}
},
};

View file

@ -0,0 +1,72 @@
<script lang="ts">
import { ReportReason } from "$api/models/moderation";
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
import RequiredFieldMarker from "$components/RequiredFieldMarker.svelte";
import { t } from "$lib/i18n";
import type { ActionData, PageData } from "./$types";
type Props = { data: PageData; form: ActionData };
let { data, form }: Props = $props();
let name = $derived(
data.member ? `${data.member.name} (@${data.user.username})` : "@" + data.user.username,
);
let link = $derived(
data.member ? `/@${data.user.username}/${data.member.name}` : `/@${data.user.username}`,
);
console.log(data.user, !!data.member);
let reasons = $derived.by(() => {
const reasons = [];
for (const value of Object.values(ReportReason)) {
const key = "report." + value.toLowerCase().replaceAll("_", "-");
reasons.push({ key, value });
}
return reasons;
});
</script>
<svelte:head>
<title>{$t("report.title", { name })} • pronouns.cc</title>
</svelte:head>
<div class="container">
<form method="POST" class="w-lg-75 mx-auto">
<h3>{$t("report.title", { name })}</h3>
<FormStatusMarker {form} successMessage={$t("report.success")} />
<input type="hidden" name="target-type" value={data.member ? "member" : "user"} />
<input type="hidden" name="target-id" value={data.member ? data.member.id : data.user.id} />
<h4 class="mt-3">{$t("report.reason-label")} <RequiredFieldMarker required /></h4>
<div class="row row-cols-1 row-cols-lg-2">
{#each reasons as reason}
<div class="col">
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="reason"
value={reason.value}
id="reason-{reason.value}"
required
/>
<label class="form-check-label" for="reason-{reason.value}">{$t(reason.key)}</label>
</div>
</div>
{/each}
</div>
<h4 class="mt-3">
{$t("report.context-label")}
<RequiredFieldMarker />
</h4>
<textarea class="form-control" name="context" style="height: 100px;" maxlength={512}></textarea>
<div class="mt-3">
<button type="submit" class="btn btn-danger">{$t("report.submit-button")}</button>
<a href={link} class="btn btn-secondary">{$t("cancel")}</a>
</div>
</form>
</div>