Event Page (#36)

* created event block component, tabs wip

* adding icons and changing type to EventPost

* refactor event block to tailwind (antoine help)

* revert nav bar to black

* using tabs from skeleton

* Fix Event tabs CSS classses and create Tab Control component

* adding space between the tabs

* created EventTabPanel component

* small styling fix

* sorting events by date

---------

Co-authored-by: Antoine Phan <antoine.phan@mail.mcgill.ca>
This commit is contained in:
Mel
2025-08-28 20:30:31 -04:00
committed by GitHub
parent 9acc9b1ae1
commit 984e7895e8
15 changed files with 884 additions and 179 deletions

805
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -13,25 +13,30 @@
}, },
"devDependencies": { "devDependencies": {
"@portabletext/svelte": "^3.0.0", "@portabletext/svelte": "^3.0.0",
"@skeletonlabs/skeleton": "^3.1.7", "@skeletonlabs/skeleton": "^3.2.0",
"@skeletonlabs/skeleton-svelte": "^1.3.1", "@skeletonlabs/skeleton-svelte": "^1.5.0",
"@sveltejs/adapter-auto": "^6.0.2", "@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.27.3", "@sveltejs/adapter-node": "^5.3.1",
"@sveltejs/kit": "^2.36.0",
"@sveltejs/vite-plugin-svelte": "^5.1.1", "@sveltejs/vite-plugin-svelte": "^5.1.1",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.12",
"@vercel/analytics": "^1.5.0",
"@vercel/speed-insights": "^1.2.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.6.14", "prettier-plugin-tailwindcss": "^0.6.14",
"svelte": "^5.38.1", "svelte": "^5.38.2",
"svelte-check": "^4.3.1", "svelte-check": "^4.3.1",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.12",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"vite": "^6.3.5" "vite": "^6.3.5"
}, },
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@lucide/svelte": "^0.539.0", "@lucide/svelte": "^0.540.0",
"@sanity/client": "^7.8.2" "@sanity/client": "^7.9.0",
"vercel": "^46.0.2"
} }
} }

View File

@@ -6,8 +6,8 @@
--color-ecsess-400: #5c8a5c; --color-ecsess-400: #5c8a5c;
--color-ecsess-600: #3b6a3a; --color-ecsess-600: #3b6a3a;
--color-ecsess-800: #0a3d2a; --color-ecsess-800: #0a3d2a;
--color-ecsess-black: #1f1f1f; --color-ecsess-black: #1F1F1F;
--color-ecsess-black-hover: #2c2c2c; --color-ecsess-black-hover: #168059;
} }
* { * {
@@ -184,3 +184,7 @@ h2 {
@apply mt-3 text-sm leading-5; @apply mt-3 text-sm leading-5;
} }
} }
.event{
@apply grid gap-6;
}

View File

