diff --git a/.env.example b/.env.example index 8e31511..78c0063 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ TZ=UTC # Copy this file to .env and customize # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.5.1 +JAMA_VERSION=0.6.2 # Default admin credentials (used on FIRST RUN only) ADMIN_NAME=Admin User @@ -19,7 +19,7 @@ USER_PASS=user@1234 # Set to true to reset admin password to ADMIN_PASS on every restart # WARNING: Leave false in production - shows a warning on login page when true -PW_RESET=false +ADMPW_RESET=false # JWT secret - change this to a random string in production! JWT_SECRET=changeme_super_secret_jwt_key_change_in_production diff --git a/Dockerfile b/Dockerfile index 2a00bd7..1bc713b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,9 @@ COPY --from=builder /app/frontend/dist ./public # Create data and uploads directories RUN mkdir -p /app/data /app/uploads/avatars /app/uploads/logos /app/uploads/images +# Copy default help.md (can be overridden by mounting /app/data/help.md) +COPY data/help.md /app/data/help.md + EXPOSE 3000 CMD ["node", "src/index.js"] diff --git a/README.md b/README.md index 08e90ed..29e76ca 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,26 @@ A modern, self-hosted team messaging Progressive Web App (PWA) built for small t ### 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 +- **Image attachments** — Attach and send images via the + menu; auto-compressed client-side before upload +- **Camera capture** — Take a photo directly from the + menu on mobile devices +- **Emoji picker** — Send standalone emoji messages at large size via the + menu - **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 +- **@Mentions** — Type `@` to search and tag users using `@[Display Name]` syntax; autocomplete scoped to group members; 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 +- **Message grouping** — Consecutive messages from the same user are visually grouped; avatar and name shown only on first message +- **Last message preview** — Sidebar shows "You:" prefix when the current user sent the last message ### 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 +- **Direct messages** — One-to-one private conversations; sidebar title always shows the other user's real name +- **Duplicate group prevention** — Creating a private group with the same member set as an existing group redirects to the existing group automatically - **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 +- **Custom group names** — Each user can set a personal display name for any group, visible only to them ### Users & Profiles - **Authentication** — Email/password login with optional Remember Me (30-day session) @@ -38,18 +45,25 @@ A modern, self-hosted team messaging Progressive Web App (PWA) built for small t ### 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 +- **App branding** — Customize app name and logo via the Settings panel - **Reset to defaults** — One-click reset of all branding customizations - **Version display** — Current app version shown in the Settings panel +- **Default user password** — Configurable via `USER_PASS` env var; shown live in User Manager + +### Help & Onboarding +- **Getting Started modal** — Appears automatically on first login; users can dismiss permanently with "Do not show again" +- **Help menu item** — Always accessible from the user menu regardless of dismissed state +- **Editable help content** — `data/help.md` is edited before build and baked into the image at build time ### 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 +- **Adaptive icons** — Separate `any` and `maskable` icon entries; maskable icons sized for Android circular crop +- **Dynamic app icon** — Uploaded logo is automatically resized and used as the PWA shortcut icon +- **Dynamic manifest** — App name and icons update live when changed in Settings +- **Pull-to-refresh disabled** — In PWA standalone mode, pull-to-refresh is disabled to prevent a layout shift bug on mobile ### 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 +- **Login page contact form** — A "Contact Support" button on the login page opens a form that posts directly into the admin Support group --- @@ -60,6 +74,8 @@ A modern, self-hosted team messaging Progressive Web App (PWA) built for small t | Backend | Node.js, Express, Socket.io | | Database | SQLite (better-sqlite3) | | Frontend | React 18, Vite | +| Markdown rendering | marked | +| Emoji picker | emoji-mart | | Image processing | sharp | | Push notifications | web-push (VAPID) | | Containerization | Docker, Docker Compose | @@ -77,24 +93,18 @@ A modern, self-hosted team messaging Progressive Web App (PWA) built for small t ## Building the Image -All builds use `build.sh`. No host Node.js installation is required — `npm install` and the Vite build run inside Docker. +All builds use `build.sh`. No host Node.js installation is required. + +> **Tip:** Edit `data/help.md` before running `build.sh` to customise the Getting Started help content baked into the image. ```bash # Build and tag as :latest only ./build.sh -# Build and tag as a specific version (also tags :latest) +# Build and tag as a specific version ./build.sh 1.0.0 - -# Build and push to Docker Hub -REGISTRY=yourdockerhubuser ./build.sh 1.0.0 push - -# Build and push to GitHub Container Registry -REGISTRY=ghcr.io/yourorg ./build.sh 1.0.0 push ``` -After a successful build the script prints the exact `.env` and `docker compose` commands needed to deploy. - --- ## Installation @@ -102,7 +112,7 @@ After a successful build the script prints the exact `.env` and `docker compose` ### 1. Clone the repository ```bash -git clone https://github.com/yourorg/jama.git +git clone https://your-gitea/youruser/jama.git cd jama ``` @@ -119,33 +129,32 @@ cp .env.example .env nano .env ``` -At minimum, change `ADMIN_EMAIL`, `ADMIN_PASS`, and `JWT_SECRET`. See [Environment Variables](#environment-variables) for all options. +At minimum, change `ADMIN_EMAIL`, `ADMIN_PASS`, and `JWT_SECRET`. ### 4. Start the container ```bash docker compose up -d - -# Follow startup logs docker compose logs -f jama ``` -On first startup you should see: -``` -[DB] Default admin created: admin@yourdomain.com -[DB] Default jama group created -[DB] Support group created -``` - ### 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. +Open `http://your-server:3000`, 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) +## HTTPS & SSL -jama does not manage SSL itself. Use **Caddy** as a reverse proxy — it obtains and renews Let's Encrypt certificates automatically. +jama does not manage SSL itself. Use **Caddy** as a reverse proxy. + +### Caddyfile + +``` +chat.yourdomain.com { + reverse_proxy jama:3000 +} +``` ### docker-compose.yaml (with Caddy) @@ -157,24 +166,20 @@ services: container_name: jama restart: unless-stopped expose: - - "3000" # internal only — Caddy is the sole entry point + - "3000" 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} + - USER_PASS=${USER_PASS:-user@1234} + - ADMPW_RESET=${ADMPW_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 @@ -183,7 +188,7 @@ services: ports: - "80:80" - "443:443" - - "443:443/udp" # HTTP/3 + - "443:443/udp" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data @@ -198,103 +203,55 @@ volumes: 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 | |---|---|---| -| `JAMA_VERSION` | `latest` | Docker image tag to run. Set by `build.sh` or manually. | +| `JAMA_VERSION` | `latest` | Docker image tag to run | +| `TZ` | `UTC` | Container timezone (e.g. `America/Toronto`) | | `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. | +| `USER_PASS` | `user@1234` | Default temporary password for bulk-imported users when no password is specified in CSV | +| `ADMPW_RESET` | `false` | If `true`, resets the **admin** password to `ADMIN_PASS` on every restart. Emergency access recovery only. Shows a warning banner when active. | +| `JWT_SECRET` | *(insecure default)* | Secret used to sign auth tokens. **Must be changed in production.** | +| `PORT` | `3000` | Host port to bind (without Caddy) | +| `APP_NAME` | `jama` | Initial application name (can also be changed in Settings UI) | +| `DEFCHAT_NAME` | `General Chat` | Name of the default public group created on first run | -> **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`. +> `ADMIN_EMAIL` and `ADMIN_PASS` are only used on the **first run**. Once the database exists they are ignored — unless `ADMPW_RESET=true`. ### Example `.env` ```env JAMA_VERSION=1.0.0 +TZ=America/Toronto ADMIN_NAME=Your Name ADMIN_EMAIL=admin@yourdomain.com ADMIN_PASS=ChangeThisNow! -PW_RESET=false +USER_PASS=Welcome@123 +ADMPW_RESET=false JWT_SECRET=replace-this-with-a-long-random-string-at-least-32-chars PORT=3000 APP_NAME=jama +DEFCHAT_NAME=General Chat ``` --- ## First Login & Setup Checklist -1. Open your app URL and log in with `ADMIN_EMAIL` / `ADMIN_PASS` +1. 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 +3. Read the **Getting Started** guide that appears on first login +4. Open ⚙️ **Settings** → upload a logo and set the app name +5. Open 👥 **User Manager** to create accounts for your team --- @@ -317,25 +274,55 @@ Accessible from the bottom-left menu (admin only). ```csv name,email,password,role John Doe,john@example.com,TempPass123,member -Jane Smith,jane@example.com,Admin@456,admin +Jane Smith,jane@example.com,,admin ``` -- `role` must be `member` or `admin` -- `password` is optional — defaults to `TempPass@123` if omitted +- `password` is optional — defaults to the value of `USER_PASS` if omitted - All imported users must change their password on first login --- ## Group Types -| | Public Channels | Private Groups | -|---|---|---| -| 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 | +| | Public Channels | Private Groups | Direct Messages | +|---|---|---|---| +| Who can create | Admin only | Any user | Any user | +| Membership | All users (automatic) | Invite-only by owner | Two users only | +| Sidebar title | Group name | Group name (customisable per user) | Other user's real name | +| Rename | Admin only | Owner only | ❌ Not allowed | +| Read-only mode | ✅ Optional | ❌ N/A | ❌ N/A | +| Duplicate prevention | N/A | ✅ Redirects to existing | ✅ Redirects to existing | + +### @Mention Scoping + +- **Public channels** — all active users appear in the `@` autocomplete +- **Private groups** — only members of that group appear +- **Direct messages** — only the other participant appears + +--- + +## Custom Group Names + +Any user can set a personal display name for any group: + +1. Open the group and tap the **ⓘ info** icon +2. Enter a name under **Your custom name** and tap **Save** +3. The custom name appears in your sidebar and chat header only +4. Message Info shows: `Custom Name (Owner's Name)` +5. Clear the field and tap **Save** to revert to the owner's name + +--- + +## Help Content + +The Getting Started guide is sourced from `data/help.md`. Edit before running `build.sh` — it is baked into the image at build time. + +```bash +nano data/help.md +./build.sh 1.0.0 +``` + +Users can access the guide at any time via **User menu → Help**. --- @@ -343,11 +330,9 @@ Jane Smith,jane@example.com,Admin@456,admin | Volume | Container path | Contents | |---|---|---| -| `jama_db` | `/app/data` | SQLite database (`jama.db`) | +| `jama_db` | `/app/data` | SQLite database (`jama.db`), `help.md` | | `jama_uploads` | `/app/uploads` | Avatars, logos, PWA icons, message images | -Both volumes survive container restarts, image upgrades, and rollbacks. - ### Backup ```bash @@ -366,71 +351,55 @@ docker run --rm \ --- -## 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 +## Upgrades & Rollbacks ```bash -# 1. Build new version +# Upgrade ./build.sh 1.1.0 +# Set JAMA_VERSION=1.1.0 in .env +docker compose up -d -# 2. Update .env -JAMA_VERSION=1.1.0 - -# 3. Redeploy (data volumes untouched) +# Rollback +# Set JAMA_VERSION=1.0.0 in .env docker compose up -d ``` -### Rollback - -```bash -# 1. Set previous version in .env -JAMA_VERSION=1.0.0 - -# 2. Redeploy -docker compose up -d -``` +Data volumes are untouched in both cases. --- -## PWA Installation +## PWA Icons -HTTPS is required for the browser install prompt to appear. - -| Platform | How to install | +| File | Purpose | |---|---| -| 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. +| `icon-192.png` / `icon-512.png` | Standard icons — PC PWA shortcuts (`purpose: any`) | +| `icon-192-maskable.png` / `icon-512-maskable.png` | Adaptive icons — Android home screen (`purpose: maskable`); logo at 75% scale on solid background | --- -## PW_RESET Flag +## ADMPW_RESET Flag -Setting `PW_RESET=true` resets the default admin password to `ADMIN_PASS` on **every container restart**. Use only for emergency access recovery. +Resets the **admin account** password to `ADMIN_PASS` on every container restart. Use only when the admin password has been lost. -When active, a ⚠️ warning banner is shown on the login page and in the Settings panel. +```env +# Enable for recovery +ADMPW_RESET=true -**Always set `PW_RESET=false` and redeploy after recovering access.** +# Disable after recovering access +ADMPW_RESET=false +``` + +A ⚠️ warning banner is shown on the login page and in Settings when active. --- ## Development ```bash -# Start backend (port 3000) +# Backend (port 3000) cd backend && npm install && npm run dev -# Start frontend in a separate terminal (port 5173) +# Frontend (port 5173) cd frontend && npm install && npm run dev ``` diff --git a/backend/package.json b/backend/package.json index 04d28d2..b9ae716 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.5.1", + "version": "0.6.2", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/backend/src/index.js b/backend/src/index.js index 3c7beda..6cdd09e 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -36,6 +36,7 @@ app.use('/api/groups', require('./routes/groups')(io)); app.use('/api/messages', require('./routes/messages')); app.use('/api/settings', require('./routes/settings')); app.use('/api/about', require('./routes/about')); +app.use('/api/help', require('./routes/help')); app.use('/api/push', pushRouter); // Link preview proxy diff --git a/backend/src/models/db.js b/backend/src/models/db.js index b628317..63684cd 100644 --- a/backend/src/models/db.js +++ b/backend/src/models/db.js @@ -134,7 +134,7 @@ function initDb() { const insertSetting = db.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)'); insertSetting.run('app_name', process.env.APP_NAME || 'jama'); insertSetting.run('logo_url', ''); - insertSetting.run('pw_reset_active', process.env.PW_RESET === 'true' ? 'true' : 'false'); + insertSetting.run('pw_reset_active', process.env.ADMPW_RESET === 'true' ? 'true' : 'false'); insertSetting.run('icon_newchat', ''); insertSetting.run('icon_groupinfo', ''); insertSetting.run('pwa_icon_192', ''); @@ -182,6 +182,12 @@ function initDb() { console.log('[DB] Migration: added direct_peer2_id column'); } catch (e) { /* column already exists */ } + // Migration: help_dismissed preference per user + try { + db.exec("ALTER TABLE users ADD COLUMN help_dismissed INTEGER NOT NULL DEFAULT 0"); + console.log('[DB] Migration: added help_dismissed column'); + } catch (e) { /* column already exists */ } + // Migration: user-customised group display names (per-user, per-group) try { db.exec(` @@ -206,7 +212,7 @@ function seedAdmin() { const adminEmail = (process.env.ADMIN_EMAIL || 'admin@jama.local').replace(/^["']|["']$/g, '').trim(); const adminName = (process.env.ADMIN_NAME || 'Admin User').replace(/^["']|["']$/g, '').trim(); const adminPass = (process.env.ADMIN_PASS || 'Admin@1234').replace(/^["']|["']$/g, '').trim(); - const pwReset = process.env.PW_RESET === 'true'; + const pwReset = process.env.ADMPW_RESET === 'true'; console.log(`[DB] Checking for default admin (${adminEmail})...`); @@ -242,7 +248,7 @@ function seedAdmin() { console.log(`[DB] Default admin already exists (id=${existing.id})`); - // Handle PW_RESET + // Handle ADMPW_RESET if (pwReset) { const hash = bcrypt.hashSync(adminPass, 10); db.prepare(` @@ -250,7 +256,7 @@ function seedAdmin() { WHERE is_default_admin = 1 `).run(hash); db.prepare("UPDATE settings SET value = 'true', updated_at = datetime('now') WHERE key = 'pw_reset_active'").run(); - console.log('[DB] Admin password reset via PW_RESET=true'); + console.log('[DB] Admin password reset via ADMPW_RESET=true'); } else { db.prepare("UPDATE settings SET value = 'false', updated_at = datetime('now') WHERE key = 'pw_reset_active'").run(); } diff --git a/backend/src/routes/help.js b/backend/src/routes/help.js new file mode 100644 index 0000000..4b24e13 --- /dev/null +++ b/backend/src/routes/help.js @@ -0,0 +1,39 @@ +const express = require('express'); +const router = express.Router(); +const fs = require('fs'); +const path = require('path'); +const { getDb } = require('../models/db'); +const { authMiddleware } = require('../middleware/auth'); + +const HELP_FILE = '/app/data/help.md'; +const HELP_FALLBACK = path.join(__dirname, '../../data/help.md'); + +// GET /api/help — returns markdown content +router.get('/', authMiddleware, (req, res) => { + let content = ''; + const filePath = fs.existsSync(HELP_FILE) ? HELP_FILE : HELP_FALLBACK; + try { + content = fs.readFileSync(filePath, 'utf8'); + } catch (e) { + content = '# Getting Started\n\nHelp content is not available yet.'; + } + res.json({ content }); +}); + +// GET /api/help/status — returns whether user has dismissed help +router.get('/status', authMiddleware, (req, res) => { + const db = getDb(); + const user = db.prepare('SELECT help_dismissed FROM users WHERE id = ?').get(req.user.id); + res.json({ dismissed: !!user?.help_dismissed }); +}); + +// POST /api/help/dismiss — set help_dismissed for current user +router.post('/dismiss', authMiddleware, (req, res) => { + const { dismissed } = req.body; + const db = getDb(); + db.prepare("UPDATE users SET help_dismissed = ? WHERE id = ?") + .run(dismissed ? 1 : 0, req.user.id); + res.json({ success: true }); +}); + +module.exports = router; diff --git a/build.sh b/build.sh index d0cb072..e3d2600 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.5.1}" +VERSION="${1:-0.6.2}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/data/help.md b/data/help.md new file mode 100644 index 0000000..1546cc8 --- /dev/null +++ b/data/help.md @@ -0,0 +1,105 @@ +# Getting Started with Jama + +Welcome to **Jama** — your private, self-hosted team messaging app. + +--- + +## Navigating Jama + +### Message List (Left Sidebar) +The sidebar shows all your message groups and direct conversations. Tap or click any group to open it. + +- **#** prefix indicates a **Public** group — visible to all users +- **Lock** icon indicates a **Private** group — invite only +- **Bold** group names have unread messages +- The last message preview shows **You:** if you sent it + +--- + +## Sending Messages + +Type your message in the input box at the bottom and press **Enter** to send. + +- **Shift + Enter** adds a new line without sending +- Tap the **+** button to attach a photo or emoji +- Use the **camera** icon to take a photo directly (mobile) + +### Mentioning Someone +Type **@** followed by the person's name to mention them. Select from the dropdown that appears. Mentioned users receive a notification. + +Example: `@[John Smith]` will notify John. + +### Replying to a Message +Hover over any message and click the **reply arrow** to quote and reply to it. + +### Reacting to a Message +Hover over any message and click the **emoji** button to react with an emoji. + +--- + +## Direct Messages + +To start a private conversation with one person: + +1. Click the **pencil / new chat** icon in the sidebar +2. Select one user from the list +3. Click **Start Conversation** + +--- + +## Group Messages + +To create a group conversation: + +1. Click the **pencil / new chat** icon +2. Select two or more users +3. Enter a **Group Name** +4. Click **Create** + +> If a group with the exact same members already exists, you will be redirected to it automatically. + +--- + +## Your Profile + +Click your name or avatar at the bottom of the sidebar to: + +- Update your **display name** (shown to others instead of your username) +- Add an **about me** note +- Upload a **profile photo** +- Change your **password** + +--- + +## Customising Group Names + +You can set a personal display name for any group that only you will see: + +1. Open the group +2. Click the **ⓘ info** icon in the top bar +3. Enter your custom name under **Your custom name** +4. Click **Save** + +Other members still see the original group name. + +--- + +## Settings + +Admins can access **Settings** from the user menu to configure: + +- App name and logo +- Default user password +- Notification preferences + +--- + +## Tips + +- 🌙 Toggle **dark mode** from the user menu +- 🔔 Enable **push notifications** when prompted to receive alerts when the app is closed +- 📱 Install Jama as a **PWA** on your device — tap *Add to Home Screen* in your browser menu for an app-like experience + +--- + +*This help file can be updated by your administrator at any time.* diff --git a/docker-compose.yaml b/docker-compose.yaml index 2186691..efa593c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,7 +14,7 @@ services: - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@jama.local} - ADMIN_PASS=${ADMIN_PASS:-Admin@1234} - USER_PASS=${USER_PASS:-user@1234} - - PW_RESET=${PW_RESET:-false} + - ADMPW_RESET=${ADMPW_RESET:-false} - JWT_SECRET=${JWT_SECRET:-changeme_super_secret_jwt_key_2024} - APP_NAME=${APP_NAME:-jama} - DEFCHAT_NAME=${DEFCHAT_NAME:-General Chat} diff --git a/frontend/package.json b/frontend/package.json index dc7df7b..7f4ecff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.5.1", + "version": "0.6.2", "private": true, "scripts": { "dev": "vite", @@ -16,7 +16,8 @@ "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", "papaparse": "^5.4.1", - "date-fns": "^3.3.1" + "date-fns": "^3.3.1", + "marked": "^12.0.0" }, "devDependencies": { "@vitejs/plugin-react": "^4.2.1", diff --git a/frontend/src/components/HelpModal.jsx b/frontend/src/components/HelpModal.jsx new file mode 100644 index 0000000..8c96093 --- /dev/null +++ b/frontend/src/components/HelpModal.jsx @@ -0,0 +1,71 @@ +import { useState, useEffect } from 'react'; +import { marked } from 'marked'; +import { api } from '../utils/api.js'; + +// Configure marked for safe rendering +marked.setOptions({ breaks: true, gfm: true }); + +export default function HelpModal({ onClose, dismissed: initialDismissed }) { + const [content, setContent] = useState(''); + const [loading, setLoading] = useState(true); + const [dismissed, setDismissed] = useState(!!initialDismissed); + + useEffect(() => { + api.getHelp() + .then(({ content }) => setContent(content)) + .catch(() => setContent('# Getting Started\n\nHelp content could not be loaded.')) + .finally(() => setLoading(false)); + }, []); + + const handleDismissToggle = async (e) => { + const val = e.target.checked; + setDismissed(val); + try { + await api.dismissHelp(val); + if (val) onClose(); // immediately close when "do not show again" checked + } catch (_) {} + }; + + return ( +
e.target === e.currentTarget && onClose()}> +
+ + {/* Header */} +
+

