fixed login and logout and started on player screen
This commit is contained in:
@@ -53,7 +53,12 @@ async function seed() {
|
|||||||
let passwordHash = await Bun.password.hash('password');
|
let passwordHash = await Bun.password.hash('password');
|
||||||
await db
|
await db
|
||||||
.insert(schema.scorers)
|
.insert(schema.scorers)
|
||||||
.values({ id: crypto.randomUUID(), username: 'admin', passwordHash: passwordHash });
|
.values({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
username: 'admin',
|
||||||
|
role: 'admin',
|
||||||
|
passwordHash: passwordHash
|
||||||
|
});
|
||||||
|
|
||||||
// Seed teams
|
// Seed teams
|
||||||
const teamsCSV = readCSV('teams.csv');
|
const teamsCSV = readCSV('teams.csv');
|
||||||
|
|||||||
@@ -127,6 +127,11 @@ export async function getAllBrackets() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPlayerInfo(playerId: number) {
|
||||||
|
const playerInfo = await db.select().from(schema.players).where(eq(schema.players.id), playerId);
|
||||||
|
return playerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getResultPreset(presetId?: number) {
|
export async function getResultPreset(presetId?: number) {
|
||||||
const resultPresets = await db
|
const resultPresets = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
15
src/routes/+layout.server.ts
Normal file
15
src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// alternative, if role isn't on locals.user
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '$lib/server/db';
|
||||||
|
import { scorers } from '$lib/server/db/schema';
|
||||||
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||||
|
if (!locals.user) return { user: null };
|
||||||
|
|
||||||
|
const [row] = await db
|
||||||
|
.select({ role: scorers.role })
|
||||||
|
.from(scorers)
|
||||||
|
.where(eq(scorers.id, locals.user.id));
|
||||||
|
return { user: { ...locals.user, role: row?.role ?? 'scorer' } };
|
||||||
|
};
|
||||||
@@ -1,22 +1,41 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import './layout.css';
|
import './layout.css';
|
||||||
import favicon from '$lib/assets/favicon.svg';
|
import favicon from '$lib/assets/favicon.svg';
|
||||||
|
import type { LayoutData } from './$types';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children, data }: { children: import('svelte').Snippet; data: LayoutData } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
||||||
|
|
||||||
<div class="header goldman flex h-15 w-full">
|
<div class="header goldman flex h-15 w-full">
|
||||||
<a
|
<a
|
||||||
class="align-text-middle justify-left mx-3 my-1 h-auto content-center rounded-sm border-2 border-solid border-red-500 px-2"
|
class="align-text-middle justify-left mx-3 my-1 h-auto content-center rounded-sm border-2
|
||||||
|
border-solid border-red-500 px-2"
|
||||||
href="/">home</a
|
href="/">home</a
|
||||||
>
|
>
|
||||||
<div class="w-full"></div>
|
<div class="w-full"></div>
|
||||||
<a
|
|
||||||
class="align-text-middle justify-right mx-3 my-1 h-auto content-center rounded-sm border-2 border-solid border-red-500 px-2"
|
{#if data.user?.role === 'admin'}
|
||||||
href="/login">login</a
|
<a
|
||||||
>
|
class="align-text-middle justify-right mx-3 my-1 h-auto content-center rounded-sm border-2
|
||||||
|
border-solid border-red-500 px-2"
|
||||||
|
href="/ledger">ledger</a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{#if data.user}
|
||||||
|
<a
|
||||||
|
class="align-text-middle justify-right mx-3 my-1 h-auto content-center rounded-sm border-2
|
||||||
|
border-solid border-red-500 px-2"
|
||||||
|
href="/login">logout</a
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
class="align-text-middle justify-right mx-3 my-1 h-auto content-center rounded-sm border-2
|
||||||
|
border-solid border-red-500 px-2"
|
||||||
|
href="/login">login</a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -47,6 +47,17 @@
|
|||||||
return n + (s[(v - 20) % 10] ?? s[v] ?? s[0]);
|
return n + (s[(v - 20) % 10] ?? s[v] ?? s[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortPlayers(items: any) {
|
||||||
|
return [...items].sort((a, b) => {
|
||||||
|
// If a is unranked and b is ranked, move a down
|
||||||
|
if (a.placement === 0 && b.placement !== 0) return 1;
|
||||||
|
// If a is ranked and b is unranked, move a up
|
||||||
|
if (a.placement !== 0 && b.placement === 0) return -1;
|
||||||
|
// If both are ranked, sort numerically ascending (1st, 2nd, 3rd...)
|
||||||
|
return a.placement - b.placement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Scroll to and highlight the currently ongoing event
|
// Scroll to and highlight the currently ongoing event
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (focusEventId == null) return;
|
if (focusEventId == null) return;
|
||||||
@@ -160,7 +171,9 @@
|
|||||||
<div
|
<div
|
||||||
class="event-card"
|
class="event-card"
|
||||||
class:ongoing-event={event.state == 1}
|
class:ongoing-event={event.state == 1}
|
||||||
|
class:completed-event={event.state == 2}
|
||||||
bind:this={eventRefs[event.id]}
|
bind:this={eventRefs[event.id]}
|
||||||
|
style={event.state == 2 ? `--event-color: ${event.winner.color}` : ''}
|
||||||
>
|
>
|
||||||
<div class="event-header">
|
<div class="event-header">
|
||||||
<a href="/event/{event.id}" class="event-name goldman">{event.name}</a>
|
<a href="/event/{event.id}" class="event-name goldman">{event.name}</a>
|
||||||
@@ -188,7 +201,7 @@
|
|||||||
<span class="brackets-name-text align-text-middle">{bracket.name}</span>
|
<span class="brackets-name-text align-text-middle">{bracket.name}</span>
|
||||||
<div class="bracket-vertical-sep"></div>
|
<div class="bracket-vertical-sep"></div>
|
||||||
</div>
|
</div>
|
||||||
{#each bracket.items as player}
|
{#each sortPlayers(bracket.items) as player}
|
||||||
<div class="player-box" style="--c:{player.teamColor}">
|
<div class="player-box" style="--c:{player.teamColor}">
|
||||||
<div class="player-ghost" aria-hidden="true">
|
<div class="player-ghost" aria-hidden="true">
|
||||||
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
|
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
|
||||||
@@ -204,10 +217,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="player-name-wrap" use:marquee>
|
<div class="player-name-wrap" use:marquee>
|
||||||
<span class="marquee-inner">
|
<a href="/stats/player/{player.id}" class="marquee-inner">
|
||||||
{player.firstName}
|
{player.firstName}
|
||||||
{player.lastName}
|
{player.lastName}
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{#if player.placement !== 0}
|
{#if player.placement !== 0}
|
||||||
<div class="player-placement goldman">{ordinal(player.placement)}</div>
|
<div class="player-placement goldman">{ordinal(player.placement)}</div>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export async function POST({ request }: any) {
|
|||||||
if (!responseBody) {
|
if (!responseBody) {
|
||||||
return new Error('nuh uh');
|
return new Error('nuh uh');
|
||||||
} else {
|
} else {
|
||||||
console.log(JSON.stringify(responseBody));
|
|
||||||
if (responseBody.eventId) {
|
if (responseBody.eventId) {
|
||||||
let eventData = await getRegisteredEvents(responseBody.eventId);
|
let eventData = await getRegisteredEvents(responseBody.eventId);
|
||||||
|
|
||||||
@@ -62,7 +61,6 @@ export async function POST({ request }: any) {
|
|||||||
.set({ placement: currentPlayerPosition })
|
.set({ placement: currentPlayerPosition })
|
||||||
.where(eq(schema.registeredPlayers.id, currentPlayer.registeredPlayerId))
|
.where(eq(schema.registeredPlayers.id, currentPlayer.registeredPlayerId))
|
||||||
.returning();
|
.returning();
|
||||||
console.log(newPlayerPlacement[0].placement, currentPlayer.firstName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +75,6 @@ export async function POST({ request }: any) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
let newScores = await db.insert(schema.scoreLedger).values(ledgerEntries).returning();
|
let newScores = await db.insert(schema.scoreLedger).values(ledgerEntries).returning();
|
||||||
console.log(newScores);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the winning team from accumulated scores
|
// Determine the winning team from accumulated scores
|
||||||
@@ -97,11 +94,11 @@ export async function POST({ request }: any) {
|
|||||||
.set({ teamWinner: winningTeamId, state: 2, timeCompleted: Date.now() })
|
.set({ teamWinner: winningTeamId, state: 2, timeCompleted: Date.now() })
|
||||||
.where(eq(schema.registeredEvents.id, responseBody.eventId))
|
.where(eq(schema.registeredEvents.id, responseBody.eventId))
|
||||||
.returning();
|
.returning();
|
||||||
console.log(teamWinnerUpdate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
globalEmitter.emit('scoreUpdate');
|
globalEmitter.emit('scoreUpdate');
|
||||||
|
globalEmitter.emit('eventUpdate');
|
||||||
return new Response('coolsies');
|
return new Response('coolsies');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/routes/api/logout/+server.ts
Normal file
12
src/routes/api/logout/+server.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import { invalidateSession, deleteSessionTokenCookie } from '$lib/server/auth';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async (event) => {
|
||||||
|
if (event.locals.session) {
|
||||||
|
await invalidateSession(event.locals.session.id);
|
||||||
|
}
|
||||||
|
deleteSessionTokenCookie(event);
|
||||||
|
|
||||||
|
throw redirect(303, '/login');
|
||||||
|
};
|
||||||
@@ -251,10 +251,13 @@
|
|||||||
<div
|
<div
|
||||||
style:background-color={event.state === 1
|
style:background-color={event.state === 1
|
||||||
? 'color-mix(in srgb, #fe640b 18%, transparent)'
|
? 'color-mix(in srgb, #fe640b 18%, transparent)'
|
||||||
: ''}
|
: event.state === 2
|
||||||
|
? 'color-mix(in srgb, #a6e3a1 18%, transparent)'
|
||||||
|
: ''}
|
||||||
class="align-text-middle my-7 h-10 w-full rounded-2xl border-2 border-solid border-ctp-surface1"
|
class="align-text-middle my-7 h-10 w-full rounded-2xl border-2 border-solid border-ctp-surface1"
|
||||||
>
|
>
|
||||||
{event.name} - {event.division} - scoring {#if event.state == 1}- ONGOING
|
{event.name} - {event.division} - scoring {#if event.state == 1}- ONGOING
|
||||||
|
{:else if event.state == 2}- FINISHED
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -210,6 +210,12 @@
|
|||||||
background-color: color-mix(in srgb, currentColor 18%, transparent);
|
background-color: color-mix(in srgb, currentColor 18%, transparent);
|
||||||
color: var(--ctp-latte-peach);
|
color: var(--ctp-latte-peach);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.completed-event {
|
||||||
|
border-color: var(--event-color);
|
||||||
|
/* If you want a subtle background tint like ongoing events usually have: */
|
||||||
|
background-color: color-mix(in srgb, var(--event-color) 10%, transparent);
|
||||||
|
}
|
||||||
/* Pulse animation for focused event card */
|
/* Pulse animation for focused event card */
|
||||||
.event-card.highlight-pulse {
|
.event-card.highlight-pulse {
|
||||||
animation: card-pulse 1.2s ease-out forwards;
|
animation: card-pulse 1.2s ease-out forwards;
|
||||||
@@ -337,9 +343,14 @@
|
|||||||
.player-name-wrap {
|
.player-name-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
text-decoration: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-name-wrap:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.marquee-inner {
|
.marquee-inner {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|||||||
23
src/routes/ledger/+page.server.ts
Normal file
23
src/routes/ledger/+page.server.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// src/routes/admin/+page.server.ts
|
||||||
|
import { error, redirect } from '@sveltejs/kit';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '$lib/server/db';
|
||||||
|
import { scorers } from '$lib/server/db/schema';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
if (!locals.user) {
|
||||||
|
throw redirect(303, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [row] = await db
|
||||||
|
.select({ role: scorers.role })
|
||||||
|
.from(scorers)
|
||||||
|
.where(eq(scorers.id, locals.user.id));
|
||||||
|
|
||||||
|
if (row?.role !== 'admin') {
|
||||||
|
throw error(403, 'Forbidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { user: locals.user };
|
||||||
|
};
|
||||||
1
src/routes/ledger/+page.svelte
Normal file
1
src/routes/ledger/+page.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<div>some</div>
|
||||||
@@ -4,7 +4,13 @@ import { eq } from 'drizzle-orm';
|
|||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { scorers } from '$lib/server/db/schema';
|
import { scorers } from '$lib/server/db/schema';
|
||||||
import { generateSessionToken, createSession, setSessionTokenCookie } from '$lib/server/auth';
|
import { generateSessionToken, createSession, setSessionTokenCookie } from '$lib/server/auth';
|
||||||
import type { Actions } from './$types';
|
import type { Actions } from '../login/$types';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from '../login/$types';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return { user: locals.user };
|
||||||
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => {
|
default: async (event) => {
|
||||||
|
|||||||
@@ -1,34 +1,46 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ActionData } from './$types';
|
import type { ActionData, PageData } from './$types';
|
||||||
|
export let data: PageData;
|
||||||
export let form: ActionData;
|
export let form: ActionData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="auth-card">
|
<div class="auth-card">
|
||||||
<h1>Log in</h1>
|
{#if data.user}
|
||||||
|
<div class="align-center flex h-full w-full flex-col text-center">
|
||||||
|
<h1>Already logged in</h1>
|
||||||
|
<p>You're signed in as <strong>{data.user.username}</strong></p>
|
||||||
|
<a href="/">Go to home</a>
|
||||||
|
<form method="POST" action="/api/logout">
|
||||||
|
<button type="submit">Log out</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<h1>Log in</h1>
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<label>
|
<label>
|
||||||
Username
|
Username
|
||||||
<input class="text-black" name="username" type="text" autocomplete="username" required />
|
<input class="text-black" name="username" type="text" autocomplete="username" required />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Password
|
Password
|
||||||
<input
|
<input
|
||||||
class="text-black"
|
class="text-black"
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{#if form?.message}
|
{#if form?.message}
|
||||||
<p class="error">{form.message}</p>
|
<p class="error">{form.message}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button type="submit">Log in</button>
|
<button type="submit">Log in</button>
|
||||||
</form>
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- <p class="switch">No account? <a href="/signup">Sign up</a></p> -->
|
<!-- <p class="switch">No account? <a href="/signup">Sign up</a></p> -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { eq } from 'drizzle-orm';
|
|||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { scorers } from '$lib/server/db/schema';
|
import { scorers } from '$lib/server/db/schema';
|
||||||
import { generateSessionToken, createSession, setSessionTokenCookie } from '$lib/server/auth';
|
import { generateSessionToken, createSession, setSessionTokenCookie } from '$lib/server/auth';
|
||||||
import type { Actions } from './$types';
|
import type { Actions } from '../signup/$types';
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => {
|
default: async (event) => {
|
||||||
@@ -9,12 +9,19 @@
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
<label>
|
<label>
|
||||||
Username
|
Username
|
||||||
<input name="username" type="text" autocomplete="username" required />
|
<input name="username" class="text-black" type="text" autocomplete="username" required />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Password
|
Password
|
||||||
<input name="password" type="password" autocomplete="new-password" minlength="8" required />
|
<input
|
||||||
|
class="text-black"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
minlength="8"
|
||||||
|
required
|
||||||
|
/>
|
||||||
<small>At least 8 characters</small>
|
<small>At least 8 characters</small>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
7
src/routes/stats/player/[playerId]/+page.server.ts
Normal file
7
src/routes/stats/player/[playerId]/+page.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { getAllInitialInfo } from '$lib/server/databaseManager';
|
||||||
|
|
||||||
|
// Provide initial data for the home page
|
||||||
|
export const load = async () => {
|
||||||
|
return await getAllInitialInfo();
|
||||||
|
};
|
||||||
|
|
||||||
20
src/routes/stats/player/[playerId]/+page.svelte
Normal file
20
src/routes/stats/player/[playerId]/+page.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import { getPlayerData } from './data.remote';
|
||||||
|
|
||||||
|
let { data, params }: PageProps = $props();
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
let some = await getPlayerData(params.playerId);
|
||||||
|
console.log(some)
|
||||||
|
|
||||||
|
let playerId = params.playerId;
|
||||||
|
console.log(parseInt(playerId));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="my-3 flex flex-col justify-center">
|
||||||
|
<div class="player-box w-full" style="--c:red">
|
||||||
|
<!-- <span>{console.log(getPlayerInfo(playerId))}</span> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
8
src/routes/stats/player/[playerId]/data.remote.ts
Normal file
8
src/routes/stats/player/[playerId]/data.remote.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { query } from '$app/server';
|
||||||
|
import { getPlayerInfo } from '$lib/server/databaseManager';
|
||||||
|
|
||||||
|
export const getPlayerData = query(async (playerId: number) => {
|
||||||
|
const playerInfo = await getPlayerInfo(playerId);
|
||||||
|
|
||||||
|
return playerInfo;
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user