diff --git a/app/src/lib/types.ts b/app/src/lib/types.ts
index 17071d1..ef52edf 100644
--- a/app/src/lib/types.ts
+++ b/app/src/lib/types.ts
@@ -42,11 +42,22 @@ export interface Mitglied {
mandatsdatum?: string;
}
+export type Rolle = 'admin' | 'trainer';
+
export interface Gruppe {
id: string;
verein_id: string;
name: string;
beschreibung?: string;
+ trainer_ids: string[];
+}
+
+export interface Einladung {
+ id: string;
+ verein_id: string;
+ rolle: Rolle;
+ token: string;
+ genutzt: boolean;
}
export interface Beitrag {
diff --git a/app/src/routes/(app)/+layout.svelte b/app/src/routes/(app)/+layout.svelte
index f18d99c..f411994 100644
--- a/app/src/routes/(app)/+layout.svelte
+++ b/app/src/routes/(app)/+layout.svelte
@@ -67,14 +67,18 @@
return Uint8Array.from([...raw].map((c) => c.charCodeAt(0)));
}
- const navItems: { href: string; label: string; icon: IconName }[] = [
- { href: '/', label: 'Übersicht', icon: 'house' },
- { href: '/mitglieder', label: 'Mitglieder', icon: 'users' },
- { href: '/termine', label: 'Termine', icon: 'calendar' },
- { href: '/beitraege', label: 'Beiträge', icon: 'currency-eur' },
- { href: '/nachrichten', label: 'Nachrichten', icon: 'envelope' },
- { href: '/einstellungen', label: 'Einstellungen', icon: 'gear' },
+ const isAdmin = () => !pb.authStore.record?.rolle || pb.authStore.record?.rolle === 'admin';
+
+ const allNavItems: { href: string; label: string; icon: IconName; adminOnly?: boolean }[] = [
+ { href: '/', label: 'Übersicht', icon: 'house' },
+ { href: '/mitglieder', label: 'Mitglieder', icon: 'users' },
+ { href: '/termine', label: 'Termine', icon: 'calendar' },
+ { href: '/beitraege', label: 'Beiträge', icon: 'currency-eur', adminOnly: true },
+ { href: '/nachrichten', label: 'Nachrichten', icon: 'envelope' },
+ { href: '/einstellungen', label: 'Einstellungen', icon: 'gear' },
];
+
+ const navItems = $derived(allNavItems.filter(i => !i.adminOnly || isAdmin()));
diff --git a/app/src/routes/(app)/beitraege/+page.svelte b/app/src/routes/(app)/beitraege/+page.svelte
index 0383cba..bc820d8 100644
--- a/app/src/routes/(app)/beitraege/+page.svelte
+++ b/app/src/routes/(app)/beitraege/+page.svelte
@@ -1,5 +1,6 @@
+
+
Einladung — vereins.haus
+
+
+
+

+ vereins.haus
+
+
+
+ {#if loading}
+
Prüfe Einladung…
+
+ {:else if fehler}
+
✕
+
Ungültige Einladung
+
{fehler}
+
+ {:else}
+
🎉
+
Du wurdest eingeladen!
+
+ Du wirst als {einladung?.rolle === 'trainer' ? 'Trainer' : 'Admin'}
+ zum Verein „{vereinName}" hinzugefügt.
+
+
+
+ {/if}
+
+
+
+
diff --git a/pocketbase/pb_migrations/1779230500_rollen.js b/pocketbase/pb_migrations/1779230500_rollen.js
new file mode 100644
index 0000000..a998ab1
--- /dev/null
+++ b/pocketbase/pb_migrations/1779230500_rollen.js
@@ -0,0 +1,92 @@
+///
+migrate((app) => {
+
+ // Users: +rolle, listRule für verein-weite Sichtbarkeit
+ {
+ const c = app.findCollectionByNameOrId("_pb_users_auth_")
+ c.fields.addAt(99, new Field({
+ "type": "select", "id": "select2001000070", "name": "rolle",
+ "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
+ "maxSelect": 1, "values": ["admin", "trainer"]
+ }))
+ c.listRule = "@request.auth.verein_id = verein_id"
+ app.save(c)
+ }
+
+ // Gruppen: +trainer_ids (welche User betreuen diese Gruppe)
+ {
+ const c = app.findCollectionByNameOrId("pbc_3099069179")
+ c.fields.addAt(99, new Field({
+ "type": "relation", "id": "relation2001000071", "name": "trainer_ids",
+ "help": "", "hidden": false, "presentable": false, "required": false, "system": false,
+ "cascadeDelete": false, "collectionId": "_pb_users_auth_",
+ "maxSelect": 99, "minSelect": 0
+ }))
+ app.save(c)
+ }
+
+ // Einladungen-Collection
+ {
+ const c = new Collection({
+ "createRule": "@request.auth.verein_id = verein_id",
+ "deleteRule": "@request.auth.verein_id = verein_id",
+ "listRule": "@request.auth.verein_id = verein_id",
+ "viewRule": "",
+ "updateRule": "@request.auth.verein_id = verein_id",
+ "fields": [
+ {
+ "autogeneratePattern": "[a-z0-9]{15}", "id": "text3208210256",
+ "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$",
+ "primaryKey": true, "required": true, "system": true, "type": "text",
+ "help": "", "hidden": false, "presentable": false
+ },
+ {
+ "type": "relation", "id": "relation2001000072", "name": "verein_id",
+ "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
+ "cascadeDelete": true, "collectionId": "pbc_3589557411", "maxSelect": 1, "minSelect": 0
+ },
+ {
+ "type": "select", "id": "select2001000073", "name": "rolle",
+ "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
+ "maxSelect": 1, "values": ["admin", "trainer"]
+ },
+ {
+ "type": "text", "id": "text2001000074", "name": "token",
+ "help": "", "hidden": false, "presentable": false, "required": true, "system": false,
+ "autogeneratePattern": "", "min": 0, "max": 0, "pattern": ""
+ },
+ {
+ "type": "bool", "id": "bool2001000075", "name": "genutzt",
+ "help": "", "hidden": false, "presentable": false, "required": false, "system": false
+ }
+ ],
+ "id": "pbc_einladungen",
+ "indexes": ["CREATE UNIQUE INDEX idx_einladungen_token ON einladungen (token)"],
+ "name": "einladungen",
+ "system": false,
+ "type": "base"
+ })
+ app.save(c)
+ }
+
+}, (app) => {
+
+ {
+ const c = app.findCollectionByNameOrId("_pb_users_auth_")
+ c.fields.removeById("select2001000070")
+ c.listRule = ""
+ app.save(c)
+ }
+
+ {
+ const c = app.findCollectionByNameOrId("pbc_3099069179")
+ c.fields.removeById("relation2001000071")
+ app.save(c)
+ }
+
+ {
+ const c = app.findCollectionByNameOrId("pbc_einladungen")
+ app.delete(c)
+ }
+
+})