feat: standardize input styling with tier system, add depth and admin features

- Define 4 input tier CSS classes (input-standard, input-search, input-embedded, input-danger)
  in globals.css, migrating ~50 inputs across ~28 component files to use them
- Add subtle border and inset shadow to solid input tiers for resting-state visibility
- Fix focus ring clipping in settings panel scroll container
- Fix phantom Tailwind tokens (border-border-primary, placeholder-txt-muted)
- Add admin user management panel, storage management, and account deletion utilities
This commit is contained in:
Jannis Braun
2026-03-14 13:49:32 +01:00
parent afbc4b5e31
commit 836f0acef6
45 changed files with 1478 additions and 235 deletions
+36
View File
@@ -34,6 +34,12 @@ import type {
ChangePasswordRequest,
ChangePasswordResponse,
DeleteAccountRequest,
StorageStats,
OrphanedFile,
CleanupResult,
AdminUserListResponse,
AdminUser,
AdminResetPasswordResponse,
ExploreSpace,
JoinRequest,
Role,
@@ -181,6 +187,16 @@ export class BackspaceApiClient {
myJoinRequests: (status?: string) => Promise<{ requests: JoinRequest[] }>;
};
readonly admin: {
storageStats: () => Promise<StorageStats>;
storageOrphans: () => Promise<{ orphans: OrphanedFile[] }>;
storageCleanup: (dryRun?: boolean) => Promise<CleanupResult>;
listUsers: (params?: { q?: string; page?: number; pageSize?: number; showDeleted?: boolean }) => Promise<AdminUserListResponse>;
setUserRole: (userId: string, isAdmin: boolean) => Promise<AdminUser>;
resetUserPassword: (userId: string) => Promise<AdminResetPasswordResponse>;
deleteUser: (userId: string) => Promise<{ success: boolean }>;
};
constructor(baseUrl: string, getToken: () => string | null) {
async function request<T>(
method: string,
@@ -487,6 +503,26 @@ export class BackspaceApiClient {
return request<{ requests: JoinRequest[] }>('GET', `/users/@me/join-requests?${params}`);
},
};
this.admin = {
storageStats: () => request<StorageStats>('GET', '/admin/storage/stats'),
storageOrphans: () => request<{ orphans: OrphanedFile[] }>('GET', '/admin/storage/orphans'),
storageCleanup: (dryRun = false) => request<CleanupResult>('POST', '/admin/storage/cleanup', { dryRun }),
listUsers: (params) => {
const qs = new URLSearchParams();
if (params?.q) qs.set('q', params.q);
if (params?.page !== undefined) qs.set('page', String(params.page));
if (params?.pageSize !== undefined) qs.set('pageSize', String(params.pageSize));
if (params?.showDeleted) qs.set('showDeleted', 'true');
return request<AdminUserListResponse>('GET', `/admin/users?${qs}`);
},
setUserRole: (userId, isAdmin) =>
request<AdminUser>('PATCH', `/admin/users/${userId}/role`, { isAdmin }),
resetUserPassword: (userId) =>
request<AdminResetPasswordResponse>('POST', `/admin/users/${userId}/reset-password`),
deleteUser: (userId) =>
request<{ success: boolean }>('DELETE', `/admin/users/${userId}`),
};
}
}