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+ */ src: url('/fonts/inter-v18-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
} }
* { pre {
--color-primary-a0: #ffffff; max-width: 100%;
--color-primary-a10: #e1e6fe; overflow: scroll;
--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;
* {
box-sizing: border-box; box-sizing: border-box;
font-size: inherit; font-size: inherit;
} }
@ -29,13 +21,13 @@ html, body {
position: relative; position: relative;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: white;
font-size: 1rem; font-size: 1rem;
background-color: var(--tg-theme-bg-color);
} }
p, a, h1, h2, h3, h4, h5, h6 { p, a, h1, h2, h3, h4, h5, h6 {
font-family: Inter, Arial, sans-serif; font-family: Inter, Arial, sans-serif;
color: var(--text-color); color: var(--tg-theme-text-color);
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
@ -58,3 +50,29 @@ svg {
max-width: 100%; max-width: 100%;
height: auto; 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 () => { export const init = async () => {
initializePocketBase() initializePocketBase()

View file

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

View file

@ -20,8 +20,6 @@
<style> <style>
.event { .event {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
background-color: var(--tg-theme-secondary-bg-color);
border: 2px solid #00000055;
border-radius: 3%;
} }
</style> </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 === "/"}> <li class:selected={page.url.pathname === "/"}>
<a href="/" >Start</a> <a href="/" >Start</a>
</li> </li>
<li class:selected={page.url.pathname === "/events"}> <li class:selected={page.url.pathname.startsWith("/events")}>
<a href="/events">Events</a> <a href="/events">Events</a>
</li> </li>
<li class:selected={page.url.pathname === "/rules"}> <li class:selected={page.url.pathname === "/rules"}>
<a href="/rules">Regeln</a> <a href="/rules">Regeln</a>
</li> </li>
<!--<li class:selected={page.url.pathname === "/debug"}>
<a href="/debug">Debug</a>
</li>-->
</ul> </ul>
</nav> </nav>
@ -29,15 +32,17 @@
li { li {
transition: border-bottom-color linear 0.2s; transition: border-bottom-color linear 0.2s;
padding: 0.6rem 0.45rem; 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 { &.selected {
border-bottom: 3px var(--color-primary-a40) solid; filter: none;
border-bottom: 3px var(--tg-theme-button-color) solid;
} }
} }
a { a {
font-size: 1.5rem; font-size: 1.35rem;
text-decoration: none; text-decoration: none;
} }
</style> </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 FooterNavigation from "$lib/components/FooterNavigation.svelte";
import ContainerGridSingle from "$lib/components/ContainerGridSingle.svelte"; import ContainerGridSingle from "$lib/components/ContainerGridSingle.svelte";
const { children } = $props(); const { children } = $props();
$effect(() => {
Telegram?.WebApp?.ready();
Telegram?.WebApp?.setBackgroundColor('FFFFFF');
});
</script> </script>
<ContainerGridSingle> <ContainerGridSingle>
@ -23,5 +17,6 @@
<style> <style>
.footer-inner { .footer-inner {
margin-top: 1rem; margin-top: 1rem;
background-color: var(--tg-theme-bg-color);
} }
</style> </style>

View file

@ -1,25 +1,19 @@
<script> <script>
import RoundedAvatar from "$lib/components/RoundedAvatar.svelte"; 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> </script>
<section id="intro"> <section id="intro">
{#if user} {#if telegram && telegram?.user}
{@const user = telegram.user}
<RoundedAvatar src={user?.photo_url}></RoundedAvatar> <RoundedAvatar src={user?.photo_url}></RoundedAvatar>
<h2>Hallo {user.first_name}!</h2> <h2>Hallo {user?.first_name}!</h2>
{:else} {:else}
<RoundedAvatar src={null}></RoundedAvatar> <RoundedAvatar src={null}></RoundedAvatar>
<h2>Hallo Besucher!</h2> <h2>Hallo Besucher!</h2>
{/if} {/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> </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 {Tile} from 'ol/layer';
import {useGeographic} from "ol/proj"; import {useGeographic} from "ol/proj";
import {onMount} from "svelte"; import {onMount} from "svelte";
import EventRegisterButton from "$lib/components/EventRegisterButton.svelte";
import {useTelegram} from "$lib/telegram.svelte.js";
const { data } = $props(); const { data } = $props();
const { event } = data; const { event } = data;
const telegram = useTelegram();
let mapRef; let mapRef;
@ -43,14 +46,36 @@
<p>{event?.description ?? "No description"}</p> <p>{event?.description ?? "No description"}</p>
{/if} {/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"> <div class="event-location-details">
<h2>Location</h2> <h2>Wo?</h2>
<p class="location-address"> <p class="location-address">
{event?.expand?.location?.name}<br /> {event?.expand?.location?.name}<br />
{event?.expand?.location?.address} {#each event?.expand?.location?.address.split(', ') as part}
{part}<br />
{/each}
</p> </p>
<div class="event-map" bind:this={mapRef}></div> <div class="event-map" bind:this={mapRef}></div>
</div> </div>
</div>
<div class="event-register">
<h2>Anmeldung</h2>
<EventRegisterButton eventId={event?.id} />
</div> </div>
<style> <style>
@ -59,13 +84,21 @@
height: 50vh; height: 50vh;
} }
.event-location-details { .event-location-details, .event-register, .event-date-time{
padding: 0 1rem 1rem 1rem; padding: 1rem;
border: 2px solid #00000055; background-color: var(--tg-theme-secondary-bg-color);
border-radius: 3%; border-radius: 5%;
h2 {
margin-top: 0;
}
} }
.location-address { .location-address {
margin-top: 0; margin-top: 0;
} }
.event-register, .event-date-time {
margin: 1rem 0;
}
</style> </style>

View file

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