Files
hackerspace.sh/docs/COMPONENT_REFERENCE.md

13 KiB

Hackerspace.sh - Component Reference

Last Updated: 2026-03-10
Complete reference for all React components, their props, and usage patterns.


Table of Contents

  1. App Shell Components
  2. Page Components
  3. Client Components
  4. UI Components
  5. Component Patterns

1. App Shell Components

AppSidebar

File: components/app-sidebar.tsx

Purpose: Main navigation sidebar for the app.

Props:

interface AppSidebarProps {
  member: SpaceMember & { spaces: Space }
  taskBadge?: number      // Count for tasks badge
  commsBadge?: number     // Count for unread messages
  paymentBadge?: number   // Count for unlinked payments
}

Usage:

<AppSidebar
  member={member}
  taskBadge={5}
  paymentBadge={3}
/>

Features:

  • Renders navigation grouped by section (Workspace, People, Admin)
  • Shows badges for tasks and payments
  • Displays user info with sign out
  • Highlights active route
  • Admin section only visible to admin/board roles

Navigation Structure:

WORKSPACE
├── Dashboard     /dashboard
├── Tasks & Chores /tasks
├── Projects      /projects
├── Ops & Facilities /ops
└── Comms         /comms

PEOPLE
├── Members       /members
├── Payments      /payments
└── Contacts      /contacts

ADMIN (admin/board only)
├── Import / Sync /import
└── Settings      /settings

AppShell

File: components/app-shell.tsx

Purpose: Wrapper component for app layout (currently unused).

Status: Available but not integrated into layout.


2. Page Components

Page Structure Pattern

All feature pages follow a consistent Server Component + Client Component pattern:

feature/
├── page.tsx           # Server Component (data fetching)
└── feature-client.tsx # Client Component (UI + interactions)

Dashboard Page

File: app/(app)/dashboard/page.tsx

Type: Server Component

Data Fetched:

  • Member stats (active members count)
  • Task stats (open tasks count)
  • Payment stats (unlinked payments count)
  • Recent tasks (top 5)
  • Active projects (in_progress, blocked, review)
  • Recent activity (last 6 items)

Features:

  • 4 stat cards with badges
  • Quick chores list with claim buttons
  • Active projects with progress bars
  • Payment alerts section
  • Recent activity feed

Tasks Page

Files:

  • app/(app)/tasks/page.tsx (Server)
  • app/(app)/tasks/tasks-client.tsx (Client)

Client Props:

interface Props {
  tasks: Task[]
  members: { id: string; display_name: string; user_id: string }[]
  currentUserId: string
  spaceId: string
}

Features:

  • Tabbed view (Chores, Ongoing, My Tasks, Done)
  • Area filter dropdown
  • Create task modal
  • Claim/Complete/Delete actions
  • Status badges

State Management:

  • Local state for tasks (optimistic updates)
  • Form state for create modal
  • Tab and filter state

Projects Page

Files:

  • app/(app)/projects/page.tsx (Server)
  • app/(app)/projects/projects-client.tsx (Client)

Client Props:

interface Props {
  projects: Project[]
  spaceId: string
}

Features:

  • 4-column Kanban (Backlog, In Progress, Review, Done)
  • Status change via dropdown
  • Create project modal
  • Delete action
  • Progress bars

Members Page

Files:

  • app/(app)/members/page.tsx (Server)
  • app/(app)/members/members-client.tsx (Client)

Client Props:

interface Props {
  members: Member[]
  currentRole: string
}

Features:

  • Tabbed view (All, Payment Issues, Pending Approval, Inactive)
  • Search and tier filter
  • Add member modal (admin/board only)
  • Edit member modal
  • Approve/Remove actions
  • Payment status indicators

Payments Page

Files:

  • app/(app)/payments/page.tsx (Server)
  • app/(app)/payments/payments-client.tsx (Client)

Client Props:

interface Props {
  payments: Payment[]
  members: Member[]
  integrations: Integration[]
  currentRole: string
  spaceId: string
}

Features:

  • Platform summary cards (PayPal, Zeffy, Venmo, Cash)
  • Transactions table with filtering
  • Log cash modal
  • Link member modal
  • Platform connection status

Comms Page

