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 { PartialMember, PartialUser, User } from "./user";
|
||||
import type { AuthMethod, PartialMember, PartialUser, User } from "./user";
|
||||
|
||||
export type CreateReportRequest = {
|
||||
reason: ReportReason;
|
||||
|
@ -70,3 +70,13 @@ export type PartialReport = {
|
|||
context: string | null;
|
||||
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 type { AuthMethod } from "$api/models";
|
||||
|
||||
type Props = { method: AuthMethod; canRemove: boolean };
|
||||
let { method, canRemove }: Props = $props();
|
||||
type Props = { method: AuthMethod; canRemove: boolean; showType?: boolean };
|
||||
let { method, canRemove, showType }: Props = $props();
|
||||
|
||||
let name = $derived(
|
||||
method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id),
|
||||
|
@ -14,6 +14,9 @@
|
|||
<div class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{#if showType}
|
||||
<code>{method.type}</code>:
|
||||
{/if}
|
||||
{name}
|
||||
{#if showId}({method.remote_id}){/if}
|
||||
</div>
|
||||
|
|
|
@ -41,6 +41,13 @@
|
|||
>
|
||||
Audit log
|
||||
</a>
|
||||
<a
|
||||
href="/admin/lookup"
|
||||
class="list-group-item list-group-item-action"
|
||||
class:active={isActive("/admin/lookup", true)}
|
||||
>
|
||||
Lookup
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<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