Skip to content

manast95/Rai

Repository files navigation

Rai — Request Board

Rai (राई) is the Hindi word for mustard seed — a nod to Jira (जीरा, cumin). Tiny things that pile up and need gathering.

A lightweight, hostable request queue. People submit what they need and watch a live public queue; you (the admin) triage, annotate, progress, and clear — with permissions enforced in the database, not just hidden in the UI.

Features

  • Public board — frictionless submission (name, text, urgency), live queue, sort by urgency/newest, urgency filter, search, shareable URL filters.
  • Live updates — granular realtime: inserts/updates/deletes patch the list in place (no full refetch per event).
  • Admin layer — email/password login, per-card status + private notes, delete-with-confirm, bulk select → mark done / delete, clear all done.
  • Undo — deletes (single, bulk, and clear-done) show an Undo toast that restores the row(s) verbatim.
  • Keyboard shortcuts (admin)j/k move, x select, e cycle status, d delete, esc clear selection.
  • Permalinks — every request has a shareable read-only page at /r/:id.
  • Private notes — admin notes live in a separate, admin-only table; anonymous visitors can't read them even via the raw API.
  • Quality of life — dark mode, optimistic submit with rollback, soft duplicate detection, client-side rate limiting, "queue is busy" banner, incremental rendering ("Show more"), skeletons, empty/error states, a top-level error boundary, and accessible (aria-live, keyboard-operable) components.

Quick start

npm install
npm run dev        # http://localhost:5173

With no Supabase credentials, the app runs in demo mode: data lives in your browser's localStorage and the RLS rules are faithfully simulated (anon can only insert clean pending rows; edit/delete require an admin session). Admin login in demo mode accepts any email with the password demo.

Going live with Supabase

  1. Create a Supabase project. In the SQL editor, run supabase/schema.sql — this creates the tables (requests + private request_notes), the Row Level Security policies, the updated_at triggers, and enables Realtime.

  2. Authentication → Users → Add user: create your one admin (email + password).

  3. Authentication → Sign In / Providers: turn off "Allow new signups". Since the only authenticated user is you, authenticated = admin by construction.

  4. Copy the Project URL and anon public key into .env:

    cp .env.example .env
    # fill in VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY

The anon key is safe to ship to the browser — RLS is the gate. Restart npm run dev after editing .env (Vite reads it at startup); the demo banner disappears once you're connected.

Already created the DB with an earlier schema?

If your project predates the private-notes change (it had an admin_notes column on requests), run the data-preserving migration once: supabase/migrate_2_private_notes.sql.

Verifying RLS for real

Open the deployed site as an anonymous visitor, open dev tools, and try a direct mutation:

import { createClient } from '@supabase/supabase-js'
const sb = createClient(URL, ANON_KEY)
await sb.from('requests').delete().eq('id', '<some-id>')   // → rejected by RLS
await sb.from('requests').update({ status: 'done' }).eq('id', '<id>') // → rejected
await sb.from('request_notes').select('*')                 // → returns [] for anon

Both mutations should fail, and request_notes returns nothing for anon. Only inserts of clean pending rows succeed.

Tests

npm test           # Vitest unit tests (sort/filter, duplicates, rate limit)
npm run test:watch

End-to-end (Playwright) is scaffolded in e2e/; enable with:

npm i -D @playwright/test && npx playwright install chromium
npm run test:e2e

Deploy to Netlify

  • Build command: npm run build
  • Publish directory: dist
  • Add VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY under Site settings → Environment variables.
  • netlify.toml already includes the SPA redirect so /admin and /r/:id survive a hard refresh.
  • The build is code-split (vendor chunks + lazy admin/detail routes) so the public board loads a lean initial bundle.

Configuration

A few knobs live in src/lib/config.ts (WIP banner threshold, page size) and src/lib/rateLimit.ts (submission cooldown/quota).

Project structure

src/
  lib/        supabase client, demo store, unified repo (events/notes/restore),
              sort/filter, status helpers, duplicates, rate limit, config, utils
  hooks/      useRequests (realtime + optimistic), useAuth, useTheme,
              useNow, useQueueFilters (URL state)
  components/ Header, SubmitCard, FilterBar, RequestCard, AdminControls,
              EmptyState, ThemeToggle, Badges, DemoBanner, ErrorBoundary,
              ui/ (button, dialog, dropdown)
  pages/      Board ("/"), Admin ("/admin"), RequestDetail ("/r/:id")
  App.tsx     router + lazy routes + LazyMotion + error boundary + toaster
supabase/     schema.sql, migrate_2_private_notes.sql, cleanup_test_rows.sql
e2e/          Playwright happy-path spec (scaffold)

About

Rai - An inbound request board

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors