Skip to content

Commit fa47c87

Browse files
committed
Merge remote-tracking branch 'origin/main' into issue-46-Add_custom_404_page
2 parents 0f73c1a + 254d5d4 commit fa47c87

30 files changed

Lines changed: 1451 additions & 109 deletions

client/next.config.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ const config = {
1313
},
1414
outputFileTracingRoot: import.meta.dirname,
1515
images: {
16-
domains: ["localhost"],
16+
remotePatterns: [
17+
{ protocol: 'http', hostname: '127.0.0.1' },
18+
{ protocol: 'http', hostname: 'localhost' },
19+
],
1720
},
1821
// Turns on file change polling for the Windows Dev Container
1922
// Doesn't work currently for turbopack, so file changes will not automatically update the client.

client/public/Constitution-V1.pdf

187 KB
Binary file not shown.

client/public/frame.svg

Lines changed: 9 additions & 0 deletions
Loading

client/public/heart.png

803 Bytes
Loading

client/src/components/main/Footer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,9 @@ export default function Footer() {
164164
<span>All rights reserved</span>
165165
</div>
166166
<Link
167-
href="/constitution"
167+
href=""
168168
className="group flex -translate-x-[52px] items-center gap-2.5 rounded-full border border-purple-500/20 bg-gradient-to-r from-purple-500/10 to-pink-500/10 px-4 py-2 transition-all duration-300 hover:border-purple-500/40 hover:shadow-lg hover:shadow-purple-500/20"
169+
onClick={() => window.open("/Constitution-V1.pdf")}
169170
>
170171
<span className="font-jersey10 text-xl text-gray-300 transition-colors group-hover:text-white">
171172
Constitution
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"use client";
2+
3+
import Image from "next/image";
4+
5+
// unused atm, as the member isnt linked a project on the backend
6+
/* export type MemberProfileProject = {
7+
id: string;
8+
name: string;
9+
description?: string;
10+
href?: string;
11+
}; */
12+
13+
export type MemberProfileData = {
14+
name: string;
15+
about: string;
16+
pronouns?: string;
17+
profile_picture?: string;
18+
};
19+
20+
type MemberProfileProps = {
21+
member: MemberProfileData;
22+
//projects?: MemberProfileProject[];
23+
};
24+
25+
function initialsFromName(name: string) {
26+
return name
27+
.trim()
28+
.split(/\s+/)
29+
.slice(0, 2)
30+
.map((part) => part[0]?.toUpperCase())
31+
.join("");
32+
}
33+
34+
export function MemberProfile({ member }: MemberProfileProps) {
35+
const initials = initialsFromName(member.name);
36+
37+
return (
38+
<>
39+
<div className="m-auto h-fit bg-card text-light_2">
40+
<div className="mx-2 flex flex-wrap items-center justify-center gap-y-5 py-7 lg:mx-10">
41+
<div className="grid grid-cols-1 grid-rows-1 items-center justify-items-center lg:mr-6">
42+
<div className="absolute size-32 overflow-clip bg-accent text-center">
43+
{member.profile_picture ? (
44+
<Image
45+
src={member.profile_picture}
46+
alt={`${member.name} profile picture`}
47+
fill
48+
className="object-cover"
49+
/>
50+
) : (
51+
<div className="flex h-full w-full items-center justify-center font-jersey10 text-5xl text-muted-foreground">
52+
{initials}
53+
</div>
54+
)}
55+
</div>
56+
<Image
57+
src="/frame.svg"
58+
alt="golden pixel art frame around profile picture"
59+
width={176}
60+
height={192}
61+
className="z-0 h-48 w-44"
62+
/>
63+
</div>
64+
<div className="flex w-4/5 flex-col gap-2 rounded-md p-2.5 font-firaCode">
65+
<div className="flex">
66+
<p className="min-w-fit font-jersey10 text-4xl">{member.name}</p>
67+
<hr className="ml-5 hidden w-full self-center border-light_2 lg:flex" />
68+
</div>
69+
<p className="text-lg">{member.pronouns}</p>
70+
<p>{member.about}</p>
71+
</div>
72+
</div>
73+
</div>
74+
{/* Template for Projects section */}
75+
<div className="m-auto min-h-80 w-11/12">
76+
<h2 className="mt-7 text-center font-jersey10 text-5xl">Projects</h2>
77+
<div className="m-auto my-5 flex flex-wrap justify-center gap-8">
78+
{/* Div below is a single project card */}
79+
<div className="w-fit rounded-md p-5">
80+
<div className="mb-2 h-44 w-96 overflow-clip rounded-md p-5 text-neutral_1">
81+
{/* Image and/or Link to Project */}
82+
</div>
83+
<p className="max-w-96 font-firaCode text-xl font-semibold">
84+
{/* Project Title */}
85+
</p>
86+
<p className="line-clamp-1 max-w-96 font-firaCode text-[--light-3]">
87+
{/* Project description */}
88+
</p>
89+
</div>
90+
</div>
91+
</div>
92+
</>
93+
);
94+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
type ItchEmbedProps = {
2+
embedID: string;
3+
name: string;
4+
};
5+
6+
export function ItchEmbed({ embedID, name }: ItchEmbedProps) {
7+
return (
8+
<div className="mb-6 w-full max-w-[552px] px-4 shadow-[0_12px_40px_-16px_hsl(var(--secondary)_/_0.45)] sm:aspect-[552/167] sm:px-0">
9+
<iframe
10+
className="h-full w-full border-0"
11+
src={`https://itch.io/embed/${embedID}?dark=1`}
12+
title={name}
13+
allowFullScreen
14+
/>
15+
</div>
16+
);
17+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { ChevronLeft, ChevronRight } from "lucide-react";
2+
import Image from "next/image";
3+
import Link from "next/link";
4+
import { useEffect, useRef, useState } from "react";
5+
6+
import { UiEvent as EventType } from "@/hooks/useEvents";
7+
8+
type EventCarouselProps = {
9+
items: EventType[];
10+
};
11+
12+
const GAP = 40;
13+
14+
export default function EventCarousel({ items }: EventCarouselProps) {
15+
const viewportRef = useRef<HTMLDivElement>(null);
16+
const firstItemRef = useRef<HTMLDivElement>(null);
17+
18+
const [currentIndex, setCurrentIndex] = useState(0);
19+
const [visibleCount, setVisibleCount] = useState(3);
20+
const [itemWidth, setItemWidth] = useState(0);
21+
22+
const maxIndex = Math.max(items.length - visibleCount, 0);
23+
const slideLeft = () => {
24+
setCurrentIndex((prev) => Math.max(prev - 1, 0));
25+
};
26+
const slideRight = () => {
27+
setCurrentIndex((prev) => Math.min(prev + 1, maxIndex));
28+
};
29+
const translateX = -(currentIndex * (itemWidth + GAP));
30+
31+
/* Observe item width */
32+
useEffect(() => {
33+
if (!firstItemRef.current) return;
34+
const observer = new ResizeObserver(() => {
35+
const width = firstItemRef.current?.clientWidth ?? 0;
36+
setItemWidth(width);
37+
});
38+
observer.observe(firstItemRef.current);
39+
return () => observer.disconnect();
40+
}, []);
41+
42+
useEffect(() => {
43+
const updateVisibleCount = () => {
44+
if (window.innerWidth < 768) {
45+
setVisibleCount(1);
46+
} else {
47+
setVisibleCount(3);
48+
}
49+
};
50+
updateVisibleCount();
51+
window.addEventListener("resize", updateVisibleCount);
52+
return () => window.removeEventListener("resize", updateVisibleCount);
53+
}, []);
54+
55+
return (
56+
<div className="container mx-auto rounded-lg bg-primary-foreground px-4 py-8 lg:px-12">
57+
<div className="flex items-center justify-between px-10">
58+
<div className="flex items-center">
59+
<h2 className="font-jersey10 text-4xl tracking-wide text-white">
60+
Upcoming Events
61+
</h2>
62+
63+
<div className="ml-5 flex gap-3 text-lg text-white/60">
64+
<ChevronLeft
65+
className={`hover:text-white ${
66+
currentIndex === 0 ? "opacity-40" : "cursor-pointer"
67+
}`}
68+
onClick={slideLeft}
69+
/>
70+
<ChevronRight
71+
className={`hover:text-white ${
72+
currentIndex === maxIndex ? "opacity-40" : "cursor-pointer"
73+
}`}
74+
onClick={slideRight}
75+
/>
76+
</div>
77+
</div>
78+
79+
<Link href="/events" className="font-jersey10">
80+
See More
81+
</Link>
82+
</div>
83+
84+
<div className="mt-10 px-10">
85+
<div ref={viewportRef} className="overflow-hidden">
86+
<div
87+
className="flex transition-transform duration-300 ease-out"
88+
style={{
89+
gap: GAP,
90+
transform: `translateX(${translateX}px)`,
91+
}}
92+
>
93+
{items.map((event, index) => (
94+
<div
95+
key={event.id}
96+
ref={index === 0 ? firstItemRef : undefined}
97+
className="w-full flex-shrink-0 md:w-[calc((100%-80px)/3)]"
98+
>
99+
<div className="relative aspect-[16/9] w-full overflow-hidden rounded-lg">
100+
<Image
101+
src={event.coverImage}
102+
alt={event.name}
103+
fill
104+
className="object-cover"
105+
/>
106+
</div>
107+
108+
<h3 className="mt-6 font-firaCode text-lg font-semibold tracking-wide text-white">
109+
{event.name}
110+
</h3>
111+
112+
{/* Needs proper processing and laying out */}
113+
<p className="text-sm tracking-wide text-white/70">
114+
{event.startTime}
115+
</p>
116+
117+
<div className="mt-3 w-full border-b border-white/20" />
118+
</div>
119+
))}
120+
</div>
121+
</div>
122+
</div>
123+
</div>
124+
);
125+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import Image from "next/image";
2+
3+
export type eventHighlightCardImage = {
4+
url: string;
5+
width: number;
6+
height: number;
7+
alt: string;
8+
};
9+
10+
export type eventHighlightCardType = {
11+
id: number;
12+
title: string;
13+
description: string;
14+
type: string;
15+
image: eventHighlightCardImage | null;
16+
row: number;
17+
};
18+
19+
// Purple card header section.
20+
const renderCardHeader = (card: eventHighlightCardType) => {
21+
// Renders differently if we want the techno border.
22+
if (card.type === "special-border") {
23+
return (
24+
<div
25+
style={{
26+
clipPath:
27+
"polygon(0% 0%, 71% 0%, 78% 7px, 100% 7px, 100% calc(100% - 8px), 0% calc(100% - 8px))",
28+
}}
29+
className="relative bg-accent"
30+
>
31+
<div
32+
style={{
33+
clipPath:
34+
"polygon(1px 1px, calc(71% - 1px) 1px, calc(78% - 1px) 8px, calc(100% - 1px) 8px, calc(100% - 1px) calc(100% - 8px - 1px), 1px calc(100% - 8px - 1px))",
35+
}}
36+
className="bg-dark_alt p-4 pt-3 font-jersey10 text-2xl font-semibold"
37+
>
38+
{card.title}
39+
</div>
40+
</div>
41+
);
42+
}
43+
44+
return (
45+
<div className="rounded-md border border-accent bg-dark_alt px-4 py-2 font-jersey10 text-2xl font-semibold">
46+
{card.title}
47+
</div>
48+
);
49+
};
50+
51+
export function EventHighlightCard({
52+
id,
53+
title,
54+
description,
55+
type,
56+
image,
57+
row,
58+
}: eventHighlightCardType) {
59+
return (
60+
<div key={id} className="flex flex-col">
61+
{renderCardHeader({ id, title, description, type, image, row })}
62+
63+
<div className="mt-4 rounded-md border border-muted bg-landingCard p-4 text-gray-200">
64+
<div className="flex gap-2">
65+
<span></span>
66+
<p>{description}</p>
67+
{image && (
68+
<Image
69+
src={image.url}
70+
width={image.width}
71+
height={image.height}
72+
alt={image.alt}
73+
className="m-3 size-20 [image-rendering:pixelated]"
74+
/>
75+
)}
76+
</div>
77+
</div>
78+
</div>
79+
);
80+
}

0 commit comments

Comments
 (0)