Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 130 additions & 69 deletions src/routes/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,79 +193,140 @@ function Events() {
<h2 className="term-title">{headingText()}</h2>
</header>

<div className="events-grid">
{filteredEvents.map((ev, idx) => {
const terms = (ev.term as Term[] | undefined) ?? [];

let media: React.ReactNode;
if (ev.image) {
media = (
<img
className="event-thumb"
src={String(ev.image)}
alt={String(ev.title)}
loading="lazy"
/>
);
} else {
media = <div className="event-thumb event-thumb--placeholder" />;
}

return (
<article key={`${ev.id}-${idx}`} className="event-card">
{active !== "Upcoming" && ev.recurring && (
<span className="event-badge">Recurring</span>
)}
{media}

<h3 className="event-title">{ev.title}</h3>

{active !== "Upcoming" && terms.length > 0 && (
<div className="term-tags" aria-label="Occurs in terms">
{terms.map((t) => (
<span key={t} className={tagClass(t)}>
{t}
</span>
))}
</div>
)}

{active === "Upcoming" &&
ev.date &&
typeof ev.date === "string" && (
<div className="event-meta">
<p>
📍 {ev.location ? ev.location : "TBA"} <br />
🗓 {new Date(ev.date).toLocaleDateString()}{" "}
{new Date(ev.date).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</p>
{ev.rsvp && typeof ev.rsvp === "string" && (
<a
href={ev.rsvp}
target="_blank"
rel="noopener"
className="btn"
>
RSVP
</a>
)}
{filteredEvents.length === 0 ? (
<div className="events-empty">
{active === "Upcoming" ? (
<>
<p className="events-empty-text">
<strong>There are no upcoming events.</strong>
<br />
That could mean two things:
</p>

<ul className="events-empty-list">
<li>
<strong>There genuinely aren't any events planned</strong>
…<br />
but let's be honest, that's basically impossible.
</li>

<li>
<strong>
Spider-Man is currently busy saving the city
</strong>{" "}
<br />
and hasn't updated the website yet.
</li>
</ul>

<p className="events-empty-text">
While he swings back, here's what you can do to stay in the
loop:
</p>

<div className="events-empty-actions">
<a
href="https://instagram.com/umdevclub"
target="_blank"
rel="noopener"
className="btn "
>
Follow us on Instagram
</a>

<button
type="button"
className="btn btn-secondary"
onClick={() => setActive("All")}
>
View all events
</button>
</div>
</>
) : (
<p className="events-empty-text">
No events in this category yet. Check back later or browse other
terms.
</p>
)}
</div>
) : (
<div className="events-grid">
{filteredEvents.map((ev, idx) => {
const terms = (ev.term as Term[] | undefined) ?? [];

let media: React.ReactNode;
if (ev.image) {
media = (
<img
className="event-thumb"
src={String(ev.image)}
alt={String(ev.title)}
loading="lazy"
/>
);
} else {
media = (
<div className="event-thumb event-thumb--placeholder" />
);
}

return (
<article key={`${ev.id}-${idx}`} className="event-card">
{active !== "Upcoming" && ev.recurring && (
<span className="event-badge">Recurring</span>
)}
{media}

<h3 className="event-title">{ev.title}</h3>

{active !== "Upcoming" && terms.length > 0 && (
<div className="term-tags" aria-label="Occurs in terms">
{terms.map((t) => (
<span key={t} className={tagClass(t)}>
{t}
</span>
))}
</div>
)}

<p className="event-blurb">{ev.description}</p>
{active === "Upcoming" &&
ev.date &&
typeof ev.date === "string" && (
<div className="event-meta">
<p>
📍 {ev.location ? ev.location : "TBA"} <br />
🗓 {new Date(ev.date).toLocaleDateString()}{" "}
{new Date(ev.date).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</p>
{ev.rsvp && typeof ev.rsvp === "string" && (
<a
href={ev.rsvp}
target="_blank"
rel="noopener"
className="btn"
>
RSVP
</a>
)}
</div>
)}

{active !== "Upcoming" && ev.path && (
<a className="btn" href={ev.path}>
Details
</a>
)}
</article>
);
})}
</div>
<p className="event-blurb">{ev.description}</p>

{active !== "Upcoming" && ev.path && (
<a className="btn" href={ev.path}>
Details
</a>
)}
</article>
);
})}
</div>
)}
</section>
</div>
);
Expand Down
67 changes: 67 additions & 0 deletions src/styles/Events.scss
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
transition: background 0.2s ease;
position: relative;
overflow: hidden;
text-decoration: none;

&:hover {
animation: btnColorCycle 3s linear infinite;
Expand Down Expand Up @@ -479,4 +480,70 @@
background: $dark-blue;
}
}

.events-empty {
max-width: 600px;
margin: 2em auto 0;
text-align: center;
padding: 2em 1.5em;
border-radius: 16px;
background: #f8f8f8;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
}

.events-empty-text {
font-size: 15px;
line-height: 1.6;
margin-bottom: 1.5em;
color: #333;
}

.events-empty-actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.75rem;

.btn-secondary {
background: #fff;
color: #000;
border: 1px solid #000;
}
}

.events-empty .btn {
padding: 0.5em 1.2em;
border-radius: 6px;
font-weight: 500;
font-size: 14px;
background: #000;
color: #fff;
border: none;
cursor: pointer;
transition: background 0.2s ease;
position: relative;
overflow: hidden;
text-decoration: none;

&:hover {
animation: btnColorCycle 3s linear infinite;
}

&:hover::after {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(
circle,
rgba(255, 255, 255, 0.6) 10%,
transparent 10.01%
);
background-size: 10px 10px;
animation: btn-sparkle 1s linear infinite;
pointer-events: none;
}
}
}