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:
23
package.json
23
package.json
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
12
src/components/EventTabControl.svelte
Normal file
12
src/components/EventTabControl.svelte
Normal 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>
|
||||||
38
src/components/EventTabPanel.svelte
Normal file
38
src/components/EventTabPanel.svelte
Normal 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>
|
||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
BIN
static/Academic.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
BIN
static/Professional.jpg
Normal file
BIN
static/Professional.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 191 KiB |
BIN
static/Social.jpg
Normal file
BIN
static/Social.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
BIN
static/Technical.jpg
Normal file
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 |
Reference in New Issue
Block a user