Add Help page with markdown support and routing
This commit is contained in:
738
dockerize.md
Normal file
738
dockerize.md
Normal 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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
419
packages/frontend/public/help.md
Normal file
419
packages/frontend/public/help.md
Normal 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
|
||||
|
||||
@@ -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>} />
|
||||
|
||||
142
packages/frontend/src/pages/Help.tsx
Normal file
142
packages/frontend/src/pages/Help.tsx
Normal 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
869
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user