Files:

  • app/(app)/comms/page.tsx (Server)
  • app/(app)/comms/comms-client.tsx (Client)

Client Props:

interface Props {
  member: any
  space: any
  channels: any[]
}

Features:

  • Channel sidebar (General, Areas, Projects)
  • Real-time message subscription
  • Message input with send
  • User avatars and timestamps
  • @mention and #channel highlighting

Real-time:

const subscription = supabase
  .channel(`channel:${selectedChannel.id}`)
  .on('postgres_changes', {
    event: '*',
    schema: 'public',
    table: 'comms_messages',
    filter: `channel_id=eq.${selectedChannel.id}`
  }, payload => {
    if (payload.eventType === 'INSERT') {
      setMessages(prev => [...prev, payload.new])
    }
  })
  .subscribe()

Contacts Page

Files:

  • app/(app)/contacts/page.tsx (Server)
  • app/(app)/contacts/contacts-client.tsx (Client)

Features:

  • Search functionality
  • Grouped by type (Vendors, Landlord, Partners)
  • Add/Edit contact modals
  • Delete with confirmation
  • Contact type badges

Ops Page

File: app/(app)/ops/page.tsx (Server Component)

Features:

  • Tabbed view (Knowledge Base, Processes, Secrets, Area Leads)
  • Search knowledge base
  • Pinned/Critical entries section
  • Area-based entries
  • Area leads sidebar
  • Secrets vault (admin/board only)

Settings Page

Files:

  • app/(app)/settings/page.tsx (Server)
  • app/(app)/settings/settings-client.tsx (Client)

Client Props:

interface Props {
  space: Space
  isAdmin: boolean
  integrations: Integration[]
  currentRole: string
}

Features:

  • Space settings tab (name, slug, approval settings)
  • Roles & permissions info
  • Integrations management (PayPal, Zeffy, Venmo, Stripe)
  • Webhook secret management

Import Page

File: app/(app)/import/page.tsx (Server Component)

Features:

  • Step indicator (Upload, Map, Preview, Import)
  • File drop zone
  • Column mapping preview
  • Database connector form

Status: UI only, file processing not implemented.


3. Client Components

TaskClaimButton

File: components/task-claim-button.tsx

Props:

interface Props {
  taskId: string
  onClaim?: () => void
}

Usage:

<TaskClaimButton taskId={task.id} onClaim={() => refresh()} />

ThemeProvider

File: components/theme-provider.tsx

Purpose: Wraps app with next-themes provider.

Usage (in layout):

<ThemeProvider attribute="class" defaultTheme="dark">
  {children}
</ThemeProvider>

4. UI Components

All shadcn/ui components are available in components/ui/. Key components:

Layout Components

  • Card, CardHeader, CardTitle, CardContent, CardFooter
  • Dialog, DialogTrigger, DialogContent, DialogHeader
  • Sheet, SheetTrigger, SheetContent
  • Tabs, TabsList, TabsTrigger, TabsContent
  • Table, TableHeader, TableRow, TableCell

Form Components

  • Button - Primary actions
  • Input - Text input
  • Textarea - Multi-line input
  • Select, SelectTrigger, SelectContent, SelectItem
  • Checkbox - Boolean input
  • Switch - Toggle input
  • Label - Form labels

Feedback Components

  • Badge - Status indicators
  • Alert, AlertTitle, AlertDescription
  • Spinner - Loading indicator
  • Skeleton - Loading placeholder
  • Toast, Toaster - Notifications

Navigation Components

  • Breadcrumb, BreadcrumbItem, BreadcrumbLink
  • DropdownMenu, DropdownMenuTrigger, DropdownMenuContent
  • NavigationMenu

5. Component Patterns

Server/Client Split

// page.tsx (Server Component)
export default async function FeaturePage() {
  const supabase = await createClient()
  const { data } = await supabase.from('table').select('*')
  
  return <FeatureClient data={data ?? []} />
}

// feature-client.tsx (Client Component)
'use client'

export function FeatureClient({ data }: { data: Item[] }) {
  const [items, setItems] = useState(data)
  // Interactive UI
}

Modal Pattern

const [showModal, setShowModal] = useState(false)
const [form, setForm] = useState({ field: '' })
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')

