This commit is contained in:
16
src/pages/admin/admins.astro
Normal file
16
src/pages/admin/admins.astro
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||
import Admins from '@app/admin/admins/Admins.svelte';
|
||||
import SidebarActions from '@app/admin/admins/SidebarActions.svelte';
|
||||
import { Session } from '@util/session.ts';
|
||||
import { Permissions } from '@util/permissions.ts';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
|
||||
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Admin);
|
||||
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||
---
|
||||
|
||||
<AdminLayout title="Website Admins">
|
||||
<SidebarActions slot="actions" client:load />
|
||||
<Admins client:load />
|
||||
</AdminLayout>
|
14
src/pages/admin/feedback.astro
Normal file
14
src/pages/admin/feedback.astro
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
import Feedback from '@app/admin/feedback/Feedback.svelte';
|
||||
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||
import { Session } from '@util/session.ts';
|
||||
import { Permissions } from '@util/permissions.ts';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
|
||||
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Feedback);
|
||||
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||
---
|
||||
|
||||
<AdminLayout title="Feedback">
|
||||
<Feedback client:load />
|
||||
</AdminLayout>
|
10
src/pages/admin/index.astro
Normal file
10
src/pages/admin/index.astro
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
import { Session } from '@util/session.ts';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||
|
||||
const session = Session.sessionFromCookies(Astro.cookies);
|
||||
if (!session) return Astro.redirect(`${BASE_PATH}/admin/login`);
|
||||
---
|
||||
|
||||
<AdminLayout title="Admin Panel" />
|
50
src/pages/admin/login.astro
Normal file
50
src/pages/admin/login.astro
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
import AdminLoginLayout from '@layouts/admin/AdminLoginLayout.astro';
|
||||
import Password from '@components/input/Password.svelte';
|
||||
import Input from '@components/input/Input.svelte';
|
||||
import Popup from '@components/popup/Popup.svelte';
|
||||
---
|
||||
|
||||
<AdminLoginLayout title="Login">
|
||||
<div class="flex justify-center items-center w-full h-screen">
|
||||
<div class="card w-96 px-6 py-6 shadow-lg">
|
||||
<h1 class="text-3xl text-center">Admin Login</h1>
|
||||
<div class="divider"></div>
|
||||
<form id="login" class="flex flex-col items-center">
|
||||
<div>
|
||||
<Input id="username" type="text" label="Nutzername" required />
|
||||
<Password id="password" label="Passwort" required />
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-neutral">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</AdminLoginLayout>
|
||||
|
||||
<Popup client:idle />
|
||||
|
||||
<script>
|
||||
import { actions } from 'astro:actions';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
import { popupState } from '@components/popup/Popup';
|
||||
|
||||
const loginForm = document.getElementById('login') as HTMLFormElement;
|
||||
loginForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const usernameInput = document.getElementById('username') as HTMLInputElement;
|
||||
const passwordInput = document.getElementById('password') as HTMLInputElement;
|
||||
|
||||
const { error } = await actions.session.login({
|
||||
username: usernameInput.value,
|
||||
password: passwordInput.value
|
||||
});
|
||||
if (error) {
|
||||
popupState.set({ type: 'error', title: 'Fehler', message: error.message });
|
||||
return;
|
||||
}
|
||||
window.location.href = `${BASE_PATH}/admin`;
|
||||
});
|
||||
</script>
|
16
src/pages/admin/reports.astro
Normal file
16
src/pages/admin/reports.astro
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
import { Session } from '@util/session';
|
||||
import { Permissions } from '@util/permissions';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||
import SidebarActions from '@app/admin/reports/SidebarActions.svelte';
|
||||
import Reports from '@app/admin/reports/Reports.svelte';
|
||||
|
||||
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Reports);
|
||||
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||
---
|
||||
|
||||
<AdminLayout title="Reports">
|
||||
<SidebarActions slot="actions" client:load />
|
||||
<Reports client:load />
|
||||
</AdminLayout>
|
17
src/pages/admin/settings.astro
Normal file
17
src/pages/admin/settings.astro
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||
import Settings from '@app/admin/settings/Settings.svelte';
|
||||
import { db } from '@db/database.ts';
|
||||
import { Session } from '@util/session.ts';
|
||||
import { Permissions } from '@util/permissions.ts';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
|
||||
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Settings);
|
||||
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||
|
||||
const settings = await db.getSettings({});
|
||||
---
|
||||
|
||||
<AdminLayout title="Einstellungen">
|
||||
<Settings {settings} client:load />
|
||||
</AdminLayout>
|
16
src/pages/admin/teams.astro
Normal file
16
src/pages/admin/teams.astro
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||
import SidebarActions from '@app/admin/teams/SidebarActions.svelte';
|
||||
import Teams from '@app/admin/teams/Teams.svelte';
|
||||
import { Session } from '@util/session.ts';
|
||||
import { Permissions } from '@util/permissions.ts';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
|
||||
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Admin);
|
||||
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||
---
|
||||
|
||||
<AdminLayout title="Teams">
|
||||
<SidebarActions slot="actions" client:load />
|
||||
<Teams client:load />
|
||||
</AdminLayout>
|
16
src/pages/admin/users.astro
Normal file
16
src/pages/admin/users.astro
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||
import Users from '@app/admin/users/Users.svelte';
|
||||
import SidebarActions from '@app/admin/users/SidebarActions.svelte';
|
||||
import { Session } from '@util/session.ts';
|
||||
import { Permissions } from '@util/permissions.ts';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
|
||||
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Admin);
|
||||
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||
---
|
||||
|
||||
<AdminLayout title="Registrierte Nutzer">
|
||||
<SidebarActions slot="actions" client:load />
|
||||
<Users client:load />
|
||||
</AdminLayout>
|
37
src/pages/api/feedback/index.ts
Normal file
37
src/pages/api/feedback/index.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { z } from 'astro:schema';
|
||||
import type { APIRoute } from 'astro';
|
||||
import { API_SECRET } from 'astro:env/server';
|
||||
import { db } from '@db/database.ts';
|
||||
import { BASE_PATH } from 'astro:env/client';
|
||||
|
||||
const postSchema = z.object({
|
||||
event: z.string(),
|
||||
title: z.string(),
|
||||
users: z.array(z.string())
|
||||
});
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
if (API_SECRET && request.headers.get('authorization') !== `Basic ${API_SECRET}`) {
|
||||
return new Response(null, { status: 401 });
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = await postSchema.parseAsync(await request.json());
|
||||
} catch (_) {
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
const feedbacks = await db.addUserFeedbacks({
|
||||
event: parsed.event,
|
||||
title: parsed.title,
|
||||
uuids: parsed.users
|
||||
});
|
||||
|
||||
const response = feedbacks.map((feedback) => ({
|
||||
uuid: feedback.uuid,
|
||||
url: `${BASE_PATH}/feedback/${feedback.urlHash}`
|
||||
}));
|
||||
|
||||
return new Response(JSON.stringify({ feedback: response }), { status: 200 });
|
||||
};
|
44
src/pages/api/player/death.ts
Normal file
44
src/pages/api/player/death.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { API_SECRET } from 'astro:env/server';
|
||||
import { z } from 'astro:schema';
|
||||
import { db } from '@db/database.ts';
|
||||
|
||||
const postSchema = z.object({
|
||||
user: z.string(),
|
||||
killer: z.string().nullable(),
|
||||
message: z.string()
|
||||
});
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
if (API_SECRET && request.headers.get('authorization') !== `Basic ${API_SECRET}`) {
|
||||
return new Response(null, { status: 401 });
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = await postSchema.parseAsync(await request.json());
|
||||
} catch (_) {
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
let users;
|
||||
if (parsed.killer) {
|
||||
users = await db.getUsersByUuid({ uuid: [parsed.user, parsed.killer] });
|
||||
} else {
|
||||
users = await db.getUsersByUuid({ uuid: [parsed.user] });
|
||||
}
|
||||
const user = users[parsed.user];
|
||||
const killer = parsed.killer ? users[parsed.killer] : null;
|
||||
|
||||
if (!user || (!killer && parsed.killer)) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
await db.addDeath({
|
||||
message: parsed.message,
|
||||
deadUserId: user.id,
|
||||
killerUserId: killer?.id
|
||||
});
|
||||
|
||||
return new Response(null, { status: 200 });
|
||||
};
|
74
src/pages/api/player/status.ts
Normal file
74
src/pages/api/player/status.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { db } from '@db/database.ts';
|
||||
import { API_SECRET } from 'astro:env/server';
|
||||
import { z } from 'astro:schema';
|
||||
|
||||
const postSchema = z.object({
|
||||
user: z.string()
|
||||
});
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
if (API_SECRET && request.headers.get('authorization') !== `Basic ${API_SECRET}`) {
|
||||
return new Response(null, { status: 401 });
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = await postSchema.parseAsync(await request.json());
|
||||
} catch (_) {
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
const user = (await db.getUsersByUuid({ uuid: [parsed.user] }))[parsed.user];
|
||||
if (user == null) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
const team = await db.getTeamByUserUuid({ uuid: parsed.user });
|
||||
if (team == null) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
const death = await db.getDeathByUserId({ userId: user.id });
|
||||
const strikes = await db.getStrikesByTeamId({ teamId: team.team.id });
|
||||
|
||||
const response = {
|
||||
team: {
|
||||
name: team.team.name,
|
||||
color: team.team.color
|
||||
},
|
||||
dead: death != null,
|
||||
strikeWeight: strikes.map((strike) => strike.reason.weight).reduce((a, b) => a + b, 0),
|
||||
lastJoined: team.team.lastJoined ? new Date(team.team.lastJoined).getTime() : null
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(response));
|
||||
};
|
||||
|
||||
const putSchema = z.object({
|
||||
user: z.string(),
|
||||
lastJoined: z.number()
|
||||
});
|
||||
|
||||
export const PUT: APIRoute = async ({ request }) => {
|
||||
if (API_SECRET && request.headers.get('authorization') !== `Basic ${API_SECRET}`) {
|
||||
return new Response(null, { status: 401 });
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = await putSchema.parseAsync(await request.json());
|
||||
} catch (_) {
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
const team = await db.getTeamByUserUuid({ uuid: parsed.user });
|
||||
if (team == null) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
await db.editTeam({
|
||||
id: team.team.id,
|
||||
lastJoined: new Date(parsed.lastJoined).toISOString()
|
||||
});
|
||||
|
||||
return new Response(null, { status: 200 });
|
||||
};
|
26
src/pages/api/team/index.ts
Normal file
26
src/pages/api/team/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { db } from '@db/database.ts';
|
||||
import { API_SECRET } from 'astro:env/server';
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
if (API_SECRET && request.headers.get('authorization') !== `Basic ${API_SECRET}`) {
|
||||
return new Response(null, { status: 401 });
|
||||
}
|
||||
|
||||
const teams = await db.getTeams({});
|
||||
|
||||
const response = [];
|
||||
for (const team of teams) {
|
||||
const users = [];
|
||||
if (team.memberOne.uuid) users.push(team.memberOne.uuid);
|
||||
if (team.memberTwo.uuid) users.push(team.memberTwo.uuid);
|
||||
|
||||
response.push({
|
||||
name: team.name,
|
||||
color: team.color,
|
||||
users: users
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(response));
|
||||
};
|
214
src/pages/faq.astro
Normal file
214
src/pages/faq.astro
Normal file
@ -0,0 +1,214 @@
|
||||
---
|
||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||
import { PAYPAL_LINK, TEAMSPEAK_LINK, DISCORD_LINK, SERVER_IP } from 'astro:env/client';
|
||||
|
||||
const faq = [
|
||||
{
|
||||
section: 'Anmeldung',
|
||||
questions: [
|
||||
{
|
||||
title: 'Wann startet Varo 5?',
|
||||
content: `<p>Der Start von Varo 5 findet gemeinsam am 23.06.2025 um 19:00 Uhr statt. Am besten bist du schon
|
||||
einige Minuten vorher auf dem Server. Denk daran: Nur wenn du und dein Teampartner beide anwesend seid, könnt ihr am
|
||||
Start teilnehmen.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Was tun, wenn ich am Starttermin nicht kann?',
|
||||
content: `<p>Wenn du oder dein Teampartner am 23.06.2025 um 19:00 Uhr verhindert seid, könnt ihr einen Tag
|
||||
später, also am 24.06.2025 ab 17:00 Uhr einsteigen. Die 30 Minuten von Tag 1 werden euch aus Fairness-Gründen allerdings
|
||||
nicht gutgeschrieben.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Wer kann alles mitspielen?',
|
||||
content: `<p>Jeder, der Minecraft Java besitzt, mindestens 6 Jahre alt ist und einen Teampartner hat, kann
|
||||
mitspielen.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Wie kann ich mitspielen?',
|
||||
content: `<p>Um mitzuspielen, musst du dich einfach hier auf der Website anmelden und der WhatsApp-Gruppe
|
||||
beitreten. Nur wenn dein Team vollständig ist, also aus zwei Spielern besteht, kannst du teilnehmen.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Auf welcher Version läuft der Server und was ist der Schwierigkeitsgrad?',
|
||||
content: `<p>Gespielt wird in der aktuellsten Minecraft Java Version mit dem Schwierigkeitsgrad „Normal“.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Kann ich auch als Bedrock-Spieler (Handy oder Konsole) mitspielen?',
|
||||
content: `<p>Nein, als Bedrock-Spieler kannst du bei Varo leider nicht mitspielen. Das gilt auch, falls du einen
|
||||
Proxie betreibst, der Bedrock zu Java proxiet.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Ich wurde in CraftAttack gebannt, kann ich bei Varo trotzdem mitspielen?',
|
||||
content: `<p>In Einzelfällen sind in CraftAttack gebannte Spieler auch von Varo ausgeschlossen. Diese können
|
||||
sich dann erst gar nicht anmelden.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Ich kann mich nicht anmelden, was kann ich tun?',
|
||||
content: `<p>Wenn du dich nicht anmelden kannst, solltest du Folgendes überprüfen:</p><p>1. Ist dein Spielername
|
||||
korrekt geschrieben?<br>2. Hast du dich bereits angemeldet? Es ist nur ein Account pro Spieler erlaubt.</p><p>Falls du
|
||||
dich aus unerklärlichen Gründen trotzdem nicht anmelden kannst, kannst du dich jederzeit beim Admin-Team melden.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Ich komme nicht auf den Server, was kann ich tun?',
|
||||
content: `<p>Wenn du dem Server nicht beitreten kannst, überprüfe Folgendes:</p>
|
||||
<ol class="list-decimal pl-8 py-3">
|
||||
<li>Hast du die korrekte IP verwendet? Sie lautet <span class="italic">varo.mhsl.eu</span>.</li>
|
||||
<li>Hast du Leerzeichen verwendet, insbesondere vor oder hinter der IP, oder dich vertippt?</li>
|
||||
<li>Kommst du auf andere Server, oder ist es nur ein Problem beim Varo-Server?</li>
|
||||
<li>Hast du dich korrekt auf der Webseite angemeldet?</li>
|
||||
</ol>
|
||||
Falls du trotzdem nicht beitreten kannst, melde dich beim Admin-Team und halte die
|
||||
Fehlermeldung bereit.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Was ist die Server-IP?',
|
||||
content: `<p>Die Serveradresse lautet: <span class="italic">${SERVER_IP}</span></p>`
|
||||
},
|
||||
{
|
||||
title: 'Ist es kostenlos mitzuspielen?',
|
||||
content: `<p>Ja, die Teilnahme ist selbstverständlich kostenlos. Wir freuen uns aber, wenn du das Projekt mit
|
||||
einer Spende nach der Anmeldung unterstützen würdest. Hier kannst du für das Projekt spenden:
|
||||
<a class="link" href=${PAYPAL_LINK} target="_blank">${PAYPAL_LINK}</a>.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Die Anmeldefrist ist vorbei, aber ich möchte mich trotzdem noch anmelden. Was kann ich tun?',
|
||||
content: `<p>Eine nachträgliche Anmeldung ist ausnahmslos nicht möglich.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Ist ein 2. Account erlaubt?',
|
||||
content: `<p>Nein, pro Teilnehmer ist nur ein Account zugelassen.</p>`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
section: 'Anderes',
|
||||
questions: [
|
||||
{
|
||||
title: 'Wie viel Spielzeit habe ich pro Tag?',
|
||||
content: `<p>Sobald du den Server betritt müssen du und dein Teampartner 30 Minuten lang am Stück auf dem Server
|
||||
spielen. Spielt dein Team an einem Tag nicht, dürft ihr am Folgetag 60 Minuten, also zwei 30-Minuten-Slots spielen. Pro
|
||||
Tag wird deinem Team also eine Zeit von 30 Minuten gutgeschrieben. Sollte dein Team mehr als 90 Minuten Spielzeit
|
||||
angesammelt haben, wird es automatisch vom Projekt ausgeschlossen.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Von wann bis wann ich dem Server täglich online?',
|
||||
content: `<p>Du kannst dem Server zu Beginn täglich von 17:00 Uhr bis 21:00 Uhr joinen.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Gibt es eine Finale und wie läuft es ab?',
|
||||
content: `<p>Es gibt planmäßig kein öffentliches Finale, aber natürlich wirst du am Ende über das Siegerteam
|
||||
informiert.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Kann ich meinen Teampartner nach dem Tod spectaten?',
|
||||
content: `<p>Nein, diese Möglichkeit besteht nicht. Er kann dir aber natürlich gerne seinen Bildschirm
|
||||
übertragen.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Wie kann ich einen Admin kontaktieren?',
|
||||
content: `<p>Einen Admin kannst du im Chat, über WhatsApp, per Teamspeak
|
||||
(<a class="link" href="${TEAMSPEAK_LINK}">mhsl.eu</a>) oder Discord
|
||||
(<a class="link" href="${DISCORD_LINK}" target="_blank">${DISCORD_LINK}</a>) kontaktieren.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Wer ist eigentlich Organisator und warum?',
|
||||
content: `<p>Wir sind ein kleines Team von Minecraft-Enthusiasten, die neben Minecraft CraftAttack dieses Jahr
|
||||
das Sommerprojekt Varo organisieren. Weitere Infos findest du auf der Teamseite.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Spielen die Admins auch mit?',
|
||||
content: `<p>Nein, alle Varo Admins sind keine Projektteilnehmer und sind lediglich für die Organisation, die
|
||||
Kontrolle der Regeln und der technische Umsetzung zuständig. Falls Euch Admin-Namen aus CraftAttack bekannt sind, keine
|
||||
Sorge: Das Adminteam von CraftAttack und Varo ist nicht zwingend identisch!</p>`
|
||||
},
|
||||
{
|
||||
title: 'Warum benötigt ihr meine Daten bei der Anmeldung?',
|
||||
content: `<p>Deine Daten werden nur intern gespeichert und dienen den Admins rein zur Organisation des
|
||||
Projekts.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Gibt es einen Teamspeak-Server?',
|
||||
content: `<p>Ja, den offiziellen Teamspeak-Server erreichst du unter der IP
|
||||
<a class="link" href="${TEAMSPEAK_LINK}">mhsl.eu</a>.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Gibt es einen Discord-Server?',
|
||||
content: `<p>Ja, den offiziellen Discord-Server erreichst du unter
|
||||
<a class="link" href="${DISCORD_LINK}" target="_blank">${DISCORD_LINK}</a>.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Wozu dient die Varo-WhatsApp-Gruppe?',
|
||||
content: `<p>In der WhatsApp-Gruppe erhältst du alle wichtigen Infos bezüglich Varo.</p>`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
section: 'Ingame',
|
||||
questions: [
|
||||
{
|
||||
title: 'Gibt es eine Worldborder?',
|
||||
content: `<p>Ja, es gibt außerdem eine Worldborder, die sich mit der Zeit – außerhalb der Spielzeiten – langsam
|
||||
verkleinert. Du wirst im Spiel über alles wichtige informiert, sobald du dich der Border gefährlich näherst.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Kann ich ein eigenes Netherportal bauen?',
|
||||
content: `<p>Nein, das einzige Netherportal steht am Spawn. Weitere können nicht eröffnet werden.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Wie kann ich einen Report erstellen?',
|
||||
content: `<p>Neben der Kontrolle durch die Admins kann mit /report ein Report erstellt werden, um Regelverstöße
|
||||
zu melden. Ohne feste Belege, also einer Bildschirmaufnahme, ist ein Report bei Ingameverstößen leider nutzlos.</p>`
|
||||
},
|
||||
{
|
||||
title: 'Welche Spielinhalte (Items etc.) sind deaktiviert?',
|
||||
content: `<ol class="list-decimal pl-8 py-3">
|
||||
<li>alle Netherite Items</li>
|
||||
<li>alle Tränke der Stufe 2, ausgenommen Direktheilung 2</li>
|
||||
<li>Totems</li>
|
||||
<li>Enderkristalle</li>
|
||||
<li>Mace</li>
|
||||
<li>OP-Goldapfel</li>
|
||||
<li>Villager Handel mit dem Bibliothekar und Panzermacher</li>
|
||||
</ol>`
|
||||
},
|
||||
{
|
||||
title: 'Warum sind diese Spielinhalte deaktiviert?',
|
||||
content: `<p>Unser Ziel ist es, ein großartiges Projekt für alle Beteiligten auf die Beine zu stellen. Varo
|
||||
haben wir in dieser Form und Größe noch nie veranstaltet – auch für uns ist es also ein neues Kapitel, bei dem wir
|
||||
wertvolle Erfahrungen sammeln wollen.<br>Wir haben die Regeln nach bestem Wissen und Gewissen so aufgestellt, um das
|
||||
bestmögliche Spielerlebnis schaffen. Trotzdem ist uns dein Feedback wichtig: Nach dem Projekt holen wir deine Meinung
|
||||
ein, um Varo – und auch unsere künftigen Projekte – nachhaltig weiterzuentwickeln.</p>`
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
---
|
||||
|
||||
<WebsiteLayout title="FAQ">
|
||||
<div class="mx-4 my-6 sm:mx-24 sm:my-12">
|
||||
<h1 class="text-3xl lg:text-5xl mb-16 text-center">FAQ</h1>
|
||||
<div class="grid lg:grid-cols-2 2xl:grid-cols-3 gap-10">
|
||||
{
|
||||
faq.map((questions) => (
|
||||
<div>
|
||||
<h2 class="text-4xl text-center mb-3">{questions.section}</h2>
|
||||
<div>
|
||||
{questions.questions.map((question) => (
|
||||
<>
|
||||
<div class="collapse collapse-arrow">
|
||||
<input type="checkbox" autocomplete="off" />
|
||||
<div class="collapse-title">{question.title}</div>
|
||||
<div class="collapse-content">
|
||||
<div class="ml-2" set:html={question.content} />
|
||||
</div>
|
||||
</div>
|
||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600" />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</WebsiteLayout>
|
119
src/pages/feedback.astro
Normal file
119
src/pages/feedback.astro
Normal file
@ -0,0 +1,119 @@
|
||||
---
|
||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||
import ConfirmPopup from '@components/popup/ConfirmPopup.svelte';
|
||||
import Popup from '@components/popup/Popup.svelte';
|
||||
import Select from '@components/input/Select.svelte';
|
||||
import Textarea from '@components/input/Textarea.svelte';
|
||||
import Input from '@components/input/Input.svelte';
|
||||
---
|
||||
|
||||
<WebsiteLayout title="Feedback & Kontakt">
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="mt-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
|
||||
<h2 class="text-3xl text-center">Feedback & Kontakt</h2>
|
||||
<form id="feedback-contact">
|
||||
<div class="space-y-4 mt-6 mb-4">
|
||||
<Select id="type" values={{ 'website-feedback': 'Feedback', 'website-contact': 'Kontakt' }} />
|
||||
<Textarea id="content" label="Feedback" dynamicWidth required rows={5} />
|
||||
<Input id="email" type="email" label="Email" required hidden />
|
||||
</div>
|
||||
<button id="send" class="btn">Senden</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</WebsiteLayout>
|
||||
|
||||
<ConfirmPopup client:idle />
|
||||
<Popup client:idle />
|
||||
|
||||
<script>
|
||||
import { actions } from 'astro:actions';
|
||||
import { confirmPopupState } from '@components/popup/ConfirmPopup';
|
||||
import { popupState } from '@components/popup/Popup';
|
||||
|
||||
const form = document.getElementById('feedback-contact') as HTMLFormElement;
|
||||
const type = document.getElementById('type') as HTMLSelectElement;
|
||||
const content = document.getElementById('content') as HTMLTextAreaElement;
|
||||
const email = document.getElementById('email') as HTMLInputElement;
|
||||
|
||||
type.addEventListener('change', () => {
|
||||
if (type.value === 'website-feedback') {
|
||||
// content input
|
||||
content.previousElementSibling!.firstChild!.textContent = 'Feedback';
|
||||
// email input
|
||||
email.parentElement!.hidden = true;
|
||||
email.required = false;
|
||||
} else if (type.value === 'website-contact') {
|
||||
// content input
|
||||
content.previousElementSibling!.firstChild!.textContent = 'Anfrage';
|
||||
// email input
|
||||
email.required = true;
|
||||
email.parentElement!.hidden = false;
|
||||
}
|
||||
});
|
||||
|
||||
email.required = false;
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (type.value === 'website-feedback') {
|
||||
confirmPopupState.set({
|
||||
title: 'Feedback abschicken',
|
||||
message: 'Soll das Feedback abgeschickt werden?',
|
||||
onConfirm: () => sendFeedback().then(() => form.reset())
|
||||
});
|
||||
} else if (type.value === 'website-contact') {
|
||||
confirmPopupState.set({
|
||||
title: 'Kontaktanfrage abschicken',
|
||||
message: 'Soll die Kontaktanfrage abgeschickt werden?',
|
||||
onConfirm: () => sendContact().then(() => form.reset())
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function sendFeedback() {
|
||||
const { error } = await actions.feedback.addWebsiteFeedback({
|
||||
type: 'website-feedback',
|
||||
content: content.value
|
||||
});
|
||||
|
||||
if (error) {
|
||||
popupState.set({
|
||||
type: 'error',
|
||||
title: 'Fehler beim senden des Feedbacks',
|
||||
message: error.message
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
popupState.set({
|
||||
type: 'info',
|
||||
title: 'Feedback abgeschickt',
|
||||
message: 'Dein Feedback wurde abgeschickt. Vielen Dank, dass du uns hilfst, das Projekt besser zu machen!'
|
||||
});
|
||||
}
|
||||
|
||||
async function sendContact() {
|
||||
const { error } = await actions.feedback.addWebsiteContact({
|
||||
type: 'website-contact',
|
||||
content: content.value,
|
||||
email: email.value
|
||||
});
|
||||
|
||||
if (error) {
|
||||
popupState.set({
|
||||
type: 'error',
|
||||
title: 'Fehler beim senden der Kontaktanfrage',
|
||||
message: error.message
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
popupState.set({
|
||||
type: 'info',
|
||||
title: 'Kontaktanfrage abgeschickt',
|
||||
message: 'Deine Kontaktanfrage wurde abgeschickt. Jemand aus dem Team wird sich nächstmöglich bei Dir melden.'
|
||||
});
|
||||
}
|
||||
</script>
|
89
src/pages/index.astro
Normal file
89
src/pages/index.astro
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||
import Scroll from '@components/website/index/Scroll.svelte';
|
||||
import Teams from '@app/webite/index/Teams.svelte';
|
||||
import Countdown from '@components/website/index/Countdown.svelte';
|
||||
import Varo from '@assets/img/varo.webp';
|
||||
import Background from '@assets/img/background.webp';
|
||||
import { START_DATE } from 'astro:env/client';
|
||||
import { getSetting, SettingKey } from '@util/settings';
|
||||
import { db } from '@db/database.ts';
|
||||
|
||||
const teams = await db.getTeams({});
|
||||
const deaths = await db.getDeaths({});
|
||||
|
||||
const signupEnabled = await getSetting(db, SettingKey.SignupEnabled, false);
|
||||
|
||||
const information = [
|
||||
{
|
||||
title: 'Was ist Varo?',
|
||||
description:
|
||||
'Varo ist ein spannendes Vanilla-Minecraft-PvP-Projekt, bei dem Zweier-Teams im Kampf ums Überleben gegeneinander antreten. Wer stirbt – ganz gleich auf welche Weise – scheidet endgültig aus. Das letzte verbleibende Team gewinnt Varo!'
|
||||
},
|
||||
{
|
||||
title: 'Warum Varo?',
|
||||
description:
|
||||
'Varo 5 ist unser erstes großes Sommerprojekt – ein PvP-Format, das für frische Abwechslung sorgt und die Wartezeit auf das nächsten CraftAttack verkürzt.\n' +
|
||||
'Tatsächlich ist es schon das fünfte Varo – auch wenn das viele überraschen dürfte.\n' +
|
||||
'Zum ersten Mal veranstalten wir es jedoch in dieser offenen Form für alle Teilnehmenden.\n' +
|
||||
'Wenn es euch gefällt, könnte Varo künftig als feste Sommerreihe weitergeführt werden.\n' +
|
||||
'Und natürlich gilt: Im Winter sehen wir uns wie gewohnt bei CraftAttack wieder!'
|
||||
},
|
||||
{
|
||||
title: 'Wer kann mitspielen?',
|
||||
description:
|
||||
'Alle Spieler mit einem Minecraft Java-Account sind herzlich eingeladen, mitzumachen. Wenn du dabei sein willst, brauchst du nur einen Teampartner – gemeinsam könnt ihr euch direkt hier auf unserer Website anmelden.'
|
||||
}
|
||||
];
|
||||
---
|
||||
|
||||
<WebsiteLayout title="Varo 5">
|
||||
<div class="bg-base-200 flex flex-col min-h-screen relative">
|
||||
<div class="flex items-center xl:w-1/2 px-6 sm:px-10 min-h-screen h-full">
|
||||
<div class="flex flex-col w-full xl:h-3/4 my-10">
|
||||
<div class="flex flex-col w-full mt-2 lg:mt-5 lg:w-10/12 h-full">
|
||||
<div class="w-2/3 m-auto">
|
||||
<img src={Varo.src} alt="Varo 5" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="divider"></div>
|
||||
<div class="flex flex-col md:flex-row xl:flex-col gap-5">
|
||||
{
|
||||
information.map((info) => (
|
||||
<div>
|
||||
<h4 class="mb-1 font-bold">{info.title}</h4>
|
||||
<p>{info.description}</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<a class="btn btn-outline btn-accent hover:bg-white" href="signup"
|
||||
>{signupEnabled ? 'Jetzt registrieren' : 'Infos zur Anmeldung'}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="hidden xl:block absolute top-0 left-0 h-full w-full"
|
||||
style="clip-path: polygon(60% 0, 100% 0, 100% 100%, 40% 100%);"
|
||||
>
|
||||
<img src={Background.src} alt="" loading="lazy" width="100%" height="100%" />
|
||||
</div>
|
||||
<div class="hidden xl:flex justify-center absolute bottom-12 right-0 w-[60%]">
|
||||
<Countdown end={Date.parse(START_DATE)} client:load />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed bottom-0 right-0 m-5">
|
||||
<Scroll href="#teams" client:load />
|
||||
</div>
|
||||
|
||||
<div class="bg-base-100 flex flex-col space-y-10 items-center py-10">
|
||||
<h2 id="teams" class="text-4xl">Teams</h2>
|
||||
<Teams {teams} {deaths} />
|
||||
</div>
|
||||
</WebsiteLayout>
|
39
src/pages/rules.astro
Normal file
39
src/pages/rules.astro
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||
import { rulesLong } from '../rules';
|
||||
---
|
||||
|
||||
<WebsiteLayout title="Regeln">
|
||||
<div class="mx-4 my-6 sm:mx-48 sm:my-12">
|
||||
<h1 class="text-3xl lg:text-5xl mb-4">Varo 5 Regelwerk</h1>
|
||||
<div class="collapse collapse-arrow">
|
||||
<input type="checkbox" autocomplete="off" checked />
|
||||
<div class="collapse-title">
|
||||
<p>0. Vorwort</p>
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<p>{rulesLong.header}</p>
|
||||
<p class="mt-1 text-[.75rem]">{rulesLong.footer}</p>
|
||||
</div>
|
||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600"></span>
|
||||
</div>
|
||||
{
|
||||
rulesLong.sections.map((section, i) => (
|
||||
<div>
|
||||
<div class="collapse collapse-arrow">
|
||||
<input type="checkbox" autocomplete="off" />
|
||||
<div class="collapse-title">
|
||||
<p>
|
||||
{i + 1}. {section.title}
|
||||
</p>
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<p>{section.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600" />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</WebsiteLayout>
|
226
src/pages/signup.astro
Normal file
226
src/pages/signup.astro
Normal file
@ -0,0 +1,226 @@
|
||||
---
|
||||
import SignupLayout from '@layouts/website/SignupLayout.astro';
|
||||
import Checkbox from '@components/input/Checkbox.svelte';
|
||||
import Input from '@components/input/Input.svelte';
|
||||
import RulesPopup from '@components/website/signup/RulesPopup.svelte';
|
||||
import Popup from '@components/popup/Popup.svelte';
|
||||
import TeamPopup from '@components/website/signup/TeamPopup.svelte';
|
||||
import RegisteredPopup from '@components/website/signup/RegisteredPopup.svelte';
|
||||
import { getSettings, SettingKey } from '@util/settings';
|
||||
import { db } from '@db/database.ts';
|
||||
|
||||
const signupSetting = await getSettings(db, [
|
||||
SettingKey.SignupEnabled,
|
||||
SettingKey.SignupDisabledMessage,
|
||||
SettingKey.SignupDisabledSubMessage
|
||||
]);
|
||||
const signupEnabled = signupSetting[SettingKey.SignupEnabled] ?? false;
|
||||
const signupDisabledMessage = signupSetting[SettingKey.SignupDisabledMessage] ?? 'Anmeldung deaktiviert';
|
||||
const signupDisabledSubMessage = signupSetting[SettingKey.SignupDisabledSubMessage] ?? '';
|
||||
---
|
||||
|
||||
<SignupLayout signupEnabled={signupEnabled}>
|
||||
<h1 class="text-center text-3xl lg:text-5xl">Anmeldung</h1>
|
||||
<form id="signup">
|
||||
<div class="divider">Persönliche Angaben</div>
|
||||
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-x-4">
|
||||
<Input
|
||||
id="firstname"
|
||||
type="text"
|
||||
label="Vorname"
|
||||
required
|
||||
validation={{
|
||||
pattern: '^\\w{2,}',
|
||||
hint: 'Bitte gib Deinen vollständigen Vornamen an, dieser muss mindestens aus 2 Zeichen bestehen.'
|
||||
}}
|
||||
dynamicWidth
|
||||
/>
|
||||
<Input
|
||||
id="lastname"
|
||||
type="text"
|
||||
label="Nachname"
|
||||
required
|
||||
validation={{
|
||||
pattern: '^\\w{2,}',
|
||||
hint: 'Bitte gib Deinen vollständigen Nachnamen an, dieser muss mindestens aus 2 Zeichen bestehen.'
|
||||
}}
|
||||
dynamicWidth
|
||||
/>
|
||||
<Input
|
||||
id="birthday"
|
||||
type="date"
|
||||
label="Geburtstag"
|
||||
required
|
||||
validation={{
|
||||
max: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 6).toLocaleDateString('sv-SE'),
|
||||
hint: 'Bitte gib Deinen vollständigen Geburtstag und die korrekte Jahreszahl an. Du musst mindestens 6 Jahre alt sein.'
|
||||
}}
|
||||
dynamicWidth
|
||||
>
|
||||
<span slot="notice">Die Angabe hat keine Auswirkungen auf das Spielgeschehen.</span>
|
||||
</Input>
|
||||
<Input
|
||||
id="phone"
|
||||
type="tel"
|
||||
label="Telefonnummer"
|
||||
validation={{
|
||||
pattern: '^[+\\(\\)\\s\\d]+$',
|
||||
hint: 'Bitte gib Deine vollständige Telefonnummer an, diese darf keine ungültigen Zeichen enthalten'
|
||||
}}
|
||||
dynamicWidth
|
||||
>
|
||||
<span slot="notice">
|
||||
Diese nutzen wir, um Dich in der Whatsapp-Gruppe zuzuordnen und kontaktieren zu können.
|
||||
<br />
|
||||
<b>Die Angabe ist freiwillig, hilft den Administratoren jedoch sehr!</b>
|
||||
</span>
|
||||
</Input>
|
||||
</div>
|
||||
<div class="divider">Spiel</div>
|
||||
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-x-4">
|
||||
<Input id="username" type="text" label="Minecraft-Spielername" required dynamicWidth />
|
||||
<Input id="teamMember" type="text" label="Mitspieler" required dynamicWidth>
|
||||
<span slot="notice"
|
||||
>Trage hier den Minecraft-Spielername des Mitspieler ein, mit dem du in ein Team möchtest. Auch dieser muss
|
||||
bei seiner Anmeldung deinen Namen eintragen. Nur wenn ihr beide eure Namen gegenseitig eingetragen habt, kann
|
||||
ein Team erstellt werden.</span
|
||||
>
|
||||
</Input>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="mx-2 grid gap-y-3 mb-6">
|
||||
<Checkbox id="checkbox" required>
|
||||
<span slot="label">
|
||||
Ich bin mit der Speicherung meiner in der Anmeldung angegebenen, persönlichen Daten einverstanden. Siehe <a
|
||||
class="link"
|
||||
href="https://mhsl.eu/id.html"
|
||||
target="_blank">Datenschutz</a
|
||||
>
|
||||
</span>
|
||||
</Checkbox>
|
||||
<Checkbox id="logs" required>
|
||||
<span slot="label">
|
||||
Ich bin mit der Speicherung in Form von Logs aller meiner, beim Spielen anfallenden, persönlichen Daten durch
|
||||
den Server einverstanden
|
||||
</span>
|
||||
<span slot="notice" class="text-[.65rem]">
|
||||
Dies betrifft jede Interaktion im Spiel und zugehörige Daten wie z.B. Chatnachrichten welche vom Minecraft
|
||||
Client an den Server übermittelt werden
|
||||
</span>
|
||||
</Checkbox>
|
||||
<Checkbox id="rules" required>
|
||||
<span slot="label">
|
||||
Ich bin mit den <a class="link" onclick="">Regeln</a> einverstanden und achte sie
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<button class="btn btn-neutral">Anmeldung absenden</button>
|
||||
</form>
|
||||
</SignupLayout>
|
||||
|
||||
<RulesPopup client:idle />
|
||||
|
||||
<Popup client:idle />
|
||||
<TeamPopup client:idle />
|
||||
|
||||
<RegisteredPopup client:idle />
|
||||
|
||||
{
|
||||
!signupEnabled && (
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-black/50 backdrop-blur-sm z-10 rounded-xl flex justify-center items-center flex-col pt-20 lg:pt-0 text-2xl sm:text-4xl">
|
||||
<h1>{signupDisabledMessage}</h1>
|
||||
<h3>{signupDisabledSubMessage}</h3>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<script>
|
||||
import { actions } from 'astro:actions';
|
||||
import { popupState } from '@components/popup/Popup';
|
||||
import { rulesPopupState, rulesPopupRead } from '@components/website/signup/RulesPopup';
|
||||
import { teamPopupName, teamPopupOpen } from '@components/website/signup/TeamPopup';
|
||||
import { registeredPopupState } from '@components/website/signup/RegisteredPopup';
|
||||
|
||||
/* ----- client validation ----- */
|
||||
const rulesCheckbox = document.getElementById('rules')! as HTMLInputElement;
|
||||
const rulesCheckboxRulesLink = rulesCheckbox.nextElementSibling!.querySelector('.link') as HTMLAnchorElement;
|
||||
|
||||
// add popup state subscriber to check when the accepted button is clicked
|
||||
rulesPopupState.subscribe((value) => {
|
||||
if (value == 'accepted') rulesCheckbox.checked = true;
|
||||
});
|
||||
|
||||
// add click handler to open rules popup to rules checkbox
|
||||
rulesCheckbox.addEventListener('click', (e) => {
|
||||
if (!rulesPopupRead.get()) {
|
||||
e.preventDefault();
|
||||
rulesPopupState.set('open');
|
||||
}
|
||||
});
|
||||
|
||||
// add click handler to open rules popup when clicking the rules link in the rules checkbox label
|
||||
rulesCheckboxRulesLink!.addEventListener('click', () => rulesPopupState.set('open'));
|
||||
|
||||
/* send signup */
|
||||
const form = document.getElementById('signup')! as HTMLFormElement;
|
||||
|
||||
async function sendSignup() {
|
||||
const { data, error } = await actions.signup.signup({
|
||||
firstname: form.firstname.value,
|
||||
lastname: form.lastname.value,
|
||||
birthday: form.birthday.value,
|
||||
phone: form.phone.value,
|
||||
username: form.username.value,
|
||||
teamMember: form.teamMember.value,
|
||||
teamName: teamPopupName.get()
|
||||
});
|
||||
|
||||
// must be done in order to show the team popup again if it's closed or an error occurs
|
||||
teamPopupName.set(null);
|
||||
|
||||
if (error) {
|
||||
if (error.code == 'BAD_REQUEST') {
|
||||
teamPopupOpen.set(true);
|
||||
const close = teamPopupName.listen(() => {
|
||||
close();
|
||||
sendSignup();
|
||||
});
|
||||
} else if (error.code == 'CONFLICT' || error.code == 'FORBIDDEN') {
|
||||
popupState.set({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: error.message
|
||||
});
|
||||
} else {
|
||||
popupState.set({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
registeredPopupState.set({
|
||||
firstname: form.firstname.value,
|
||||
lastname: form.lastname.value,
|
||||
birthday: form.birthday.value,
|
||||
phone: form.phone.value,
|
||||
username: form.username.value,
|
||||
team: data.team.name,
|
||||
teamMember: form.teamMember.value,
|
||||
teamColor: data.team.color
|
||||
});
|
||||
|
||||
const cancel = registeredPopupState.subscribe((value) => {
|
||||
if (value) return;
|
||||
cancel();
|
||||
form.reset();
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
sendSignup();
|
||||
});
|
||||
</script>
|
76
src/pages/team.astro
Normal file
76
src/pages/team.astro
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||
const team = [
|
||||
{
|
||||
name: 'Elias',
|
||||
nickname: 'MineTec',
|
||||
roles: ['Gründer', 'Support', 'Organisation', 'Softwareentwicklung', 'Systemadministrator'],
|
||||
links: [{ name: 'Website', href: 'https://mhsl.eu/aboutme/', icon: 'heroicons:globe-alt' }]
|
||||
},
|
||||
{
|
||||
name: 'Jannik',
|
||||
nickname: 'Goldi187',
|
||||
roles: ['Support', 'Organisation']
|
||||
},
|
||||
{
|
||||
name: 'Adrian',
|
||||
nickname: 'h0nny27',
|
||||
roles: ['Support']
|
||||
},
|
||||
{
|
||||
name: 'Ruben',
|
||||
nickname: 'bytedream',
|
||||
roles: ['Softwareentwicklung'],
|
||||
links: [{ name: 'Website', href: 'https://bytedream.dev', icon: 'heroicons:globe-alt' }]
|
||||
},
|
||||
{
|
||||
name: 'Lars',
|
||||
nickname: '28Pupsi28',
|
||||
roles: ['Support', 'Softwareentwicklung'],
|
||||
links: [
|
||||
{
|
||||
name: 'Website',
|
||||
href: 'https://mathemann.ddns.net/turtle_game/',
|
||||
icon: 'heroicons:globe-alt'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Hanad',
|
||||
nickname: 'Voldemort2212',
|
||||
roles: ['Support', 'Mediengestaltung']
|
||||
}
|
||||
];
|
||||
---
|
||||
|
||||
<WebsiteLayout title="Team">
|
||||
<div class="m-auto flex flex-col justify-center items-center px-4 py-6 2xl:px-48 sm:py-12">
|
||||
<h1 class="text-5xl mb-10">Das Team</h1>
|
||||
<div class="grid md:grid-cols-2 xl:grid-cols-3 gap-4 my-4 justify-center">
|
||||
{
|
||||
team.map((member) => (
|
||||
<div class="card max-w-96 bg-base-200">
|
||||
<div class="card-body px-4 py-6">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="avatar placeholder mb-2">
|
||||
<div class="bg-neutral text-neutral-content w-24 rounded-xl">
|
||||
<img
|
||||
class="m-[7.5px]"
|
||||
style="width: 85%; height: 85%"
|
||||
src={`https://mc-heads.net/head/${member.nickname}`}
|
||||
alt={member.name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-lg mb-1">
|
||||
{member.name} · {member.nickname}
|
||||
</p>
|
||||
<p class="text-center text-sm font-light">{member.roles.join(' · ')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</WebsiteLayout>
|
Reference in New Issue
Block a user