Add Help page with markdown support and routing

This commit is contained in:
2026-01-18 15:52:46 +01:00
parent eab5303c0f
commit 22fb4b9b20
7 changed files with 2172 additions and 278 deletions

738
dockerize.md Normal file
View File

@@ -0,0 +1,738 @@
# Docker Production Build Guide
## Goal
Build a production-ready Docker image containing both the frontend (React/Vite) and backend (Express/Node.js) that connects to an external MySQL database. The container will serve the frontend on port 5173 and proxy API requests to the backend running on localhost:3000 inside the container.
**SUCCESS CRITERIA**: Docker image is built successfully and ready for deployment.
## Architecture
- **Single Docker Container**: Contains both FE and BE
- **External Database**: MySQL server (not in container)
- **Port Exposure**: Only port 5173 (frontend) exposed
- **Internal Communication**: FE → BE via localhost:3000 inside container
- **Reverse Proxy**: Caddy handles SSL/domain externally (Unraid setup)
---
## Task 1: Create Dockerfile
**File**: `Dockerfile` (in project root)
### Requirements:
1. **Multi-stage build** for optimal image size
2. **Stage 1 - Build Frontend**:
- Use `node:20-alpine` as base
- Set working directory to `/app`
- Copy `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`
- Copy `packages/frontend/package.json`
- Install pnpm globally: `npm install -g pnpm`
- Install dependencies: `pnpm install --frozen-lockfile`
- Copy entire `packages/frontend` directory
- Build frontend: `cd packages/frontend && pnpm build`
- Output will be in `packages/frontend/dist`
3. **Stage 2 - Build Backend**:
- Use `node:20-alpine` as base
- Set working directory to `/app`
- Copy workspace files (same as stage 1)
- Install pnpm globally
- Install dependencies: `pnpm install --frozen-lockfile`
- Copy entire `packages/backend` directory
- Generate Prisma client: `cd packages/backend && npx prisma generate`
- Build backend: `cd packages/backend && pnpm build`
- Output will be in `packages/backend/dist`
4. **Stage 3 - Production Runtime**:
- Use `node:20-alpine` as base
- Install pnpm globally and `serve` package globally: `npm install -g pnpm serve`
- Set working directory to `/app`
- Copy production dependencies setup:
- Copy `package.json`, `pnpm-workspace.yaml`, `pnpm-lock.yaml`
- Copy `packages/backend/package.json` to `packages/backend/`
- Copy `packages/frontend/package.json` to `packages/frontend/`
- Install ONLY production dependencies: `pnpm install --prod --frozen-lockfile`
- Copy backend build artifacts:
- From stage 2: `packages/backend/dist``/app/packages/backend/dist`
- From stage 2: `packages/backend/prisma``/app/packages/backend/prisma`
- From stage 2: `packages/backend/node_modules/.prisma``/app/packages/backend/node_modules/.prisma`
- Copy frontend build artifacts:
- From stage 1: `packages/frontend/dist``/app/packages/frontend/dist`
- Create uploads directory: `mkdir -p /app/packages/backend/uploads`
- Expose port 5173
- Create startup script (see Task 2)
- Set CMD to run startup script
### Important Notes:
- Use `.dockerignore` to exclude `node_modules`, `dist`, `.env` files
- Ensure Prisma client is generated and copied correctly
- Backend needs access to Prisma schema for migrations
---
## Task 2: Create Startup Script
**File**: `docker-entrypoint.sh` (in project root)
### Requirements:
1. **Bash script** with proper shebang: `#!/bin/sh`
2. **Set error handling**: `set -e`
3. **Environment validation**:
- Check required env vars: `DATABASE_URL`, `SESSION_SECRET`
- Exit with error message if missing
4. **Database migration**:
- Navigate to backend: `cd /app/packages/backend`
- Run Prisma migrations: `npx prisma migrate deploy`
- Optional: Run seed if `RUN_SEED=true` env var is set
5. **Start backend**:
- Start backend in background: `node dist/server.js &`
- Store PID: `BACKEND_PID=$!`
6. **Wait for backend**:
- Add 5-second sleep or health check loop
- Ensure backend is ready before starting frontend
7. **Start frontend**:
- Serve frontend on port 5173: `serve -s /app/packages/frontend/dist -l 5173`
8. **Signal handling**:
- Trap SIGTERM/SIGINT to gracefully shutdown both processes
- Kill backend PID on exit
### Script Template:
```bash
#!/bin/sh
set -e
echo "🚀 Starting Znakovni.hr Production Container..."
# Validate environment
if [ -z "$DATABASE_URL" ]; then
echo "❌ ERROR: DATABASE_URL is required"
exit 1
fi
# Run migrations
cd /app/packages/backend
echo "📦 Running database migrations..."
npx prisma migrate deploy
# Start backend
echo "🔧 Starting backend server..."
cd /app/packages/backend
node dist/server.js &
BACKEND_PID=$!
# Wait for backend
sleep 5
# Start frontend
echo "🎨 Starting frontend server..."
cd /app
serve -s packages/frontend/dist -l 5173 -n &
FRONTEND_PID=$!
# Trap signals
trap "kill $BACKEND_PID $FRONTEND_PID" SIGTERM SIGINT
# Wait for processes
wait $BACKEND_PID $FRONTEND_PID
```
---
## Task 3: Create .dockerignore
**File**: `.dockerignore` (in project root)
### Exclude:
```
node_modules
dist
.env
.env.local
.env.*.local
*.log
.git
.gitignore
.vscode
.idea
*.md
!README.md
packages/*/node_modules
packages/*/dist
packages/backend/uploads/*
!packages/backend/uploads/.gitkeep
.DS_Store
```
---
## Task 4: Update Frontend Build Configuration
**File**: `packages/frontend/vite.config.ts`
### Changes Required:
1. **Remove hardcoded host**: Change `host: '192.168.1.238'` to `host: '0.0.0.0'` or remove entirely
2. **Production build**: Ensure build outputs to `dist` directory
3. **API proxy**: Remove proxy config (not needed in production, handled by env var)
### Updated Config:
```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
host: '0.0.0.0',
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
},
});
```
---
## Task 5: Update Frontend API Configuration
**File**: `packages/frontend/.env.production` (create new file)
### Content:
```env
VITE_API_URL=http://localhost:3000
```
**Note**: In production Docker container, frontend connects to backend via localhost since they're in the same container.
---
## Task 6: Update Backend Server Configuration
**File**: `packages/backend/src/server.ts`
### Verify/Update:
1. **HOST binding**: Ensure `HOST = process.env.HOST || '0.0.0.0'` (line 85)
2. **PORT**: Ensure `PORT = parseInt(process.env.PORT || '3000', 10)` (line 16)
3. **CORS origin**: Should accept `FRONTEND_URL` from env (line 24)
4. **Static uploads**: Ensure uploads path is correct (line 57-58)
### No changes needed if these are already correct (they are based on codebase retrieval).
---
## Task 7: Create Docker Compose Example
**File**: `docker-compose.yml` (in project root)
### Purpose:
Provide example for running the container with proper environment variables.
### Content:
```yaml
version: '3.8'
services:
znakovni:
build:
context: .
dockerfile: Dockerfile
image: znakovni:latest
container_name: znakovni-app
restart: unless-stopped
ports:
- "5173:5173"
environment:
# Database Configuration
DATABASE_URL: "mysql://user:password@192.168.1.74:3306/znakovni"
# Optional: Shadow database for Prisma migrations
# SHADOW_DATABASE_URL: "mysql://user:password@192.168.1.74:3306/znakovni_shadow"
# Server Configuration
NODE_ENV: production
PORT: 3000
HOST: 0.0.0.0
FRONTEND_URL: http://localhost:5173
# Session Secret (CHANGE THIS!)
SESSION_SECRET: your-super-secret-session-key-change-in-production-min-32-chars
# OAuth - Google (optional, configure if using)
# GOOGLE_CLIENT_ID: your-google-client-id
# GOOGLE_CLIENT_SECRET: your-google-client-secret
# GOOGLE_CALLBACK_URL: https://yourdomain.com/api/auth/google/callback
# OAuth - Microsoft (optional, configure if using)
# MICROSOFT_CLIENT_ID: your-microsoft-client-id
# MICROSOFT_CLIENT_SECRET: your-microsoft-client-secret
# MICROSOFT_CALLBACK_URL: https://yourdomain.com/api/auth/microsoft/callback
# File Upload
UPLOAD_DIR: ./uploads
MAX_FILE_SIZE: 104857600
# Optional: Run database seed on startup
# RUN_SEED: "false"
volumes:
# Persist uploaded files
- ./uploads:/app/packages/backend/uploads
networks:
- znakovni-network
# Health check
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
znakovni-network:
driver: bridge
```
---
## Task 8: Create Production Environment Template
**File**: `.env.production.example` (in project root)
### Content:
```env
# ===========================================
# ZNAKOVNI.HR PRODUCTION CONFIGURATION
# ===========================================
# Database Configuration (REQUIRED)
# Point to your external MySQL server
DATABASE_URL="mysql://username:password@192.168.1.74:3306/znakovni"
# Optional: Shadow database for Prisma migrations
# SHADOW_DATABASE_URL="mysql://username:password@192.168.1.74:3306/znakovni_shadow"
# Server Configuration
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
FRONTEND_URL=http://localhost:5173
# Session Secret (REQUIRED - CHANGE THIS!)
# Generate with: openssl rand -base64 32
SESSION_SECRET=CHANGE-THIS-TO-A-RANDOM-STRING-MIN-32-CHARACTERS-LONG
# OAuth - Google (Optional)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=https://yourdomain.com/api/auth/google/callback
# OAuth - Microsoft (Optional)
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_CALLBACK_URL=https://yourdomain.com/api/auth/microsoft/callback
# File Upload Configuration
UPLOAD_DIR=./uploads
MAX_FILE_SIZE=104857600
# Database Seeding (Optional)
# Set to "true" to run seed on container startup
RUN_SEED=false
```
---
## Task 9: Build Docker Image
### Build Command:
```bash
# From project root
docker build -t znakovni:latest .
```
### Verify Build Success:
```bash
# Check image exists
docker images | grep znakovni
# Should show:
# znakovni latest <image-id> <time> <size>
```
### Expected Build Output:
- ✅ Stage 1: Frontend build completes successfully
- ✅ Stage 2: Backend build completes successfully
- ✅ Stage 3: Production image created
- ✅ Image tagged as `znakovni:latest`
- ✅ No build errors
**TASK COMPLETE** when image is built and shows in `docker images` output.
---
## Task 10: Optional - Test Container Locally
**NOTE**: This task is OPTIONAL. The main goal is to build the image. User will handle deployment.
### Run Container (Manual):
```bash
docker run -d \
--name znakovni-app \
-p 5173:5173 \
-e DATABASE_URL="mysql://user:password@192.168.1.74:3306/znakovni" \
-e SESSION_SECRET="your-secret-key-here" \
-e NODE_ENV=production \
-v $(pwd)/uploads:/app/packages/backend/uploads \
znakovni:latest
```
### Run with Docker Compose:
```bash
# Edit docker-compose.yml with your database credentials
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
```
### Verify Container:
```bash
# Check if container is running
docker ps
# Check logs
docker logs znakovni-app
# Test backend health
curl http://localhost:5173/api/health
# Test frontend
curl http://localhost:5173
```
---
## Task 11: Save Docker Image for Deployment
**NOTE**: This task is for preparing the image for transfer to Unraid. User will handle actual deployment.
### Save Image to File:
```bash
# Save image to tar file
docker save znakovni:latest -o znakovni.tar
# Verify file created
ls -lh znakovni.tar
```
### Image is Ready for Deployment
The `znakovni.tar` file can now be:
- Transferred to Unraid server
- Loaded with: `docker load -i znakovni.tar`
- Deployed by user
---
## DEPLOYMENT REFERENCE (For User)
### Unraid Deployment Steps:
1. **Transfer image**: `scp znakovni.tar user@unraid:/mnt/user/appdata/`
2. **Load image**: `docker load -i znakovni.tar`
3. **Create container** in Unraid UI:
- **Repository**: `znakovni:latest`
- **Port**: `5173``5173` (or any host port)
- **Environment Variables**: Add all required vars from `.env.production.example`
- **Volume**: Map host path to `/app/packages/backend/uploads`
4. **Configure Caddy** reverse proxy:
- Point domain to `http://unraid-ip:5173`
- Caddy handles SSL/HTTPS
### Caddy Configuration Example:
```
znakovni.yourdomain.com {
reverse_proxy http://localhost:5173
}
```
---
## Task 12: Troubleshooting Build Issues
### Build-Time Issues:
1. **pnpm install fails**:
- Ensure `pnpm-lock.yaml` is present
- Check Node.js version compatibility (needs Node 20)
- Verify `pnpm-workspace.yaml` is copied correctly
2. **Frontend build fails**:
- Check TypeScript compilation errors
- Verify all dependencies are installed
- Ensure Vite config is correct
- Check for missing environment variables
3. **Backend build fails**:
- Check TypeScript compilation errors
- Verify Prisma schema is valid
- Ensure all dependencies are installed
4. **Prisma generate fails**:
- Verify `DATABASE_URL` format is correct (even for build)
- Check Prisma schema syntax
- Ensure MySQL provider is specified
5. **Docker build context too large**:
- Verify `.dockerignore` is present and correct
- Ensure `node_modules` and `dist` are excluded
### Debug Build:
```bash
# Build with no cache to see all steps
docker build --no-cache -t znakovni:latest .
# Build with progress output
docker build --progress=plain -t znakovni:latest .
# Check build context size
docker build --no-cache -t znakovni:latest . 2>&1 | grep "Sending build context"
```
### Runtime Issues (If Testing Container):
1. **Database Connection Failed**:
- Verify `DATABASE_URL` is correct
- Ensure MySQL server allows connections from Docker container IP
- Check MySQL user permissions: `GRANT ALL ON znakovni.* TO 'user'@'%';`
2. **Frontend Can't Reach Backend**:
- Verify `VITE_API_URL=http://localhost:3000` in frontend build
- Check backend is listening on `0.0.0.0:3000`
- Verify CORS settings in backend
3. **Container Exits Immediately**:
- Check logs: `docker logs znakovni-app`
- Verify all required env vars are set
- Ensure database is accessible
### Debug Commands (For Running Container):
```bash
# Enter running container
docker exec -it znakovni-app sh
# Check backend process
docker exec znakovni-app ps aux | grep node
# Check environment variables
docker exec znakovni-app env | grep DATABASE
```
---
## REFERENCE: Security Checklist (For User - Deployment Phase)
Before deploying to production:
- [ ] Change `SESSION_SECRET` to a strong random string (min 32 chars)
- [ ] Use strong MySQL password
- [ ] Configure OAuth credentials if using Google/Microsoft login
- [ ] Update OAuth callback URLs to production domain
- [ ] Ensure MySQL server has firewall rules (only allow necessary IPs)
- [ ] Set up regular database backups
- [ ] Configure Caddy with proper SSL certificates
- [ ] Review and restrict MySQL user permissions
- [ ] Set up monitoring/logging for the container
- [ ] Configure automatic container restarts (`restart: unless-stopped`)
---
## Summary
This setup creates a **fully self-contained Docker image** with:
- ✅ Frontend (React/Vite) built and served on port 5173
- ✅ Backend (Express/Node.js) running on internal port 3000
- ✅ Prisma ORM with automatic migrations on startup
- ✅ External MySQL database connection
- ✅ Persistent uploads via Docker volumes
- ✅ Health checks and graceful shutdown
- ✅ Production-optimized multi-stage build
- ✅ Ready for Unraid + Caddy deployment
---
## IMPLEMENTATION PLAN FOR AI AGENTS
### Phase 1: Create Required Files (Tasks 1-8)
1. ✅ Create `Dockerfile` with multi-stage build
2. ✅ Create `docker-entrypoint.sh` startup script
3. ✅ Create `.dockerignore` file
4. ✅ Update `packages/frontend/vite.config.ts`
5. ✅ Create `packages/frontend/.env.production`
6. ✅ Verify `packages/backend/src/server.ts` configuration
7. ✅ Create `docker-compose.yml` example
8. ✅ Create `.env.production.example` template
### Phase 2: Build Docker Image (Task 9)
9. ✅ Run `docker build -t znakovni:latest .`
10. ✅ Verify image exists with `docker images | grep znakovni`
### Phase 3: Optional - Save Image (Task 11)
11. ⚠️ OPTIONAL: Save image to tar file for transfer
### SUCCESS CRITERIA:
**✅ COMPLETE** when `docker build` succeeds and image `znakovni:latest` exists.
User will handle deployment to Unraid.
---
## Quick Reference: Files to Create/Modify
### New Files to Create:
1.`Dockerfile` - Multi-stage Docker build
2.`docker-entrypoint.sh` - Container startup script (make executable)
3.`.dockerignore` - Exclude unnecessary files from build
4.`docker-compose.yml` - Example compose configuration
5.`.env.production.example` - Production environment template
6.`packages/frontend/.env.production` - Frontend production env
### Files to Modify:
1. ⚠️ `packages/frontend/vite.config.ts` - Update host to `0.0.0.0`
2.`packages/backend/src/server.ts` - Already correct (verify HOST=0.0.0.0)
### Commands to Run After Creating Files:
```bash
# Make entrypoint executable
chmod +x docker-entrypoint.sh
# Build the image (THIS IS THE MAIN GOAL)
docker build -t znakovni:latest .
# Verify build success
docker images | grep znakovni
# OPTIONAL: Test run locally
docker-compose up -d
# OPTIONAL: Check logs
docker-compose logs -f
```
---
## Important Implementation Notes for AI Agents
### 1. Dockerfile Multi-Stage Build Pattern:
- **Stage 1 (frontend-builder)**: Build React app with Vite
- **Stage 2 (backend-builder)**: Build Express app with TypeScript + Prisma
- **Stage 3 (production)**: Combine built artifacts, minimal runtime
### 2. Critical Prisma Considerations:
- Prisma client MUST be generated during build (`npx prisma generate`)
- Copy `node_modules/.prisma` directory to production stage
- Copy `prisma/` directory for migration files
- Run `prisma migrate deploy` in entrypoint script (NOT `migrate dev`)
### 3. Frontend Build Environment:
- Create `packages/frontend/.env.production` with `VITE_API_URL=http://localhost:3000`
- This ensures frontend makes API calls to localhost (same container)
- Vite will embed this at build time
### 4. Networking Inside Container:
- Backend binds to `0.0.0.0:3000` (accessible from frontend)
- Frontend served on `0.0.0.0:5173` (exposed to host)
- Both processes run in same container, communicate via localhost
### 5. Database Connection:
- MySQL server is EXTERNAL (not in container)
- Container must be able to reach MySQL IP (192.168.1.74)
- Ensure MySQL allows connections from Docker network
- Test with: `mysql -h 192.168.1.74 -u user -p znakovni`
### 6. Volume Mounting for Uploads:
- Backend uploads to `/app/packages/backend/uploads`
- Mount host directory to persist files across container restarts
- Example: `-v /mnt/user/appdata/znakovni/uploads:/app/packages/backend/uploads`
### 7. Environment Variables Priority:
**Required**:
- `DATABASE_URL` - MySQL connection string
- `SESSION_SECRET` - Random string (min 32 chars)
**Recommended**:
- `NODE_ENV=production`
- `PORT=3000`
- `HOST=0.0.0.0`
- `FRONTEND_URL=http://localhost:5173`
**Optional**:
- OAuth credentials (if using Google/Microsoft login)
- `RUN_SEED=true` (to seed database on first run)
### 8. Startup Sequence:
1. Container starts → runs `docker-entrypoint.sh`
2. Validate environment variables
3. Run Prisma migrations (`migrate deploy`)
4. Start backend in background
5. Wait 5 seconds for backend to initialize
6. Start frontend server (blocking, keeps container alive)
7. Trap signals for graceful shutdown
### 9. Build Success Checklist:
- [ ] All files created (Tasks 1-8)
- [ ] `docker build` command runs without errors
- [ ] Image `znakovni:latest` appears in `docker images` output
- [ ] No TypeScript compilation errors
- [ ] No Prisma generation errors
- [ ] Multi-stage build completes all 3 stages
### 10. Optional Testing Checklist (If Running Container):
- [ ] Container starts and stays running
- [ ] Backend health check responds: `curl http://localhost:5173/api/health`
- [ ] Frontend loads: `curl http://localhost:5173`
- [ ] Database connection works (check logs)
- [ ] Uploads persist after container restart
### 10. Deployment Flow (For User Reference):
```bash
# 1. Build image (AI AGENT TASK - MAIN GOAL)
docker build -t znakovni:latest .
# 2. OPTIONAL: Save image (if deploying to different machine)
docker save znakovni:latest -o znakovni.tar
# 3-7. USER HANDLES DEPLOYMENT
# - Transfer to Unraid
# - Load image
# - Create container
# - Configure Caddy
# - Monitor
```
---
## End of Guide
**GOAL**: Build Docker image `znakovni:latest` successfully.
**AI AGENT TASKS**:
1. Implement Tasks 1-8 (create/modify files)
2. Execute Task 9 (build image)
3. Verify image exists
**USER RESPONSIBILITY**: Deployment to Unraid and production configuration.
This guide provides complete instructions for dockerizing the Znakovni.hr application. Follow the tasks in order, and refer to the troubleshooting section if build issues arise.