@@ -0,0 +1,106 @@
<script lang="ts">
import { PortableText } from '@portabletext/svelte';
import { CalendarDays, MapPin, Link as LinkIcon, FilePen } from '@lucide/svelte';
let {
eventTitle,
date,
location,
eventDescription,
thumbnail,
registrationLink,
paymentLink,
eventCategory
} = $props();
</script>
<div
class="mx-auto lg:w-[64%] lg:max-w-3xl w-[100%] rounded-2xl bg-[#E8FFD9] p-5 text-[#0A3D2A]"
>
<div class="rounded-[20px] bg-[#A6D6B8]">
<div
class="grid h-[200px] place-items-center overflow-hidden rounded-[16px] bg-[#5CAF95]"
aria-label="Event banner"
>
{#if thumbnail}
<img class="h-full object-fill" src={thumbnail} alt="Event banner" />
{:else if eventCategory?.[0] === 'social'}
<img class="h-full object-fill" src="/Social.jpg" alt="Social Placeholder" />
{:else if eventCategory?.[0] === 'technical'}
<img class="h-full object-fill" src="/Technical.jpg" alt="Technical Placeholder" />
{:else if eventCategory?.[0] === 'professional'}
<img class="h-full w-full object-cover" src="/Professional.jpg" alt="Professional Placeholder" />
{:else if eventCategory?.[0] === 'academic'}
<img class="h-full object-fill" src="/Academic.jpg" alt="Academic Placeholder" />
{:else}
<img class="h-full object-fill" src="/ECSESS.png" alt="Default Placeholder" />
{/if}
</div>
</div>
<!-- content -->
<div class="mt-[22px] grid gap-[18px]">
<p class="m-0 text-center text-[clamp(26px,3vw,34px)] leading-[1.05] tracking-[0.3px] text-[#0A3D2A]">
{eventTitle}
</p>
{#if eventDescription}
<div class="max-w-[75ch] mx-auto leading-relaxed text-[#5E8174]">
<PortableText value={eventDescription} />
</div>
{/if}
<div class="mt-[6px] grid gap-4 md:grid-cols-2">
<div class="grid gap-[10px] rounded-2xl bg-[#CCE7BA] px-4 py-[14px]">
<div class="flex items-center gap-2 text-[#0A3D2A]">
<CalendarDays class="shrink-0" strokeWidth={2.5} />
<span class="font-bold tracking-[0.2px]">Datetime:</span>
<p class="m-0 text-left">{date}</p>
</div>
<div class="flex items-center gap-2 text-[#0A3D2A]">
<MapPin class="shrink-0" strokeWidth={2.5} />
<span class="font-bold tracking-[0.2px]">Location:</span>
<p class="m-0 text-left">{location ?? 'TBA'}</p>
</div>
</div>
<div class="grid gap-[10px] rounded-2xl bg-[#CCE7BA] px-4 py-[14px]">
<div class="flex items-center gap-2 text-[#0A3D2A]">
<FilePen class="shrink-0" strokeWidth={2.5} />
<span class="font-bold tracking-[0.2px]">Registration:</span>
{#if registrationLink}
<a
href={registrationLink}
target="_blank"
rel="noopener noreferrer"
class="text-left text-[#0A3D2A] underline-offset-4 hover:underline"
>
Register Here
</a>
{:else}
<p class="m-0 text-left">Just drop in!</p>
{/if}
</div>
<div class="flex items-center gap-2 text-[#0A3D2A]">
<LinkIcon class="shrink-0" strokeWidth={2.5} />
<span class="font-bold tracking-[0.2px]">Payment:</span>
{#if paymentLink}
<a
href={paymentLink}
target="_blank"
rel="noopener noreferrer"
class="text-left text-[#0A3D2A] underline-offset-4 hover:underline"
>
Pay Here
</a>
{:else}
<p class="m-0 text-left">Free!</p>
{/if}
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,12 @@
<script>
import { Tabs } from '@skeletonlabs/skeleton-svelte';
let { value, children } = $props();
</script>
<Tabs.Control
{value}
classes="hover:border-b-ecsess-200 border-b-4 transition-all ease-in-out pb-2 text-lg active:border-b-ecsess-600 px-2"
stateActive="border-b-ecsess-400"
>
{@render children()}
</Tabs.Control>

View File

@@ -0,0 +1,38 @@
<script lang="ts">
import { Tabs } from '@skeletonlabs/skeleton-svelte';
import EventBlock from 'components/EventBlock.svelte';
import type { EventPost } from '$lib/schemas';
type Category = 'allEvents' | 'academic' | 'professional' | 'social' | 'technical';
let { value, category, events } = $props<{
value: Category;
category: Category;
events: EventPost[];
}>();
const matchCategory = (e: EventPost): boolean => {
if (category === 'allEvents') return true;
const c: unknown = e.category ?? [];
return Array.isArray(c) ? c.includes(category) : (c as string) === category;
};
const filtered = $derived((events ?? []).filter(matchCategory));
</script>
<Tabs.Panel {value}>
<div class="flex flex-wrap gap-4 m-1 lg:m-4">
{#each filtered as e (e._id ?? e.name)}
<EventBlock
eventTitle={e.name}
date={e.date}
location={e.location}
eventDescription={e.description}
thumbnail={e.thumbnail}
registrationLink={e.reglink}
paymentLink={e.paylink}
eventCategory={e.category}
/>
{/each}
</div>
</Tabs.Panel>

View File

@@ -1,14 +1,14 @@
export type EventPost = { export type EventPost = {
id: string; id: string;
title: string; name: string;
description: string; description: string;
date: string; date: string;
time: string; time: string;
location: string; location: string;
image: string; thumbnail: string;
link: string; reglink: string;
category: string; category: string;
payment: string; // event payment link (e.g., Zeffy) paylink: string; // event payment link (e.g., Zeffy)
}; };
export type FAQ = { export type FAQ = {

View File

@@ -1,17 +1,25 @@
import type { EventPost } from '$lib/schemas';
import { getFromCMS } from '$lib/utils.js'; import { getFromCMS } from '$lib/utils.js';
// needs to concat and format this text
const eventQuery = `*[_type == "events"]{ const eventQuery = `*[_type == "events"]{
name, name,
category, category,
date, date,
location, location,
description, description,
reglink,
paylink,
"thumbnail": thumbnail.asset->url,
"lastUpdated": _updatedAt, "lastUpdated": _updatedAt,
}`; }`;
export const load = async () => { export const load = async () => {
let listOfEvents: EventPost[] = await getFromCMS(eventQuery);
//TODO: Why is this mad?
let sortedEvents = listOfEvents.sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
);
return { return {
events: await getFromCMS(eventQuery), events: sortedEvents
}; };
}; };

View File

@@ -1,29 +1,32 @@
<script> <script lang="ts">
import { PortableText } from '@portabletext/svelte';
import Section from 'components/Section.svelte'; import Section from 'components/Section.svelte';
let { data } = $props(); import EventTabControl from 'components/EventTabControl.svelte';
</script> import { Tabs } from '@skeletonlabs/skeleton-svelte';
import EventTabPanel from 'components/EventTabPanel.svelte';
import type { EventPost } from '$lib/schemas';
<title> ECSESS Events </title> let { data } = $props();
let events: EventPost[] = data.events ?? [];
let group = $state('allEvents');
</script>
<Section> <Section>
<p class="page-title">Events</p> <p class="page-title">Events</p>
{#each data.events as event} <Tabs value={group} onValueChange={(e) => (group = e.value)} listClasses="flex-wrap place-content-center">
<div class="rounded-lg border-4 p-4"> {#snippet list()}
<p>{event.name}</p> <EventTabControl value="allEvents">All Events</EventTabControl>
<p>{event.date}</p> <EventTabControl value="academic">Academic</EventTabControl>
<p>{event.location}</p> <EventTabControl value="professional">Professional</EventTabControl>
{#if event.description} <EventTabControl value="social">Social</EventTabControl>
<PortableText value={event.description} /> <EventTabControl value="technical">Technical</EventTabControl>
{/if} {/snippet}
Category:
<div class="list"> {#snippet content()}
<ul class="list-inside list-disc space-y-2"> <EventTabPanel value="allEvents" category="allEvents" {events} />
{#each event.category as cat} <EventTabPanel value="academic" category="academic" {events} />
<li>{cat}</li> <EventTabPanel value="professional" category="professional" {events} />
{/each} <EventTabPanel value="social" category="social" {events} />
</ul> <EventTabPanel value="technical" category="technical" {events} />
</div> {/snippet}
</div> </Tabs>
{/each}
</Section> </Section>

BIN
static/Academic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

BIN
static/Professional.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

BIN
static/Social.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

BIN
static/Technical.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 423 KiB