From e0f2702eca22753b8429a485472256496d9bc81e Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 1 Oct 2023 10:04:04 +0200 Subject: [PATCH] Changes --- astro.config.mjs | 5 +- package.json | 17 +- src/components/admin/App.svelte | 28 ++ .../admin/components/ErrorModal.svelte | 13 + .../admin/components/FightEditPart.svelte | 103 +++++++ .../admin/components/TypeAheadSearch.svelte | 30 ++ src/components/admin/pages/Event.svelte | 47 +++ src/components/admin/pages/Generate.svelte | 42 +++ src/components/admin/pages/Home.svelte | 73 +++++ src/components/admin/pages/Login.svelte | 66 +++++ src/components/admin/pages/NotFound.svelte | 9 + src/components/admin/pages/Perms.svelte | 123 ++++++++ .../admin/pages/event/EventEdit.svelte | 140 +++++++++ .../admin/pages/event/FightCard.svelte | 96 +++++++ .../admin/pages/event/FightList.svelte | 267 ++++++++++++++++++ .../admin/pages/event/TeamList.svelte | 21 ++ .../event/modals/CreateFightModal.svelte | 87 ++++++ .../pages/event/modals/FightEditModal.svelte | 68 +++++ .../admin/pages/generate/DragAcceptor.svelte | 28 ++ .../pages/generate/GroupGenerator.svelte | 236 ++++++++++++++++ .../admin/pages/generate/TeamChip.svelte | 17 ++ .../admin/pages/home/CreateEventModal.svelte | 71 +++++ .../admin/pages/home/EventCard.svelte | 22 ++ src/components/admin/repo/event.ts | 96 +++++++ src/components/admin/repo/fight.ts | 92 ++++++ src/components/admin/repo/perms.ts | 57 ++++ src/components/admin/repo/repo.ts | 15 + src/components/admin/stores/cached.ts | 63 +++++ src/components/admin/stores/stores.ts | 47 +++ src/components/admin/types/data.ts | 16 ++ src/components/admin/types/event.ts | 46 +++ src/components/admin/types/perms.ts | 23 ++ src/components/admin/types/team.ts | 12 + src/components/admin/util.ts | 49 ++++ src/content/config.ts | 14 + src/content/pages/en/faq.md | 144 ++++++++++ src/content/pages/en/join.md | 5 + src/env.d.ts | 1 + src/layouts/Basic.astro | 5 +- src/layouts/NavbarLayout.astro | 109 +++++-- src/locales/en.json | 55 +++- src/pages/[...slug].astro | 54 ++++ src/pages/about.astro | 8 + src/pages/admin/index.astro | 8 + src/pages/index.astro | 82 ++++-- src/pages/join.astro | 7 - src/styles/button.css | 26 +- tailwind.config.cjs | 13 +- testfile.txt | 1 - 49 files changed, 2589 insertions(+), 68 deletions(-) create mode 100644 src/components/admin/App.svelte create mode 100644 src/components/admin/components/ErrorModal.svelte create mode 100644 src/components/admin/components/FightEditPart.svelte create mode 100644 src/components/admin/components/TypeAheadSearch.svelte create mode 100644 src/components/admin/pages/Event.svelte create mode 100644 src/components/admin/pages/Generate.svelte create mode 100644 src/components/admin/pages/Home.svelte create mode 100644 src/components/admin/pages/Login.svelte create mode 100644 src/components/admin/pages/NotFound.svelte create mode 100644 src/components/admin/pages/Perms.svelte create mode 100644 src/components/admin/pages/event/EventEdit.svelte create mode 100644 src/components/admin/pages/event/FightCard.svelte create mode 100644 src/components/admin/pages/event/FightList.svelte create mode 100644 src/components/admin/pages/event/TeamList.svelte create mode 100644 src/components/admin/pages/event/modals/CreateFightModal.svelte create mode 100644 src/components/admin/pages/event/modals/FightEditModal.svelte create mode 100644 src/components/admin/pages/generate/DragAcceptor.svelte create mode 100644 src/components/admin/pages/generate/GroupGenerator.svelte create mode 100644 src/components/admin/pages/generate/TeamChip.svelte create mode 100644 src/components/admin/pages/home/CreateEventModal.svelte create mode 100644 src/components/admin/pages/home/EventCard.svelte create mode 100644 src/components/admin/repo/event.ts create mode 100644 src/components/admin/repo/fight.ts create mode 100644 src/components/admin/repo/perms.ts create mode 100644 src/components/admin/repo/repo.ts create mode 100644 src/components/admin/stores/cached.ts create mode 100644 src/components/admin/stores/stores.ts create mode 100644 src/components/admin/types/data.ts create mode 100644 src/components/admin/types/event.ts create mode 100644 src/components/admin/types/perms.ts create mode 100644 src/components/admin/types/team.ts create mode 100644 src/components/admin/util.ts create mode 100644 src/content/config.ts create mode 100644 src/content/pages/en/faq.md create mode 100644 src/content/pages/en/join.md create mode 100644 src/pages/[...slug].astro create mode 100644 src/pages/about.astro create mode 100644 src/pages/admin/index.astro delete mode 100644 src/pages/join.astro delete mode 100644 testfile.txt diff --git a/astro.config.mjs b/astro.config.mjs index 9f587d9..c0db775 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -13,5 +13,8 @@ export default defineConfig({ }, integrations: [i18n(), svelte(), tailwind(), prefetch({ selector: "a" - })] + })], + vite: { + + } }); diff --git a/package.json b/package.json index 2025eb9..18c8d45 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,28 @@ "@astrojs/svelte": "^4.0.2", "@astrojs/tailwind": "^5.0.0", "@astropub/icons": "^0.2.0", + "@types/crypto-js": "^4.1.2", "@types/node": "^20.6.3", "astro-i18n": "^1.8.1", "cssnano": "^6.0.1", "postcss-nesting": "^12.0.1", + "sass": "^1.68.0", "svelte": "^4.0.0", - "tailwindcss": "^3.0.24" + "tailwind-merge": "^1.13.2", + "tailwindcss": "^3.3.2" }, "dependencies": { "astro": "^3.1.1", - "sharp": "^0.32.6" + "color": "^4.2.3", + "crypto-js": "^4.1.1", + "flowbite": "^1.7.0", + "flowbite-svelte": "^0.39.2", + "flowbite-svelte-icons": "^0.2.5", + "gitea-js": "^1.20.1", + "moment": "^2.29.4", + "sharp": "^0.32.6", + "svelte-awesome": "^3.2.0", + "svelte-spa-router": "^3.3.0", + "zod": "^3.21.4" } } diff --git a/src/components/admin/App.svelte b/src/components/admin/App.svelte new file mode 100644 index 0000000..e54ea7d --- /dev/null +++ b/src/components/admin/App.svelte @@ -0,0 +1,28 @@ + + +
+ +
diff --git a/src/components/admin/components/ErrorModal.svelte b/src/components/admin/components/ErrorModal.svelte new file mode 100644 index 0000000..ecd9f1a --- /dev/null +++ b/src/components/admin/components/ErrorModal.svelte @@ -0,0 +1,13 @@ + + +{#if (error instanceof Error)} + +

{error.stack}

+ +
+{/if} diff --git a/src/components/admin/components/FightEditPart.svelte b/src/components/admin/components/FightEditPart.svelte new file mode 100644 index 0000000..85ae704 --- /dev/null +++ b/src/components/admin/components/FightEditPart.svelte @@ -0,0 +1,103 @@ + + +
+ + +
+
+ + +
+
+ + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
diff --git a/src/components/admin/components/TypeAheadSearch.svelte b/src/components/admin/components/TypeAheadSearch.svelte new file mode 100644 index 0000000..c08a615 --- /dev/null +++ b/src/components/admin/components/TypeAheadSearch.svelte @@ -0,0 +1,30 @@ + + + + +
+ open = true} on:keydown={() => open = true}/> +
+ {#each filteredItems as item} + + {/each} +
diff --git a/src/components/admin/pages/Event.svelte b/src/components/admin/pages/Event.svelte new file mode 100644 index 0000000..c644963 --- /dev/null +++ b/src/components/admin/pages/Event.svelte @@ -0,0 +1,47 @@ + + +{#await event} +
+ +
+{:then data} + + + + + {data.event.name} + + + + + + + Event + + + + Teams + + + + Fights + + + +{:catch error} +

+ {error.message} +

+{/await} diff --git a/src/components/admin/pages/Generate.svelte b/src/components/admin/pages/Generate.svelte new file mode 100644 index 0000000..85bd28e --- /dev/null +++ b/src/components/admin/pages/Generate.svelte @@ -0,0 +1,42 @@ + + +{#await event} +
+ +
+{:then data} + + + + + {data.event.name} - Generate + + + + + + + + + +

WIP

+
+
+{:catch error} +

+ {error.message} +

+{/await} diff --git a/src/components/admin/pages/Home.svelte b/src/components/admin/pages/Home.svelte new file mode 100644 index 0000000..fbe8ab8 --- /dev/null +++ b/src/components/admin/pages/Home.svelte @@ -0,0 +1,73 @@ + + + + + + Eventplanner + + + + + Permissions + showLogoutModal = true} class="cursor-pointer select-none">Logout + + + + events = $eventRepo.listEvents()}/> + + +

Do you really want to logout?

+ + + + +
+ +{#await events} +
+ +
+{:then data} + + +

Upcoming

+
+ {#each data.filter((e) => e.start > millis) as event} + + {/each} +
+

Past

+
+ {#each data.filter((e) => e.start < millis).reverse() as event} + + {/each} +
+{:catch error} +

+ {error.message} +

+{/await} + + + SteamWar.de Multitool - Home + diff --git a/src/components/admin/pages/Login.svelte b/src/components/admin/pages/Login.svelte new file mode 100644 index 0000000..34db74b --- /dev/null +++ b/src/components/admin/pages/Login.svelte @@ -0,0 +1,66 @@ + + +
+
+
+
+ + + + +
+
+ +
+
+ + + + + Error icon + + Invalid Token. + + + + SteamWar.de Multitool - Login + diff --git a/src/components/admin/pages/NotFound.svelte b/src/components/admin/pages/NotFound.svelte new file mode 100644 index 0000000..7822d79 --- /dev/null +++ b/src/components/admin/pages/NotFound.svelte @@ -0,0 +1,9 @@ + + diff --git a/src/components/admin/pages/Perms.svelte b/src/components/admin/pages/Perms.svelte new file mode 100644 index 0000000..a7c0306 --- /dev/null +++ b/src/components/admin/pages/Perms.svelte @@ -0,0 +1,123 @@ + + +
+ + + + + Permissions + + + + +
+
+ +
+ + +
+ {#if filteredPlayers.length < 100} +
    + {#each filteredPlayers as player} +
  • selectedPlayer = player.id}> + {player.name} +
  • + {/each} +
+ {/if} +
+ + {#if selectedPlayer} +

{player.name}

+ {#await permsFuture} + + {:then perms} + {#await playerPerms} + + {:then player} +

Prefix

+ {#each Object.entries(perms.prefixes) as [key, prefix]} + {capitalize(prefix.name.substring(7).toLowerCase())} + {/each} +

Permissions

+ {#each perms.perms as perm} + {capitalize(perm.toLowerCase())} + {/each} +
+ +
+ {:catch error} +

{error.toString()}

+ {/await} + {:catch error} +

{error.toString()}

+ {/await} + {/if} +
+
+
+
diff --git a/src/components/admin/pages/event/EventEdit.svelte b/src/components/admin/pages/event/EventEdit.svelte new file mode 100644 index 0000000..2891fa4 --- /dev/null +++ b/src/components/admin/pages/event/EventEdit.svelte @@ -0,0 +1,140 @@ + + + + {event.name} - Edit + + +
+
+ + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + +
+
+ + + + +
+

{deltaTime.asMilliseconds() < 0 ? '' : '+'}{("0" + deltaTime.hours()).slice(-2)}:{("0" + deltaTime.minutes()).slice(-2)}

+ + + + + + + + createOpen = true}> + + + + + + diff --git a/src/components/admin/pages/event/TeamList.svelte b/src/components/admin/pages/event/TeamList.svelte new file mode 100644 index 0000000..8625ab0 --- /dev/null +++ b/src/components/admin/pages/event/TeamList.svelte @@ -0,0 +1,21 @@ + +
+ {#each data.teams as team} +
+ {team.kuerzel} +
+

{team.name}

+

Fights: {data.fights.filter(value => value.blueTeam.id === team.id || value.redTeam.id === team.id).length}

+
+
+ {/each} +
+ + + {data.event.name} - Teams + diff --git a/src/components/admin/pages/event/modals/CreateFightModal.svelte b/src/components/admin/pages/event/modals/CreateFightModal.svelte new file mode 100644 index 0000000..15e81a5 --- /dev/null +++ b/src/components/admin/pages/event/modals/CreateFightModal.svelte @@ -0,0 +1,87 @@ + + + +
+ +
+ + + + +
+ + errorOpen = false}/> diff --git a/src/components/admin/pages/event/modals/FightEditModal.svelte b/src/components/admin/pages/event/modals/FightEditModal.svelte new file mode 100644 index 0000000..c94c55c --- /dev/null +++ b/src/components/admin/pages/event/modals/FightEditModal.svelte @@ -0,0 +1,68 @@ + + + +
+ +
+
+ + +
+
+ + errorOpen = false}/> diff --git a/src/components/admin/pages/generate/DragAcceptor.svelte b/src/components/admin/pages/generate/DragAcceptor.svelte new file mode 100644 index 0000000..64e6988 --- /dev/null +++ b/src/components/admin/pages/generate/DragAcceptor.svelte @@ -0,0 +1,28 @@ + + +
dragover = false}> + +
+ + diff --git a/src/components/admin/pages/generate/GroupGenerator.svelte b/src/components/admin/pages/generate/GroupGenerator.svelte new file mode 100644 index 0000000..2004736 --- /dev/null +++ b/src/components/admin/pages/generate/GroupGenerator.svelte @@ -0,0 +1,236 @@ + + +
+
resetDragOver = false} on:drop={dropReset} role="group"> + {#each teamsNotInGroup as team} + teamDragStart(ev, team)}/> + {/each} +
+ +
+ +
+
+ +
+ {#each groups as group, i} + dropGroup(ev, i)}> +

Group {i + 1} ({group.length})

+ {#each group as teamId} + teamDragStart(ev, teams.get(teamId))}/> + {/each} +
+ {/each} + +

Create Group

+
+
+ +
+ + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ {#each groupsFights as fightsGroup, i} +
+

Group: {i + 1}

+ {#each fightsGroup as fightsRound, j} +
+

Round: {j + 1}

+ {#each fightsRound as fightTeams, k} +
+ {startMoment.clone().add(roundTime * j, "minutes").add(startDelay * (k + (i * fightsRound.length)), "seconds").format("DD.MM.yyyy HH:mm:ss")} + {teams.get(fightTeams[0]).name} vs. {teams.get(fightTeams[1]).name} +
+ {/each} +
+ {/each} +
+ {/each} +
+ + + + + + + + + + + + + + + + diff --git a/src/components/admin/pages/generate/TeamChip.svelte b/src/components/admin/pages/generate/TeamChip.svelte new file mode 100644 index 0000000..9b5d04e --- /dev/null +++ b/src/components/admin/pages/generate/TeamChip.svelte @@ -0,0 +1,17 @@ + + +
hover = true} + on:mouseleave={() => hover = false}> + {team.name} +
+ diff --git a/src/components/admin/pages/home/CreateEventModal.svelte b/src/components/admin/pages/home/CreateEventModal.svelte new file mode 100644 index 0000000..2c36417 --- /dev/null +++ b/src/components/admin/pages/home/CreateEventModal.svelte @@ -0,0 +1,71 @@ + + + +
+
+ + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+ + diff --git a/src/components/admin/pages/home/EventCard.svelte b/src/components/admin/pages/home/EventCard.svelte new file mode 100644 index 0000000..d9713e4 --- /dev/null +++ b/src/components/admin/pages/home/EventCard.svelte @@ -0,0 +1,22 @@ + + + + +
{event.name}
+ {#if !sameDate} +

Startet: {new Intl.DateTimeFormat().format(event.start)}

+

Endet: {new Intl.DateTimeFormat().format(event.end)}

+ {:else} +

Am: {new Intl.DateTimeFormat().format(event.start)}

+

 

+ {/if} +
+
diff --git a/src/components/admin/repo/event.ts b/src/components/admin/repo/event.ts new file mode 100644 index 0000000..e14b6b1 --- /dev/null +++ b/src/components/admin/repo/event.ts @@ -0,0 +1,96 @@ +import type {ExtendedEvent, ShortEvent, SWEvent} from "../types/event.js"; +import {fetchWithToken} from "./repo.js"; +import type {Moment} from "moment"; +import {ExtendedEventSchema, ShortEventSchema, SWEventSchema} from "../types/event.js"; +import {z} from "zod"; + +export interface CreateEvent { + name: string + start: Moment + end: Moment +} + +export interface UpdateEvent { + name: string + start: Moment + end: Moment + deadline: Moment + maxTeamMembers: number + schemType: string | null + publicSchemsOnly: boolean + spectateSystem: boolean +} + +export class EventRepo { + constructor(private token: string) {} + + public async listEvents(): Promise { + const res = await fetchWithToken(this.token, "/events"); + + if (res.ok) { + return z.array(ShortEventSchema).parse(await res.json()); + } else { + throw new Error("Could not fetch events: " + res.statusText); + } + } + + public async getEvent(id: string): Promise { + const res = await fetchWithToken(this.token, `/events/${id}`); + + if (res.ok) { + return ExtendedEventSchema.parse(await res.json()); + } else { + throw new Error("Could not fetch event: " + res.statusText); + } + } + + public async createEvent(event: CreateEvent): Promise { + const res = await fetchWithToken(this.token, "/events", { + method: "POST", + body: JSON.stringify({ + name: event.name, + start: +event.start, + end: +event.end + }), + }); + + if (res.ok) { + return SWEventSchema.parse(await res.json()); + } else { + throw new Error("Could not create event: " + res.statusText); + } + } + + public async updateEvent(id: string, event: UpdateEvent): Promise { + const res = await fetchWithToken(this.token, `/events/${id}`, { + method: "PUT", + body: JSON.stringify({ + name: event.name, + start: +event.start, + end: +event.end, + deadline: +event.deadline, + maxTeamMembers: event.maxTeamMembers, + schemType: event.schemType, + publicSchemsOnly: event.publicSchemsOnly, + spectateSystem: event.spectateSystem + }), + headers: { + "Content-Type": "application/json" + } + }); + + if (res.ok) { + return SWEventSchema.parse(await res.json()); + } else { + throw new Error("Could not update event: " + res.statusText); + } + } + + public async deleteEvent(id: string): Promise { + const res = await fetchWithToken(this.token, `/events/${id}`, { + method: "DELETE" + }); + + return res.ok; + } +} diff --git a/src/components/admin/repo/fight.ts b/src/components/admin/repo/fight.ts new file mode 100644 index 0000000..1a6cc5e --- /dev/null +++ b/src/components/admin/repo/fight.ts @@ -0,0 +1,92 @@ +import type {EventFight} from "../types/event.js"; +import {fetchWithToken} from "./repo.js"; +import type {Moment} from "moment"; +import {z} from "zod"; +import {EventFightSchema} from "../types/event.js"; + +export interface CreateFight { + spielmodus: string + map: string + blueTeam: number + redTeam: number + start: Moment + kampfleiter: number | null + group: string | null +} + +export interface UpdateFight { + spielmodus: string | null + map: string | null + blueTeam: number | null + redTeam: number | null + start: Moment | null + kampfleiter: number | null + group: string | null +} + +export class FightRepo { + constructor(private token: string) {} + + public async listFights(eventId: number): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/fights`); + + if (res.ok) { + return z.array(EventFightSchema).parse(await res.json()); + } else { + throw new Error("Could not fetch fights: " + res.statusText); + } + } + + public async createFight(eventId: number, fight: CreateFight): Promise { + let res = await fetchWithToken(this.token, `/fights`, { + method: "POST", + body: JSON.stringify({ + event: eventId, + spielmodus: fight.spielmodus, + map: fight.map, + blueTeam: fight.blueTeam, + redTeam: fight.redTeam, + start: +fight.start, + kampfleiter: fight.kampfleiter, + group: fight.group + }) + }) + + if (res.ok) { + return EventFightSchema.parse(await res.json()); + } else { + throw new Error("Could not create fight: " + res.statusText); + } + } + + public async updateFight(fightId: number, fight: UpdateFight): Promise { + let res = await fetchWithToken(this.token, `/fights/${fightId}`, { + method: "PUT", + body: JSON.stringify({ + spielmodus: fight.spielmodus, + map: fight.map, + blueTeam: fight.blueTeam, + redTeam: fight.redTeam, + start: fight.start?.valueOf(), + kampfleiter: fight.kampfleiter, + group: fight.group + }) + }) + + if (res.ok) { + return EventFightSchema.parse(await res.json()); + } else { + throw new Error("Could not update fight: " + res.statusText); + } + } + + public async deleteFight(fightId: number): Promise { + let res = await fetchWithToken(this.token, `/fights/${fightId}`, { + method: "DELETE" + }) + + if (!res.ok) { + throw new Error("Could not delete fight: " + res.statusText); + } + } +} diff --git a/src/components/admin/repo/perms.ts b/src/components/admin/repo/perms.ts new file mode 100644 index 0000000..0f354c9 --- /dev/null +++ b/src/components/admin/repo/perms.ts @@ -0,0 +1,57 @@ +import type {Perms, UserPerms} from "../types/perms.js"; +import {fetchWithToken} from "./repo.js"; +import {PermsSchema, UserPermsSchema} from "../types/perms.js"; + +export class PermsRepo { + constructor(private token: string) {} + + public async listPerms(): Promise { + const res = await fetchWithToken(this.token, "/perms"); + + if (res.ok) { + return PermsSchema.parse(await res.json()); + } else { + throw new Error("Could not fetch perms: " + res.statusText); + } + } + + public async getPerms(userId: number): Promise { + const res = await fetchWithToken(this.token, `/perms/user/${userId}`); + + if (res.ok) { + return UserPermsSchema.parse(await res.json()); + } else { + throw new Error("Could not fetch perms: " + res.statusText); + } + } + + public async setPrefix(userId: number, prefix: string): Promise { + const res = await fetchWithToken(this.token, `/perms/user/${userId}/prefix/${prefix}`, { + method: "PUT", + }); + + if (!res.ok) { + throw new Error("Could not set prefix: " + res.statusText); + } + } + + public async addPerm(userId: number, perm: string): Promise { + const res = await fetchWithToken(this.token, `/perms/user/${userId}/${perm}`, { + method: "PUT", + }); + + if (!res.ok) { + throw new Error("Could not add perm: " + res.statusText); + } + } + + public async removePerm(userId: number, perm: string): Promise { + const res = await fetchWithToken(this.token, `/perms/user/${userId}/${perm}`, { + method: "DELETE", + }); + + if (!res.ok) { + throw new Error("Could not remove perm: " + res.statusText); + } + } +} diff --git a/src/components/admin/repo/repo.ts b/src/components/admin/repo/repo.ts new file mode 100644 index 0000000..2dbf045 --- /dev/null +++ b/src/components/admin/repo/repo.ts @@ -0,0 +1,15 @@ +import {derived, writable} from "svelte/store"; +import {EventRepo} from "./event.js"; +import {FightRepo} from "./fight.js"; +import {PermsRepo} from "./perms.js"; + +export { EventRepo } from "./event.js" + +export const fetchWithToken = (token: string, url: string, params: RequestInit = {}) => fetch(`https://steamwar.de/eventplanner-api${url}`, {...params, headers: {"X-SW-Auth": token, "Content-Type": "application/json", ...params.headers}}); + +export const tokenStore = writable(localStorage.getItem("sw-api-token") ?? "") +tokenStore.subscribe((value) => localStorage.setItem("sw-api-token", value)) + +export const eventRepo = derived(tokenStore, ($token) => new EventRepo($token)) +export const fightRepo = derived(tokenStore, ($token) => new FightRepo($token)) +export const permsRepo = derived(tokenStore, ($token) => new PermsRepo($token)) diff --git a/src/components/admin/stores/cached.ts b/src/components/admin/stores/cached.ts new file mode 100644 index 0000000..ba88b26 --- /dev/null +++ b/src/components/admin/stores/cached.ts @@ -0,0 +1,63 @@ +import {readonly, writable} from "svelte/store"; + +import type {Readable, Subscriber, Unsubscriber} from "svelte/store"; + +export interface Cached extends Readable{ + reload: () => void; +} + +export function cached(normal: T, init: () => Promise): Cached { + const store = writable(normal); + let first = true; + + const reload = () => { + init().then(data => { + store.set(data); + }); + } + + return { + ...readonly(store), + subscribe: (run: Subscriber, invalidate?: (value?: T) => void): Unsubscriber => { + if(first) { + first = false; + reload(); + } + return store.subscribe(run, invalidate); + }, + reload + }; +} + +export function cachedFamily(normal: K, init: (T) => Promise): (T) => Cached { + const stores: Map> = new Map(); + return (arg: T) => { + if(stores.has(arg)) { + return stores.get(arg); + } else { + const store = writable(normal); + let first = true; + + const reload = () => { + init(arg).then(data => { + store.set(data); + }); + } + + const cachedStore = { + ...readonly(store), + subscribe: (run: Subscriber, invalidate?: (value?: K) => void): Unsubscriber => { + if(first) { + first = false; + reload(); + } + return store.subscribe(run, invalidate); + }, + reload + } as Cached; + + stores.set(arg, cachedStore); + return cachedStore; + } + } +} diff --git a/src/components/admin/stores/stores.ts b/src/components/admin/stores/stores.ts new file mode 100644 index 0000000..100a837 --- /dev/null +++ b/src/components/admin/stores/stores.ts @@ -0,0 +1,47 @@ +import type {Player, SchematicType} from "../types/data.js"; +import {PlayerSchema} from "../types/data.js"; +import {cached, cachedFamily} from "./cached.js"; +import type {Team} from "../types/team.js"; +import {TeamSchema} from "../types/team.js"; +import {get, writable} from "svelte/store"; +import {permsRepo, tokenStore} from "../repo/repo.js"; +import {z} from "zod"; + +export const schemTypes = cached([], () => { + return fetch("https://steamwar.de/eventplanner-api/data/schematicTypes", {headers: {"X-SW-Auth": get(tokenStore)}}) + .then(res => res.json()) +}) + +export const players = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/data/users", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(PlayerSchema).parse(await res.json()); +}) + +export const gamemodes = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/data/gamemodes", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(z.string()).parse(await res.json()); +}) + +export const maps = cachedFamily([], async (gamemode) => { + if (get(gamemodes).every(value => value !== gamemode)) return []; + + const res = await fetch(`https://steamwar.de/eventplanner-api/data/gamemodes/${gamemode}/maps`, {headers: {"X-SW-Auth": get(tokenStore)}}); + if (!res.ok) { + return []; + } else { + return res.json(); + } +}) + +export const groups = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/data/groups", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(z.string()).parse(await res.json()); +}) + +export const teams = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/team", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(TeamSchema).parse(await res.json()); +}) + +export const isWide = writable(window.innerWidth >= 640); +window.addEventListener("resize", () => isWide.set(window.innerWidth >= 640)); diff --git a/src/components/admin/types/data.ts b/src/components/admin/types/data.ts new file mode 100644 index 0000000..a14757a --- /dev/null +++ b/src/components/admin/types/data.ts @@ -0,0 +1,16 @@ +import {z} from "zod"; + +export const SchematicTypeSchema = z.object({ + name: z.string(), + db: z.string(), +}) + +export type SchematicType = z.infer; + +export const PlayerSchema = z.object({ + id: z.number(), + name: z.string(), + uuid: z.string(), +}) + +export type Player = z.infer; diff --git a/src/components/admin/types/event.ts b/src/components/admin/types/event.ts new file mode 100644 index 0000000..523ce98 --- /dev/null +++ b/src/components/admin/types/event.ts @@ -0,0 +1,46 @@ +import type {Team} from "./team.js"; +import type {Player} from "./data.js"; +import {z} from "zod"; +import {TeamSchema} from "./team.js"; +import {PlayerSchema} from "./data.js"; + +export const ShortEventSchema = z.object({ + id: z.number(), + name: z.string(), + start: z.number(), + end: z.number(), +}) + +export type ShortEvent = z.infer; + +export const SWEventSchema = ShortEventSchema.extend({ + deadline: z.number(), + maxTeamMembers: z.number(), + schemType: z.string().nullable(), + publicSchemsOnly: z.boolean(), + spectateSystem: z.boolean(), +}) + +export type SWEvent = z.infer; + +export const EventFightSchema = z.object({ + id: z.number(), + spielmodus: z.string(), + map: z.string(), + blueTeam: TeamSchema, + redTeam: TeamSchema, + kampfleiter: PlayerSchema.nullable(), + start: z.number(), + ergebnis: z.number(), + group: z.string().nullable(), +}) + +export type EventFight = z.infer; + +export const ExtendedEventSchema = z.object({ + event: SWEventSchema, + teams: z.array(TeamSchema), + fights: z.array(EventFightSchema), +}) + +export type ExtendedEvent = z.infer; diff --git a/src/components/admin/types/perms.ts b/src/components/admin/types/perms.ts new file mode 100644 index 0000000..6f88335 --- /dev/null +++ b/src/components/admin/types/perms.ts @@ -0,0 +1,23 @@ +import {z} from "zod"; + +export const PrefixSchema = z.object({ + name: z.string().startsWith("PREFIX_"), + colorCode: z.string().length(2).startsWith("§"), + chatPrefix: z.string() +}) + +export type Prefix = z.infer; + +export const PermsSchema = z.object({ + perms: z.array(z.string()), + prefixes: z.record(PrefixSchema), +}) + +export type Perms = z.infer; + +export const UserPermsSchema = z.object({ + prefix: PrefixSchema, + perms: z.array(z.string()), +}) + +export type UserPerms = z.infer; diff --git a/src/components/admin/types/team.ts b/src/components/admin/types/team.ts new file mode 100644 index 0000000..ee2b4ab --- /dev/null +++ b/src/components/admin/types/team.ts @@ -0,0 +1,12 @@ +import {z} from "zod"; + +export const TeamSchema = z.object({ + id: z.number(), + name: z.string(), + kuerzel: z.string().min(1).max(4), + color: z.string().max(1), +}) + +export type Team = z.infer; + + diff --git a/src/components/admin/util.ts b/src/components/admin/util.ts new file mode 100644 index 0000000..d5cfc1e --- /dev/null +++ b/src/components/admin/util.ts @@ -0,0 +1,49 @@ +import Color from "color"; +import type {Team} from "./types/team.js"; + +export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); + +export function colorFromTeam(team: Team): string { + switch (team.color) { + case "1": + return "#0000AA"; + case "2": + return "#00AA00"; + case "3": + return "#00AAAA"; + case "4": + return "#AA0000"; + case "5": + return "#AA00AA"; + case "6": + return "#FFAA00"; + case "7": + return "#AAAAAA"; + case "8": + return "#555555"; + case "9": + return "#5555FF"; + case "a": + return "#55FF55"; + case "b": + return "#55FFFF"; + case "c": + return "#FF5555"; + case "d": + return "#FF55FF"; + case "e": + return "#FFFF55"; + case "f": + return "#FFFFFF"; + default: + return "#000000"; + } +} + +export function lighten(color: string) { + return brightness(color) ? Color(color).lighten(0.2).hex() : Color(color).darken(0.2).hex() +} + +export function brightness(color: string) { + return Color(color).isLight() +} diff --git a/src/content/config.ts b/src/content/config.ts new file mode 100644 index 0000000..fb699a5 --- /dev/null +++ b/src/content/config.ts @@ -0,0 +1,14 @@ +// @ts-ignore +import { defineCollection, z } from 'astro:content'; + +export const pages = defineCollection({ + type: "content", + schema: z.object({ + title: z.string().min(1).max(80), + description: z.string().min(1).max(120), + }) +}) + +export const collections = { + 'pages': pages +} diff --git a/src/content/pages/en/faq.md b/src/content/pages/en/faq.md new file mode 100644 index 0000000..d8b79e8 --- /dev/null +++ b/src/content/pages/en/faq.md @@ -0,0 +1,144 @@ +--- +title: FAQ +description: Frequently asked questions +slug: faq +--- + +# Server/ IP-Adresse + +#### Was ist die IP? Wie kann ich auf den Server joinen? +Steamwar.de + +#### Mit welcher Version kann ich auf den Server joinen? +SteamWar läuft auf der 1.19.4, du kannst aber mit den Versionen 1.12.x -1.19.4 joinen. + +#### Kann ich auch mit der Bedrock-Version joinen? +Ja, über die IP: steamwar.de + +# Allgemeines + +#### Wo finde ich die Verhaltensrichtlinien/ Serverregeln +Über diese Website unter „Startseite“ oder indem du hier drauf klickst + +# Minigames/ Fights + +#### Wie kann ich Kämpfe starten? +Mit „/fight“ kannst du Kämpfe mit dem aktuellen WarGear Regelwerk und der WG Submodi starten. Du kannst aber auch die Lobby-Portale nutzen, indem du entweder durch die großen Portale am Spawn gehst, oder im Teleporter auf „Arenen“ drückst, und dann dort über die Portale eine Runde startest. + +#### Wie kann ich Kämpfe mit alten Regelwerken starten? +Mit „/historic“ kannst du alle historischen Spielmodi spielen, bis zur Version 1.7. + +#### Wie kann ich gegen einen bestimmten Spieler kämpfen? +Mit „/challenge“ kannst du den gewählten Spieler herausfordern. So können auch z. B. Private MissileWars Runden gestartet werden, in der jeder Spieler eingeladen werden muss, um teilnehmen zu können. Der Challenge-Befehl funktioniert nicht mit historischen Spielmodi. + +#### Was sind rotierende Modi? +Für manche Modi gibt es nicht genug Nachfrage, um eine dauerhafte Aufnahme dieser in die regulär spielbaren Modi zu rechtfertigen. Diese Modi kann man daher alle paar Monate spielen und werden allgemein als rotierende Modi bezeichnet. Es steht immer ein rotierender Modus zur Verfügung, und dieser wechselt ungefähr jeden Monat. Zu den rotierenden Modi gehören z. B. MicroWarGear und MegaWarGear. + +#### Wie kann ich anderen Kämpfen beitreten? +Entweder klickst du auf die entsprechende Nachricht im Chat, trittst einer Arena mit „/arena [arena]“ bei, oder folgst einem Spieler mit „/join [spieler]“ + +#### Nach welchen Regeln muss ich meine Kampfmaschinen bauen? +Die Regelwerke findest du hier auf der Website unter dem Menüpunkt „Spielmodi“ + +# Bauserver + +#### Was ist ein Bauserver? +Ein Bauserver ist ein privater Server, auf welchem Redstoneschaltungen, Kanonen und Schematics für unsere Spielmodi im Kreativmodus mit einer großen Liste an Hilfsmitteln gebaut werden können. + +#### Wie kann ich meinen Bauserver starten? +Entweder mit „/bau“, „/b“ oder „/build“. Solltest du auf einen Bau einer anderen Spielversion wollen, benutze „/b [version]“. + +#### Wie lasse ich andere auf meinen Bau? +Entweder über „/b addmember [spieler]“ oder klicke auf die Nachricht im Chat, wenn sie versuchen deinem Bau per command beizutreten. Du wirst sie erkennen, wenn sie erscheint. + +#### Wie trete ich anderen Baus bei? +Entweder über „/b tp [spieler]“ oder „/join [spieler]“, wenn du weißt, dass er sich auf seinem Bau befindet. Solltest du auf den Bau einer anderen Spielversion wollen, gebe „/b tp [spieler] [version]“ ein. + +#### Wie gebe ich anderen Spielern Rechte? +Für Worldrechte (Regionsverwaltung) „/b toggleworld [spieler]“ + +Für Worldedit „/b togglewe [spieler]“ + +#### Wie kann ich die verschiedenen Funktionen meines Baus nutzen? +Am Anfang ist es am einfachsten, das Bau-GUI zu nutzen. Das geht entweder über „/gui“ oder indem du deine Taste zum Hand wechseln zweimal schnell hintereinander drückst (standardmäßig „F“) + +#### Wie schreibe ich im lokalen Chat bzw. im Bau Chat? +„/bc [Nachricht]“, „/local [Nachricht]“ oder „+ [Nachricht]“ + +# Schematics + +#### Wo kann ich meine Schematics hochladen? +Entweder über https://steamwar.de/startseite/schematicupload/ + Oder schicke unserem Discord Bot (Steamwar Bot#9952) die Schematic per Privatnachricht. Dafür musst du deinen Minecraft und Discord Account bei uns verknüpft haben. + +#### Wie kann ich meine Schematics herunterladen? +Über die ingame Download Funktion im Schem-GUI „//schem gui“, und „//schem download [schematic]“, + +#### Wie kann ich meine Schematic auf die Arena bringen/ freigeben lassen? +„//schem changetype [schemname] [Typ]“ oder wenn du dich mit den Typen nicht auskennst „//schem changetype [schemname]“. Danach muss die Schematic von einem Supporter oder Moderator geprüft werden. + +#### Wie erfahre ich den Grund für die Ablehnung meiner Schematic? +Wenn du zum Zeitpunkt der Ablehnung offline warst „//schem search [schemname]“ + +Wenn du zum Zeitpunkt der Ablehnung online bist, steht eine Nachricht im Chat. + +Über eine Privatnachricht vom SteamWar Discord Bot. Verbinde dafür deinen Minecraft und Discord Account (Informationen dazu unter dem Punkt „Discord“) + +#### Wie erstelle ich eine Schematic/ speichere ich mein WarGear, WarShip usw.? +Gehe auf die Region mit deinem Bauwerk. Gebe nun „/select build“ ein. Stelle dich jetzt mittig vor den Baubereich (normalerweise der gelbe Glasblock), und gebe „//copy“ ein. Jetzt kannst du dein Bauwerk mit „//schem save [schemname]“ speichern. + +Gib „//wand“ ein, nimm das Item in die Hand und wähle mit Linksklick bspw. die untere linke Ecke des Bereichs aus, in welchem sich dein Bauwerk befindet, und wähle dann mit Rechtsklick die schräg gegenüberliegende Ecke aus. Alternativ kannst du die Ecken auch mit „//pos1“ und „//pos2“ auswählen. Dafür müssen dann die Füße deines Charakters in der jeweiligen Ecke bei Senden des Commands befinden. Jetzt sollte sich, wenn du das Item in der Hand hältst, dein Bauwerk innerhalb eines Rahmens aus Partikeln befinden. Sollte dies nicht der Fall sein, passe die Ecken an, bis sich dein Bauwerk in dem Rahmen befindet. Gehe nun an die Stelle, wo du dein Bauwerk kopieren möchtest, was üblicherweise mittig auf dem Boden vor diesem ist (der gelbe Glasblock im Boden). Gebe jetzt „//copy“ ein. Um das Ganze letztendlich zu speichern, gebe „//schem save [schemname]“ ein. + +#### Wie sende ich ein Public ein? +Informiere ein entsprechendes Ratsmitglied (einsehbar auf unserem Discord Server) und füge alle Mitglieder des entsprechenden Rates auf die Schem hinzu. Danach wird der Antrag innerhalb des Rates diskutiert. Sollte der Rat dem Antrag zustimmen, muss ein Admin entscheiden, ob es Public wird. Wenn ja, wirst du aufgefordert alle wieder von der Schematic zu entfernen und es wird zur Public Schem geändert. + +# Discord + +#### Wo finde ich den Discord Server? +[steamwar.de/discord](https://steamwar.de/discord) + +#### Wo finde ich den Social Media Discord Server? +[Discord](https://discord.gg/PvXFsRvZfB) + +#### Wie verbinde ich meinen Discord und Minecraft Account? +Klicke in dem Discord Channel „#「👮」regel-infos“ auf den Button „Minecraft verknüpfen“. Folge dann den von dem SteamWar Bot angewiesenen Schritten. + +# Support + +#### Wie öffne ich ein Support Ticket? +* Auf unserem Discord-Server über den Channel „#「❓」support“. Dort kannst du Fragen stellen, Bugs und Spieler melden, sowie Features vorschlagen und Entbannungsanträge stellen. + +#### Wie melde ich schnell einen Bug? +„/bug [beschreibung des bugs]“ + +# Events + +#### Wie kann ich mich für ein Event anmelden? +„/team event [eventname]“ oder, wenn du deinen Minecraft Account mit deinem Discord Accout verknüpft hast, über den Discord Channel „#「📣」events“ auf unserem Discord-Server. + +#### Kann ich mit zwei Accounts gleichzeitig teilnehmen? +Nein + +#### Kann ich während eines Events noch Spieler in mein Team einladen? +Nein. Während eines laufenden Events sind Neuaufnahmen in ein Team nicht möglich. + +# Forum + +#### Wo finde ich das Forum? +Auf unserem Discord Server unter der Kategorie „Forum“ + +# Website + +#### Wie melde ich mich auf der Website an? +Tritt unserem Minecraft Server bei, und gebe „/webpw [passwort]“ ein + +# Teams + +#### Wie trete ich einem Team bei? +Mit „/team join [Team]“. Mit „/team“ siehst du alle weiteren Commands. + +# Sonstiges + +#### Wo erhalte ich weitere Hilfe? +Mit „/tutorial“ kannst du dir Tutorial-Welten von Spielern ansehen. + diff --git a/src/content/pages/en/join.md b/src/content/pages/en/join.md new file mode 100644 index 0000000..cabb3ac --- /dev/null +++ b/src/content/pages/en/join.md @@ -0,0 +1,5 @@ +--- +title: Join Now! +description: How to join SteamWar.de +slug: join +--- diff --git a/src/env.d.ts b/src/env.d.ts index 29be4a1..1e58cf7 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,3 +1,4 @@ +/// /// /// diff --git a/src/layouts/Basic.astro b/src/layouts/Basic.astro index 411fd28..5d4a7b6 100644 --- a/src/layouts/Basic.astro +++ b/src/layouts/Basic.astro @@ -4,18 +4,19 @@ import {astroI18n} from "astro-i18n"; const { title, description } = Astro.props.frontmatter || Astro.props; --- - + + {title} - + diff --git a/src/layouts/NavbarLayout.astro b/src/layouts/NavbarLayout.astro index e89554f..77f6c5b 100644 --- a/src/layouts/NavbarLayout.astro +++ b/src/layouts/NavbarLayout.astro @@ -11,25 +11,61 @@ const { title } = Astro.props; -
- + - - {t("navbar.logo.alt")} -

- {t("navbar.title")} -

-
- diff --git a/src/locales/en.json b/src/locales/en.json index 039f20f..c567416 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,17 +1,64 @@ { "home": { - "title": "SteamWar.de", + "page": "SteamWar - Home", + "title": { + "first": "Steam", + "second": "War" + }, "subtitle": { "1": "Players Online: ", "2": "WarGears, AirShips, WarShips", "3": "Version: 1.12 - 1.20" }, - "join": "Join Now" + "join": "Join Now", + "benefits": { + "historic": { + "title": "Historical Gamemodes", + "description": { + "1": "Play with your old WarGears and WarShips!", + "2": "You can play with your old tech from 1.7 WarGear to 1.12 WarShips" + } + }, + "server": { + "title": "Own Build Server", + "description": "To garantuee the best performance, every player has his own build server, which runs independently from other servers." + }, + "events": { + "title": "Regular Turnaments", + "description": { + "1": "Test your skills in regular turnaments!", + "2": "Our own event system is very flexible and allows us to create unique turnaments." + } + } + } }, "navbar": { - "title": "SteamWar.de", + "title": "SteamWar", "logo": { - "alt": "SteamWar.de Logo" + "alt": "SteamWar Logo" + }, + "links": { + "home": { + "title": "Home", + "about": "About", + "downloads": "Downloads", + "faq": "FAQ" + }, + "announcements": "Announcements", + "rules": { + "title": "Rules", + "gamemode": "Gamemodes", + "wg": "WarGear", + "mwg": "MiniWarGear", + "ws": "WarShip", + "as": "AirShip", + "rotating": "Rotating", + "megawg": "MegaWarGear", + "micro": "MicroWarGear", + "sf": "StreetFight", + "general": "General", + "coc": "Code of Conduct" + } } } } diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro new file mode 100644 index 0000000..0de348f --- /dev/null +++ b/src/pages/[...slug].astro @@ -0,0 +1,54 @@ +--- +import { getCollection } from 'astro:content' +import {astroI18n} from "astro-i18n"; +import NavbarLayout from "../layouts/NavbarLayout.astro"; + +export async function getStaticPaths() { + let posts = await getCollection("pages"); + + return posts.filter(value => value.id.split("/")[0] === astroI18n.langCode).map((page) => ({ + props: { page }, params: { slug: page.slug } + }) ) +} + +const { page } = Astro.props; +const { Content } = await page.render(); +--- + + + +
+

{page.data.title}

+

{page.data.description}

+ +
+
+ + diff --git a/src/pages/about.astro b/src/pages/about.astro new file mode 100644 index 0000000..a79f9aa --- /dev/null +++ b/src/pages/about.astro @@ -0,0 +1,8 @@ +--- + +import NavbarLayout from "../layouts/NavbarLayout.astro"; +--- + + + + diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro new file mode 100644 index 0000000..9ab312f --- /dev/null +++ b/src/pages/admin/index.astro @@ -0,0 +1,8 @@ +--- +import App from '../../components/admin/App.svelte' +import Basic from "../../layouts/Basic.astro"; +--- + + + + diff --git a/src/pages/index.astro b/src/pages/index.astro index e79e19e..db163ce 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -4,14 +4,17 @@ import NavbarLayout from "../layouts/NavbarLayout.astro"; import { Image } from "astro:assets"; import localBau from "../images/2022-03-28_13.18.25.png"; import {l, t} from "astro-i18n"; -import {CaretRight} from "@astropub/icons" +import {CaretRight, Archive, Rocket, Bell} from "@astropub/icons" --- - +
- Bau + Bau -

{t("home.title")}

+

+ {t("home.title.first")} + {t("home.title.second")} +

{t("home.subtitle.1")}

{t("home.subtitle.2")}

@@ -22,12 +25,8 @@ import {CaretRight} from "@astropub/icons" class TextCarousel extends HTMLElement { current = 0; - constructor() { - super(); - } - connectedCallback() { - this.children[this.current].classList.add("!opacity-100") + this._current.classList.add("!opacity-100") for (let i = 0; i < this.children.length; i++) { if (i !== this.current) { @@ -40,32 +39,28 @@ import {CaretRight} from "@astropub/icons" }, 5000) } + get _current() { + return this.children[this.current] + } + next() { - this.children[this.current].classList.remove("!opacity-100") - this.children[this.current].classList.add("translate-y-8") - this.children[this.current].classList.remove("!delay-500") + this._current.classList.remove("!opacity-100") + this._current.classList.add("translate-y-8") + this._current.classList.remove("!delay-500") this.current = (this.current + 1) % this.children.length - this.children[this.current].classList.add("!opacity-100") - this.children[this.current].classList.remove("translate-y-8") - this.children[this.current].classList.add("!delay-500") + this._current.classList.add("!opacity-100") + this._current.classList.remove("translate-y-8") + this._current.classList.add("!delay-500") } } class PlayerCount extends HTMLElement { - constructor() { - super(); - } - connectedCallback() { this.innerText = String(Math.floor(Math.random() * 100)) } } class DropIn extends HTMLElement { - constructor() { - super(); - } - connectedCallback() { for (let child of this.children) { if(child.classList.contains("opacity-0")) { @@ -85,15 +80,37 @@ import {CaretRight} from "@astropub/icons"
-

HELLO!

+
+
+
+ +

{t("home.benefits.historic.title")}

+

{t("home.benefits.historic.description.1")}

+

{t("home.benefits.historic.description.2")}

+
+
+ +

{t("home.benefits.server.title")}

+

{t("home.benefits.server.description")}

+
+
+ +

{t("home.benefits.events.title")}

+

{t("home.benefits.events.description.1")}

+

{t("home.benefits.events.description.2")}

+
+
+ Read More +
+
+ +
diff --git a/src/pages/join.astro b/src/pages/join.astro deleted file mode 100644 index 284ea45..0000000 --- a/src/pages/join.astro +++ /dev/null @@ -1,7 +0,0 @@ ---- - -import NavbarLayout from "../layouts/NavbarLayout.astro";--- - - - - diff --git a/src/styles/button.css b/src/styles/button.css index 2b8cfc1..c0c7207 100644 --- a/src/styles/button.css +++ b/src/styles/button.css @@ -1,6 +1,6 @@ .btn { - @apply bg-blue-800 text-white font-bold py-2 px-4 rounded cursor-pointer select-none mx-2; - @apply hover:bg-blue-700; + @apply bg-yellow-400 font-bold py-2 px-4 rounded cursor-pointer select-none mx-2 text-black; + @apply hover:bg-yellow-300 hover:text-black; transition: all 0.5s cubic-bezier(.2,3,.67,.6), background-color .1s ease-in-out, outline-width .1s ease-in-out, @@ -12,12 +12,30 @@ } } +.btn-dropdown { + @apply relative mx-2; + + >:nth-child(1) { + @apply block !mx-0; + } + + >:nth-child(2) { + @apply hidden absolute top-full left-1/2 -translate-x-1/2 bg-gray-800 list-none text-white rounded py-2 flex-col text-sm; + } + + &:hover,&:focus-within { + >:nth-child(2) { + @apply flex; + } + } +} + .btn-gray { - @apply bg-gray-800 text-white font-bold py-2 px-4 rounded cursor-pointer select-none mx-2; + @apply bg-gray-800 text-white; } .btn-text { - @apply bg-transparent underline; + @apply bg-transparent underline text-white; @apply hover:bg-transparent hover:outline hover:outline-1; .btn__text { diff --git a/tailwind.config.cjs b/tailwind.config.cjs index a07817c..e67029b 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -2,10 +2,19 @@ const defaultTheme = require("tailwindcss/defaultTheme"); /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], + content: [ + './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', + "./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}" + ], theme: { extend: { + colors: { + // flowbite-svelte + primary: { 50: '#FFF5F2', 100: '#FFF1EE', 200: '#FFE4DE', 300: '#FFD5CC', 400: '#FFBCAD', 500: '#FE795D', 600: '#EF562F', 700: '#EB4F27', 800: '#CC4522', 900: '#A5371B'}, + } }, }, - plugins: [], + plugins: [ + require('flowbite/plugin') + ], } diff --git a/testfile.txt b/testfile.txt deleted file mode 100644 index 5e1c309..0000000 --- a/testfile.txt +++ /dev/null @@ -1 +0,0 @@ -Hello World \ No newline at end of file