Skip to main content

Overview

The activity feed is a real-time stream of community actions displayed on the landing page and dashboard. It showcases recent signups, project creations, and team formations to create a sense of momentum and collective progress.

Activity Types

The feed tracks three types of community actions:

Signups

New users join the platform

Projects

Projects are created and linked to events

Team Joins

Builders accept team invites and join projects

Visual Design

The activity feed uses animated transitions and visual hierarchy to create engaging displays:

Animation Behavior

  • Initial batch: First 20 activities load immediately in reverse chronological order
  • Auto-scroll: New items appear every 2.4 seconds at the top
  • Smooth transitions: Items fade in with blur effect and slide down
  • Layout animations: Existing items smoothly reposition as new ones arrive
  • Gradient fades: Top and bottom gradients create infinite scroll effect

Activity Icons

Each activity type has a distinct symbol:
  • Signup: → (arrow)
  • Project: ◆ (diamond)
  • Team join: ⬡ (hexagon)

Avatars

Activity items show user avatars with fallback behavior:
  • If avatarUrl exists: Display Clerk profile image
  • If no avatar: Generate colored circle with initials
  • Color assigned from rotating palette of 6 muted colors

Data Structure

interface SerializedActivity {
  type: "signup" | "project" | "team_join";
  displayName: string;
  username: string | null;
  avatarUrl: string | null;
  detail: string | null;  // Project name for projects, project name for team joins
  timestamp: string;
}

Feed Formatting

Activities are formatted as:
  • Signup: [Name] signed up
  • Project: [Name] added a project: '[Project Name]'
  • Team join: [Name] joined team on '[Project Name]'

Implementation

The activity feed is a React component at components/activity-feed.tsx:
interface ActivityFeedProps {
  activities: SerializedActivity[];
}

export function ActivityFeed({ activities }: ActivityFeedProps)

Key Features

Uses motion.div with AnimatePresence for smooth enter/exit animations:
initial={{ opacity: 0, y: -20, filter: "blur(8px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
transition={{
  opacity: { duration: 0.5, ease: [0.25, 0.1, 0.25, 1] },
  y: { duration: 0.5, ease: [0.25, 0.1, 0.25, 1] },
  filter: { duration: 0.5, ease: [0.25, 0.1, 0.25, 1] },
  layout: { type: "spring", stiffness: 200, damping: 28 },
}}
useEffect(() => {
  if (activities.length === 0) return;
  const interval = setInterval(addItem, 2400);
  return () => clearInterval(interval);
}, [addItem, activities.length]);
Cycles through activities array, looping back to start when reaching the end.
Maintains a maximum of 20 visible items:
const BATCH_SIZE = 20;

setItems((prev) => {
  const newItem: FeedItem = { /* ... */ };
  return [newItem, ...prev].slice(0, BATCH_SIZE);
});

Public Stats Integration

The activity feed is paired with public statistics from lib/queries.ts:
export async function getPublicStats() {
  // Returns:
  // - totalProfiles: number
  // - totalProjects: number
  // - totalRegistrations: number (for hackathon)
  // - recentActivity: SerializedActivity[]
}
This query:
  • Filters out banned and hidden profiles
  • Only includes profiles registered for events
  • Only includes projects linked to events
  • Limits to most recent 100 activities

Landing Page Display

On the homepage (app/page.tsx), the activity feed appears in the “Where” section:
const stats = await getPublicStats();

<ActivityFeed activities={stats.recentActivity} />
Displayed alongside:
  • Total signups counter
  • Total projects counter
  • Discord community link

Dashboard Display

Authenticated users see a similar feed on their dashboard (app/(app)/dashboard/page.tsx) showing the same community-wide activity.

Privacy Filtering

All activity queries use the isProfileVisible helper to exclude:
function isProfileVisible(profile: { bannedAt: Date | null; hiddenAt: Date | null }) {
  return profile.bannedAt === null && profile.hiddenAt === null;
}
This ensures:
  • Banned users don’t appear in the feed
  • Hidden users (soft-deleted) don’t appear in the feed
  • Only active, visible community members are showcased

Performance Considerations

The activity feed is server-rendered with static data passed to the client component. The animation timer runs entirely client-side, requiring no additional API calls.

Optimization Strategy

  1. Server-side query: Fetch activities once during page render
  2. Client-side cycling: Rotate through the static array
  3. No polling: No real-time updates (page refresh required for new data)
  4. Efficient animations: CSS transforms and opacity (GPU-accelerated)
  5. Fixed batch size: Memory usage capped at 20 items

Empty State

If no activities exist:
if (activities.length === 0) {
  return (
    <div className="h-full flex items-center justify-center">
      <p className="text-sm text-white/30">No activity yet</p>
    </div>
  );
}

Milestone Celebrations

The activity feed complements the milestone notification system in lib/milestones.ts:
  • Feed shows individual actions
  • Milestones celebrate community thresholds (10, 25, 50, 75, 100, 150, 200, 250, 500, 1000)
  • Both create a sense of momentum and collective progress
The combination of real-time feed + milestone pings creates a “show, don’t tell” culture — the platform demonstrates community growth rather than just claiming it.

Build docs developers (and LLMs) love