Skip to content

Commit 0390ee2

Browse files
Merge pull request #155 from umdevclub/feat/NoUpcomingEvents
feat: add empty upcoming events message
2 parents 1190193 + 31e8f36 commit 0390ee2

File tree

2 files changed

+197
-69
lines changed

2 files changed

+197
-69
lines changed

src/routes/Events.tsx

Lines changed: 130 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -193,79 +193,140 @@ function Events() {
193193
<h2 className="term-title">{headingText()}</h2>
194194
</header>
195195

196-
<div className="events-grid">
197-
{filteredEvents.map((ev, idx) => {
198-
const terms = (ev.term as Term[] | undefined) ?? [];
199-
200-
let media: React.ReactNode;
201-
if (ev.image) {
202-
media = (
203-
<img
204-
className="event-thumb"
205-
src={String(ev.image)}
206-
alt={String(ev.title)}
207-
loading="lazy"
208-
/>
209-
);
210-
} else {
211-
media = <div className="event-thumb event-thumb--placeholder" />;
212-
}
213-
214-
return (
215-
<article key={`${ev.id}-${idx}`} className="event-card">
216-
{active !== "Upcoming" && ev.recurring && (
217-
<span className="event-badge">Recurring</span>
218-
)}
219-
{media}
220-
221-
<h3 className="event-title">{ev.title}</h3>
222-
223-
{active !== "Upcoming" && terms.length > 0 && (
224-
<div className="term-tags" aria-label="Occurs in terms">
225-
{terms.map((t) => (
226-
<span key={t} className={tagClass(t)}>
227-
{t}
228-
</span>
229-
))}
230-
</div>
231-
)}
232-
233-
{active === "Upcoming" &&
234-
ev.date &&
235-
typeof ev.date === "string" && (
236-
<div className="event-meta">
237-
<p>
238-
📍 {ev.location ? ev.location : "TBA"} <br />
239-
🗓 {new Date(ev.date).toLocaleDateString()}{" "}
240-
{new Date(ev.date).toLocaleTimeString([], {
241-
hour: "2-digit",
242-
minute: "2-digit",
243-
})}
244-
</p>
245-
{ev.rsvp && typeof ev.rsvp === "string" && (
246-
<a
247-
href={ev.rsvp}
248-
target="_blank"
249-
rel="noopener"
250-
className="btn"
251-
>
252-
RSVP
253-
</a>
254-
)}
196+
{filteredEvents.length === 0 ? (
197+
<div className="events-empty">
198+
{active === "Upcoming" ? (
199+
<>
200+
<p className="events-empty-text">
201+
<strong>There are no upcoming events.</strong>
202+
<br />
203+
That could mean two things:
204+
</p>
205+
206+
<ul className="events-empty-list">
207+
<li>
208+
<strong>There genuinely aren't any events planned</strong>
209+
<br />
210+
but let's be honest, that's basically impossible.
211+
</li>
212+
213+
<li>
214+
<strong>
215+
Spider-Man is currently busy saving the city
216+
</strong>{" "}
217+
<br />
218+
and hasn't updated the website yet.
219+
</li>
220+
</ul>
221+
222+
<p className="events-empty-text">
223+
While he swings back, here's what you can do to stay in the
224+
loop:
225+
</p>
226+
227+
<div className="events-empty-actions">
228+
<a
229+
href="https://instagram.com/umdevclub"
230+
target="_blank"
231+
rel="noopener"
232+
className="btn "
233+
>
234+
Follow us on Instagram
235+
</a>
236+
237+
<button
238+
type="button"
239+
className="btn btn-secondary"
240+
onClick={() => setActive("All")}
241+
>
242+
View all events
243+
</button>
244+
</div>
245+
</>
246+
) : (
247+
<p className="events-empty-text">
248+
No events in this category yet. Check back later or browse other
249+
terms.
250+
</p>
251+
)}
252+
</div>
253+
) : (
254+
<div className="events-grid">
255+
{filteredEvents.map((ev, idx) => {
256+
const terms = (ev.term as Term[] | undefined) ?? [];
257+
258+
let media: React.ReactNode;
259+
if (ev.image) {
260+
media = (
261+
<img
262+
className="event-thumb"
263+
src={String(ev.image)}
264+
alt={String(ev.title)}
265+
loading="lazy"
266+
/>
267+
);
268+
} else {
269+
media = (
270+
<div className="event-thumb event-thumb--placeholder" />
271+
);
272+
}
273+
274+
return (
275+
<article key={`${ev.id}-${idx}`} className="event-card">
276+
{active !== "Upcoming" && ev.recurring && (
277+
<span className="event-badge">Recurring</span>
278+
)}
279+
{media}
280+
281+
<h3 className="event-title">{ev.title}</h3>
282+
283+
{active !== "Upcoming" && terms.length > 0 && (
284+
<div className="term-tags" aria-label="Occurs in terms">
285+
{terms.map((t) => (
286+
<span key={t} className={tagClass(t)}>
287+
{t}
288+
</span>
289+
))}
255290
</div>
256291
)}
257292

258-
<p className="event-blurb">{ev.description}</p>
293+
{active === "Upcoming" &&
294+
ev.date &&
295+
typeof ev.date === "string" && (
296+
<div className="event-meta">
297+
<p>
298+
📍 {ev.location ? ev.location : "TBA"} <br />
299+
🗓 {new Date(ev.date).toLocaleDateString()}{" "}
300+
{new Date(ev.date).toLocaleTimeString([], {
301+
hour: "2-digit",
302+
minute: "2-digit",
303+
})}
304+
</p>
305+
{ev.rsvp && typeof ev.rsvp === "string" && (
306+
<a
307+
href={ev.rsvp}
308+
target="_blank"
309+
rel="noopener"
310+
className="btn"
311+
>
312+
RSVP
313+
</a>
314+
)}
315+
</div>
316+
)}
259317

260-
{active !== "Upcoming" && ev.path && (
261-
<a className="btn" href={ev.path}>
262-
Details
263-
</a>
264-
)}
265-
</article>
266-
);
267-
})}
268-
</div>
318+
<p className="event-blurb">{ev.description}</p>
319+
320+
{active !== "Upcoming" && ev.path && (
321+
<a className="btn" href={ev.path}>
322+
Details
323+
</a>
324+
)}
325+
</article>
326+
);
327+
})}
328+
</div>
329+
)}
269330
</section>
270331
</div>
271332
);

src/styles/Events.scss

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@
243243
transition: background 0.2s ease;
244244
position: relative;
245245
overflow: hidden;
246+
text-decoration: none;
246247

247248
&:hover {
248249
animation: btnColorCycle 3s linear infinite;
@@ -479,4 +480,70 @@
479480
background: $dark-blue;
480481
}
481482
}
483+
484+
.events-empty {
485+
max-width: 600px;
486+
margin: 2em auto 0;
487+
text-align: center;
488+
padding: 2em 1.5em;
489+
border-radius: 16px;
490+
background: #f8f8f8;
491+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
492+
}
493+
494+
.events-empty-text {
495+
font-size: 15px;
496+
line-height: 1.6;
497+
margin-bottom: 1.5em;
498+
color: #333;
499+
}
500+
501+
.events-empty-actions {
502+
display: flex;
503+
flex-wrap: wrap;
504+
justify-content: center;
505+
gap: 0.75rem;
506+
507+
.btn-secondary {
508+
background: #fff;
509+
color: #000;
510+
border: 1px solid #000;
511+
}
512+
}
513+
514+
.events-empty .btn {
515+
padding: 0.5em 1.2em;
516+
border-radius: 6px;
517+
font-weight: 500;
518+
font-size: 14px;
519+
background: #000;
520+
color: #fff;
521+
border: none;
522+
cursor: pointer;
523+
transition: background 0.2s ease;
524+
position: relative;
525+
overflow: hidden;
526+
text-decoration: none;
527+
528+
&:hover {
529+
animation: btnColorCycle 3s linear infinite;
530+
}
531+
532+
&:hover::after {
533+
content: "";
534+
position: absolute;
535+
top: -50%;
536+
left: -50%;
537+
width: 200%;
538+
height: 200%;
539+
background: radial-gradient(
540+
circle,
541+
rgba(255, 255, 255, 0.6) 10%,
542+
transparent 10.01%
543+
);
544+
background-size: 10px 10px;
545+
animation: btn-sparkle 1s linear infinite;
546+
pointer-events: none;
547+
}
548+
}
482549
}

0 commit comments

Comments
 (0)