View File

@@ -1,277 +0,0 @@
# Znakopis Page - Bug Fixes and Feature Additions
## Overview
This document outlines the bugs to fix and features to add to the Znakopis page for document and sentence management.
---
## 🐛 BUG FIXES
### Bug 1: Sentence Deletion Error
**Issue:** When loading a document and trying to delete a sentence, an error occurs and the sentence is not deleted.
**Root Cause:** The API endpoint path is incorrect in the frontend.
- Frontend calls: `/api/sentences/${sentenceId}` (via `documentApi.deleteSentence()`)
- Backend expects: `/api/sentences/:sentenceId`
- Backend route is registered at: `app.use('/api', sentenceRoutes)` in `server.ts`
- This means the actual endpoint is: `/api/sentences/:sentenceId`
**Investigation Needed:**
1. Check browser console for the exact error message
2. Verify the sentence ID is being passed correctly
3. Check if authentication is working properly
4. Verify the backend route is handling the DELETE request correctly
**Files to Check:**
- `packages/frontend/src/lib/documentApi.ts` - Line 121-123 (deleteSentence method)
- `packages/backend/src/routes/sentences.ts` - Line 169-199 (DELETE endpoint)
- `packages/frontend/src/pages/Znakopis.tsx` - Line 129-144 (handleDeleteSentence)
- `packages/frontend/src/components/znakopis/DocumentPanel.tsx` - Line 119-125 (delete button)
**Fix Steps:**
1. Add error logging to identify the exact issue
2. Verify the sentence ID format (should be UUID string)
3. Ensure the DELETE request includes authentication credentials
4. Test the endpoint directly to confirm it works
5. Add better error handling and user feedback
---
### Bug 2: Incorrect API Endpoint for Creating Pages
**Issue:** The frontend uses wrong endpoint path for creating pages.
**Current State:**
- Frontend calls: `/api/${documentId}/pages` (Line 88 in documentApi.ts)
- Backend expects: `/api/documents/:documentId/pages` (Line 205 in sentences.ts)
**Fix Required:**
- Update `packages/frontend/src/lib/documentApi.ts` line 88
- Change from: `api.post(\`/api/${documentId}/pages\`, { title })`
- Change to: `api.post(\`/api/documents/${documentId}/pages\`, { title })`
---
### Bug 3: Incorrect API Endpoint for Creating Sentences
**Issue:** The frontend uses wrong endpoint path for creating sentences.
**Current State:**
- Frontend calls: `/api/${documentId}/pages/${pageIndex}/sentences` (Line 99 in documentApi.ts)
- Backend expects: `/api/documents/:documentId/pages/:pageIndex/sentences` (Line 11 in sentences.ts)
**Fix Required:**
- Update `packages/frontend/src/lib/documentApi.ts` line 99
- Change from: `api.post(\`/api/${documentId}/pages/${pageIndex}/sentences\`, data)`
- Change to: `api.post(\`/api/documents/${documentId}/pages/${pageIndex}/sentences\`, data)`
---
## ✨ NEW FEATURES
### Feature 1: Delete Document Functionality
**Description:** Add ability to delete documents from the Znakopis page.
**Backend:** Already implemented ✅
- Endpoint: `DELETE /api/documents/:id` (Line 236 in documents.ts)
- Method: `documentApi.deleteDocument(id)` already exists (Line 82 in documentApi.ts)
**Frontend Changes Needed:**
1. **Update DocumentPanel Component** (`packages/frontend/src/components/znakopis/DocumentPanel.tsx`):
- Add `onDeleteDocument` prop to interface (Line 6-15)
- Add delete button in the document info section (around Line 56-65)
- Use Trash2 icon from lucide-react (already imported)
- Add confirmation dialog before deletion
2. **Update Znakopis Page** (`packages/frontend/src/pages/Znakopis.tsx`):
- Add `handleDeleteDocument` function (similar to handleDeleteSentence)
- Pass handler to DocumentPanel component
- After deletion:
- Clear selectedDocument state
- Reload documents list
- Show success toast
- Handle errors with error toast
3. **UI/UX Considerations:**
- Add confirmation dialog: "Jeste li sigurni da želite obrisati ovaj dokument?"
- Show document title in confirmation
- Disable delete button while deleting (loading state)
- Clear current tokens if deleting the selected document
---
### Feature 2: Edit Document Name
**Description:** Allow users to edit document title and description.
**Backend:** Already implemented ✅
- Endpoint: `PATCH /api/documents/:id` (Line 174 in documents.ts)
- Method: `documentApi.updateDocument(id, data)` already exists (Line 76 in documentApi.ts)
**Frontend Changes Needed:**
1. **Update DocumentPanel Component** (`packages/frontend/src/components/znakopis/DocumentPanel.tsx`):
- Add `onUpdateDocument` prop to interface
- Add edit mode state for document title and description
- Add edit button (Pencil icon from lucide-react)
- When in edit mode:
- Show input field for title
- Show textarea for description
- Show Save and Cancel buttons
- When not in edit mode:
- Show title and description as text
- Show edit button
2. **Update Znakopis Page** (`packages/frontend/src/pages/Znakopis.tsx`):
- Add `handleUpdateDocument` function
- Accept documentId, title, and description
- Call `documentApi.updateDocument()`
- Reload document after update
- Update documents list
- Show success/error toast
3. **UI/UX Considerations:**
- Inline editing for better UX
- Validate title is not empty
- Show loading state while saving
- Revert changes on cancel
- Auto-focus title input when entering edit mode
---
### Feature 3: Name Document on Creation
**Description:** Allow users to provide a custom name when creating a new document instead of auto-generated name.
**Current Behavior:**
- Auto-generates name: `Dokument ${new Date().toLocaleDateString('hr-HR')}`
- Creates with description: "Novi dokument"
**Proposed Changes:**
1. **Add Document Creation Dialog:**
- Create new component: `CreateDocumentDialog.tsx`
- Use Dialog component from UI library
- Include:
- Title input field (required)
- Description textarea (optional)
- Create and Cancel buttons
2. **Update Znakopis Page** (`packages/frontend/src/pages/Znakopis.tsx`):
- Add state for dialog open/closed
- Modify "Novi dokument" button to open dialog
- Update `handleSaveDocument`:
- If no document selected, check if user wants to create new or use dialog
- Option 1: Always show dialog for new documents
- Option 2: Show dialog only when clicking "Novi dokument", auto-create when saving first sentence
- Pass document data from dialog to createDocument API
3. **Alternative Approach (Simpler):**
- Add inline form in DocumentPanel when no document is selected
- Show title and description inputs
- First save creates document with provided info
- If fields empty, use default values
4. **UI/UX Considerations:**
- Default title could be: "Novi dokument" (user can change)
- Placeholder for description: "Dodajte opis dokumenta..."
- Validate title is not empty
- Show character count for title (max 255)
---
## 📋 IMPLEMENTATION CHECKLIST
### Phase 1: Bug Fixes (Priority: HIGH)
- [ ] Fix API endpoint for creating pages (documentApi.ts line 88)
- [ ] Fix API endpoint for creating sentences (documentApi.ts line 99)
- [ ] Debug and fix sentence deletion error
- [ ] Add console logging to identify error
- [ ] Verify authentication is working
- [ ] Test endpoint directly
- [ ] Fix any issues found
- [ ] Add better error handling
### Phase 2: Delete Document Feature (Priority: HIGH)
- [ ] Add onDeleteDocument prop to DocumentPanel interface
- [ ] Add delete button to DocumentPanel UI
- [ ] Implement confirmation dialog
- [ ] Add handleDeleteDocument function in Znakopis page
- [ ] Test deletion flow
- [ ] Verify state updates correctly after deletion
### Phase 3: Edit Document Name (Priority: MEDIUM)
- [ ] Add onUpdateDocument prop to DocumentPanel interface
- [ ] Add edit mode state to DocumentPanel
- [ ] Implement inline editing UI (title and description)
- [ ] Add edit/save/cancel buttons
- [ ] Add handleUpdateDocument function in Znakopis page
- [ ] Add validation for title
- [ ] Test edit flow
- [ ] Verify state updates correctly after edit
### Phase 4: Name Document on Creation (Priority: MEDIUM)
- [ ] Decide on approach (dialog vs inline form)
- [ ] Create CreateDocumentDialog component (if using dialog approach)
- [ ] Update "Novi dokument" button behavior
- [ ] Add form validation
- [ ] Update handleSaveDocument or create separate handler
- [ ] Test document creation with custom name
- [ ] Ensure backward compatibility (auto-name if not provided)
---
## 🧪 TESTING REQUIREMENTS
### For Each Bug Fix:
1. Test the specific scenario that was failing
2. Verify error messages are clear and helpful
3. Test edge cases (empty data, invalid IDs, etc.)
4. Verify authentication is required
### For Each Feature:
1. Test happy path (normal usage)
2. Test validation (empty fields, too long text, etc.)
3. Test error handling (network errors, server errors)
4. Test UI states (loading, success, error)
5. Test on different screen sizes (responsive design)
6. Verify Croatian language text is correct
---
## 📝 NOTES FOR IMPLEMENTATION
1. **Consistency:** Follow existing patterns in the codebase
2. **Error Handling:** Always show user-friendly error messages in Croatian
3. **Loading States:** Show loading indicators for async operations
4. **Confirmation Dialogs:** Use for destructive actions (delete)
5. **Toast Messages:** Use Sonner toast for feedback (already imported)
6. **Icons:** Use lucide-react icons (already in use)
7. **Styling:** Use existing Tailwind classes for consistency
8. **TypeScript:** Ensure all types are properly defined
9. **API Calls:** Always handle errors with try-catch
10. **State Management:** Update all relevant state after API calls
---
## 🔍 FILES TO MODIFY
### Bug Fixes:
1. `packages/frontend/src/lib/documentApi.ts` - Fix API endpoints
2. `packages/frontend/src/pages/Znakopis.tsx` - Improve error handling
3. `packages/backend/src/routes/sentences.ts` - Verify DELETE endpoint (if needed)
### Features:
1. `packages/frontend/src/components/znakopis/DocumentPanel.tsx` - Add delete and edit UI
2. `packages/frontend/src/pages/Znakopis.tsx` - Add handlers for delete and edit
3. `packages/frontend/src/components/znakopis/CreateDocumentDialog.tsx` - New file (if using dialog approach)
4. `packages/frontend/src/components/ui/dialog.tsx` - May need to create if not exists
---
## ⚠️ IMPORTANT CONSIDERATIONS
1. **Data Loss Prevention:** Always confirm before deleting documents
2. **Concurrent Edits:** Consider what happens if document is edited while viewing
3. **Permissions:** Verify user owns document before allowing edit/delete
4. **Validation:** Ensure title is not empty and within length limits
5. **Accessibility:** Ensure all interactive elements are keyboard accessible
6. **Mobile:** Test on mobile devices for touch interactions

