Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@

&:disabled,
&.disabled {
@apply cursor-not-allowed opacity-80 hover:outline-0;
@apply cursor-not-allowed opacity-70 hover:outline-0;
}

&.red {
Expand All @@ -257,6 +257,10 @@
@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;
}

.themed-input-on-box {
@apply bg-primary-900 border-3 border-primary-800 ring-primary-700 focus:ring-2 transition-shadow rounded-lg placeholder:text-primary-700;
}

.checkbox {
@apply border-2 border-primary-600 bg-primary-900 ring-0 h-4 w-4 rounded-sm;
}
Expand Down
10 changes: 7 additions & 3 deletions src/routes/auth/callback/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ export async function GET(event) {
return redirect(302, 'https://fraud.land');
}

const isSuperAdmin =
env.SUPER_ADMIN_SLACK_ID != undefined &&
env.SUPER_ADMIN_SLACK_ID.length > 0 &&
slack_id === env.SUPER_ADMIN_SLACK_ID;

if (databaseUser) {
// Update user (update name and profile picture and lastLoginAt on login)
await db
Expand All @@ -173,12 +178,11 @@ export async function GET(event) {
name: username,
profilePicture: profilePic,
lastLoginAt: new Date(Date.now()),
hackatimeTrust
hackatimeTrust,
hasAdmin: isSuperAdmin ? true : undefined
})
.where(eq(user.idvId, id));
} else {
const isSuperAdmin = slack_id === env.SUPER_ADMIN_SLACK_ID;

// Create user
await db.insert(user).values({
idvId: id,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/dashboard/Sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<p class="truncate font-medium">
{user.name}
</p>
<p class="text-sm">{user.clay} clay · {user.brick} brick</p>
<p class="text-sm">{Math.floor(user.clay)} clay · {Math.floor(user.brick)} brick</p>
</div>
</a>
<SidebarButton icon={LogOut} href="/auth/logout">Log out</SidebarButton>
Expand Down
13 changes: 12 additions & 1 deletion src/routes/dashboard/admin/admin/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import Head from '$lib/components/Head.svelte';
import { Users } from '@lucide/svelte';

let { data } = $props();
</script>
Expand All @@ -9,5 +10,15 @@
<div class="flex h-full flex-col">
<h1 class="mt-5 mb-3 font-hero text-3xl font-medium">Admin</h1>

<a href="/dashboard/admin/admin/users" class="underline">Users</a>
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6">
<a
class="themed-box flex flex-col items-center justify-center gap-2 p-3 shadow-xl transition-transform hover:scale-105"
href="/dashboard/admin/admin/users"
>
<div>
<Users size={40} />
</div>
<p class="text-2xl font-bold">Users</p>
</a>
</div>
</div>
139 changes: 18 additions & 121 deletions src/routes/dashboard/admin/admin/users/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@

let userSearch = $state('');

let users = $derived(data.users); //form?.users ??
let users = $derived(data.users); //form?.users ??

let filteredProjects = $derived(
data.users.filter((user) =>
user.name?.toLowerCase().includes(userSearch.toLowerCase())
)
data.users.filter((user) => user.name?.toLowerCase().includes(userSearch.toLowerCase()))
);
let filteredUsers = $derived(
data.users.filter((user) => user.name.toLowerCase().includes(userSearch.toLowerCase()))
Expand All @@ -28,92 +26,6 @@
<div class="flex h-full flex-col">
<h1 class="mt-5 mb-3 font-hero text-3xl font-medium">Users</h1>

<!--<div class="flex flex-col-reverse gap-5 lg:flex-row">
<div class="themed-box grow p-3">
<h2 class="mb-2 text-xl font-bold">Filter & Sort</h2>
<form
method="POST"
use:enhance={() => {
formPending = true;
return async ({ update }) => {
await update();
formPending = false;
};
}}
>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3">
<!-- Project status --/>
<label class="flex flex-col gap-1">
<span class="font-medium">Status</span>
<select
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"
name="status"
value={form?.fields.status ?? ['submitted']}
multiple
>
<option value="building" class="truncate">Building</option>
<option value="submitted" class="truncate">Submitted</option>
<option value="t1_approved" class="truncate">Review approved</option>
<option value="t2_approved" class="truncate">YSWS review approved</option>
<option value="finalized" class="truncate">Finalized</option>
<option value="rejected" class="truncate">Rejected</option>
<option value="rejected_locked" class="truncate">Rejected (locked)</option>
</select>
</label>

<!-- Project --/>
<label class="flex flex-col">
<span class="mb-1 font-medium">Project</span>
<div class="flex h-40 flex-col">
<input
type="text"
placeholder="search"
bind:value={projectSearch}
class="themed-input-light border-b-0 py-1.5"
/>
<select
class="themed-input-light grow"
name="project"
value={form?.fields.project ?? []}
multiple
>
{#each filteredProjects as project}
<option value={project.id} class="truncate">{project.name}</option>
{/each}
</select>
</div>
</label>

<!-- User --/>
<label class="flex flex-col">
<span class="mb-1 font-medium">User</span>
<div class="flex h-40 flex-col">
<input
type="text"
placeholder="search"
bind:value={userSearch}
class="themed-input-light border-b-0 py-1.5"
/>
<select
class="themed-input-light grow"
name="user"
value={form?.fields.user ?? []}
multiple
>
{#each filteredUsers as user}
<option value={user?.id} class="truncate">{user?.name}</option>
{/each}
</select>
</div>
</label>
</div>
<button type="submit" class="button md primary mt-3 w-full" disabled={formPending}>Apply!</button>
</form>
</div>
</div>

<h2 class="mt-4 mb-2 text-2xl font-bold">Filtered users</h2>-->

{#if users.length == 0}
<div class="flex grow items-center justify-center">
<div>
Expand All @@ -135,44 +47,29 @@
<a
class="absolute inset-0 z-1"
href={`/dashboard/admin/admin/users/${user.id}`}
aria-label="project"
aria-label="user"
>
</a>
<h1 class="flex flex-row gap-1 text-xl font-semibold">
<span class="grow truncate">{user.name}</span>
</h1>
<!-- <p class="text-sm">
by <a class="relative z-2 underline" href={`/dashboard/users/${project.user?.id}`}
>{project.user?.name}</a
<p>
<span class="rounded-sm bg-primary-800 px-1 text-nowrap"
>{user.hasT1Review ? 't1 review' : '-'}</span
>
<span class="rounded-sm bg-primary-700 px-1 text-nowrap"
>{user.hasT2Review ? 't2 review' : '-'}</span
>
<span class="rounded-sm bg-primary-600 px-1 text-nowrap"
>{user.hasAdmin ? 'admin' : '-'}</span
>
</p>
<p class="grow">{project.project.description}</p>
{#if project.project.url && project.project.url.length > 0}
<div class="my-2 flex">
<a class="button sm primary relative z-2" href={project.project.url} target="_blank">
<ExternalLink />
Link to project
</a>
</div>
{:else}
<div class="mb-2"></div>
{/if}
<p class="text-sm">
{project.devlogCount} journal{project.devlogCount !== 1 ? 's' : ''} ∙ {Math.floor(
project.timeSpent / 60
)}h {project.timeSpent % 60}min
</p>
<div class="flex flex-row gap-4">
<p class="grow text-sm">
Created <abbr
title={`${project.project.createdAt.toUTCString()}`}
class="relative z-2"
>
{relativeDate(project.project.createdAt)}
</abbr>
</p>
<p class="text-sm">{projectStatuses[project.project.status]}</p>
</div> -->
<code>
{user.slackId}
</code>
<p>Hackatime: {user.hackatimeTrust}</p>
<p>Trust: {user.trust}</p>
<p>{user.clay} clay, {user.brick} brick, {user.shopScore} market</p>
</div>
{/each}
</div>
Expand Down
59 changes: 54 additions & 5 deletions src/routes/dashboard/admin/admin/users/[id]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { db } from '$lib/server/db/index.js';
import { user, devlog, session } from '$lib/server/db/schema.js';
import { error } from '@sveltejs/kit';
import { eq, sql } from 'drizzle-orm';
import { error, fail } from '@sveltejs/kit';
import { and, eq, sql } from 'drizzle-orm';
import type { Actions } from './$types';

export async function load({ locals, params }) {
Expand All @@ -20,7 +20,7 @@ export async function load({ locals, params }) {
devlogCount: sql`COALESCE(COUNT(${devlog.id}), 0)`
})
.from(user)
.leftJoin(devlog, eq(devlog.userId, user.id))
.leftJoin(devlog, and(eq(devlog.userId, user.id), eq(devlog.deleted, false)))
.where(eq(user.id, id))
.groupBy(user.id)) ?? [{ devlogCount: 0 }];

Expand Down Expand Up @@ -51,8 +51,6 @@ export const actions = {
const hasAdmin = data.get('has_admin');
const hasProjectAuditLogs = data.get('has_project_audit_logs');

// TODO: add check to disable un-admining superadmin

await db
.update(user)
.set({
Expand All @@ -74,6 +72,57 @@ export const actions = {
};
},

currency: async ({ locals, request, params }) => {
if (!locals.user) {
throw error(500);
}
if (!locals.user.hasAdmin) {
throw error(403, { message: 'get out, peasant' });
}

const id: number = parseInt(params.id);

const data = await request.formData();
const clay = data.get('clay');
const brick = data.get('brick');
const shopScore = data.get('market_score');

if (
!clay ||
isNaN(parseFloat(clay.toString())) ||
!brick ||
isNaN(parseFloat(brick.toString())) ||
!shopScore ||
isNaN(parseFloat(shopScore.toString()))
) {
return fail(400, {
currency: {
fields: { clay, brick, shopScore },
invalidFields: true
}
});
}

await db
.update(user)
.set({
clay: parseFloat(clay.toString()),
brick: parseFloat(brick.toString()),
shopScore: parseFloat(shopScore.toString())
})
.where(eq(user.id, id));

const [queriedUser] = await db.select().from(user).where(eq(user.id, id));

if (!queriedUser) {
throw error(404, { message: 'user not found' });
}

return {
queriedUser
};
},

refreshHackatime: async ({ locals, params }) => {
if (!locals.user) {
throw error(500);
Expand Down
Loading
Loading