Implement User Details page at /core/users/[user_id]

- Added dynamic route for user details with manager-level access
- Implemented permission management UI (Super, Manager, Admin, Verified)
- Added account status toggles and timestamp visibility
- Integrated with V3 User CRUD logic and editable field whitelist
This commit is contained in:
Scott Idem
2026-01-06 15:46:13 -05:00
parent a0f04726e0
commit 0e93514262
2 changed files with 247 additions and 0 deletions

View File

@@ -0,0 +1,219 @@
<script lang="ts">
import { onMount } from 'svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { goto } from '$app/navigation';
import { update_ae_obj__user, delete_ae_obj_id__user } from '$lib/ae_core/ae_core__user';
import { editable_fields__user } from '$lib/ae_core/ae_core__user.editable_fields';
import {
ShieldCheck,
Mail,
User as UserIcon,
ArrowLeft,
Save,
Trash2,
Lock,
Clock,
Key,
CheckCircle2,
XCircle
} from 'lucide-svelte';
interface Props {
data: any;
}
let { data }: Props = $props();
let user = $state(data.user);
let saving = $state(false);
onMount(() => {
if (!$ae_loc.manager_access) {
goto('/core');
}
});
async function handle_save() {
saving = true;
const data_kv: any = {};
editable_fields__user.forEach(field => {
if (user[field] !== undefined) {
data_kv[field] = user[field];
}
});
const result = await update_ae_obj__user({
api_cfg: $ae_api,
user_id: user.user_id_random,
data_kv,
log_lvl: 1
});
if (result) {
alert('User updated successfully');
}
saving = false;
}
async function handle_delete() {
if (!confirm('Are you sure you want to disable this user account?')) return;
const result = await delete_ae_obj_id__user({
api_cfg: $ae_api,
user_id: user.user_id_random,
method: 'disable',
log_lvl: 1
});
if (result) {
goto('/core/users');
}
}
</script>
<div class="container mx-auto p-4 space-y-6">
<header class="flex justify-between items-center">
<div class="flex items-center gap-4">
<a class="btn btn-sm variant-soft" href="/core/users">
<ArrowLeft size={16} />
</a>
<div class="flex items-center gap-3">
<div class="avatar variant-filled-primary w-12 h-12 flex items-center justify-center rounded-full">
<UserIcon size={24} />
</div>
<div>
<h1 class="h2">{user.username}</h1>
<p class="text-sm opacity-60">ID: {user.user_id_random}</p>
</div>
</div>
</div>
<div class="flex gap-2">
<button class="btn variant-filled-error" onclick={handle_delete} disabled={saving}>
<Trash2 size={16} class="mr-2" /> Disable
</button>
<button class="btn variant-filled-primary" onclick={handle_save} disabled={saving}>
<Save size={16} class="mr-2" /> Save Changes
</button>
</div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Main Info -->
<div class="lg:col-span-2 space-y-6">
<div class="card p-4 space-y-4">
<h3 class="h4 border-b border-surface-500/30 pb-2 flex items-center gap-2">
<UserIcon size={18} /> Basic Profile
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="label">
<span>Display Name</span>
<input class="input" type="text" bind:value={user.name} placeholder="Full Name" />
</label>
<label class="label">
<span>Email Address</span>
<input class="input" type="email" bind:value={user.email} />
</label>
<label class="label md:col-span-2">
<span>Username</span>
<input class="input font-mono variant-soft" type="text" bind:value={user.username} disabled />
<p class="text-xs opacity-60 mt-1 italic">Username changes are currently restricted.</p>
</label>
</div>
</div>
<div class="card p-4 space-y-4">
<h3 class="h4 border-b border-surface-500/30 pb-2 flex items-center gap-2">
<ShieldCheck size={18} /> System Permissions
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-4">
<label class="flex items-center space-x-3 card p-3 variant-soft-error cursor-pointer">
<input class="checkbox" type="checkbox" bind:checked={user.super} />
<div class="flex flex-col">
<span class="font-bold">Super User</span>
<span class="text-xs opacity-70">Full system access</span>
</div>
</label>
<label class="flex items-center space-x-3 card p-3 variant-soft-warning cursor-pointer">
<input class="checkbox" type="checkbox" bind:checked={user.manager} />
<div class="flex flex-col">
<span class="font-bold">Manager</span>
<span class="text-xs opacity-70">Global account & user mgmt</span>
</div>
</label>
<label class="flex items-center space-x-3 card p-3 variant-soft-primary cursor-pointer">
<input class="checkbox" type="checkbox" bind:checked={user.administrator} />
<div class="flex flex-col">
<span class="font-bold">Administrator</span>
<span class="text-xs opacity-70">Account-level admin access</span>
</div>
</label>
<label class="flex items-center space-x-3 card p-3 variant-soft-secondary cursor-pointer">
<input class="checkbox" type="checkbox" bind:checked={user.verified} />
<div class="flex flex-col">
<span class="font-bold">Verified User</span>
<span class="text-xs opacity-70">Identity confirmed</span>
</div>
</label>
</div>
</div>
</div>
<!-- Sidebar / Meta -->
<div class="space-y-6">
<div class="card p-4 space-y-4">
<h3 class="h4 border-b border-surface-500/30 pb-2 flex items-center gap-2">
<Clock size={18} /> Account Status
</h3>
<div class="space-y-4">
<label class="flex items-center space-x-2">
<input class="checkbox" type="checkbox" bind:checked={user.enable} />
<p>Login Enabled</p>
</label>
<label class="flex items-center space-x-2">
<input class="checkbox" type="checkbox" bind:checked={user.hide} />
<p>Hidden from Lists</p>
</label>
<label class="flex items-center space-x-2">
<input class="checkbox" type="checkbox" bind:checked={user.allow_auth_key} />
<p>Allow Email Auth Key</p>
</label>
</div>
</div>
<div class="card p-4 space-y-4 variant-soft-surface">
<h3 class="h4 border-b border-surface-500/30 pb-2 flex items-center gap-2">
<Lock size={18} /> Linking Info
</h3>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="opacity-60">Linked Person:</span>
<span class="font-mono">
{#if user.person_id_random}
<a href="/core/people/{user.person_id_random}" class="text-primary-500 underline">
{user.person_id_random}
</a>
{:else}
<span class="italic text-error-500">Unlinked</span>
{/if}
</span>
</div>
<div class="flex justify-between">
<span class="opacity-60">Account ID:</span>
<span class="font-mono">{user.account_id_random || '--'}</span>
</div>
</div>
</div>
<div class="card p-4 space-y-4">
<h3 class="h4 border-b border-surface-500/30 pb-2 flex items-center gap-2">
<Clock size={18} /> Timestamps
</h3>
<div class="space-y-2 text-xs">
<p><span class="opacity-60">Created:</span> {new Date(user.created_on).toLocaleString()}</p>
{#if user.updated_on}
<p><span class="opacity-60">Updated:</span> {new Date(user.updated_on).toLocaleString()}</p>
{/if}
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,28 @@
import type { PageLoad } from './$types';
import { error } from '@sveltejs/kit';
import { load_ae_obj_id__user } from '$lib/ae_core/ae_core__user';
import { get } from 'svelte/store';
import { ae_api } from '$lib/stores/ae_stores';
export const load: PageLoad = async ({ params, parent }) => {
const parentData = await parent();
const { user_id } = params;
if (!user_id) {
error(404, 'User ID not specified');
}
const user = await load_ae_obj_id__user({
api_cfg: get(ae_api),
user_id,
log_lvl: 1
});
if (!user) {
error(404, 'User not found');
}
return {
user
};
};