Zum Hauptinhalt springen
  1. Blog/

Soccer App - Users Only Attempt

·859 Wörter·5 min
Autor
Kees Fluitman
I am a pedagog, hobbyist, allrounder and self-taught beginning software engineer. I am also a privacy advocate and enjoy privacy enhancing technologies.
Inhaltsverzeichnis
In This Post, I will attempt to dissassemble my database structure. I will remove the players collection. The users will be the only collection for users of any kind. So users can be players, manage other players, manage teams, or manage clubs. This will make complicated cases easier to handle. For instance, where a user is a trainer, parent and player himself.

Schema Changes
#

erDiagram USERS { string id PK string email string username string name boolean disabled relation club FK relation players FK "Users this user manages" date created date updated } CLUBS { string id PK string name string location relation managers FK "Users who manage this club" } TEAMS { string id PK string name string category relation trainers FK "Users who trains this team" relation players FK "Users who play in this team" relation club FK } USERS ||--o{ USERS : "manages as guardian" USERS ||--o| CLUBS : "is managed by" TEAMS ||--o| CLUBS : "belongs to" TEAMS ||--o| USERS : "trained by" TEAMS ||--o{ USERS : "has players"
erDiagram events { date date string organizer string location } teams { string category string team } players { string first_name string last_name date birthday } clubs { string name string location date founded } events ||--o{ teams : "participates" events ||--o{ players : "participates" events ||--o{ clubs : "participates"

Data retrieval
#

My Sveltekit app will be partly serverside, partly clientside.

  • Initial Data Load holds User Data with all relevant players, team and club data.
  • An event Watcher is initialized clientside only, subscribing to Changes in the pocketbase Backend.

Steps
#

What do I have to change in my app?

  1. Initial Serverside load. server.layout.ts
  2. Calendar Data loading

Spatz-2
#

Spatz-2 uses Shadcn-svelte components, zod and superforms (but not everything from superforms) and gsap for animations.

Components
#

A component like a back button is defined like this. I give it props like onclick, size, variant, extra classes and an Icon. The Icon is also a component which makes it easier to add SVG icons, without having my code become unreadable and get cluttered with SVG code.

<Button
    onclick={() => window.history.back()}
    size="sm"
    variant="outline"
    class="group/backButton backButton flex items-center gap-2">
    <Icon
        icon="mdi:arrow-left"
        class="h-5 w-5 transition-all duration-300 md:group-hover/backButton:-translate-x-1"
    />
    <span class="text-sm">back</span>
</Button>

Gsap
#

Gsap is a complex, comprehensive library to create smooth and modern looking animations. Makes everything look all the more professional, without having to understand all the ins and outs of CSS and get lost in Frontend Development.

Gsap in animations.ts file.

import { gsap } from 'gsap';

export const animateMainStagger = () => {
	gsap.from('.animate-item', {
		opacity: 0,
		y: 20,
		duration: 1,
		delay: 0.1,
		stagger: 0.1,
		ease: 'power4.out'
	});
};

the onMount executes the exported animateMainStagger function. Gsap takes over.

	onMount(() => {
		animateMainStagger();
	});

Typescript
#

As an example for my problems with Typescript, I provide an example here.

	import type {
		EventsRecord,
		UsersRecord,
		TeamsRecord,
		ClubsRecord
	} from '$lib/types/pocketbase-types';

	type ExpandedEvent = EventsRecord & {
		expand: {
			players: Array<UsersRecord>;
			teams: Array<TeamsRecord>;
			clubs: Array<ClubsRecord>;
		};
	};

	interface Props {
		data?: {
			events?: Array<ExpandedEvent>;
		};
	}

	let { data }: Props = $props();

	// Ensure events is always an array
	let events: ExpandedEvent[] = $derived(data?.events ?? []);

	function getEventsByMonth(events: Array<ExpandedEvent>): Record<string, Array<ExpandedEvent>> {
		const grouped: Record<string, Array<ExpandedEvent>> = {};
		const sortedEvents = [...events].sort(
			(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
		);

		sortedEvents.forEach((event) => {
			const date = new Date(event.date);
			const monthYear = date.toLocaleString('default', { month: 'long', year: 'numeric' });

			if (!grouped[monthYear]) {
				grouped[monthYear] = [];
			}
			grouped[monthYear].push(event);
		});

		return grouped;
	}

This Code imports the Pocketbase Types from my pocketbase-types.ts file. Basically a Schema of an Event it’s properties (aka variables). For example for Teams:

export type TeamsRecord = {
	category?: string
	created?: IsoDateString
	id: string
	players?: RecordIdString[]
	team?: string
	trainers?: RecordIdString[]
	updated?: IsoDateString
}

The same code in Javascript would be:

let { data } = $props();
let events = $derived(data.events ?? []);

function getEventsByMonth(events) {
	const grouped = {};
	const sortedEvents = [...events].sort(
		(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
	);

	sortedEvents.forEach((event) => {
		const date = new Date(event.date);
		const monthYear = date.toLocaleString('default', { month: 'long', year: 'numeric' });

		if (!grouped[monthYear]) {
			grouped[monthYear] = [];
		}
		grouped[monthYear].push(event);
	});

	return grouped;
}

So there is a clear difference. For getting more IDE support and better maintainability, we have to sacrifice concise Syntax. The code with all it’s :: <> and {} becomes quite confusing at times.

Goals
#

There is a lot to do. I want to especially focus on a deep understanding of the workings of Sveltekit, Javascript and Typescript. Especially the Types sometimes confuse me. The syntax makes the code harder to read, and my codebase is not yet that complicated, that it has really benefited me. Rather only confused me more. My general goals are:

  • Improve my Typescript understanding
  • Sveltekit understanding
  • Write more code myself instead of Claude’s LLM

My next steps are:

  • Implement a Current Player (in case of Users that have multiple Children or Trainers that should be able to change multiple players)
  • Create the filters of the Calendar
  • Signup button (is still only visually)
  • More comprehensive Pocketbase API Authorization of individual Tables (Still open to anyone)