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

@ -9,7 +9,7 @@ export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
/**
* Optional arguments for a request. `load` and `action` functions should always pass `fetch` and `cookies`.
*/
export type RequestArgs = {
export type RequestArgs<T> = {
/**
* The token for this request. Where possible, `cookies` should be passed instead.
* Will override `cookies` if both are passed.
@ -23,7 +23,7 @@ export type RequestArgs = {
/**
* The body for this request, which will be serialized to JSON. Should be a plain JS object.
*/
body?: unknown;
body?: T;
/**
* The fetch function to use. Should be passed in loader and action functions, but can be safely ignored for client-side requests.
*/
@ -41,10 +41,10 @@ export type RequestArgs = {
* @param args Optional arguments to the request function.
* @returns A Response object.
*/
export async function baseRequest(
export async function baseRequest<T = unknown>(
method: Method,
path: string,
args: RequestArgs = {},
args: RequestArgs<T> = {},
): Promise<Response> {
const token = args.token ?? args.cookies?.get(TOKEN_COOKIE_NAME);
@ -72,11 +72,11 @@ export async function baseRequest(
* @param args Optional arguments to the request function.
* @returns The response deserialized as `T`.
*/
export async function apiRequest<T>(
export async function apiRequest<TResponse, TRequest = unknown>(
method: Method,
path: string,
args: RequestArgs = {},
): Promise<T> {
args: RequestArgs<TRequest> = {},
): Promise<TResponse> {
const resp = await baseRequest(method, path, args);
if (resp.status < 200 || resp.status > 299) {
@ -84,7 +84,7 @@ export async function apiRequest<T>(
if ("code" in err) throw new ApiError(err);
else throw new ApiError();
}
return (await resp.json()) as T;
return (await resp.json()) as TResponse;
}
/**
@ -94,10 +94,10 @@ export async function apiRequest<T>(
* @param args Optional arguments to the request function.
* @param enforce204 Whether to throw an error on a non-204 status code.
*/
export async function fastRequest(
export async function fastRequest<T = unknown>(
method: Method,
path: string,
args: RequestArgs = {},
args: RequestArgs<T> = {},
enforce204: boolean = false,
): Promise<void> {
const resp = await baseRequest(method, path, args);

View file

@ -0,0 +1,26 @@
export type CreateReportRequest = {
reason: ReportReason;
context: string | null;
};
export enum ReportReason {
Totalitarianism = "TOTALITARIANISM",
HateSpeech = "HATE_SPEECH",
Racism = "RACISM",
Homophobia = "HOMOPHOBIA",
Transphobia = "TRANSPHOBIA",
Queerphobia = "QUEERPHOBIA",
Exclusionism = "EXCLUSIONISM",
Sexism = "SEXISM",
Ableism = "ABLEISM",
ChildPornography = "CHILD_PORNOGRAPHY",
PedophiliaAdvocacy = "PEDOPHILIA_ADVOCACY",
Harassment = "HARASSMENT",
Impersonation = "IMPERSONATION",
Doxxing = "DOXXING",
EncouragingSelfHarm = "ENCOURAGING_SELF_HARM",
Spam = "SPAM",
Trolling = "TROLLING",
Advertisement = "ADVERTISEMENT",
CopyrightViolation = "COPYRIGHT_VIOLATION",
}

View file

@ -0,0 +1,12 @@
<script lang="ts">
import { t } from "$lib/i18n";
type Props = { required?: boolean };
let { required }: Props = $props();
</script>
{#if required}
<small class="text-danger"><abbr title={$t("form.required")}>*</abbr></small>
{:else}
<small class="text-body-secondary">{$t("form.optional")}</small>
{/if}

View file

@ -0,0 +1,36 @@
<script lang="ts">
import type { MeUser } from "$api/models";
import { PUBLIC_BASE_URL, PUBLIC_SHORT_URL } from "$env/static/public";
import { t } from "$lib/i18n";
type Props = {
user: string;
member?: string;
sid: string;
reportUrl: string;
meUser: MeUser | null;
};
let { user, member, sid, reportUrl, meUser }: Props = $props();
let profileUrl = $derived(
member ? `${PUBLIC_BASE_URL}/@${user}/${member}` : `${PUBLIC_BASE_URL}/@${user}`,
);
let shortUrl = $derived(`${PUBLIC_SHORT_URL}/${sid}`);
const copyUrl = async (url: string) => {
await navigator.clipboard.writeText(url);
};
</script>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" onclick={() => copyUrl(profileUrl)}>
{$t("profile.copy-link-button")}
</button>
<button type="button" class="btn btn-outline-secondary" onclick={() => copyUrl(shortUrl)}>
{$t("profile.copy-short-link-button")}
</button>
{#if meUser && meUser.username !== user}
<a class="btn btn-outline-danger" href={reportUrl}>{$t("profile.report-button")}</a>
{/if}
</div>

View file

@ -18,7 +18,10 @@
"pronouns-header": "Pronouns",
"default-members-header": "Members",
"create-member-button": "Create member",
"back-to-user": "Back to {{name}}"
"back-to-user": "Back to {{name}}",
"copy-link-button": "Copy link",
"copy-short-link-button": "Copy short link",
"report-button": "Report profile"
},
"title": {
"log-in": "Log in",
@ -237,5 +240,35 @@
"custom-preference-muted": "Show as muted text",
"custom-preference-favourite": "Treat like favourite"
},
"cancel": "Cancel"
"cancel": "Cancel",
"report": {
"title": "Reporting {{name}}",
"totalitarianism": "Support of totalitarian regimes",
"hate-speech": "Hate speech",
"racism": "Racism or xenophobia",
"homophobia": "Homophobia",
"transphobia": "Transphobia",
"queerphobia": "Queerphobia (other)",
"exclusionism": "Queer or plural exclusionism",
"sexism": "Sexism or misogyny",
"ableism": "Ableism",
"child-pornography": "Child pornography",
"pedophilia-advocacy": "Pedophilia advocacy",
"harassment": "Harassment",
"impersonation": "Impersonation",
"doxxing": "Doxxing",
"encouraging-self-harm": "Encouraging self-harm or suicide",
"spam": "Spam",
"trolling": "Trolling",
"advertisement": "Advertising",
"copyright-violation": "Copyright or trademark violation",
"success": "Successfully submitted report!",
"reason-label": "Why are you reporting this profile?",
"context-label": "Is there any context you'd like to give us?",
"submit-button": "Submit report"
},
"form": {
"optional": "(optional)",
"required": "Required"
}
}