Ravioli is a digital information display for Online, the student organization at NTNU. It runs on a wall-mounted screen in room A4 and rotates through live content - upcoming events, Slack memes and blasts, YouTube videos, seasonal pages, and more.
The app is a full-screen React SPA that cycles through a set of pages. Each page has two properties:
duration- how many seconds the page stays on screenpriority- a function that returns a number; higher = shown more often
The scheduler converts priorities into probabilities and picks the next page randomly, weighted by those probabilities. This means a priority of 4 is twice as likely to appear as a priority of 2. A priority of 0 means the page is never shown.
Priority functions are called at selection time, so they can depend on the current date and time. This is how seasonal pages work - they return a non-zero value only during their season.
// Example: only show during Christmas season (Oct 1 – Dec 24)
priority: () => {
const today = new Date();
const start = new Date(today.getFullYear(), 9, 1);
const end = new Date(today.getFullYear(), 11, 24);
return start <= today && today <= end ? 1 : 0;
}The header shows a countdown bar for the current page.
Dev tricks:
←/→arrow keys - step through pages sequentially- Click the pie/progress indicator in the header - jump to a random page picked by the scheduler
- Click the Online logo or the clock in the header - toggle between light and dark mode
infoskjerm/
├── frontend/ # React + Vite app (the display itself)
└── backend/ # Express API (Slack proxy, event data, etc.)
| Tool | Purpose |
|---|---|
| React + TypeScript | UI framework |
| Vite | Build tool and dev server |
| TanStack React Query | Data fetching and caching |
| Tailwind CSS | Styling |
| Framer Motion | Animations |
cd frontend
npm install
cp .env.template .envEdit .env and fill in the values (see Environment variables below).
npm run devThe app runs at http://localhost:5173 by default.
| Variable | Required | Description |
|---|---|---|
NODE_ENV |
Yes | Set to development. Hides the cursor in production. |
VITE_VIDEO_API_KEY |
No | YouTube Data API v3 key. Only required if you want VideoPage to work. Get one at Google Cloud Console. |
VITE_BACKEND_URL |
Yes | URL to the backend. Use https://infoskjerm-backend-appkom.vercel.app for the live backend, or http://localhost:3000 if running it locally. |
All pages live in frontend/src/components/pages/. Some examples:
| Page | What it shows | Notes |
|---|---|---|
EventsPage |
Next 8 upcoming events from Online | Refetches every 5 min |
SlackPage |
Recent Slack memes (left) + text blasts (right) | Refetches every 15–60 min |
Pages marked fullScreen: true hide the header bar.
- Create
frontend/src/components/pages/MyPage.tsxand export a React component. - Import it in frontend/src/components/pages/MainPage.tsx.
- Add an entry to the
pageSpecificationsarray:
{
component: <MyPage />,
duration: 30, // seconds to display
priority: () => 1, // relative weight; use a function for time-based logic
fullScreen: true, // optional: hides the header bar
}API files are in frontend/src/api/. Each file exports a fetcher function used by React Query hooks inside the relevant page component. Cache times and refetch intervals are set per-query.
| Path | Component | Purpose |
|---|---|---|
/ |
MainPage |
The main rotating display |
/online-appen |
AppRedirect |
Used for the QR code on OnlineAppBlastPage - detects whether the scanning device is Android or iOS and redirects to the appropriate app store |
The backend is an Express API that acts as a proxy and content aggregator. It pulls messages and media from specific Slack channels, processes and stores them in Supabase (PostgreSQL + file storage), and exposes read-only endpoints for the frontend. It also fetches event data from the Online API.
A cron job runs hourly (7am–9pm UTC) to keep content fresh by polling Slack for new messages.
| Tool | Purpose |
|---|---|
| Express + TypeScript | HTTP server |
| Supabase | PostgreSQL database + file storage |
| Slack Web API | Source of memes, blasts, and media |
| node-cron / Vercel | Scheduled Slack polling |
cd backend
npm install
cp .env.template .envEdit .env and fill in the values (see Backend environment variables below).
npm run devThe server runs at http://localhost:3000 by default.
| Variable | Required | Description |
|---|---|---|
SLACK_TOKEN |
Yes | Slack Bot User OAuth Token (xoxb-…). Used to fetch messages and media from Slack channels. |
SUPABASE_URL |
Yes | Supabase project URL. Found in your Supabase project settings. |
SUPABASE_SERVICE_KEY |
Yes | Supabase service role key. Grants admin access to the database and storage. |
SUPABASE_CONNECTION_STRING |
Yes | PostgreSQL connection string from Supabase. Used for direct database queries. |
CRON_SECRET |
Yes | Bearer token that protects the /cron endpoint. Set to any secret string. |
NODE_ENV |
No | Set to development locally. |
| Endpoint | Description |
|---|---|
GET /latest-memes |
Returns the latest memes from the #memeogvinogklinoggrin2 Slack channel. Query params: count (1–10), type (image or video). |
GET /latest-blasts |
Returns the latest text blasts from the korktavla, online, utveksling, and kontoret channels. Query param: count (1–10). |
GET /movember |
Returns Movember media from a given cutoff date. Query param: date (ISO 8601). |
GET /events |
Fetches upcoming events from the Online API (no auth required). |
GET /cron |
Internal - triggers a Slack poll and stores new media. Protected by CRON_SECRET bearer token. Called automatically by Vercel on a schedule. |
When the cron job runs, the backend:
- Fetches new messages from monitored Slack channels
- Downloads any attached images or videos using the Slack bot token
- Compresses images with Sharp (resizes to max 1920px wide, 80% JPEG quality)
- Uploads processed files to Supabase Storage under
media/{channelName}/{fileId}-{fileName} - Upserts metadata (author, reactions, timestamp, public URL) into the
MediaFilestable