async function handleSubmit(e: React.FormEvent) {
  e.preventDefault()
  setLoading(true)
  setError('')
  
  const result = await serverAction(form)
  
  if (result.error) {
    setError(result.error)
    setLoading(false)
    return
  }
  
  // Update local state
  setShowModal(false)
  setForm({ field: '' })
  setLoading(false)
}

return (
  <>
    <button onClick={() => setShowModal(true)}>Open</button>
    
    {showModal && (
      <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
        <div className="bg-card border border-border rounded-lg">
          <form onSubmit={handleSubmit}>
            {/* Form fields */}
            {error && <p className="text-red-500">{error}</p>}
            <button disabled={loading}>
              {loading ? 'Saving...' : 'Save'}
            </button>
          </form>
        </div>
      </div>
    )}
  </>
)

Optimistic Update Pattern

async function handleAction(id: string) {
  // Optimistically update UI
  setItems(prev => prev.map(item => 
    item.id === id ? { ...item, status: 'updated' } : item
  ))
  
  // Call server
  const result = await serverAction(id)
  
  if (result.error) {
    // Revert on error
    setItems(prev => prev.map(item =>
      item.id === id ? { ...item, status: 'original' } : item
    ))
  }
}

Tab Pattern

const [activeTab, setActiveTab] = useState<'tab1' | 'tab2'>('tab1')

const tabData = {
  tab1: items.filter(i => i.type === 'type1'),
  tab2: items.filter(i => i.type === 'type2'),
}

return (
  <>
    <div className="flex gap-4 border-b">
      {(['tab1', 'tab2'] as const).map(tab => (
        <button
          key={tab}
          onClick={() => setActiveTab(tab)}
          className={activeTab === tab ? 'border-b-2 border-primary' : ''}
        >
          {tab} ({tabData[tab].length})
        </button>
      ))}
    </div>
    
    <div>
      {tabData[activeTab].map(item => (
        <div key={item.id}>{/* Render item */}</div>
      ))}
    </div>
  </>
)

Filter Pattern

const [search, setSearch] = useState('')
const [filter, setFilter] = useState('')

const filtered = items.filter(item => {
  const matchesSearch = !search || 
    item.name.toLowerCase().includes(search.toLowerCase())
  const matchesFilter = !filter || item.type === filter
  return matchesSearch && matchesFilter
})

return (
  <>
    <input
      type="text"
      value={search}
      onChange={e => setSearch(e.target.value)}
      placeholder="Search..."
    />
    <select value={filter} onChange={e => setFilter(e.target.value)}>
      <option value="">All</option>
      <option value="type1">Type 1</option>
    </select>
    
    {filtered.map(item => (
      <div key={item.id}>{/* Render */}</div>
    ))}
  </>
)

6. Styling Patterns

Page Header

<div className="bg-sidebar px-6 py-3 flex items-center justify-between">
  <h1 className="text-white font-sans text-lg font-semibold">Page Title</h1>
  <button className="flex items-center gap-1.5 bg-primary text-white text-xs font-sans px-3 py-1.5 rounded">
    <PlusIcon className="w-3.5 h-3.5" />
    Action
  </button>
</div>

Tab Bar

<div className="bg-card border-b border-border px-6 flex gap-6">
  {tabs.map(tab => (
    <button
      key={tab}
      className={`font-sans text-sm py-3 border-b-2 transition ${
        activeTab === tab
          ? 'border-primary text-primary'
          : 'border-transparent text-muted-foreground hover:text-foreground'
      }`}
    >
      {tab}
    </button>
  ))}
</div>

Card List

<div className="bg-card rounded border border-border divide-y divide-border">
  {items.map(item => (
    <div key={item.id} className="flex items-center gap-3 px-4 py-3">
      {/* Item content */}
    </div>
  ))}
</div>

Status Badge

<span className={`font-mono text-[10px] px-2 py-0.5 rounded ${
  status === 'active' ? 'text-primary bg-primary/10' :
  status === 'warning' ? 'text-orange-600 bg-orange-50' :
  'text-muted-foreground bg-muted'
}`}>
  {status.toUpperCase()}
</span>

Monospace Label

<p className="font-mono text-[10px] tracking-widest text-muted-foreground uppercase">
  SECTION LABEL
</p>