View File

@@ -27,7 +27,9 @@
"plyr-react": "^5.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^6.21.1",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",

View File

@@ -0,0 +1,419 @@
# Korištenje aplikacije - Znakovni.hr
## Priručnik za korištenje aplikacije za hrvatski znakovni jezik
---
## 📚 Sadržaj
1. [Uvod](#uvod)
2. [Prijava i autentifikacija](#prijava-i-autentifikacija)
3. [Početna stranica](#početna-stranica)
4. [Rječnik (Riječi)](#rječnik-riječi)
5. [Znakopis (Graditelj rečenica)](#znakopis-graditelj-rečenica)
6. [Oblak (Upravljanje dokumentima)](#oblak-upravljanje-dokumentima)
7. [Video rečenica](#video-rečenica)
8. [Portal za podršku](#portal-za-podršku)
9. [Administracija (samo za administratore)](#administracija-samo-za-administratore)
10. [Česta pitanja](#česta-pitanja)
---
## Uvod
Znakovni.hr je web aplikacija za učenje i korištenje hrvatskog znakovnog jezika (HZJ). Aplikacija omogućava:
- **Pretraživanje rječnika** - Video rječnik hrvatskog znakovnog jezika
- **Gradnju rečenica** - Alat za sastavljanje rečenica pomoću znakova
- **Spremanje dokumenata** - Pohrana vaših rečenica u oblaku
- **Video reprodukciju** - Reprodukcija video rečenica
- **Zajednicu** - Komentari, povratne informacije i prijava grešaka
---
## Prijava i autentifikacija
### Kako se prijaviti
1. Kliknite na **"Sign in"** u donjem dijelu bočne trake
2. Unesite svoju **email adresu** i **lozinku**
3. Kliknite **"Sign in"**
### Metode prijave
Aplikacija podržava sljedeće metode prijave:
- **Email/Lozinka** - Tradicionalna lokalna autentifikacija
- **Google OAuth** - Prijava pomoću Google računa (u razvoju)
- **Microsoft OAuth** - Prijava pomoću Microsoft računa (u razvoju)
### Odjava
1. Kliknite na **"Sign out"** u donjem dijelu bočne trake
2. Bit ćete automatski odjavljeni i preusmjereni na stranicu za prijavu
### Napomena o pristupima
- **Anonimni korisnici** mogu pregledavati rječnik
- **Prijavljeni korisnici** mogu spremati dokumente, komentirati i prijavljivati greške
- **Administratori** imaju pristup upravljanju korisnicima i riječima
---
## Početna stranica
Početna stranica prikazuje pregled svih glavnih funkcionalnosti aplikacije:
### Dostupne funkcionalnosti
1. **Riječi** (Rječnik) - Pregledajte i pretražujte rječnik hrvatskog znakovnog jezika
2. **Znakopis** - Gradite rečenice koristeći znakovni jezik
3. **Video rečenica** - Gledajte i učite iz video rečenica
4. **Oblak** - Spremite i upravljajte svojim dokumentima u oblaku
Kliknite na bilo koju karticu za pristup toj funkcionalnosti.
---
## Rječnik (Riječi)
Rječnik omogućava pretraživanje i pregledavanje hrvatskog znakovnog jezika.
### Pretraživanje riječi
1. Idite na **"Riječi"** u bočnoj traci
2. Koristite **traku za pretraživanje** za unos teksta
3. Rezultati se automatski filtriraju dok tipkate
### Filtriranje riječi
Možete filtrirati riječi prema:
- **Vrsta riječi** (Word Type):
- NOUN (Imenica)
- VERB (Glagol)
- ADJECTIVE (Pridjev)
- ADVERB (Prilog)
- PRONOUN (Zamjenica)
- PREPOSITION (Prijedlog)
- CONJUNCTION (Veznik)
- INTERJECTION (Uzvik)
- NUMERAL (Broj)
- PARTICLE (Čestica)
- OTHER (Ostalo)
- **CEFR razina** (Razina složenosti):
- A1 (Početna razina)
- A2 (Osnovna razina)
- B1 (Srednja razina)
- B2 (Viša srednja razina)
- C1 (Napredna razina)
- C2 (Majstorska razina)
### Prikaz detalja riječi
1. Kliknite na **ikonu informacija ()** na kartici riječi
2. Otvara se modal s detaljima:
- Video prikaz znaka
- Vrsta riječi
- CEFR razina
- Kratki opis
- Primjeri korištenja
- Oznake (tags)
### Dodavanje riječi u rečenicu
1. Kliknite na **"Dodaj"** gumb na kartici riječi
2. Riječ se automatski dodaje u trenutnu rečenicu
3. Idite na **"Znakopis"** za uređivanje rečenice
### Paginacija
- Koristite **"Prethodna"** i **"Sljedeća"** gumbe za navigaciju kroz stranice
- Prikazuje se trenutna stranica i ukupan broj stranica
---
## Znakopis (Graditelj rečenica)
Znakopis je alat za sastavljanje rečenica pomoću znakova iz rječnika.
### Dodavanje riječi u rečenicu
1. Idite na **"Riječi"** i kliknite **"Dodaj"** na željenim riječima
2. Riječi se dodaju u **"Trenutna rečenica"** panel
3. Idite na **"Znakopis"** za uređivanje
### Preuređivanje riječi (Drag & Drop)
1. Kliknite i držite riječ u **"Trenutna rečenica"** panelu
2. Povucite riječ na željenu poziciju
3. Pustite za postavljanje riječi na novu poziciju
### Uklanjanje riječi
- Kliknite na **"X"** gumb na riječi za uklanjanje pojedinačne riječi
- Kliknite **"Očisti sve"** za uklanjanje svih riječi iz rečenice
### Spremanje rečenice
1. Kliknite **"Spremi"** gumb
2. Ako nemate otvoren dokument, unesite:
- **Naslov dokumenta** (obavezno)
- **Opis dokumenta** (opcionalno)
3. Kliknite **"Spremi dokument"**
4. Rečenica se sprema na trenutnu stranicu dokumenta
### Učitavanje dokumenta
1. Kliknite **"Učitaj dokument"** gumb
2. Odaberite dokument iz popisa
3. Dokument se učitava s svim stranicama i rečenicama
### Upravljanje stranicama
- **Nova stranica** - Kliknite za dodavanje nove stranice u dokument
- **Navigacija** - Koristite **"Prethodna"** i **"Sljedeća"** za navigaciju između stranica
- Prikazuje se trenutna stranica i ukupan broj stranica
### Popis rečenica
- Prikazuje sve rečenice na trenutnoj stranici
- Kliknite **"Obriši"** za brisanje rečenice
- Rečenice su numerirane (Rečenica 1, Rečenica 2, itd.)
---
## Oblak (Upravljanje dokumentima)
Oblak omogućava centralizirano spremanje i upravljanje vašim dokumentima.
### Pregled dokumenata
1. Idite na **"Oblak"** u bočnoj traki
2. Prikazuju se svi vaši spremljeni dokumenti
3. Za svaki dokument vidite:
- Naslov
- Opis
- Datum zadnje izmjene
- Broj stranica
- Broj rečenica
### Učitavanje dokumenta
1. Kliknite na dokument u popisu
2. Dokument se učitava u Znakopis
3. Možete nastaviti s uređivanjem
### Brisanje dokumenta
1. Kliknite **"Obriši"** gumb na dokumentu
2. Potvrdite brisanje
3. Dokument se trajno briše
### Uređivanje dokumenta
1. Kliknite **"Uredi"** gumb na dokumentu
2. Možete promijeniti:
- Naslov dokumenta
- Opis dokumenta
3. Kliknite **"Spremi"** za spremanje promjena
---
## Video rečenica
Video rečenica omogućava reprodukciju video zapisa rečenica sastavljenih od znakova.
### Reprodukcija video rečenice
1. Idite na **"Video rečenica"** u bočnoj traci
2. Odaberite dokument s rečenicama
3. Video se automatski reproducira
4. Kontrole:
- **Play/Pause** - Reprodukcija/Pauza
- **Brzina reprodukcije** - Prilagodite brzinu
- **Glasnoća** - Kontrola glasnoće
- **Puni zaslon** - Prikaz u punom zaslonu
### Playlist
- Sve rečenice u dokumentu se reproduciraju redom
- Možete preskočiti na sljedeću ili prethodnu rečenicu
- Prikazuje se trenutna rečenica i ukupan broj rečenica
---
## Portal za podršku
### Korištenje aplikacije
- Ovaj priručnik s uputama za korištenje aplikacije
### Zajednica
- Pregledajte komentare i povratne informacije drugih korisnika
- Sudjelujte u raspravi o aplikaciji
### Komentari
1. Idite na **"Komentari"** u bočnoj traci
2. Napišite svoj komentar ili povratnu informaciju
3. Kliknite **"Pošalji"**
4. Vaš komentar će biti vidljiv svim korisnicima
### Prijavi grešku
1. Idite na **"Prijavi grešku"** u bočnoj traci
2. Ispunite obrazac:
- **Naslov** - Kratki opis greške
- **Opis** - Detaljan opis problema
- **Koraci za reprodukciju** - Kako reproducirati grešku
- **Očekivano ponašanje** - Što ste očekivali
- **Stvarno ponašanje** - Što se zapravo dogodilo
3. Kliknite **"Pošalji prijavu"**
4. Vaša prijava će biti pregledana od strane administratora
---
## Administracija (samo za administratore)
Administratorski panel je dostupan samo korisnicima s administratorskim pravima.
### Upravljanje korisnicima
1. Idite na **"Korisnici"** u sekciji Administracija
2. Pregledajte sve korisnike u sustavu
3. Dostupne akcije:
- **Dodaj korisnika** - Kreirajte novog korisnika
- **Uredi korisnika** - Promijenite podatke korisnika
- **Obriši korisnika** - Uklonite korisnika iz sustava
- **Aktiviraj/Deaktiviraj** - Omogućite ili onemogućite pristup
#### Kreiranje novog korisnika
1. Kliknite **"Dodaj korisnika"**
2. Ispunite obrazac:
- **Email** (obavezno)
- **Ime** (opcionalno)
- **Lozinka** (obavezno)
- **Uloga** - USER ili ADMIN
- **Aktivan** - Da/Ne
3. Kliknite **"Kreiraj"**
#### Uređivanje korisnika
1. Kliknite **ikonu olovke** pored korisnika
2. Promijenite željene podatke
3. Kliknite **"Spremi"**
#### Resetiranje lozinke
1. Uredi korisnika
2. Unesite novu lozinku
3. Kliknite **"Spremi"**
### Upravljanje riječima
1. Idite na **"Upravljanje riječima"** u sekciji Administracija
2. Pregledajte sve riječi u rječniku
3. Dostupne akcije:
- **Dodaj riječ** - Dodajte novu riječ u rječnik
- **Uredi riječ** - Promijenite podatke riječi
- **Obriši riječ** - Uklonite riječ iz rječnika
- **Dodaj video** - Dodajte video prikaz znaka
#### Dodavanje nove riječi
1. Kliknite **"Dodaj riječ"**
2. Ispunite obrazac:
- **Riječ** (obavezno) - Tekst riječi
- **Vrsta riječi** (obavezno) - NOUN, VERB, itd.
- **CEFR razina** (obavezno) - A1, A2, B1, B2, C1, C2
- **Kratki opis** (opcionalno)
- **Oznake** (opcionalno) - Odvojene zarezom
3. Kliknite **"Spremi"**
#### Dodavanje videa
1. Kliknite **ikonu videa** pored riječi
2. Odaberite video datoteku (MP4, WebM, OGG)
3. Kliknite **"Upload"**
4. Video se automatski učitava i povezuje s riječju
#### Uređivanje riječi
1. Kliknite **ikonu olovke** pored riječi
2. Promijenite željene podatke
3. Kliknite **"Spremi"**
#### Brisanje riječi
1. Kliknite **ikonu kante** pored riječi
2. Potvrdite brisanje
3. Riječ i svi povezani videi se trajno brišu
---
## Česta pitanja
### Mogu li koristiti aplikaciju bez prijave?
Da, možete pregledavati rječnik bez prijave. Međutim, za spremanje dokumenata, komentiranje i prijavu grešaka potrebna je prijava.
### Kako mogu promijeniti svoju lozinku?
Trenutno možete promijeniti lozinku samo putem administratora. Kontaktirajte administratora za resetiranje lozinke.
### Mogu li dijeliti svoje dokumente s drugim korisnicima?
Trenutno su svi dokumenti privatni. Funkcionalnost dijeljenja dokumenata je u razvoju.
### Koliko dokumenata mogu spremiti?
Nema ograničenja na broj dokumenata koje možete spremiti.
### Koliko stranica može imati dokument?
Nema ograničenja na broj stranica u dokumentu.
### Koliko rečenica može imati stranica?
Nema ograničenja na broj rečenica po stranici.
### Koje video formate podržava aplikacija?
Aplikacija podržava MP4, WebM i OGG video formate.
### Kako mogu prijaviti grešku?
Idite na "Prijavi grešku" u portalu za podršku i ispunite obrazac s detaljima greške.
### Kako mogu kontaktirati podršku?
Možete ostaviti komentar u sekciji "Komentari" ili prijaviti grešku u sekciji "Prijavi grešku".
### Je li aplikacija dostupna na mobilnim uređajima?
Da, aplikacija je responzivna i radi na mobilnim uređajima, tabletima i desktop računalima.
---
## Tehnička podrška
Za dodatnu pomoć ili pitanja, molimo kontaktirajte:
- **Email**: podrska@znakovni.hr
- **Komentari**: Koristite sekciju "Komentari" u aplikaciji
- **Prijava grešaka**: Koristite sekciju "Prijavi grešku" u aplikaciji
---
**Verzija priručnika**: 1.0
**Datum ažuriranja**: 2026-01-18
**Status aplikacije**: Faza 3 dovršena (Znakopis)
---
© 2026 Znakovni.hr - Sva prava pridržana

View File

@@ -7,6 +7,7 @@ import { AdminTerms } from './pages/AdminTerms';
import Dictionary from './pages/Dictionary';
import Znakopis from './pages/Znakopis';
import VideoSentence from './pages/VideoSentence';
import Help from './pages/Help';
import { ProtectedRoute } from './components/ProtectedRoute';
import { useAuthStore } from './stores/authStore';
import { Toaster } from 'sonner';
@@ -56,7 +57,7 @@ function App() {
<Route path="/video-sentence" element={<ProtectedRoute><VideoSentence /></ProtectedRoute>} />
{/* Placeholder routes for other pages */}
<Route path="/cloud" element={<ProtectedRoute><div>Cloud (Coming Soon)</div></ProtectedRoute>} />
<Route path="/help" element={<ProtectedRoute><div>Help (Coming Soon)</div></ProtectedRoute>} />
<Route path="/help" element={<ProtectedRoute><Help /></ProtectedRoute>} />
<Route path="/community" element={<ProtectedRoute><div>Community (Coming Soon)</div></ProtectedRoute>} />
<Route path="/comments" element={<ProtectedRoute><div>Comments (Coming Soon)</div></ProtectedRoute>} />
<Route path="/bug-report" element={<ProtectedRoute><div>Bug Report (Coming Soon)</div></ProtectedRoute>} />

View File

@@ -0,0 +1,142 @@
import { useState, useEffect } from 'react';
import { Layout } from '../components/layout/Layout';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
function Help() {
const [content, setContent] = useState<string>('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchHelpContent = async () => {
try {
const response = await fetch('/help.md');
if (!response.ok) {
throw new Error('Failed to load help documentation');
}
const text = await response.text();
setContent(text);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
fetchHelpContent();
}, []);
if (loading) {
return (
<Layout>
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 mx-auto mb-4"></div>
<p className="text-gray-600">Učitavanje priručnika...</p>
</div>
</div>
</Layout>
);
}
if (error) {
return (
<Layout>
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<p className="text-red-600 mb-2">Greška pri učitavanju priručnika</p>
<p className="text-gray-600">{error}</p>
</div>
</div>
</Layout>
);
}
return (
<Layout>
<div className="max-w-4xl mx-auto">
<div className="bg-white rounded-lg shadow-sm p-8">
<article className="prose prose-indigo max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ children }) => (
<h1 className="text-4xl font-bold text-gray-900 mb-4 pb-4 border-b-2 border-indigo-200">
{children}
</h1>
),
h2: ({ children }) => (
<h2 className="text-3xl font-bold text-gray-900 mt-8 mb-4 pb-2 border-b border-gray-200">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="text-2xl font-semibold text-gray-800 mt-6 mb-3">
{children}
</h3>
),
h4: ({ children }) => (
<h4 className="text-xl font-semibold text-gray-800 mt-4 mb-2">
{children}
</h4>
),
p: ({ children }) => (
<p className="text-gray-700 leading-relaxed mb-4">
{children}
</p>
),
ul: ({ children }) => (
<ul className="list-disc list-inside space-y-2 mb-4 text-gray-700">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="list-decimal list-inside space-y-2 mb-4 text-gray-700">
{children}
</ol>
),
li: ({ children }) => (
<li className="ml-4">
{children}
</li>
),
a: ({ href, children }) => (
<a
href={href}
className="text-indigo-600 hover:text-indigo-800 underline"
>
{children}
</a>
),
strong: ({ children }) => (
<strong className="font-semibold text-gray-900">
{children}
</strong>
),
code: ({ children }) => (
<code className="bg-gray-100 text-indigo-600 px-1.5 py-0.5 rounded text-sm font-mono">
{children}
</code>
),
hr: () => (
<hr className="my-8 border-t-2 border-gray-200" />
),
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-indigo-500 pl-4 italic text-gray-700 my-4">
{children}
</blockquote>
),
}}
>
{content}
</ReactMarkdown>
</article>
</div>
</div>
</Layout>
);
}
export default Help;

869
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff