Skip to content

Commit 393c586

Browse files
authored
Merge pull request #9 from hackclub/staging
User admin page + small scaling fix
2 parents ad9df88 + 2d36c3f commit 393c586

File tree

8 files changed

+568
-4
lines changed

8 files changed

+568
-4
lines changed

src/app.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@
256256
.themed-input-light {
257257
@apply border-3 border-primary-700 bg-primary-900 fill-primary-50 p-2 text-sm ring-primary-900 placeholder:text-primary-700 active:ring-3;
258258
}
259+
260+
.checkbox {
261+
@apply border-2 border-primary-600 bg-primary-900 ring-0 h-4 w-4 rounded-sm;
262+
}
259263
}
260264

261265
@layer utilities {

src/lib/components/DataCard.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script lang="ts">
2+
let { children, title } = $props();
3+
</script>
4+
5+
<div class="themed-box flex flex-col p-3 shadow-xl">
6+
<p class="text-xs opacity-60">{title}:</p>
7+
<p>
8+
{@render children?.()}
9+
</p>
10+
</div>

src/routes/dashboard/admin/admin/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
<div class="flex h-full flex-col">
1010
<h1 class="mt-5 mb-3 font-hero text-3xl font-medium">Admin</h1>
1111

12-
12+
<a href="/dashboard/admin/admin/users" class="underline">Users</a>
1313
</div>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { db } from '$lib/server/db/index.js';
2+
import { project, user, devlog } from '$lib/server/db/schema.js';
3+
import { error } from '@sveltejs/kit';
4+
import { eq, sql } from 'drizzle-orm';
5+
import type { Actions } from './$types';
6+
7+
export async function load({ locals }) {
8+
if (!locals.user) {
9+
throw error(500);
10+
}
11+
if (!locals.user.hasAdmin) {
12+
throw error(403, { message: 'get out, peasant' });
13+
}
14+
15+
const users = await db.select().from(user);
16+
17+
return {
18+
users
19+
};
20+
}
21+
22+
export const actions = {
23+
default: async ({ locals, request }) => {
24+
if (!locals.user) {
25+
throw error(500);
26+
}
27+
if (!locals.user.hasAdmin) {
28+
throw error(403, { message: 'get out, peasant' });
29+
}
30+
31+
const data = await request.formData();
32+
const statusFilter = data.getAll('status') as (typeof project.status._.data)[];
33+
34+
const userFilter = data.getAll('user').map((userId) => {
35+
const parsedInt = parseInt(userId.toString());
36+
if (!parsedInt) throw error(400, { message: 'malformed user filter' });
37+
return parseInt(userId.toString());
38+
});
39+
40+
return {
41+
// users,
42+
fields: {
43+
status: statusFilter,
44+
user: userFilter
45+
}
46+
};
47+
}
48+
} satisfies Actions;
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<script lang="ts">
2+
import { enhance } from '$app/forms';
3+
import Head from '$lib/components/Head.svelte';
4+
import { projectStatuses } from '$lib/utils.js';
5+
import { ExternalLink } from '@lucide/svelte';
6+
import relativeDate from 'tiny-relative-date';
7+
8+
let { data, form } = $props();
9+
10+
let userSearch = $state('');
11+
12+
let users = $derived(data.users); //form?.users ??
13+
14+
let filteredProjects = $derived(
15+
data.users.filter((user) =>
16+
user.name?.toLowerCase().includes(userSearch.toLowerCase())
17+
)
18+
);
19+
let filteredUsers = $derived(
20+
data.users.filter((user) => user.name.toLowerCase().includes(userSearch.toLowerCase()))
21+
);
22+
23+
let formPending = $state(false);
24+
</script>
25+
26+
<Head title="Review" />
27+
28+
<div class="flex h-full flex-col">
29+
<h1 class="mt-5 mb-3 font-hero text-3xl font-medium">Users</h1>
30+
31+
<!--<div class="flex flex-col-reverse gap-5 lg:flex-row">
32+
<div class="themed-box grow p-3">
33+
<h2 class="mb-2 text-xl font-bold">Filter & Sort</h2>
34+
<form
35+
method="POST"
36+
use:enhance={() => {
37+
formPending = true;
38+
return async ({ update }) => {
39+
await update();
40+
formPending = false;
41+
};
42+
}}
43+
>
44+
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3">
45+
<!-- Project status --/>
46+
<label class="flex flex-col gap-1">
47+
<span class="font-medium">Status</span>
48+
<select
49+
class="h-40 grow border-3 border-primary-700 bg-primary-900 fill-primary-50 p-2 text-sm ring-primary-900 placeholder:text-primary-900 active:ring-3"
50+
name="status"
51+
value={form?.fields.status ?? ['submitted']}
52+
multiple
53+
>
54+
<option value="building" class="truncate">Building</option>
55+
<option value="submitted" class="truncate">Submitted</option>
56+
<option value="t1_approved" class="truncate">Review approved</option>
57+
<option value="t2_approved" class="truncate">YSWS review approved</option>
58+
<option value="finalized" class="truncate">Finalized</option>
59+
<option value="rejected" class="truncate">Rejected</option>
60+
<option value="rejected_locked" class="truncate">Rejected (locked)</option>
61+
</select>
62+
</label>
63+
64+
<!-- Project --/>
65+
<label class="flex flex-col">
66+
<span class="mb-1 font-medium">Project</span>
67+
<div class="flex h-40 flex-col">
68+
<input
69+
type="text"
70+
placeholder="search"
71+
bind:value={projectSearch}
72+
class="themed-input-light border-b-0 py-1.5"
73+
/>
74+
<select
75+
class="themed-input-light grow"
76+
name="project"
77+
value={form?.fields.project ?? []}
78+
multiple
79+
>
80+
{#each filteredProjects as project}
81+
<option value={project.id} class="truncate">{project.name}</option>
82+
{/each}
83+
</select>
84+
</div>
85+
</label>
86+
87+
<!-- User --/>
88+
<label class="flex flex-col">
89+
<span class="mb-1 font-medium">User</span>
90+
<div class="flex h-40 flex-col">
91+
<input
92+
type="text"
93+
placeholder="search"
94+
bind:value={userSearch}
95+
class="themed-input-light border-b-0 py-1.5"
96+
/>
97+
<select
98+
class="themed-input-light grow"
99+
name="user"
100+
value={form?.fields.user ?? []}
101+
multiple
102+
>
103+
{#each filteredUsers as user}
104+
<option value={user?.id} class="truncate">{user?.name}</option>
105+
{/each}
106+
</select>
107+
</div>
108+
</label>
109+
</div>
110+
<button type="submit" class="button md primary mt-3 w-full" disabled={formPending}>Apply!</button>
111+
</form>
112+
</div>
113+
</div>
114+
115+
<h2 class="mt-4 mb-2 text-2xl font-bold">Filtered users</h2>-->
116+
117+
{#if users.length == 0}
118+
<div class="flex grow items-center justify-center">
119+
<div>
120+
<p class="themed-box p-3 shadow-lg/20">
121+
No users found matching the filter <img
122+
src="https://emoji.slack-edge.com/T0266FRGM/heavysob/55bf09f6c9d93d08.png"
123+
alt="heavysob"
124+
class="inline h-5.5"
125+
/>
126+
</p>
127+
</div>
128+
</div>
129+
{:else}
130+
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 2xl:grid-cols-3">
131+
{#each users as user}
132+
<div
133+
class="themed-box relative flex flex-col p-3 shadow-lg/20 transition-all hover:scale-102"
134+
>
135+
<a
136+
class="absolute inset-0 z-1"
137+
href={`/dashboard/admin/admin/users/${user.id}`}
138+
aria-label="project"
139+
>
140+
</a>
141+
<h1 class="flex flex-row gap-1 text-xl font-semibold">
142+
<span class="grow truncate">{user.name}</span>
143+
</h1>
144+
<!-- <p class="text-sm">
145+
by <a class="relative z-2 underline" href={`/dashboard/users/${project.user?.id}`}
146+
>{project.user?.name}</a
147+
>
148+
</p>
149+
<p class="grow">{project.project.description}</p>
150+
{#if project.project.url && project.project.url.length > 0}
151+
<div class="my-2 flex">
152+
<a class="button sm primary relative z-2" href={project.project.url} target="_blank">
153+
<ExternalLink />
154+
Link to project
155+
</a>
156+
</div>
157+
{:else}
158+
<div class="mb-2"></div>
159+
{/if}
160+
<p class="text-sm">
161+
{project.devlogCount} journal{project.devlogCount !== 1 ? 's' : ''} ∙ {Math.floor(
162+
project.timeSpent / 60
163+
)}h {project.timeSpent % 60}min
164+
</p>
165+
<div class="flex flex-row gap-4">
166+
<p class="grow text-sm">
167+
Created <abbr
168+
title={`${project.project.createdAt.toUTCString()}`}
169+
class="relative z-2"
170+
>
171+
{relativeDate(project.project.createdAt)}
172+
</abbr>
173+
</p>
174+
<p class="text-sm">{projectStatuses[project.project.status]}</p>
175+
</div> -->
176+
</div>
177+
{/each}
178+
</div>
179+
{/if}
180+
</div>

0 commit comments

Comments
 (0)