Getting Started

+ +
+ + {/* Scrollable markdown content */} +
+ {loading ? ( +
Loading…
+ ) : ( +
+ )} +
+ + {/* Footer */} +
+ + +
+ +
+
+ ); +} diff --git a/frontend/src/components/SettingsModal.jsx b/frontend/src/components/SettingsModal.jsx index 2965f47..6aa60c4 100644 --- a/frontend/src/components/SettingsModal.jsx +++ b/frontend/src/components/SettingsModal.jsx @@ -148,7 +148,7 @@ export default function SettingsModal({ onClose }) { {settings.pw_reset_active === 'true' && (
⚠️ - PW_RESET is active. The default admin password is being reset on every restart. Set PW_RESET=false in your environment variables to stop this. + ADMPW_RESET is active. The default admin password is being reset on every restart. Set ADMPW_RESET=false in your environment variables to stop this.
)}
diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 37f4714..ad79b3c 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -43,7 +43,7 @@ function useAppSettings() { return settings; } -export default function Sidebar({ groups, activeGroupId, onSelectGroup, notifications, unreadGroups = new Map(), onNewChat, onProfile, onUsers, onSettings: onOpenSettings, onGroupsUpdated, isMobile, onAbout }) { +export default function Sidebar({ groups, activeGroupId, onSelectGroup, notifications, unreadGroups = new Map(), onNewChat, onProfile, onUsers, onSettings: onOpenSettings, onGroupsUpdated, isMobile, onAbout, onHelp }) { const { user, logout } = useAuth(); const { connected } = useSocket(); const toast = useToast(); @@ -219,6 +219,10 @@ export default function Sidebar({ groups, activeGroupId, onSelectGroup, notifica )}
+