feat(frontend): partial user lookup
This commit is contained in:
parent
9d3d46bf33
commit
db22e35f0d
7 changed files with 157 additions and 3 deletions
|
@ -1,5 +1,5 @@
|
||||||
import type { Member } from "./member";
|
import type { Member } from "./member";
|
||||||
import type { PartialMember, PartialUser, User } from "./user";
|
import type { AuthMethod, PartialMember, PartialUser, User } from "./user";
|
||||||
|
|
||||||
export type CreateReportRequest = {
|
export type CreateReportRequest = {
|
||||||
reason: ReportReason;
|
reason: ReportReason;
|
||||||
|
@ -70,3 +70,13 @@ export type PartialReport = {
|
||||||
context: string | null;
|
context: string | null;
|
||||||
target_type: "USER" | "MEMBER";
|
target_type: "USER" | "MEMBER";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type QueriedUser = {
|
||||||
|
user: User;
|
||||||
|
member_list_hidden: boolean;
|
||||||
|
last_active: string;
|
||||||
|
last_sid_reroll: string;
|
||||||
|
suspended: boolean;
|
||||||
|
deleted: boolean;
|
||||||
|
auth_methods?: AuthMethod[];
|
||||||
|
};
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import { t } from "$lib/i18n";
|
import { t } from "$lib/i18n";
|
||||||
import type { AuthMethod } from "$api/models";
|
import type { AuthMethod } from "$api/models";
|
||||||
|
|
||||||
type Props = { method: AuthMethod; canRemove: boolean };
|
type Props = { method: AuthMethod; canRemove: boolean; showType?: boolean };
|
||||||
let { method, canRemove }: Props = $props();
|
let { method, canRemove, showType }: Props = $props();
|
||||||
|
|
||||||
let name = $derived(
|
let name = $derived(
|
||||||
method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id),
|
method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id),
|
||||||
|
@ -14,6 +14,9 @@
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
{#if showType}
|
||||||
|
<code>{method.type}</code>:
|
||||||
|
{/if}
|
||||||
{name}
|
{name}
|
||||||
{#if showId}({method.remote_id}){/if}
|
{#if showId}({method.remote_id}){/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,6 +41,13 @@
|
||||||
>
|
>
|
||||||
Audit log
|
Audit log
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
href="/admin/lookup"
|
||||||
|
class="list-group-item list-group-item-action"
|
||||||
|
class:active={isActive("/admin/lookup", true)}
|
||||||
|
>
|
||||||
|
Lookup
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
|
|
27
Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts
Normal file
27
Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { apiRequest } from "$api";
|
||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
default: async ({ request, fetch, cookies }) => {
|
||||||
|
const body = await request.formData();
|
||||||
|
const query = body.get("query") as string;
|
||||||
|
const fuzzy = body.get("fuzzy") === "yes";
|
||||||
|
|
||||||
|
const users = await apiRequest<Array<{ id: string; username: string }>>(
|
||||||
|
"POST",
|
||||||
|
"/moderation/lookup",
|
||||||
|
{
|
||||||
|
fetch,
|
||||||
|
cookies,
|
||||||
|
body: {
|
||||||
|
query,
|
||||||
|
fuzzy,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fuzzy && users.length > 0) redirect(303, `/admin/lookup/${users[0].id}`);
|
||||||
|
|
||||||
|
return { users };
|
||||||
|
},
|
||||||
|
};
|
33
Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte
Normal file
33
Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { ActionData } from "./$types";
|
||||||
|
|
||||||
|
type Props = { form: ActionData };
|
||||||
|
let { form }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Look up a user • pronouns.cc</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h1>Look up a user</h1>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="input-group w-lg-50 mb-2">
|
||||||
|
<input type="text" class="form-control" name="query" placeholder="Query" required />
|
||||||
|
<button class="btn btn-primary" type="submit">Search</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" value="yes" name="fuzzy" id="fuzzy" />
|
||||||
|
<label class="form-check-label" for="fuzzy">Fuzzy?</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="list-group">
|
||||||
|
{#each form?.users || [] as user (user.id)}
|
||||||
|
<a href="/admin/lookup/{user.id}" class="list-group-item list-group-item-action">
|
||||||
|
{user.username} <span class="text-secondary">({user.id})</span>
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<div class="list-group-item">No results</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { apiRequest } from "$api";
|
||||||
|
import type { QueriedUser } from "$api/models/moderation";
|
||||||
|
|
||||||
|
export const load = async ({ params, fetch, cookies }) => {
|
||||||
|
const user = await apiRequest<QueriedUser>("GET", `/moderation/lookup/${params.id}`, {
|
||||||
|
fetch,
|
||||||
|
cookies,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { user };
|
||||||
|
};
|
63
Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte
Normal file
63
Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { idTimestamp } from "$lib";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import type { PageData } from "./$types";
|
||||||
|
import ProfileHeader from "$components/profile/ProfileHeader.svelte";
|
||||||
|
import ProfileFields from "$components/profile/ProfileFields.svelte";
|
||||||
|
import { mergePreferences } from "$api/models";
|
||||||
|
import AuthMethodRow from "$components/settings/AuthMethodRow.svelte";
|
||||||
|
|
||||||
|
type Props = { data: PageData };
|
||||||
|
let { data }: Props = $props();
|
||||||
|
|
||||||
|
let createdAt = $derived(idTimestamp(data.user.user.id));
|
||||||
|
let lastActive = $derived(DateTime.fromISO(data.user.last_active));
|
||||||
|
let lastSidReroll = $derived(DateTime.fromISO(data.user.last_sid_reroll));
|
||||||
|
|
||||||
|
let authMethods = $derived.by(() => {
|
||||||
|
if (!data.user.auth_methods) return undefined;
|
||||||
|
|
||||||
|
return data.user.auth_methods.sort((a, b) => a.type.localeCompare(b.type));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Looking up @{data.user.user.username} • pronouns.cc</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h2>Basic profile</h2>
|
||||||
|
|
||||||
|
<ProfileHeader name="@{data.user.user.username}" profile={data.user.user} />
|
||||||
|
|
||||||
|
<ProfileFields
|
||||||
|
profile={data.user.user}
|
||||||
|
allPreferences={mergePreferences(data.user.user.custom_preferences)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h2>Extra information</h2>
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Created at</th>
|
||||||
|
<td>{createdAt.toLocaleString(DateTime.DATETIME_MED)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Last active</th>
|
||||||
|
<td>{lastActive.toLocaleString(DateTime.DATETIME_MED)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Last SID reroll</th>
|
||||||
|
<td>{lastSidReroll.toLocaleString(DateTime.DATETIME_MED)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{#if authMethods}
|
||||||
|
<h2>Authentication methods</h2>
|
||||||
|
<div class="list-group">
|
||||||
|
{#each authMethods as method (method.id)}
|
||||||
|
<AuthMethodRow {method} canRemove={false} showType />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
Loading…
Reference in a new issue