From b4d0d60ab5d39bc20a3ed67a9539d0aeed5fde2f Mon Sep 17 00:00:00 2001
From: AbdulmalikAlayande
<114596864+AbdulmalikAlayande@users.noreply.github.com>
Date: Mon, 30 Mar 2026 14:49:11 +0100
Subject: [PATCH 1/2] feat(web): add reusable upcoming events empty state
component
Introduce a dedicated UpcomingEventsEmptyState component so the authenticated home dashboard can render a polished, self-contained upcoming-events placeholder without keeping the illustration, copy, motion, and CTA logic inline inside the page component.
Implementation details:
- builds the empty state as a client component under apps/web/components/empty-state for clear reuse and easier review
- uses the existing branded zero badge and empty-state illustration assets already present in the web app to stay visually aligned with the Agora dashboard
- applies the requested Framer Motion entrance behavior with a reduced-motion-aware fallback so the component remains accessible
- preserves layout stability with a fixed minimum-height container and constrained card sizing that matches the design intent
- adds a prominent CTA using the existing shared Button component and routes users to /discover so the empty state is actionable instead of decorative
This commit is intentionally isolated from the page integration so maintainers can review the visual building block first, then inspect the dashboard wiring separately in the follow-up commit.
---
.../upcoming-events-empty-state.tsx | 76 +++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100644 apps/web/components/empty-state/upcoming-events-empty-state.tsx
diff --git a/apps/web/components/empty-state/upcoming-events-empty-state.tsx b/apps/web/components/empty-state/upcoming-events-empty-state.tsx
new file mode 100644
index 0000000..fb90c69
--- /dev/null
+++ b/apps/web/components/empty-state/upcoming-events-empty-state.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import { motion, useReducedMotion } from "framer-motion";
+import Image from "next/image";
+import { useRouter } from "next/navigation";
+import { Button } from "@/components/ui/button";
+import EmptyStateBg from "@/public/icons/empty-state-bg.svg";
+import ZeroIcon from "@/public/icons/zero.svg";
+
+export function UpcomingEventsEmptyState() {
+ const router = useRouter();
+ const shouldReduceMotion = useReducedMotion();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Nothing Here, Yet
+
+
+
+
+ You don't have any upcoming events right now. Explore what's
+ happening and grab a ticket for your next event.
+
+
+
+
+ );
+}
From 2aa22e2b0ee64a19a4a423194ed9b01128461471 Mon Sep 17 00:00:00 2001
From: AbdulmalikAlayande
<114596864+AbdulmalikAlayande@users.noreply.github.com>
Date: Mon, 30 Mar 2026 14:49:54 +0100
Subject: [PATCH 2/2] feat(web): render upcoming events empty state on
authenticated home
Wire the new UpcomingEventsEmptyState component into the authenticated dashboard so the My Events > Upcoming tab now shows the designed empty experience whenever there are no upcoming events instead of leaving a generic placeholder block in place.
Implementation details:
- imports the new reusable empty-state component into apps/web/app/home/page.tsx and removes the older inline illustration-specific imports
- updates the temporary upcomingEvents mock data to an empty array so the issue state is represented in the current authenticated dashboard implementation
- adds an isUpcomingTab guard inside MyEventsContent so only the upcoming tab receives the high-fidelity empty state while other empty tabs keep a lightweight fallback message
- replaces the previous inline markup with the dedicated component, reducing duplication and keeping the page component easier to scan
- removes the unused map index parameter introduced by the refactor to keep the rendering code tidy
Validation performed before this commit:
- pnpm install
- pnpm --filter web lint
- pnpm --filter web build
The split between this commit and the previous one is deliberate: this commit answers where and when the empty state appears, while the previous commit answers how the empty state UI itself is built.
---
apps/web/app/home/page.tsx | 74 +++++++-------------------------------
1 file changed, 13 insertions(+), 61 deletions(-)
diff --git a/apps/web/app/home/page.tsx b/apps/web/app/home/page.tsx
index 84fe068..6a38134 100644
--- a/apps/web/app/home/page.tsx
+++ b/apps/web/app/home/page.tsx
@@ -6,12 +6,11 @@ import Image from "next/image";
import Link from "next/link";
import { Navbar } from "@/components/layout/navbar";
import { Footer } from "@/components/layout/footer";
+import { UpcomingEventsEmptyState } from "@/components/empty-state/upcoming-events-empty-state";
import CalendarIcon from "@/public/icons/calendar.svg";
import HostingIcon from "@/public/icons/ticket-star.svg";
import PastIcon from "@/public/icons/camera-smile-01.svg";
import BubbleChatIcon from "@/public/icons/bubble-chat.svg";
-import ZeroIcon from "@/public/icons/zero.svg";
-import EmptyStateBg from "@/public/icons/empty-state-bg.svg";
type MyEventsTab = "upcoming" | "hosting" | "past";
type ForYouTab = "discover" | "following";
@@ -57,43 +56,7 @@ interface GridEvent {
}
// Mock data for My Events (Timeline)
-const upcomingEvents: TimelineEvent[] = [
- {
- id: 1,
- date: "6 Mar, Friday",
- day: "Friday",
- time: "18:00 - 20:00 UTC",
- title: "Stellar Developers Meetup",
- location: "Discord",
- imageUrl: "/images/event1.png",
- isFree: true,
- attendees: 24,
- status: "going",
- },
- {
- id: 2,
- date: "8 Mar, Sunday",
- day: "Sunday",
- time: "10:00 - 12:00 UTC",
- title: "Web3 Design Workshop",
- location: "Lagos, Nigeria",
- imageUrl: "/images/event2.png",
- isFree: false,
- price: "$25.00",
- attendees: 156,
- },
- {
- id: 3,
- date: "12 Mar, Thursday",
- day: "Thursday",
- time: "14:00 - 16:00 UTC",
- title: "Blockchain Fundamentals",
- location: "Virtual",
- imageUrl: "/images/event3.png",
- isFree: true,
- attendees: 89,
- },
-];
+const upcomingEvents: TimelineEvent[] = [];
const hostingEvents: TimelineEvent[] = [
{
@@ -537,6 +500,7 @@ function GridEventCard({ event }: { event: GridEvent }) {
// My Events Section Content
function MyEventsContent({ activeTab }: { activeTab: MyEventsTab }) {
let events: TimelineEvent[] = [];
+ const isUpcomingTab = activeTab === "upcoming";
switch (activeTab) {
case "upcoming":
@@ -551,35 +515,22 @@ function MyEventsContent({ activeTab }: { activeTab: MyEventsTab }) {
}
if (events.length === 0) {
- return (
-