A real-time Discord-light chat app built with Next.js 14, Supabase, and Tailwind CSS. Multiple groups, multiple channels, live messaging, presenze indicators, and a full role-based permission system — no external UI libraries.
- Send and receive messages instantly via Supabase Realtime Broadcast
- Compact mode — consecutive messages from the same user collapse the avatar/header
- Date separators, edited indicators, and smooth auto-scroll
- Inline edit and delete for your own messages
- Paginated history — load earlier messages on demand
- Create groups with a name and optional icon
- Invite others with a shareable invite code
- Leave or delete groups
#welcomeand#generalchannels created automatically
- Create and delete text channels (admins / moderators)
- Reorder channels with up/down controls
- Noobs are restricted to
#welcomeuntil promoted
| Role | Capabilities |
|---|---|
| admin | Full control — manage channels, assign roles, kick members, delete group |
| moderator | Manage channels, kick users/noobs, view members panel |
| user | Send messages, edit/delete own messages |
| noob | Read-only access to #welcome until promoted |
- Roles enforced at both database level (Postgres RLS) and UI level
- Admins can promote/demote members live via dropdown in the sidebar
- Kick button with confirmation dialog
- Typing indicators — "[user] is typing…" updates live
- Collapsible Online Members panel on the right side of the chat area
- Green online dot on avatars
- Full desktop 3-panel layout
- Mobile: sidebars slide in as an overlay via hamburger menu
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router, Server Actions) |
| Database | Supabase (Postgres + Realtime + Auth) |
| Styling | Tailwind CSS — no external UI libraries |
| Auth | Supabase Auth (email / password) |
| Deployment | Cloudflare Pages via @cloudflare/next-on-pages |
| Language | TypeScript (strict mode) |
├── app/
│ ├── (auth)/ # Login, signup, profile setup
│ └── (app)/ # Authenticated shell + routes
│ ├── channels/ # Channel pages + server actions
│ ├── groups/ # Group actions
│ ├── members/ # Role assignment + kick actions
│ └── messages/ # Message CRUD actions
├── components/
│ ├── chat/ # MessageList, MessageInput, ChannelShell,
│ │ # TypingIndicator, PresencePanel
│ ├── modals/ # Create/join group, channel, settings
│ ├── sidebar/ # GroupsSidebar, ChannelsSidebar, MembersPanel
│ └── ui/ # Avatar, Button, Modal
├── lib/
│ ├── hooks/ # useMessages, usePresence, useGroups, useChannels
│ ├── supabase/ # Browser + server + middleware clients
│ ├── permissions.ts # Central RBAC helper
│ └── types.ts # TypeScript interfaces
├── schema.sql # Full DB setup — run on a fresh Supabase project
├── migrate-roles.sql # RBAC migration (role enum + updated RLS policies)
└── wrangler.toml # Cloudflare Pages config
git clone https://github.com/ColeMatthewBienek/pepchat.git
cd pepchat
npm install- Create a new project at supabase.com
- Run
schema.sqlin the Supabase SQL Editor to create all tables, RLS policies, and indexes - Run
image-paste-migration.sqlin the SQL Editor to add theattachmentscolumn and create thechat-imagesstorage bucket with RLS policies - Enable Email confirmations off under Authentication → Email (for local dev)
cp .env.local.example .env.localFill in your values:
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-keynpm run devOpen http://localhost:3000.
# Build for Cloudflare Pages
npm run pages:build
# Deploy
npm run pages:deploySet NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY as environment variables in the Cloudflare Pages dashboard.
The full schema lives in schema.sql and can be re-run on any fresh Supabase project to recreate everything:
profiles— extendsauth.userswith username + avatargroups— servers with invite codesgroup_members— membership + role (admin | moderator | user | noob)channels— text channels with position orderingmessages— channel messages with edit historydirect_messages— 1:1 DM table (stubbed)
Row Level Security is enforced on all tables. Security-definer helper functions prevent RLS recursion.
MIT