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
5 changes: 4 additions & 1 deletion src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="viewport"
content="width=device-width, initial-scale=1 interactive-widget=resizes-content"
/>
<meta
name="description"
content="A quick and easy chilli cook-off voting app! Create events, invite friends, and let the voting begin!"
Expand Down
23 changes: 23 additions & 0 deletions src/lib/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,27 @@ export class BaseAPI {

return this.parseResponse(response, parseReturn);
};

patch = async (
endpoint: string,
data: unknown,
headers: HeadersInit = {},
parseReturn: ParseReturn = 'JSON'
): Promise<ResponseJSON | Response> => {
let response = undefined;
try {
response = await this.fetch(this.baseUrl + this.endpoint + endpoint, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
...headers
},
body: JSON.stringify(data)
});
} catch (error) {
throw new APIError(response?.status, response?.statusText, (error as Error).message);
}

return this.parseResponse(response, parseReturn);
};
}
19 changes: 18 additions & 1 deletion src/lib/api/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ export type EventCreateRequestData = {
electoral_system: string;
};

export type EventStatus = 'RE' | 'VO' | 'CL';

export type EventResponseData = {
id: number;
name: string;
choices: string[];
status: EventStatus;
electoral_system: string;
share_token: string;
show_results: boolean;
Expand Down Expand Up @@ -68,13 +71,27 @@ export class EventsAPI extends BaseAPI {
'X-API-Key': shareToken
}) as Promise<BallotCreateResponseData>;
};

updateStatus = async (eventID: number, token: string, status: EventStatus) => {
return this.patch(
`/${eventID}/update-status`,
{ status },
{
'X-API-Key': token
}
);
};
}

export class BallotAPI extends BaseAPI {
endpoint: string = '/vote/ballot';

submitBallot = async (ballotID: number, token: string, submission: unknown) => {
return this.post(`/${ballotID}/submit`, { vote: submission }, { 'X-API-Key': token });
return this.post(
`/${ballotID}/submit`,
{ vote: submission },
{ 'X-API-Key': token }
) as Promise<BallotResponseData>;
};

getBallot = async (ballotID: number, token: string) => {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/EventForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

storage.saveEvent(response.id, response.name, response.host_token);

await goto(resolve(`/event/${response.id}/host/invitation/`));
await goto(resolve(`/event/${response.id}`));
};
</script>

Expand Down
9 changes: 4 additions & 5 deletions src/lib/components/VotingWrapper.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { setContext } from 'svelte';
import votingSystems from '$lib/voting-system/config';
import type { SubmissionContext } from '$lib/types';
import { BallotAPI, type EventResponseData } from '$lib/api/events';
import { BallotAPI, type BallotResponseData, type EventResponseData } from '$lib/api/events';
import { ExclamationCircleOutline } from 'flowbite-svelte-icons';

const {
Expand All @@ -15,7 +15,7 @@
ballotID: number;
event: EventResponseData;
token: string;
onSubmitVote: () => void;
onSubmitVote: (value: BallotResponseData) => void;
} = $props();
const config = $derived(votingSystems.find((value) => value.id === event.electoral_system));
const submissionContext: SubmissionContext = $state({
Expand All @@ -28,9 +28,8 @@

const submitVote = async () => {
const ballotAPI = new BallotAPI();

await ballotAPI.submitBallot(ballotID, token, submissionContext.submission);
onSubmitVote();
const ballot = await ballotAPI.submitBallot(ballotID, token, submissionContext.submission);
onSubmitVote(ballot);
};
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
);
</script>

{#each votes as vote (vote.choice)}
{#each votes.sort((a, b) => b.count - a.count) as vote (vote.choice)}
<P class="my-2">
{vote.choice}: {vote.count}
</P>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
});
</script>

<P class="text-center">Select your top pick.</P>
<P>Select your top pick.</P>

{#each event.choices as choice (choice)}
<Radio name="candidates" id={choice} value={choice} bind:group={selectedChoice} class="my-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { ResultComponentProps } from '$lib/voting-system/types';
import type { BallotResponseData, EventResponseData } from '$lib/api/events';
import type { RankedSubmission } from './types';
import { P } from 'flowbite-svelte';

let { event, ballots }: ResultComponentProps = $props();

Expand Down Expand Up @@ -80,50 +81,23 @@
});
</script>

<h3>
<P class="mb-4 font-bold">
{#if overallWinner === '__TIE_FLAG__'}
It's a tie!
{:else}
{overallWinner} wins!
{/if}
</h3>
</P>

<div class="rounds">
<div class="flex flex-col-reverse">
{#each roundData as voteCount, i (i)}
<div class="round">
<div class="round-number">Round {i + 1}</div>
<div class="vote-counts">
<div class="round mt-2 w-3xs rounded-lg bg-gray-200 p-2">
<div class="mb-2 text-sm font-light text-gray-600 uppercase">Round {i + 1}</div>
<div class="text-gray-700">
{#each Object.keys(voteCount) as candidate (candidate)}
<div>{candidate}: {voteCount[candidate]}</div>
{/each}
</div>
</div>
{/each}
</div>

<style>
.rounds {
display: flex;
flex-direction: column-reverse;
}

.round {
width: 300px;
margin-top: 1em;
padding: 1em 1.5em;
background-color: #efefef;
border-radius: 8px;
}

.round-number {
color: #585a60;
text-transform: uppercase;
font-weight: lighter;
font-size: 14px;
margin-bottom: 0.5em;
}

.vote-counts {
color: #474747;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { SubmissionContext } from '$lib/types';
import type { VotingComponentProps } from '$lib/voting-system/types';
import type { RankedSubmission } from './types';
import { P } from 'flowbite-svelte';

let { event }: VotingComponentProps = $props();

Expand Down Expand Up @@ -34,16 +35,20 @@
});
</script>

<p>Drag your preferred options to the top.</p>
<P>Drag your preferred options to the top.</P>

<section
class="min-h-md"
use:dndzone={{ items, flipDurationMs, dropTargetStyle: { outline: 'none' } }}
onconsider={handleSort}
onfinalize={handleFinalize}
>
{#each items as item, index (item.id)}
<div class="choice-card" animate:flip={{ duration: flipDurationMs }}>
<div class="rank-number">
<div
class="my-4 flex min-w-48 rounded-lg bg-gray-200 px-4 py-3.5"
animate:flip={{ duration: flipDurationMs }}
>
<div class="pr-2 font-bold text-gray-500">
{index + 1}.
</div>
<div>
Expand All @@ -52,24 +57,3 @@
</div>
{/each}
</section>

<style>
.choice-card {
display: flex;
min-width: 15em;
background-color: #e4e4e4;
border-radius: 8px;
margin: 15px;
padding: 15px 20px;
}

.rank-number {
padding-right: 10px;
font-weight: bold;
color: #909090;
}

section {
min-height: 12em;
}
</style>
8 changes: 4 additions & 4 deletions src/lib/voting-system/components/star/StarResults.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,19 @@
</script>

{#each totalRatings.sort(sortRatings) as vote (vote.choice)}
<div class="flex flex-wrap items-center justify-center gap-2">
<div class="flex flex-wrap items-center gap-2">
<P class="my-2">
{vote.choice} - {vote.total}
</P>
<Star fillPercent={100} size={20} ariaLabel="Star icon" />
{#if firstPlace && vote.choice === firstPlace.winner}
<P size="sm" class="font-bold text-green-600">(Winner) {firstPlace.reasoning}</P>
<P size="sm" class="font-bold text-green-600">(Winner)</P>
{/if}
{#if secondPlace && vote.choice === secondPlace.winner}
<P size="sm" class="font-bold text-blue-600">(Second Place) {secondPlace.reasoning}</P>
<P size="sm" class="font-bold text-blue-600">(Second Place)</P>
{/if}
{#if thirdPlace && vote.choice === thirdPlace.winner}
<P size="sm" class="font-bold text-yellow-600">(Third Place) {thirdPlace.reasoning}</P>
<P size="sm" class="font-bold text-yellow-600">(Third Place)</P>
{/if}
</div>
{/each}
20 changes: 9 additions & 11 deletions src/lib/voting-system/components/star/StarVoting.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@
});
</script>

<P class="text-center">
Rate each item from 0-5 stars. You may rate multiple items with the same number of stars.
</P>
<P>Rate each item from 0-5 stars. You may rate multiple items with the same number of stars.</P>

<div class="items-between flex-col">
{#each event.choices as choice (choice)}
Expand All @@ -37,14 +35,6 @@
<div class="my-2 w-full flex-col items-center justify-between gap-4">
<P size="lg" class="font-bold">{choice}</P>
<div class="relative flex items-center">
{#if ratingObj.rating > 0}
<button
class="absolute right-full flex cursor-pointer items-center justify-center p-2 pt-1.5 pb-0.5"
onclick={() => onRatingClick(choice, 0)}
>
<RefreshOutline class="h-5 w-5 shrink-0" />
</button>
{/if}
{#each Array(5), index (index)}
<button
class="flex cursor-pointer items-center justify-center"
Expand All @@ -59,6 +49,14 @@
/>
</button>
{/each}
{#if ratingObj.rating > 0}
<button
class="flex cursor-pointer items-center justify-center p-2 pt-1.5 pb-0.5"
onclick={() => onRatingClick(choice, 0)}
>
<RefreshOutline class="h-5 w-5 shrink-0" />
</button>
{/if}
</div>
</div>
{/if}
Expand Down
8 changes: 3 additions & 5 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,12 @@
<ThemeProvider theme={defaultTheme}>
<StorageProvider onLoad={() => (loaded = true)}>
{#if !loaded}
<div class="flex min-h-screen w-screen flex-col items-center justify-center">
<div class="m-auto min-h-screen w-screen">
<p class="text-center">Loading...</p>
</div>
{:else}
<div class="justify-top flex min-h-screen w-screen flex-col items-center">
<div class="w-fulls h-full p-4 sm:max-w-lg">
{@render children?.()}
</div>
<div class="m-auto sm:max-w-lg">
{@render children?.()}
</div>
{/if}
</StorageProvider>
Expand Down
20 changes: 11 additions & 9 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
import { Heading, P, Hr } from 'flowbite-svelte';
</script>

<Heading tag="h1" class="mb-6">Vote The Bowl</Heading>
<div class="p-4">
<Heading tag="h1" class="mb-6">Vote The Bowl</Heading>

<P>
Make your cook off voting easy! Create an event and invite others to vote. You don't need an
account just start by creating an event below and sharing the registration link or QR-code. You
can choose the voting system that best suits your needs (more on the way).
</P>
<P>
Make your cook off voting easy! Create an event and invite others to vote. You don't need an
account just start by creating an event below and sharing the registration link or QR-code. You
can choose the voting system that best suits your needs (more on the way).
</P>

<Hr />
<Hr />

<Heading tag="h2">Create an Event</Heading>
<EventForm />
<Heading tag="h2">Create an Event</Heading>
<EventForm />
</div>
Loading