-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add election editing #650
Add election editing #650
Changes from 9 commits
1a70185
382f3b6
f842f9e
1a5e6e9
f27fa4f
7884cb1
e727dd8
41063a4
bcf1c30
43e2556
2ebf664
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,19 @@ | |
import * as m from "$paraglide/messages"; | ||
import MarkdownBody from "$lib/components/MarkdownBody.svelte"; | ||
import { languageTag } from "$paraglide/runtime"; | ||
import { isAuthorized } from "$lib/utils/authorization"; | ||
import apiNames from "$lib/utils/apiNames"; | ||
import CommitteeIcon from "$lib/components/images/CommitteeIcon.svelte"; | ||
export let data: PageData; | ||
</script> | ||
|
||
<PageHeader title={m.openElections()} /> | ||
<!-- TODO: make this editable by board members with Markdown page --> | ||
<div class="flex flex-row"> | ||
<PageHeader title={m.openElections()} /> | ||
{#if isAuthorized(apiNames.ELECTION.CREATE, data.user)} | ||
<a href="/elections/create" class="btn btn-primary ml-auto">+ Nytt val</a> | ||
{/if} | ||
</div> | ||
|
||
<section class="mb-5 space-y-5"> | ||
<p> | ||
{m.elections_description()} | ||
|
@@ -19,11 +27,16 @@ | |
{#each data.openElections as election} | ||
<div class="card bg-base-200 shadow-xl"> | ||
<div class="card-body"> | ||
<img | ||
class="w-5/12 self-center text-center" | ||
src={election.committee.darkImageUrl} | ||
alt="Committee logo" | ||
/> | ||
{#if isAuthorized(apiNames.ELECTION.UPDATE, data.user)} | ||
<a | ||
href={"/elections/" + election.id + "/edit"} | ||
class="btn btn-outline btn-sm ml-auto" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a lot of purple ( I'm not really an expert on UI design (clearly... 😄), but colors tend to draw attention and usually signify some kind of main action we want the user to take. Editing is more of an optional action and thus doesn't need to be highlighted. |
||
>Redigera | ||
</a> | ||
{/if} | ||
<div class="w-5/12 self-center text-center"> | ||
<CommitteeIcon committee={election.committee} /> | ||
</div> | ||
|
||
<h2 class="card-title self-center text-2xl font-bold"> | ||
{election.committee.name} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<script lang="ts"> | ||
import Input from "$lib/components/Input.svelte"; | ||
import type { SuperValidated } from "sveltekit-superforms"; | ||
import { superForm } from "$lib/utils/client/superForms"; | ||
import type { ElectionSchema } from "./schemas"; | ||
import * as m from "$paraglide/messages"; | ||
|
||
export let isCreating: boolean; | ||
export let data: { | ||
form: SuperValidated<ElectionSchema>; | ||
committees: Array<{ id: string; name: string; nameEn: string | null }>; | ||
election: { | ||
markdown: string; | ||
markdownEn: string | null; | ||
link: string; | ||
expiresAt: Date; | ||
committeeId: string; | ||
}; | ||
}; | ||
const { form, errors, constraints, enhance } = superForm(data.form); | ||
</script> | ||
|
||
<form | ||
id="election-editor" | ||
method="POST" | ||
action={isCreating ? "?/create" : "?/update"} | ||
use:enhance | ||
class="flex flex-col gap-3" | ||
> | ||
<h1 class="text-2xl font-bold"> | ||
{isCreating ? m.elections_create() : m.elections_edit()} | ||
</h1> | ||
<Input | ||
name="markdown" | ||
label={m.elections_content_sv()} | ||
required | ||
textarea | ||
bind:value={$form.markdown} | ||
error={$errors.markdown} | ||
{...$constraints.markdown} | ||
/> | ||
<Input | ||
name="markdownEn" | ||
label={m.elections_content_en()} | ||
required | ||
textarea | ||
bind:value={$form.markdownEn} | ||
error={$errors.markdownEn} | ||
{...$constraints.markdownEn} | ||
/> | ||
<Input | ||
name="link" | ||
label={m.elections_link()} | ||
required | ||
bind:value={$form.link} | ||
error={$errors.link} | ||
{...$constraints.link} | ||
/> | ||
<Input | ||
name="expiresAt" | ||
label={m.elections_expiryDate()} | ||
type="date" | ||
required | ||
bind:value={$form.expiresAt} | ||
error={$errors.expiresAt} | ||
{...$constraints.expiresAt} | ||
/> | ||
|
||
<label class="label-text" for="committee">{m.elections_committee()}*</label> | ||
<select | ||
id="committee" | ||
name="committeeId" | ||
class="max-w select select-bordered w-full" | ||
bind:value={$form.committeeId} | ||
{...$constraints.committeeId} | ||
> | ||
{#each data.committees as committeeOption} | ||
<option value={committeeOption.id}>{committeeOption.name}</option> | ||
{/each} | ||
</select> | ||
{#if $errors.committeeId} | ||
<p class="text-error">{$errors.committeeId}</p> | ||
{/if} | ||
<button type="submit" class="btn btn-primary mx-auto mt-4 w-4/12"> | ||
{m.elections_save()} | ||
</button> | ||
</form> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { error, fail } from "@sveltejs/kit"; | ||
import type { Actions, PageServerLoad } from "./$types"; | ||
import { superValidate } from "sveltekit-superforms/server"; | ||
import { zod } from "sveltekit-superforms/adapters"; | ||
import { redirect } from "$lib/utils/redirect"; | ||
import { electionSchema } from "../../schemas"; | ||
import * as m from "$paraglide/messages"; | ||
import dayjs from "dayjs"; | ||
|
||
export const load: PageServerLoad = async ({ locals, params }) => { | ||
const { prisma } = locals; | ||
const electionPromise = prisma.election.findFirst({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of instantly awaiting the Promise that |
||
where: { id: params.id }, | ||
}); | ||
|
||
const committees = await prisma.committee.findMany({ | ||
orderBy: [{ shortName: "asc" }], | ||
select: { | ||
id: true, | ||
name: true, | ||
nameEn: true, | ||
}, | ||
}); | ||
|
||
const election = await electionPromise; | ||
|
||
if (!election) { | ||
throw error(404, m.elections_notFound()); | ||
} | ||
|
||
return { | ||
election, | ||
committees, | ||
form: await superValidate( | ||
{ | ||
...election, | ||
expiresAt: dayjs(election.expiresAt).format("YYYY-MM-DD"), | ||
}, | ||
Comment on lines
+35
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By passing an object into the |
||
zod(electionSchema), | ||
), | ||
}; | ||
}; | ||
|
||
export const actions: Actions = { | ||
update: async (event) => { | ||
const { request, locals, params } = event; | ||
const { prisma } = locals; | ||
const form = await superValidate(request, zod(electionSchema)); | ||
if (!form.valid) return fail(400, { form }); | ||
const id = params.id; | ||
const { markdown, markdownEn, link, expiresAt, committeeId } = form.data; | ||
await prisma.election.update({ | ||
where: { | ||
id, | ||
}, | ||
data: { | ||
markdown, | ||
markdownEn, | ||
link, | ||
expiresAt: dayjs(expiresAt).endOf("day").toDate(), | ||
committeeId, | ||
}, | ||
}); | ||
throw redirect( | ||
"/elections", | ||
{ | ||
message: m.elections_updated(), | ||
type: "success", | ||
}, | ||
event, | ||
); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<script lang="ts"> | ||
import SetPageTitle from "$lib/components/nav/SetPageTitle.svelte"; | ||
import ElectionEditor from "../../ElectionEditor.svelte"; | ||
|
||
import type { PageData } from "./$types"; | ||
export let data: PageData; | ||
</script> | ||
|
||
<SetPageTitle title={data.form.data.markdown} /> | ||
<ElectionEditor isCreating={false} {data} /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { fail } from "@sveltejs/kit"; | ||
import type { Actions, PageServerLoad } from "./$types"; | ||
import { superValidate } from "sveltekit-superforms/server"; | ||
import { zod } from "sveltekit-superforms/adapters"; | ||
import { redirect } from "$lib/utils/redirect"; | ||
import { electionSchema } from "../schemas"; | ||
import * as m from "$paraglide/messages"; | ||
import dayjs from "dayjs"; | ||
|
||
export const load: PageServerLoad = async ({ locals }) => { | ||
const { prisma } = locals; | ||
|
||
const committees = await prisma.committee.findMany({ | ||
orderBy: [{ shortName: "asc" }], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed some whitespace because Prisma query options are already quite long as it is. Note that this is one of the rare cases where our formatter Prettier allows you to choose how something should be formatted. |
||
select: { | ||
id: true, | ||
name: true, | ||
nameEn: true, | ||
}, | ||
}); | ||
|
||
const election = { | ||
markdown: "", | ||
markdownEn: null, | ||
link: "", | ||
expiresAt: dayjs().endOf("day").toDate(), | ||
committeeId: "", | ||
}; | ||
|
||
return { | ||
committees, | ||
election, | ||
form: await superValidate(zod(electionSchema)), | ||
}; | ||
}; | ||
|
||
export const actions: Actions = { | ||
create: async (event) => { | ||
const { request, locals } = event; | ||
const { prisma } = locals; | ||
const form = await superValidate(request, zod(electionSchema)); | ||
if (!form.valid) return fail(400, { form }); | ||
const { markdown, markdownEn, link, expiresAt, committeeId } = form.data; | ||
await prisma.election.create({ | ||
data: { | ||
markdown, | ||
markdownEn, | ||
link, | ||
expiresAt: dayjs(expiresAt).endOf("day").toDate(), | ||
committeeId, | ||
}, | ||
}); | ||
throw redirect( | ||
"/elections", | ||
{ | ||
message: m.elections_created(), | ||
type: "success", | ||
}, | ||
event, | ||
); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<script lang="ts"> | ||
import SetPageTitle from "$lib/components/nav/SetPageTitle.svelte"; | ||
import ElectionEditor from "../ElectionEditor.svelte"; | ||
|
||
import type { PageData } from "./$types"; | ||
export let data: PageData; | ||
</script> | ||
|
||
<SetPageTitle title={data.form.data.markdown} /> | ||
<ElectionEditor isCreating {data} /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { Infer } from "sveltekit-superforms"; | ||
import { z } from "zod"; | ||
|
||
export const electionSchema = z.object({ | ||
markdown: z.string(), | ||
markdownEn: z.string().nullable(), | ||
link: z.string(), | ||
expiresAt: z.string().date(), | ||
committeeId: z.string().uuid(), | ||
}); | ||
export type ElectionSchema = Infer<typeof electionSchema>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -782,6 +782,17 @@ | |
"elections_description": "Here are the application forms for all committees that currently have positions open for application. The requirement profiles and post descriptions are located in the application forms.", | ||
"elections_close": "The elections close at", | ||
"elections_apply": "Apply", | ||
"elections_notFound": "Elections not found", | ||
"elections_updated": "Election updated", | ||
"elections_created": "Election created", | ||
"elections_create": "Create election", | ||
"elections_edit": "Edit election", | ||
"elections_content_sv": "Content (SV)", | ||
"elections_content_en": "Content (EN)", | ||
"elections_link": "Link", | ||
"elections_expiryDate": "Last application date (23:59)", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed to this to make it more concise, I think it should already be pretty obvious that the last application date is inclusive. |
||
"elections_committee": "Committee", | ||
"elections_save": "Save", | ||
"expenses": "Expenses", | ||
"expense": "Expense", | ||
"expenseCreated": "Expense created", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to create since that's more consistent with our other pages (except for one for some reason...).