Skip to content

Commit

Permalink
Fix person editor
Browse files Browse the repository at this point in the history
  • Loading branch information
MangoSwirl committed Jun 6, 2024
1 parent 4bdde28 commit 7b3ca1b
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 28 deletions.
11 changes: 10 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"dotenv": "^16.3.1",
"magnolia-ui-svelte": "github:highlanderrobotics/magnolia-ui-svelte#production",
"pngjs": "^7.0.0",
"sqlite3": "^5.1.6"
"sqlite3": "^5.1.6",
"zod": "^3.23.8"
}
}
2 changes: 1 addition & 1 deletion src/lib/util/person/role/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ export const roles = [
"MENTOR",
"SUPERVISOR",
"OTHER",
]
] as const;

export type RoleString = typeof roles[number];
2 changes: 1 addition & 1 deletion src/routes/tools/people/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const actions = {
if (!name) throw error(400, 'Missing "name" field.');
if (!email) throw error(400, 'Missing "email" field.');
if (!role) throw error(400, 'Missing "role" field.');
if (!roles.includes(role.toString())) throw error(400, 'Invalid "role" field');
if (!(role.toString() in roles)) throw error(400, 'Invalid "role" field');

try {
await addPerson(name.toString(), email.toString(), role as RoleString, teamAffiliated);
Expand Down
60 changes: 59 additions & 1 deletion src/routes/tools/people/[id]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { hasPermission } from '$lib/server/util/permission/hasPermission';
import { error } from '@sveltejs/kit';
import { error, fail } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { getPersonFromUser } from '$lib/server/util/person/getPersonFromUser';
import type { Prisma } from '@prisma/client';
import prisma from '$lib/server/util/prisma';
import * as z from 'zod';
import { roles } from '$lib/util/person/role/roles';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';

export const load: PageServerLoad = async ({ params, locals }) => {
const session = await locals.getSession();
Expand Down Expand Up @@ -57,3 +60,58 @@ export const load: PageServerLoad = async ({ params, locals }) => {
person
};
};

export const actions = {
edit: async (event) => {
const session = await event.locals.getSession();
if (!session?.user) throw error(500);

const requester = await getPersonFromUser(session?.user);
if (!requester) throw error(500);

const canEdit = await hasPermission(requester, 'people.edit');

if (!canEdit) {
throw error(403, 'You do not have permission to edit people.');
}

const formData = await event.request.formData();

// Validate data
const schema = z.object({
name: z.string().min(1),
role: z.enum(roles),
email: z.string().email(),
teamAffiliated: z.enum(['on', 'off']).optional().transform((value) => value === 'on'),
});

try {
const data = schema.parse(Object.fromEntries(formData));

await prisma.person.update({
where: {
id: event.params.id
},
data
});
} catch (e) {
console.error(e);

if (e instanceof z.ZodError) {
const sentError = e.errors[0];

return fail(400, {
message: sentError.message,
});
}

if (e instanceof PrismaClientKnownRequestError) {
return fail(400, {
message: 'An error occured. Check if the email is already used.',
});
}

throw error(500);
}
}
}
181 changes: 158 additions & 23 deletions src/routes/tools/people/[id]/ProfileCard.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { localizedRole } from '$lib/util/person/role/localized.js';
import { roles } from '$lib/util/person/role/roles';
import type { Prisma } from '@prisma/client';
import { Button } from 'magnolia-ui-svelte';
import { Button, Select, TextField } from 'magnolia-ui-svelte';
export let canEdit = false;
export let person: Prisma.PersonGetPayload<{
Expand All @@ -14,34 +16,106 @@
labCertification: boolean;
};
}>;
let editing = false;
let editSaving = false;
let editError: string | null = null;
</script>

<div class="person">
{#if canEdit}
<div class="right-actions">
<!-- <DensityProvider density="compact"> -->
<Button variant="secondary" element="a" href="/tools/people/{person.id}/edit">Edit</Button>
<!-- </DensityProvider> -->
{#if editing}
<form
action="?/edit"
method="post"
use:enhance={() => {
return async ({ result, update }) => {
editSaving = false;

if (result.type === 'success') {
console.log('success');

editing = false;
} else if (result.type === 'failure') {
editError =
typeof result.data?.message === 'string'
? result.data.message
: 'Failed to save changes.';
}

update();
};
}}
on:submit={() => (editSaving = true)}
>
<div class="person">
<div class="right-actions">
<Button variant="secondary" type="submit" disabled={editSaving}>Save</Button>
</div>
<div class="flex-row">
<img src="/api/people/{person.id}/picture?size=120" alt="Profile" />
</div>
<div class="field">
<label for="name">Name</label>
<TextField id="name" name="name" value={person.name} required />
</div>
<div class="field">
<label for="email">Email</label>
<TextField id="email" name="email" value={person.email} required />
</div>
<div class="field">
<label for="role">Role</label>
<Select
id="role"
name="role"
required
items={roles.map((role) => ({
label: localizedRole(role),
value: role
}))}
value={person.role}
/>
</div>
<div class="labeled-selectable">
<input
type="checkbox"
id="teamAffiliated"
name="teamAffiliated"
checked={person.teamAffiliated}
/>
<label for="teamAffiliated">Affiliated with 8033</label>
</div>
{#if editError}
<div class="error">{editError}</div>
{/if}
</div>
{/if}
<div class="flex-row">
<img src="/api/people/{person.id}/picture?size=120" alt="Profile" />
<div>
<h1>{person.name}</h1>
<h2>{localizedRole(person.role)}</h2>
</form>
{:else}
<div class="person">
{#if canEdit}
<div class="right-actions">
<!-- <DensityProvider density="compact"> -->
<Button variant="secondary" on:click={() => (editing = true)}>Edit</Button>
<!-- </DensityProvider> -->
</div>
{/if}
<div class="flex-row">
<img src="/api/people/{person.id}/picture?size=120" alt="Profile" />
<div>
<h1>{person.name}</h1>
<h2>{localizedRole(person.role)}</h2>
</div>
</div>
</div>
<div class="datum">
<div class="label">Email</div>
<a href="mailto:{person.email}" class="value">{person.email}</a>
</div>
<div class="datum">
<div class="label">Affiliation</div>
<div class="value">
{person.teamAffiliated ? 'Affiliated with 8033' : 'Not affiliated with 8033'}
<div class="datum">
<div class="label">Email</div>
<a href="mailto:{person.email}" class="value">{person.email}</a>
</div>
<div class="datum">
<div class="label">Affiliation</div>
<div class="value">
{person.teamAffiliated ? 'Affiliated with 8033' : 'Not affiliated with 8033'}
</div>
</div>
</div>
</div>
{/if}

<style>
.flex-row {
Expand Down Expand Up @@ -101,4 +175,65 @@
a {
text-decoration: none;
}
.field {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 7px;
}
label {
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-top: 14px;
}
.labeled-selectable {
display: flex;
align-items: center;
gap: 8px;
margin-top: 14px;
}
.labeled-selectable label {
margin: 0;
}
input[type='checkbox'] {
appearance: none;
border: 2px solid var(--light-gray);
background-color: var(--secondary-container);
border-radius: 5px;
width: 24px;
height: 24px;
position: relative;
transition: 100ms;
}
input[type='checkbox']:checked {
background-color: var(--victory-purple);
border: 12px solid var(--victory-purple);
}
input[type='checkbox']:checked::after {
content: 'check';
color: var(--secondary-container);
font-family: 'Material Symbols Rounded';
font-variation-settings: 'opsz' 22, 'GRAD' 100;
font-size: 22px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scaleX(0.8);
}
.error {
color: var(--danger);
margin-top: 14px;
}
</style>

0 comments on commit 7b3ca1b

Please sign in to comment.