version 0.0.24
This commit is contained in:
492
README.md
492
README.md
@@ -1,221 +1,443 @@
|
||||
# TeamChat 💬
|
||||
# jama 💬
|
||||
### *just another messaging app*
|
||||
|
||||
A modern, self-hosted team chat Progressive Web App (PWA) — similar to Google Messages / Facebook Messenger for teams.
|
||||
A modern, self-hosted team messaging Progressive Web App (PWA) built for small to medium teams. jama runs entirely in a single Docker container with no external database dependencies — all data is stored locally using SQLite.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- 🔐 **Authentication** — Login, remember me, forced password change on first login
|
||||
- 💬 **Real-time messaging** — WebSocket (Socket.io) powered chat
|
||||
- 👥 **Public channels** — Admin-created, all users auto-joined
|
||||
- 🔒 **Private groups** — User-created, owner-managed
|
||||
- 📷 **Image uploads** — Attach images to messages
|
||||
- 💬 **Message quoting** — Reply to any message with preview
|
||||
- 😎 **Emoji reactions** — Quick reactions + full emoji picker
|
||||
- @**Mentions** — @mention users with autocomplete, they get notified
|
||||
- 🔗 **Link previews** — Auto-fetches OG metadata for URLs
|
||||
- 📱 **PWA** — Install to home screen, works offline
|
||||
- 👤 **Profiles** — Custom avatars, display names, about me
|
||||
- ⚙️ **Admin settings** — Custom logo, app name
|
||||
- 👨💼 **User management** — Create, suspend, delete, bulk CSV import
|
||||
- 📢 **Read-only channels** — Announcement-style public channels
|
||||
### Messaging
|
||||
- **Real-time messaging** — WebSocket-powered (Socket.io); messages appear instantly across all clients
|
||||
- **Image attachments** — Attach and send images; auto-compressed client-side before upload
|
||||
- **Message replies** — Quote and reply to any message with an inline preview
|
||||
- **Emoji reactions** — Quick-react with common emojis or open the full emoji picker; one reaction per user, replaceable
|
||||
- **@Mentions** — Type `@` to search and tag users with autocomplete; mentioned users receive a notification
|
||||
- **Link previews** — URLs are automatically expanded with Open Graph metadata (title, image, site name)
|
||||
- **Typing indicators** — See when others are composing a message
|
||||
- **Image lightbox** — Tap any image to open it full-screen with pinch-to-zoom support
|
||||
|
||||
### Channels & Groups
|
||||
- **Public channels** — Admin-created; all users are automatically added
|
||||
- **Private groups / DMs** — Any user can create; membership is invite-only by the owner
|
||||
- **Read-only channels** — Admin-configurable announcement-style channels; only admins can post
|
||||
- **Support group** — A private admin-only group that receives submissions from the login page contact form
|
||||
|
||||
### Users & Profiles
|
||||
- **Authentication** — Email/password login with optional Remember Me (30-day session)
|
||||
- **Forced password change** — New users must change their password on first login
|
||||
- **User profiles** — Custom display name, avatar upload, About Me text
|
||||
- **Profile popup** — Click any user's avatar in chat to view their profile card
|
||||
- **Admin badge** — Admins display a role badge; can be hidden per-user in Profile settings
|
||||
|
||||
### Notifications
|
||||
- **In-app notifications** — Mention alerts with toast notifications
|
||||
- **Unread indicators** — Private groups with new unread messages are highlighted and bolded in the sidebar
|
||||
- **Web Push notifications** — Badge and push notifications for mentions and new private messages when the app is backgrounded or closed (requires HTTPS)
|
||||
|
||||
### Admin & Settings
|
||||
- **User Manager** — Create, suspend, activate, delete users; reset passwords; change roles
|
||||
- **Bulk CSV import** — Import multiple users at once from a CSV file
|
||||
- **App branding** — Customize app name, logo, New Chat icon, and Group Info icon via the Settings panel
|
||||
- **Reset to defaults** — One-click reset of all branding customizations
|
||||
- **Version display** — Current app version shown in the Settings panel
|
||||
|
||||
### PWA
|
||||
- **Installable** — Install to home screen on mobile and desktop via the browser install prompt
|
||||
- **Dynamic app icon** — Uploaded logo is automatically resized to 192×192 and 512×512 and used as the PWA shortcut icon
|
||||
- **Dynamic manifest** — App name and icons in the PWA manifest update live when changed in Settings
|
||||
- **Offline fallback** — Basic offline support via service worker caching
|
||||
|
||||
### Contact Form
|
||||
- **Login page contact form** — A "Contact Support" button on the login page opens a form (name, email, message, math captcha) that posts directly into the admin Support group
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## Tech Stack
|
||||
|
||||
### Prerequisites
|
||||
- Docker & Docker Compose
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Backend | Node.js, Express, Socket.io |
|
||||
| Database | SQLite (better-sqlite3) |
|
||||
| Frontend | React 18, Vite |
|
||||
| Image processing | sharp |
|
||||
| Push notifications | web-push (VAPID) |
|
||||
| Containerization | Docker, Docker Compose |
|
||||
| Reverse proxy / SSL | Caddy (recommended) |
|
||||
|
||||
### 1. Build a versioned image
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Docker** and **Docker Compose v2**
|
||||
- A domain name with DNS pointed at your server (required for HTTPS and Web Push notifications)
|
||||
- Ports **80** and **443** open on your server firewall (if using Caddy for SSL)
|
||||
|
||||
---
|
||||
|
||||
## Building the Image
|
||||
|
||||
All builds use `build.sh`. No host Node.js installation is required — `npm install` and the Vite build run inside Docker.
|
||||
|
||||
```bash
|
||||
# Build and tag as v1.0.0 (also tags :latest)
|
||||
./build.sh 1.0.0
|
||||
|
||||
# Build latest only
|
||||
# Build and tag as :latest only
|
||||
./build.sh
|
||||
```
|
||||
|
||||
### 2. Deploy with Docker Compose
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env — set TEAMCHAT_VERSION, admin credentials, JWT_SECRET
|
||||
nano .env
|
||||
|
||||
docker compose up -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
App will be available at **http://localhost:3000**
|
||||
|
||||
---
|
||||
|
||||
## Release Workflow
|
||||
|
||||
TeamChat uses a **build-then-run** pattern. You build the image once on your build machine (or CI), then the compose file just runs the pre-built image — no build step at deploy time.
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌──────────────────────────┐
|
||||
│ Build machine / CI │ │ Server / Portainer │
|
||||
│ │ │ │
|
||||
│ ./build.sh 1.2.0 │─────▶│ TEAMCHAT_VERSION=1.2.0 │
|
||||
│ (or push to │ │ docker compose up -d │
|
||||
│ registry first) │ │ │
|
||||
└─────────────────────┘ └──────────────────────────┘
|
||||
```
|
||||
|
||||
### Build script usage
|
||||
|
||||
```bash
|
||||
# Build locally (image stays on this machine)
|
||||
# Build and tag as a specific version (also tags :latest)
|
||||
./build.sh 1.0.0
|
||||
|
||||
# Build and push to Docker Hub
|
||||
REGISTRY=yourdockerhubuser ./build.sh 1.0.0 push
|
||||
|
||||
# Build and push to GHCR
|
||||
# Build and push to GitHub Container Registry
|
||||
REGISTRY=ghcr.io/yourorg ./build.sh 1.0.0 push
|
||||
```
|
||||
|
||||
### Deploying a specific version
|
||||
After a successful build the script prints the exact `.env` and `docker compose` commands needed to deploy.
|
||||
|
||||
Set `TEAMCHAT_VERSION` in your `.env` before running compose:
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Clone the repository
|
||||
|
||||
```bash
|
||||
# .env
|
||||
TEAMCHAT_VERSION=1.2.0
|
||||
git clone https://github.com/yourorg/jama.git
|
||||
cd jama
|
||||
```
|
||||
|
||||
### 2. Build the Docker image
|
||||
|
||||
```bash
|
||||
./build.sh 1.0.0
|
||||
```
|
||||
|
||||
### 3. Configure environment
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
At minimum, change `ADMIN_EMAIL`, `ADMIN_PASS`, and `JWT_SECRET`. See [Environment Variables](#environment-variables) for all options.
|
||||
|
||||
### 4. Start the container
|
||||
|
||||
```bash
|
||||
docker compose pull # if pulling from a registry
|
||||
docker compose up -d
|
||||
|
||||
# Follow startup logs
|
||||
docker compose logs -f jama
|
||||
```
|
||||
|
||||
### Rolling back
|
||||
|
||||
```bash
|
||||
# .env
|
||||
TEAMCHAT_VERSION=1.1.0
|
||||
|
||||
docker compose up -d # instantly rolls back to previous image
|
||||
On first startup you should see:
|
||||
```
|
||||
[DB] Default admin created: admin@yourdomain.com
|
||||
[DB] Default jama group created
|
||||
[DB] Support group created
|
||||
```
|
||||
|
||||
Data volumes are unaffected by version changes.
|
||||
### 5. Log in
|
||||
|
||||
Open `http://your-server:3000` in a browser, log in with your `ADMIN_EMAIL` and `ADMIN_PASS`, and change your password when prompted.
|
||||
|
||||
---
|
||||
|
||||
## HTTPS & SSL (Required for Web Push and PWA install prompt)
|
||||
|
||||
jama does not manage SSL itself. Use **Caddy** as a reverse proxy — it obtains and renews Let's Encrypt certificates automatically.
|
||||
|
||||
### docker-compose.yaml (with Caddy)
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
jama:
|
||||
image: jama:${JAMA_VERSION:-latest}
|
||||
container_name: jama
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "3000" # internal only — Caddy is the sole entry point
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ADMIN_NAME=${ADMIN_NAME:-Admin User}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@jama.local}
|
||||
- ADMIN_PASS=${ADMIN_PASS:-Admin@1234}
|
||||
- PW_RESET=${PW_RESET:-false}
|
||||
- JWT_SECRET=${JWT_SECRET:-changeme}
|
||||
- APP_NAME=${APP_NAME:-jama}
|
||||
- JAMA_VERSION=${JAMA_VERSION:-latest}
|
||||
volumes:
|
||||
- jama_db:/app/data
|
||||
- jama_uploads:/app/uploads
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
caddy:
|
||||
image: caddy:alpine
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy_data:/data
|
||||
- caddy_certs:/config
|
||||
depends_on:
|
||||
- jama
|
||||
|
||||
volumes:
|
||||
jama_db:
|
||||
jama_uploads:
|
||||
caddy_data:
|
||||
caddy_certs:
|
||||
```
|
||||
|
||||
### Caddyfile
|
||||
|
||||
Create a `Caddyfile` in the same directory as `docker-compose.yaml`:
|
||||
|
||||
```
|
||||
chat.yourdomain.com {
|
||||
reverse_proxy jama:3000
|
||||
}
|
||||
```
|
||||
|
||||
> **Prerequisites:** Your domain's DNS A record must point to your server's public IP *before* starting Caddy, so the Let's Encrypt HTTP challenge can complete.
|
||||
|
||||
---
|
||||
|
||||
## docker-compose.yaml Reference (without Caddy)
|
||||
|
||||
The default `docker-compose.yaml` exposes jama directly on a host port:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
jama:
|
||||
image: jama:${JAMA_VERSION:-latest}
|
||||
container_name: jama
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${PORT:-3000}:3000" # change PORT in .env to use a different host port
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ADMIN_NAME=${ADMIN_NAME:-Admin User}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@jama.local}
|
||||
- ADMIN_PASS=${ADMIN_PASS:-Admin@1234}
|
||||
- PW_RESET=${PW_RESET:-false}
|
||||
- JWT_SECRET=${JWT_SECRET:-changeme_super_secret_jwt_key_2024}
|
||||
- APP_NAME=${APP_NAME:-jama}
|
||||
volumes:
|
||||
- jama_db:/app/data # SQLite database
|
||||
- jama_uploads:/app/uploads # avatars, logos, message images
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
jama_db:
|
||||
driver: local
|
||||
jama_uploads:
|
||||
driver: local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ADMIN_NAME` | `Admin User` | Default admin display name |
|
||||
| `ADMIN_EMAIL` | `admin@teamchat.local` | Default admin email (login) |
|
||||
| `ADMIN_PASS` | `Admin@1234` | Default admin password (first run only) |
|
||||
| `PW_RESET` | `false` | If `true`, resets admin password to `ADMIN_PASS` on every restart |
|
||||
| `JWT_SECRET` | *(insecure default)* | **Change this!** Used to sign auth tokens |
|
||||
| `PORT` | `3000` | HTTP port to listen on |
|
||||
| `APP_NAME` | `TeamChat` | Initial app name (can be changed in Settings) |
|
||||
|---|---|---|
|
||||
| `JAMA_VERSION` | `latest` | Docker image tag to run. Set by `build.sh` or manually. |
|
||||
| `ADMIN_NAME` | `Admin User` | Display name of the default admin account |
|
||||
| `ADMIN_EMAIL` | `admin@jama.local` | Login email for the default admin account |
|
||||
| `ADMIN_PASS` | `Admin@1234` | Initial password for the default admin account |
|
||||
| `PW_RESET` | `false` | If `true`, resets the admin password to `ADMIN_PASS` on every container restart. Shows a warning banner on the login page. For emergency access recovery only. |
|
||||
| `JWT_SECRET` | *(insecure default)* | Secret used to sign auth tokens. **Must be changed in production.** Use a long random string. |
|
||||
| `PORT` | `3000` | Host port to bind (only applies when not using Caddy's `expose` setup) |
|
||||
| `APP_NAME` | `jama` | Initial application name. Can also be changed at any time in the Settings UI. |
|
||||
|
||||
> **Important:** `ADMIN_EMAIL` and `ADMIN_PASS` are only used on the very first run to create the admin account. After the admin changes their password, these variables are ignored — **unless** `PW_RESET=true`.
|
||||
> **Note:** `ADMIN_EMAIL` and `ADMIN_PASS` are only used on the **very first run** to seed the admin account. Once the database exists these values are ignored — unless `PW_RESET=true`.
|
||||
|
||||
### Example `.env`
|
||||
|
||||
```env
|
||||
JAMA_VERSION=1.0.0
|
||||
|
||||
ADMIN_NAME=Your Name
|
||||
ADMIN_EMAIL=admin@yourdomain.com
|
||||
ADMIN_PASS=ChangeThisNow!
|
||||
|
||||
PW_RESET=false
|
||||
|
||||
JWT_SECRET=replace-this-with-a-long-random-string-at-least-32-chars
|
||||
|
||||
PORT=3000
|
||||
APP_NAME=jama
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## First Login
|
||||
## First Login & Setup Checklist
|
||||
|
||||
1. Navigate to `http://localhost:3000`
|
||||
2. Login with `ADMIN_EMAIL` / `ADMIN_PASS`
|
||||
3. You'll be prompted to **change your password** immediately
|
||||
4. You're in! The default **TeamChat** public channel is ready
|
||||
|
||||
---
|
||||
|
||||
## PW_RESET Warning
|
||||
|
||||
If you set `PW_RESET=true`:
|
||||
- The admin password resets to `ADMIN_PASS` on **every container restart**
|
||||
- A ⚠️ warning banner appears on the login page
|
||||
- This is intentional for emergency access recovery
|
||||
- **Always set back to `false` after recovering access**
|
||||
1. Open your app URL and log in with `ADMIN_EMAIL` / `ADMIN_PASS`
|
||||
2. Change your password when prompted
|
||||
3. Open ⚙️ **Settings** (bottom-left menu → Settings):
|
||||
- Upload a custom logo
|
||||
- Set the app name
|
||||
- Optionally upload custom New Chat and Group Info icons
|
||||
4. Open 👥 **User Manager** to create accounts for your team
|
||||
5. Create public channels or let users create private groups
|
||||
|
||||
---
|
||||
|
||||
## User Management
|
||||
|
||||
Admins can access **User Manager** from the bottom menu:
|
||||
Accessible from the bottom-left menu (admin only).
|
||||
|
||||
- **Create single user** — Name, email, temp password, role
|
||||
- **Bulk import via CSV** — Format: `name,email,password,role`
|
||||
- **Reset password** — User is forced to change on next login
|
||||
- **Suspend / Activate** — Suspended users cannot login
|
||||
- **Delete** — Soft delete; messages remain, sessions invalidated
|
||||
- **Elevate / Demote** — Change member ↔ admin role
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| Create user | Set name, email, temporary password, and role |
|
||||
| Bulk CSV import | Upload a CSV to create multiple users at once |
|
||||
| Reset password | User is forced to set a new password on next login |
|
||||
| Suspend | Blocks login; messages are preserved |
|
||||
| Activate | Re-enables a suspended account |
|
||||
| Delete | Removes account; messages remain attributed to user |
|
||||
| Change role | Promote member → admin or demote admin → member |
|
||||
|
||||
### CSV Import Format
|
||||
|
||||
```csv
|
||||
name,email,password,role
|
||||
John Doe,john@example.com,TempPass123,member
|
||||
Jane Smith,jane@example.com,Admin@456,admin
|
||||
```
|
||||
|
||||
- `role` must be `member` or `admin`
|
||||
- `password` is optional — defaults to `TempPass@123` if omitted
|
||||
- All imported users must change their password on first login
|
||||
|
||||
---
|
||||
|
||||
## Group Types
|
||||
|
||||
| | Public Channels | Private Groups |
|
||||
|--|--|--|
|
||||
| Creator | Admin only | Any user |
|
||||
| Members | All users (auto) | Invited by owner |
|
||||
| Visible to admins | ✅ Yes | ❌ No (unless admin takes ownership) |
|
||||
|---|---|---|
|
||||
| Who can create | Admin only | Any user |
|
||||
| Membership | All users (automatic) | Invite-only by owner |
|
||||
| Visible to admins | ✅ Yes | ❌ No |
|
||||
| Leave | ❌ Not allowed | ✅ Yes |
|
||||
| Rename | Admin only | Owner only |
|
||||
| Read-only mode | ✅ Optional | ❌ N/A |
|
||||
| Default group | TeamChat (permanent) | — |
|
||||
|
||||
---
|
||||
|
||||
## CSV Import Format
|
||||
|
||||
```csv
|
||||
name,email,password,role
|
||||
John Doe,john@example.com,TempPass123,member
|
||||
Jane Admin,jane@example.com,Admin@456,admin
|
||||
```
|
||||
|
||||
- `role` can be `member` or `admin`
|
||||
- `password` defaults to `TempPass@123` if omitted
|
||||
- All imported users must change password on first login
|
||||
|
||||
---
|
||||
|
||||
## Data Persistence
|
||||
|
||||
All data is stored in Docker volumes:
|
||||
- `teamchat_db` — SQLite database
|
||||
- `teamchat_uploads` — User avatars, logos, message images
|
||||
| Volume | Container path | Contents |
|
||||
|---|---|---|
|
||||
| `jama_db` | `/app/data` | SQLite database (`jama.db`) |
|
||||
| `jama_uploads` | `/app/uploads` | Avatars, logos, PWA icons, message images |
|
||||
|
||||
Data survives container restarts and redeployments.
|
||||
Both volumes survive container restarts, image upgrades, and rollbacks.
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# Backup database
|
||||
docker run --rm \
|
||||
-v jama_db:/data \
|
||||
-v $(pwd):/backup alpine \
|
||||
tar czf /backup/jama_db_$(date +%Y%m%d).tar.gz -C /data .
|
||||
|
||||
# Backup uploads
|
||||
docker run --rm \
|
||||
-v jama_uploads:/data \
|
||||
-v $(pwd):/backup alpine \
|
||||
tar czf /backup/jama_uploads_$(date +%Y%m%d).tar.gz -C /data .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Versioning, Upgrades & Rollbacks
|
||||
|
||||
jama uses a build-once, deploy-anywhere pattern:
|
||||
|
||||
```
|
||||
Build machine Server
|
||||
./build.sh 1.1.0 → JAMA_VERSION=1.1.0 → docker compose up -d
|
||||
```
|
||||
|
||||
### Upgrade
|
||||
|
||||
```bash
|
||||
# 1. Build new version
|
||||
./build.sh 1.1.0
|
||||
|
||||
# 2. Update .env
|
||||
JAMA_VERSION=1.1.0
|
||||
|
||||
# 3. Redeploy (data volumes untouched)
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# 1. Set previous version in .env
|
||||
JAMA_VERSION=1.0.0
|
||||
|
||||
# 2. Redeploy
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PWA Installation
|
||||
|
||||
On mobile: **Share → Add to Home Screen**
|
||||
On desktop (Chrome): Click the install icon in the address bar
|
||||
HTTPS is required for the browser install prompt to appear.
|
||||
|
||||
| Platform | How to install |
|
||||
|---|---|
|
||||
| Android (Chrome) | Tap the install banner, or Menu → Add to Home Screen |
|
||||
| iOS (Safari) | Share → Add to Home Screen |
|
||||
| Desktop Chrome/Edge | Click the install icon (⊕) in the address bar |
|
||||
|
||||
After uploading a custom logo in Settings, the PWA shortcut icon updates automatically on the next app load.
|
||||
|
||||
---
|
||||
|
||||
## Portainer / Dockhand Deployment
|
||||
## PW_RESET Flag
|
||||
|
||||
Use the `docker-compose.yaml` directly in Portainer's Stack editor. Set environment variables in the `.env` section or directly in the compose file.
|
||||
Setting `PW_RESET=true` resets the default admin password to `ADMIN_PASS` on **every container restart**. Use only for emergency access recovery.
|
||||
|
||||
When active, a ⚠️ warning banner is shown on the login page and in the Settings panel.
|
||||
|
||||
**Always set `PW_RESET=false` and redeploy after recovering access.**
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
# Start backend (port 3000)
|
||||
cd backend && npm install && npm run dev
|
||||
|
||||
# Frontend (in another terminal)
|
||||
# Start frontend in a separate terminal (port 5173)
|
||||
cd frontend && npm install && npm run dev
|
||||
```
|
||||
|
||||
Frontend dev server proxies API calls to `localhost:3000`.
|
||||
The Vite dev server proxies all `/api` and `/socket.io` requests to the backend automatically.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user