Finish up most content and pages

This commit is contained in:
Leon Grünewald 2025-02-11 00:59:46 +01:00
parent 17b69c2a81
commit 6144a5b9d5
13 changed files with 209 additions and 50 deletions

View file

@ -7,20 +7,12 @@
src: url('/fonts/inter-v18-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
* {
--color-primary-a0: #ffffff;
--color-primary-a10: #e1e6fe;
--color-primary-a20: #c2cdfd;
--color-primary-a30: #a1b5fb;
--color-primary-a40: #7d9df9;
--color-primary-a50: #5087f6;
--color-primary-a60: #436bc0;
--color-primary-a70: #364f8c;
--color-primary-a80: #28365c;
--color-primary-a90: #191e2f;
--color-primary-a100: #000000;
--text-color: black;
pre {
max-width: 100%;
overflow: scroll;
}
* {
box-sizing: border-box;
font-size: inherit;
}
@ -29,13 +21,13 @@ html, body {
position: relative;
margin: 0;
padding: 0;
background-color: white;
font-size: 1rem;
background-color: var(--tg-theme-bg-color);
}
p, a, h1, h2, h3, h4, h5, h6 {
font-family: Inter, Arial, sans-serif;
color: var(--text-color);
color: var(--tg-theme-text-color);
}
h1, h2, h3, h4, h5, h6 {
@ -58,3 +50,29 @@ svg {
max-width: 100%;
height: auto;
}
a {
color: var(--tg-theme-link-color);
}
button {
padding: 0.45rem 0.65rem;
background-color: var(--tg-theme-button-color);
color: var(--tg-theme-button-text-color);
font-size: 1.35rem;
border: none;
&:disabled {
filter: grayscale(1.0);
}
}
label {
color: var(--tg-theme-text-color);
}
input {
color: var(--tg-theme-text-color);
background-color: var(--tg-theme-secondary-bg-color);
}

View file

@ -1,4 +1,4 @@
import {initializePocketBase} from "$lib/pocketbase.js";
import { initializePocketBase } from "$lib/pocketbase.js";
export const init = async () => {
initializePocketBase()

View file

@ -13,6 +13,8 @@
<style>
.container-grid {
max-width: 800px;
margin: 0 auto;
display: grid;
height: 100vh;
grid-template-columns: [content-full-start] 2rem [content-start] 1fr [content-end] 2rem [content-full-end];
@ -30,7 +32,5 @@
position: sticky;
grid-column: content;
grid-row: content-footer;
background-color: white;
}
</style>

View file

@ -20,8 +20,6 @@
<style>
.event {
padding: 0.5rem 1rem;
border: 2px solid #00000055;
border-radius: 3%;
background-color: var(--tg-theme-secondary-bg-color);
}
</style>

View file

@ -0,0 +1,39 @@
<script>
import {useTelegram} from "$lib/telegram.svelte.js";
const { eventId = null } = $props();
const telegram = useTelegram();
let dataPrivacyConsent = $state(false);
let ageConsent = $state(false)
let buttonEnabled = $derived(() => dataPrivacyConsent && ageConsent && telegram?.initData);
</script>
{#if eventId}
{#if telegram?.initData}
<form method="POST" action="?/register">
<input name="init-data" type="hidden" value={telegram?.initData} />
<input name="event-id" type="hidden" value={eventId} />
<div class="form-row">
<label><input type="checkbox" bind:checked={dataPrivacyConsent}> Ich stimme zu das meine Daten bis zu 14 Tagen nach der Veranstaltung beibehalten werden</label>
</div>
<div class="form-row">
<label><input type="checkbox" bind:checked={ageConsent}> Ich bin mindestens 16 Jahre alt oder komme in Begleitung eines Erwachsenen</label>
</div>
<div class="form-row">
<button disabled={!buttonEnabled()}>Anmelden</button>
</div>
</form>
{:else}
<p>Bitte öffne diese Mini App im Telegram Bot um dich anzumelden! <a target="_blank" href="https://t.me/doggoatbot?startapp">Öffnen</a></p>
{/if}
{:else}
<p>Invalide Event-ID</p>
{/if}
<style>
form {
display: flex;
flex-flow: column wrap;
gap: 1rem;
}
</style>

View file

@ -7,12 +7,15 @@
<li class:selected={page.url.pathname === "/"}>
<a href="/" >Start</a>
</li>
<li class:selected={page.url.pathname === "/events"}>
<li class:selected={page.url.pathname.startsWith("/events")}>
<a href="/events">Events</a>
</li>
<li class:selected={page.url.pathname === "/rules"}>
<a href="/rules">Regeln</a>
</li>
<!--<li class:selected={page.url.pathname === "/debug"}>
<a href="/debug">Debug</a>
</li>-->
</ul>
</nav>
@ -29,15 +32,17 @@
li {
transition: border-bottom-color linear 0.2s;
padding: 0.6rem 0.45rem;
border-bottom: 3px var(--color-primary-a80) solid;
border-bottom: 3px var(--tg-theme-button-color) solid;
filter: grayscale(0.8);
&.selected {
border-bottom: 3px var(--color-primary-a40) solid;
filter: none;
border-bottom: 3px var(--tg-theme-button-color) solid;
}
}
a {
font-size: 1.5rem;
font-size: 1.35rem;
text-decoration: none;
}
</style>

View file

@ -0,0 +1,34 @@
const telegram = $state({user: null, initData: null})
let checkCount = $state(0);
export function useTelegram() {
const checkTelegramUser = () => {
checkCount = checkCount+1;
if (typeof window === 'undefined') {
setTimeout(checkTelegramUser, 100);
return;
}
telegram.initData = Telegram?.WebApp?.initData;
console.log(Telegram?.WebApp);
if (!telegram.initData) {
setTimeout(checkTelegramUser, 100);
console.warn("initData could not be loaded");
return;
}
const searchParams = new URLSearchParams(telegram.initData);
const tempUser = searchParams.get('user');
if (!tempUser) {
console.warn("User could not be loaded");
return;
}
telegram.user = JSON.parse(tempUser);
}
$effect(() => {
setTimeout(checkTelegramUser, 100);
});
return telegram
}

View file

@ -3,12 +3,6 @@
import FooterNavigation from "$lib/components/FooterNavigation.svelte";
import ContainerGridSingle from "$lib/components/ContainerGridSingle.svelte";
const { children } = $props();
$effect(() => {
Telegram?.WebApp?.ready();
Telegram?.WebApp?.setBackgroundColor('FFFFFF');
});
</script>
<ContainerGridSingle>
@ -23,5 +17,6 @@
<style>
.footer-inner {
margin-top: 1rem;
background-color: var(--tg-theme-bg-color);
}
</style>

View file

@ -1,25 +1,19 @@
<script>
import RoundedAvatar from "$lib/components/RoundedAvatar.svelte";
let user = $state(null);
import { useTelegram } from "$lib/telegram.svelte.js";
const telegram = useTelegram();
$effect(() => {
const searchParams = new URLSearchParams(Telegram?.WebApp?.initData);
const tempUser = searchParams.get('user');
if (!tempUser) {
console.warn("User could not be loaded");
return;
}
user = JSON.parse(tempUser);
})
</script>
<section id="intro">
{#if user}
{#if telegram && telegram?.user}
{@const user = telegram.user}
<RoundedAvatar src={user?.photo_url}></RoundedAvatar>
<h2>Hallo {user.first_name}!</h2>
<h2>Hallo {user?.first_name}!</h2>
{:else}
<RoundedAvatar src={null}></RoundedAvatar>
<h2>Hallo Besucher!</h2>
{/if}
<p>Du befinest dich auf der Anmeldeseite des Kölner Stammtischs. Schau dir die unteren Menüpunkte an, falls du mehr erfahren möchtest.</p>
<p>Du befindest dich auf der Mini App des Kölner Stammtischs. Schau dir die unteren Menüpunkte an, falls du mehr erfahren möchtest.</p>
</section>

View file

@ -0,0 +1,8 @@
<script>
import { useTelegram } from "$lib/telegram.svelte.js";
const telegram = useTelegram()
</script>
<pre>
{JSON.stringify(telegram.user, null, 4)}
</pre>

View file

@ -0,0 +1,34 @@
import { subtle } from 'node:crypto';
import {PRIVATE_TELEGRAM_BOT_ID, PRIVATE_TELEGRAM_PUBLIC_KEY} from "$env/static/private";
export const actions = {
register: async ({ request }) => {
const formData = await request.formData();
if (!formData) return { success: false, error: "No valid form data was provided" };
const initDataUrlEncoded = formData.get('init-data');
if (!initDataUrlEncoded) return { success: false, error: "No valid form data was provided" };
const eventId = formData.get('event-id');
if (!eventId) return { success: false, error: "Missing event-id" };
const dataArrayParams = new URLSearchParams(initDataUrlEncoded)
const initDataArray = [...dataArrayParams];
const signature = dataArrayParams.get('signature');
if (!signature) return { success: false, error: "No signature was provided" };
initDataArray.sort((d1, d2) => d1[0].charCodeAt(0) - d2[0].charCodeAt(0));
const checkString = initDataArray.reduce((acc, currentValue) => {
if (['signature', 'hash'].includes(currentValue[0])) return acc;
acc.push(currentValue.join("="))
return acc;
}, [`${PRIVATE_TELEGRAM_BOT_ID}:WebAppData`]).join("\n")
const algo = 'Ed25519';
const key = await subtle.importKey("raw", Buffer.from(PRIVATE_TELEGRAM_PUBLIC_KEY, 'hex'), algo, false, ['verify']);
const validSignature = await subtle.verify(algo, key, Buffer.from(signature, 'base64url'), Buffer.from(checkString));
if (!validSignature) return { success: false, error: "Invalid signature" }
return { success: true, error: null }
}
}

View file

@ -5,9 +5,12 @@
import {Tile} from 'ol/layer';
import {useGeographic} from "ol/proj";
import {onMount} from "svelte";
import EventRegisterButton from "$lib/components/EventRegisterButton.svelte";
import {useTelegram} from "$lib/telegram.svelte.js";
const { data } = $props();
const { event } = data;
const telegram = useTelegram();
let mapRef;
@ -43,14 +46,36 @@
<p>{event?.description ?? "No description"}</p>
{/if}
{#if event?.start}
{@const date = new Date(event.start)}
<div class="event-date-time">
<h2>Wann?</h2>
{#if telegram?.user?.language_code}
<p>{date.toLocaleDateString(telegram.user.language_code, {dateStyle: 'long'})}<br />
{date.toLocaleTimeString(telegram.user.language_code, {timeStyle: 'long'})}</p>
{:else}
<p>{date.toLocaleDateString('en', {dateStyle: 'long'})}<br />
{date.toLocaleTimeString('en', {timeStyle: 'long'})}</p>
{/if}
</div>
{/if}
<div class="event-location-details">
<h2>Location</h2>
<h2>Wo?</h2>
<p class="location-address">
{event?.expand?.location?.name}<br />
{event?.expand?.location?.address}
{#each event?.expand?.location?.address.split(', ') as part}
{part}<br />
{/each}
</p>
<div class="event-map" bind:this={mapRef}></div>
</div>
</div>
<div class="event-register">
<h2>Anmeldung</h2>
<EventRegisterButton eventId={event?.id} />
</div>
<style>
@ -59,13 +84,21 @@
height: 50vh;
}
.event-location-details {
padding: 0 1rem 1rem 1rem;
border: 2px solid #00000055;
border-radius: 3%;
.event-location-details, .event-register, .event-date-time{
padding: 1rem;
background-color: var(--tg-theme-secondary-bg-color);
border-radius: 5%;
h2 {
margin-top: 0;
}
}
.location-address {
margin-top: 0;
}
.event-register, .event-date-time {
margin: 1rem 0;
}
</style>

View file

@ -5,6 +5,7 @@
<style>
iframe {
background-color: white;
height: 100%;
width: 100%;
}