Skip to content

Commit

Permalink
feat: add ranking feature for projects and events
Browse files Browse the repository at this point in the history
refactor: migrate some stuff to Svelte 5
  • Loading branch information
slashtechno committed Nov 2, 2024
1 parent 9a58164 commit b9ffbc3
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 15 deletions.
80 changes: 80 additions & 0 deletions pb_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,85 @@
"updateRule": "@request.auth.id = owner",
"deleteRule": null,
"options": {}
},
{
"id": "7d9wkqk0hr7agjc",
"name": "votes",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "xllnuwnv",
"name": "user",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "oxat5osp",
"name": "project",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "6t8g1s10mf2zczx",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "dtchsslg",
"name": "event",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "zqucn4409kyw2wv",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "chgljpdv",
"name": "ranking",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": true
}
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_user_project` ON `votes` (\n `user`,\n `project`\n)",
"CREATE UNIQUE INDEX `idx_user_ranking` ON `votes` (\n `user`,\n `ranking`,\n `event`\n)"
],
"listRule": "",
"viewRule": "",
"createRule": "@request.auth.id = @request.data.user && @request.data.event = @request.data.project.event",
"updateRule": "",
"deleteRule": "",
"options": {}
}
]
2 changes: 1 addition & 1 deletion src/lib/components/CreateEvent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { pb } from '$lib/pocketbase';
import { toast } from 'svelte-sonner';
import { currentUser } from '$lib/pocketbase';
let eventName = '';
let eventName = $state('');
async function createEvent() {
try {
Expand Down
92 changes: 92 additions & 0 deletions src/lib/components/RankProjects.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<script>
import { pb } from '$lib/pocketbase';
import { toast } from 'svelte-sonner';
import { currentUser } from '$lib/pocketbase';
import { onMount } from 'svelte';
let events = $state([]);
let selectedEvent = $state('');
let projects = $state([]);
let rankings = $state([]);
// Fetch events on component mount
onMount(fetchEvents);
async function fetchEvents() {
try {
events = await pb.collection('events').getFullList();
console.log($state.snapshot(events));
} catch (error) {
console.error('Error fetching events:', error);
toast.error('Failed to load events.');
}
}
async function fetchProjects() {
if (!selectedEvent) {
projects = [];
rankings = [];
return;
}
try {
projects = await pb.collection('projects').getFullList({
filter: `event = "${selectedEvent}"`
});
rankings = projects.map(() => null);
} catch (error) {
console.error('Error fetching projects:', error);
toast.error('Failed to load projects.');
}
}
async function submitVotes() {
// https://svelte.dev/docs/svelte/v5-migration-guide#Event-changes-Event-modifiers
event.preventDefault();
const assignedRanks = rankings.filter(rank => rank !== null);
const uniqueRanks = new Set(assignedRanks);
if (assignedRanks.length !== uniqueRanks.size) {
toast.error('Duplicate rankings detected.');
return;
}
if (assignedRanks.length !== projects.length) {
toast.error('Please rank all projects.');
return;
}
try {
await Promise.all(
projects.map((project, index) => {
return pb.collection('votes').create({
user: $currentUser.id,
project: project.id,
event: selectedEvent,
ranking: parseInt(rankings[index])
});
})
);
toast.success('Votes submitted successfully!');
} catch (error) {
console.error('Error submitting votes:', error);
toast.error('Failed to submit votes: ' + error.message);
}
}
</script>

<select bind:value={selectedEvent} onchange={fetchProjects}>
<option value="" disabled>Select an event</option>
{#each events as event}
<option value={event.id}>{event.name}</option>
{/each}
</select>

{#if projects.length}
<form onsubmit={submitVotes}>
{#each projects as project, index}
<div>
<span>{project.name}</span>
<!-- <input type="number" min="1" max={projects.length} bind:value={rankings[index]} /> -->
<input type="number" min="1" max={projects.length} bind:value={rankings[index]} class="w-16 p-2 border rounded-lg text-center" />
</div>
{/each}
<button type="submit">Submit Rankings</button>
</form>
{/if}
23 changes: 16 additions & 7 deletions src/lib/components/UserAuth.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<script>
import { pb } from '$lib/pocketbase';
import { toast, Toaster } from "svelte-sonner";
/** @type {{ [key: string]: any }} */
let { ...rest } = $props();
let isLoading = false;
let username = '';
let password = '';
let isLoading = $state(false);
let username = $state('');
let password = $state('');
async function login() {
isLoading = true;
Expand Down Expand Up @@ -36,10 +38,17 @@
isLoading = false;
}
}
function preventDefault(fn) {
return function (event) {
event.preventDefault();
fn.call(this, event);
};
}
</script>

<div class="grid gap-6" {...$$restProps}>
<form on:submit|preventDefault={login}>
<div class="grid gap-6" {...rest}>
<form onsubmit={preventDefault(login)}>
<div class="grid gap-2">
<div class="grid gap-1">
<label class="sr-only" for="username">Username</label>
Expand All @@ -64,7 +73,7 @@
<div class="flex justify-center">
<button
type="button"
on:click={login}
onclick={login}
disabled={isLoading}
class="mx-2"
>
Expand All @@ -75,7 +84,7 @@
</button>
<button
type="button"
on:click={signUp}
onclick={signUp}
disabled={isLoading}
class="mx-2"
>
Expand Down
1 change: 0 additions & 1 deletion src/lib/pocketbase.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import PocketBase from 'pocketbase';
import { writable } from 'svelte/store';
import { toast } from 'svelte-sonner';
import { env } from '$env/dynamic/public';

// Connect to the PocketBase server
Expand Down
30 changes: 24 additions & 6 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@
import CreateProject from '$lib/components/CreateProject.svelte';
import CreateEvent from '$lib/components/CreateEvent.svelte';
import CheckLogin from '$lib/components/CheckLogin.svelte';
import RankProjects from '$lib/components/RankProjects.svelte';
</script>

<div>
<CheckLogin />
<CreateEvent />
<!-- Add space between the components -->
<div class="my-4"></div>
<CreateProject />
<div class="space-y-8 p-4">
<section class="p-4 border rounded-lg shadow-sm">
<h2 class="text-xl font-semibold mb-4">Login</h2>
<CheckLogin />
</section>

<section class="p-4 border rounded-lg shadow-sm">
<h2 class="text-xl font-semibold mb-4">Create Event</h2>
<CreateEvent />
</section>

<section class="p-4 border rounded-lg shadow-sm">
<h2 class="text-xl font-semibold mb-4">Create Project</h2>
<CreateProject />
</section>

<section class="p-4 border rounded-lg shadow-sm">
<div class="mb-4">
<h2 class="text-xl font-semibold">Rank Projects</h2>
<p>Duplicate votes or rankings will result in an error.</p>
</div>
<RankProjects />
</section>
</div>

0 comments on commit b9ffbc3

Please sign in to comment.