- Implement backend API for term CRUD operations - Add frontend dictionary page with search and filtering - Integrate shadcn/ui components (Dialog) - Create term management UI with add/edit/delete functionality - Update database seed with initial terms - Add API client for term operations - Complete Phase 2 of development plan
1671 lines
50 KiB
Markdown
1671 lines
50 KiB
Markdown
# Znakovni.hr - Proof of Concept Implementation Plan
|
||
|
||
## Executive Summary
|
||
This document provides a complete implementation plan for a 1:1 functional and visual replica of the Znakovni.hr Croatian Sign Language web application. This is a proof-of-concept build optimized for 1-2 concurrent users with local storage.
|
||
|
||
---
|
||
|
||
## 🎯 CORE APPLICATION FUNCTIONALITY (Quick Reference)
|
||
|
||
**The application is a Croatian Sign Language learning tool with THREE main interconnected screens:**
|
||
|
||
### 1️⃣ RIJEČI (Dictionary) - Browse & Add Words
|
||
- **What it shows:** Grid of pre-recorded sign language words with icons
|
||
- **What you do:**
|
||
- Click **"Dodaj"** (Add) to add words to your sentence
|
||
- Click **"Info"** to watch the sign video for any word
|
||
- **Screenshot reference:** `1 dodaj rijeci.png`
|
||
|
||
### 2️⃣ ZNAKOPIS (Sentence Builder) - Order & Save
|
||
- **What it shows:** All words you added, displayed as draggable tokens
|
||
- **What you do:**
|
||
- Drag and drop words to arrange them in correct sentence order
|
||
- Create multiple sentences across multiple pages
|
||
- Click **"Spremi"** (Save) to save document to cloud (stored under your user account)
|
||
- **Screenshot reference:** `2 dodaj rijeci znakopis.png`
|
||
|
||
### 3️⃣ VIDEO REČENICA (Video Player) - Watch & Learn
|
||
- **What it shows:** Video player + sentence list
|
||
- **What you do:**
|
||
- Click Play to watch all sign videos play in sequence
|
||
- Current word being signed is **highlighted** in the sentence list
|
||
- Control playback (pause, speed, navigate)
|
||
- **Screenshot reference:** `3 dodaj rijeci recenica.png`
|
||
|
||
### 🔄 Complete User Journey:
|
||
```
|
||
RIJEČI (add words) → ZNAKOPIS (order & save) → VIDEO REČENICA (play & watch)
|
||
↓
|
||
OBLAK (cloud storage)
|
||
(documents saved under user account)
|
||
```
|
||
|
||
**Key Technical Points:**
|
||
- Words are pre-recorded in the database with associated sign videos
|
||
- Sentence state persists across all three screens (global state management)
|
||
- Documents are saved to database and associated with logged-in user
|
||
- Video playback is synchronized with word highlighting
|
||
- All Croatian text labels must match exactly
|
||
|
||
---
|
||
|
||
## 📊 DATA FLOW & STATE MANAGEMENT
|
||
|
||
### How the Three Screens Work Together
|
||
|
||
**1. Pre-recorded Words (Database)**
|
||
```
|
||
Terms Table:
|
||
- id: uuid
|
||
- wordText: "dobar" (good)
|
||
- wordType: ADJECTIVE
|
||
- cefrLevel: A1
|
||
- iconAssetId: "path/to/icon.png"
|
||
|
||
TermMedia Table:
|
||
- termId: (links to Term)
|
||
- kind: VIDEO
|
||
- url: "/uploads/videos/dobar.mp4"
|
||
- durationMs: 2500
|
||
```
|
||
|
||
**2. User Adds Words (Frontend State → Database)**
|
||
```
|
||
User clicks "Dodaj" in Riječi
|
||
↓
|
||
Word added to sentenceStore (Zustand)
|
||
↓
|
||
User navigates to Znakopis
|
||
↓
|
||
sentenceStore displays tokens
|
||
↓
|
||
User reorders tokens via drag-and-drop
|
||
↓
|
||
User clicks "Spremi" (Save)
|
||
↓
|
||
API call: POST /api/documents
|
||
↓
|
||
Database creates:
|
||
- Document (title, ownerUserId)
|
||
- DocumentPage (documentId, pageIndex)
|
||
- Sentence (documentPageId, sentenceIndex)
|
||
- SentenceToken[] (sentenceId, termId, tokenIndex, displayText)
|
||
```
|
||
|
||
**3. User Plays Video (Database → Frontend)**
|
||
```
|
||
User navigates to Video Rečenica
|
||
↓
|
||
Loads document from Oblak or current sentenceStore
|
||
↓
|
||
API call: GET /api/playlists/generate?sentenceId=xxx
|
||
↓
|
||
Backend resolves:
|
||
Sentence → SentenceTokens → Terms → TermMedia (videos)
|
||
↓
|
||
Returns playlist:
|
||
[
|
||
{ tokenId, termId, displayText, videoUrl, durationMs },
|
||
{ tokenId, termId, displayText, videoUrl, durationMs },
|
||
...
|
||
]
|
||
↓
|
||
Frontend plays videos sequentially
|
||
↓
|
||
Highlights current token in sentence list
|
||
```
|
||
|
||
### State Management Strategy
|
||
|
||
**Frontend State (Zustand stores):**
|
||
- `sentenceStore`: Current sentence being built (tokens, order)
|
||
- Used by: Riječi (add), Znakopis (edit), Video Rečenica (play)
|
||
- Persists across navigation
|
||
- Cleared when document is saved or loaded
|
||
|
||
- `documentStore`: Current loaded document
|
||
- Used by: Znakopis (edit), Oblak (list), Video Rečenica (play)
|
||
- Contains: documentId, title, pages[], sentences[]
|
||
|
||
- `authStore`: Current user session
|
||
- Used by: All screens
|
||
- Contains: userId, email, role, isAuthenticated
|
||
|
||
**Backend Persistence:**
|
||
- All saved documents stored in MySQL via Prisma
|
||
- Documents linked to User via ownerUserId
|
||
- Full hierarchy: Document → DocumentPage → Sentence → SentenceToken → Term
|
||
|
||
---
|
||
|
||
## 1. Technology Stack (PoC-Optimized)
|
||
|
||
### Frontend
|
||
- **Framework**: React 18.2+ with TypeScript
|
||
- **Build Tool**: Vite 5+
|
||
- **UI Framework**: Tailwind CSS 3+ (for exact pixel-perfect replication)
|
||
- **Component Library**: shadcn/ui (customizable, matches screenshot aesthetics)
|
||
- **State Management**: Zustand (lightweight sentence/document state)
|
||
- **Video Player**: Plyr (clean, customizable controls)
|
||
- **Drag & Drop**: @dnd-kit/core (sentence token reordering)
|
||
- **Routing**: React Router v6
|
||
- **HTTP Client**: Axios
|
||
- **Forms**: React Hook Form + Zod
|
||
- **Icons**: Lucide React (modern, clean icons)
|
||
|
||
### Backend
|
||
- **Runtime**: Node.js 20 LTS
|
||
- **Framework**: Express.js with TypeScript
|
||
- **Authentication**: Simple admin user + local user management (OAuth deferred to later phases)
|
||
- **Session**: express-session with MySQL session store
|
||
- **Validation**: Zod (shared with frontend)
|
||
- **File Upload**: Multer (for video/image uploads)
|
||
- **CORS**: cors middleware
|
||
|
||
### Database
|
||
- **RDBMS**: MySQL 8.0+
|
||
- **ORM**: Prisma 5+ (type-safe, excellent MySQL support)
|
||
- **Migrations**: Prisma Migrate
|
||
- **Full-Text Search**: MySQL FULLTEXT indexes
|
||
|
||
### Media Storage
|
||
- **Storage**: Local filesystem (`/uploads` directory)
|
||
- **Structure**: `/uploads/videos/`, `/uploads/icons/`, `/uploads/documents/`
|
||
- **Serving**: Express static middleware with proper MIME types
|
||
- **Video Format**: MP4 (H.264) for maximum compatibility
|
||
|
||
### Development Tools
|
||
- **Package Manager**: pnpm
|
||
- **Monorepo Structure**: pnpm workspaces (frontend + backend)
|
||
- **Linting**: ESLint + Prettier
|
||
- **Type Checking**: TypeScript strict mode
|
||
- **Environment**: dotenv
|
||
- **Development**: Concurrently (run frontend + backend together)
|
||
|
||
### Deployment (PoC)
|
||
- **Server**: Single VPS or local machine
|
||
- **Process Manager**: PM2
|
||
- **Reverse Proxy**: nginx (optional, for production-like setup)
|
||
- **Database**: MySQL server (local or managed)
|
||
|
||
---
|
||
|
||
## 2. Project Folder Structure
|
||
|
||
```
|
||
turkshop/
|
||
├── packages/
|
||
│ ├── frontend/ # React + Vite application
|
||
│ │ ├── public/
|
||
│ │ │ └── assets/ # Static assets
|
||
│ │ ├── src/
|
||
│ │ │ ├── components/ # Reusable UI components
|
||
│ │ │ │ ├── ui/ # shadcn/ui base components
|
||
│ │ │ │ ├── layout/ # Sidebar, Header, Layout
|
||
│ │ │ │ ├── dictionary/ # WordCard, WordGrid, FilterBar
|
||
│ │ │ │ ├── znakopis/ # SentenceBuilder, TokenTray
|
||
│ │ │ │ ├── video/ # VideoPlayer, PlaylistControls
|
||
│ │ │ │ └── cloud/ # DocumentBrowser, DocumentList
|
||
│ │ │ ├── pages/ # Route pages
|
||
│ │ │ │ ├── Home.tsx
|
||
│ │ │ │ ├── Dictionary.tsx
|
||
│ │ │ │ ├── Znakopis.tsx
|
||
│ │ │ │ ├── VideoSentence.tsx
|
||
│ │ │ │ ├── Cloud.tsx
|
||
│ │ │ │ ├── Auth.tsx
|
||
│ │ │ │ ├── Help.tsx
|
||
│ │ │ │ ├── Community.tsx
|
||
│ │ │ │ ├── Comments.tsx
|
||
│ │ │ │ ├── BugReport.tsx
|
||
│ │ │ │ └── admin/
|
||
│ │ │ │ └── UserManagement.tsx
|
||
│ │ │ ├── stores/ # Zustand stores
|
||
│ │ │ │ ├── authStore.ts
|
||
│ │ │ │ ├── sentenceStore.ts
|
||
│ │ │ │ └── documentStore.ts
|
||
│ │ │ ├── lib/ # Utilities
|
||
│ │ │ │ ├── api.ts # Axios instance
|
||
│ │ │ │ ├── utils.ts
|
||
│ │ │ │ └── constants.ts
|
||
│ │ │ ├── types/ # TypeScript types
|
||
│ │ │ │ └── index.ts
|
||
│ │ │ ├── hooks/ # Custom React hooks
|
||
│ │ │ ├── App.tsx
|
||
│ │ │ ├── main.tsx
|
||
│ │ │ └── index.css # Tailwind imports
|
||
│ │ ├── package.json
|
||
│ │ ├── vite.config.ts
|
||
│ │ ├── tailwind.config.js
|
||
│ │ └── tsconfig.json
|
||
│ │
|
||
│ └── backend/ # Express + TypeScript API
|
||
│ ├── src/
|
||
│ │ ├── routes/ # API route handlers
|
||
│ │ │ ├── auth.ts
|
||
│ │ │ ├── admin.ts
|
||
│ │ │ ├── terms.ts
|
||
│ │ │ ├── documents.ts
|
||
│ │ │ ├── sentences.ts
|
||
│ │ │ ├── playlists.ts
|
||
│ │ │ ├── community.ts
|
||
│ │ │ └── index.ts
|
||
│ │ ├── controllers/ # Business logic
|
||
│ │ │ ├── authController.ts
|
||
│ │ │ ├── adminController.ts
|
||
│ │ │ ├── termController.ts
|
||
│ │ │ ├── documentController.ts
|
||
│ │ │ └── sentenceController.ts
|
||
│ │ ├── middleware/ # Express middleware
|
||
│ │ │ ├── auth.ts
|
||
│ │ │ ├── validation.ts
|
||
│ │ │ ├── errorHandler.ts
|
||
│ │ │ └── upload.ts
|
||
│ │ ├── services/ # External services
|
||
│ │ │ ├── authService.ts
|
||
│ │ │ ├── storageService.ts
|
||
│ │ │ └── playlistService.ts
|
||
│ │ ├── lib/ # Utilities
|
||
│ │ │ ├── prisma.ts # Prisma client
|
||
│ │ │ └── auth.ts # Auth utilities (bcrypt, session)
|
||
│ │ ├── types/ # TypeScript types
|
||
│ │ │ └── index.ts
|
||
│ │ ├── config/ # Configuration
|
||
│ │ │ └── index.ts
|
||
│ │ └── server.ts # Express app entry
|
||
│ ├── prisma/
|
||
│ │ ├── schema.prisma # Database schema
|
||
│ │ ├── migrations/ # Migration files
|
||
│ │ └── seed.ts # Seed data
|
||
│ ├── uploads/ # Local file storage
|
||
│ │ ├── videos/
|
||
│ │ ├── icons/
|
||
│ │ └── documents/
|
||
│ ├── package.json
|
||
│ └── tsconfig.json
|
||
│
|
||
├── shared/ # Shared types/schemas (optional)
|
||
│ ├── types/
|
||
│ └── schemas/
|
||
│
|
||
├── package.json # Root package.json (workspace)
|
||
├── pnpm-workspace.yaml
|
||
├── .gitignore
|
||
├── README.md
|
||
└── main-plan.md # This file
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Database Schema (Prisma + MySQL)
|
||
|
||
### Complete Prisma Schema
|
||
|
||
```prisma
|
||
// prisma/schema.prisma
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "mysql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
// ============================================
|
||
// AUTHENTICATION & USERS
|
||
// ============================================
|
||
|
||
model User {
|
||
id String @id @default(uuid())
|
||
email String @unique
|
||
displayName String? @map("display_name")
|
||
passwordHash String @map("password_hash")
|
||
role UserRole @default(USER)
|
||
isActive Boolean @default(true) @map("is_active")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
documents Document[]
|
||
comments Comment[]
|
||
bugReports BugReport[]
|
||
|
||
@@map("users")
|
||
}
|
||
|
||
enum UserRole {
|
||
ADMIN
|
||
USER
|
||
}
|
||
|
||
// ============================================
|
||
// DICTIONARY / TERMS
|
||
// ============================================
|
||
|
||
model Term {
|
||
id String @id @default(uuid())
|
||
wordText String @map("word_text") @db.VarChar(255)
|
||
normalizedText String @map("normalized_text") @db.VarChar(255)
|
||
language String @default("hr") @db.VarChar(10)
|
||
wordType WordType @map("word_type")
|
||
cefrLevel CefrLevel @map("cefr_level")
|
||
shortDescription String? @map("short_description") @db.Text
|
||
tags String? @db.Text // JSON array stored as text
|
||
iconAssetId String? @map("icon_asset_id")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
media TermMedia[]
|
||
examples TermExample[]
|
||
sentenceTokens SentenceToken[]
|
||
|
||
@@index([normalizedText])
|
||
@@fulltext([wordText, normalizedText])
|
||
@@map("terms")
|
||
}
|
||
|
||
model TermMedia {
|
||
id String @id @default(uuid())
|
||
termId String @map("term_id")
|
||
kind MediaKind
|
||
url String @db.VarChar(500)
|
||
durationMs Int? @map("duration_ms")
|
||
width Int?
|
||
height Int?
|
||
checksum String? @db.VarChar(64)
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
term Term @relation(fields: [termId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([termId])
|
||
@@map("term_media")
|
||
}
|
||
|
||
model TermExample {
|
||
id String @id @default(uuid())
|
||
termId String @map("term_id")
|
||
exampleText String @map("example_text") @db.Text
|
||
notes String? @db.Text
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
term Term @relation(fields: [termId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([termId])
|
||
@@map("term_examples")
|
||
}
|
||
|
||
// ============================================
|
||
// DOCUMENTS & SENTENCES (ZNAKOPIS)
|
||
// ============================================
|
||
|
||
model Document {
|
||
id String @id @default(uuid())
|
||
ownerUserId String? @map("owner_user_id")
|
||
title String @db.VarChar(255)
|
||
description String? @db.Text
|
||
visibility Visibility @default(PRIVATE)
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
owner User? @relation(fields: [ownerUserId], references: [id], onDelete: SetNull)
|
||
pages DocumentPage[]
|
||
|
||
@@index([ownerUserId])
|
||
@@map("documents")
|
||
}
|
||
|
||
model DocumentPage {
|
||
id String @id @default(uuid())
|
||
documentId String @map("document_id")
|
||
pageIndex Int @map("page_index")
|
||
title String? @db.VarChar(255)
|
||
|
||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||
sentences Sentence[]
|
||
|
||
@@unique([documentId, pageIndex])
|
||
@@index([documentId])
|
||
@@map("document_pages")
|
||
}
|
||
|
||
model Sentence {
|
||
id String @id @default(uuid())
|
||
documentPageId String @map("document_page_id")
|
||
sentenceIndex Int @map("sentence_index")
|
||
rawText String? @map("raw_text") @db.Text
|
||
|
||
documentPage DocumentPage @relation(fields: [documentPageId], references: [id], onDelete: Cascade)
|
||
tokens SentenceToken[]
|
||
|
||
@@unique([documentPageId, sentenceIndex])
|
||
@@index([documentPageId])
|
||
@@map("sentences")
|
||
}
|
||
|
||
model SentenceToken {
|
||
id String @id @default(uuid())
|
||
sentenceId String @map("sentence_id")
|
||
tokenIndex Int @map("token_index")
|
||
termId String? @map("term_id")
|
||
displayText String @map("display_text") @db.VarChar(255)
|
||
isPunctuation Boolean @default(false) @map("is_punctuation")
|
||
|
||
sentence Sentence @relation(fields: [sentenceId], references: [id], onDelete: Cascade)
|
||
term Term? @relation(fields: [termId], references: [id], onDelete: SetNull)
|
||
|
||
@@unique([sentenceId, tokenIndex])
|
||
@@index([sentenceId])
|
||
@@index([termId])
|
||
@@map("sentence_tokens")
|
||
}
|
||
|
||
// ============================================
|
||
// COMMUNITY & SUPPORT
|
||
// ============================================
|
||
|
||
model Comment {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
content String @db.Text
|
||
context String? @db.VarChar(255) // e.g., "term:uuid" or "general"
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([userId])
|
||
@@map("comments")
|
||
}
|
||
|
||
model BugReport {
|
||
id String @id @default(uuid())
|
||
userId String? @map("user_id")
|
||
title String @db.VarChar(255)
|
||
description String @db.Text
|
||
status BugStatus @default(OPEN)
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||
|
||
@@index([userId])
|
||
@@index([status])
|
||
@@map("bug_reports")
|
||
}
|
||
|
||
// ============================================
|
||
// ENUMS
|
||
// ============================================
|
||
|
||
enum WordType {
|
||
NOUN
|
||
VERB
|
||
ADJECTIVE
|
||
ADVERB
|
||
PRONOUN
|
||
PREPOSITION
|
||
CONJUNCTION
|
||
INTERJECTION
|
||
PHRASE
|
||
OTHER
|
||
}
|
||
|
||
enum CefrLevel {
|
||
A1
|
||
A2
|
||
B1
|
||
B2
|
||
C1
|
||
C2
|
||
}
|
||
|
||
enum MediaKind {
|
||
VIDEO
|
||
IMAGE
|
||
ILLUSTRATION
|
||
}
|
||
|
||
enum Visibility {
|
||
PRIVATE
|
||
SHARED
|
||
PUBLIC
|
||
}
|
||
|
||
enum BugStatus {
|
||
OPEN
|
||
IN_PROGRESS
|
||
RESOLVED
|
||
CLOSED
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. API Routes & Endpoints
|
||
|
||
### Base URL
|
||
- Development: `http://localhost:3000/api`
|
||
- Production: `https://yourdomain.com/api`
|
||
|
||
### 4.1 Authentication Routes (`/api/auth`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| POST | `/auth/login` | Login with email/password | No |
|
||
| POST | `/auth/logout` | Logout current user | Yes |
|
||
| GET | `/auth/me` | Get current user info | Yes |
|
||
|
||
### 4.2 Admin User Management Routes (`/api/admin/users`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| GET | `/admin/users` | List all users | Yes (Admin) |
|
||
| POST | `/admin/users` | Create new user | Yes (Admin) |
|
||
| PUT | `/admin/users/:id` | Update user | Yes (Admin) |
|
||
| DELETE | `/admin/users/:id` | Delete user | Yes (Admin) |
|
||
| PUT | `/admin/users/:id/password` | Reset user password | Yes (Admin) |
|
||
| PUT | `/admin/users/:id/activate` | Activate/deactivate user | Yes (Admin) |
|
||
|
||
### 4.3 Dictionary/Terms Routes (`/api/terms`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| GET | `/terms` | List/search terms with filters | No |
|
||
| GET | `/terms/:id` | Get single term details | No |
|
||
| GET | `/terms/:id/media` | Get all media for a term | No |
|
||
| GET | `/terms/:id/examples` | Get examples for a term | No |
|
||
| POST | `/terms` | Create new term (admin) | Yes (Admin) |
|
||
| PUT | `/terms/:id` | Update term (admin) | Yes (Admin) |
|
||
| DELETE | `/terms/:id` | Delete term (admin) | Yes (Admin) |
|
||
|
||
**Query Parameters for GET `/terms`:**
|
||
- `query` (string): Search text
|
||
- `wordType` (enum): Filter by word type
|
||
- `cefrLevel` (enum): Filter by CEFR level
|
||
- `page` (number): Page number (default: 1)
|
||
- `limit` (number): Items per page (default: 20)
|
||
- `sortBy` (string): Sort field (default: wordText)
|
||
- `sortOrder` (asc|desc): Sort direction (default: asc)
|
||
|
||
### 4.4 Document Routes (`/api/documents`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| GET | `/documents` | List user's documents | Yes |
|
||
| GET | `/documents/:id` | Get full document with pages/sentences | Yes |
|
||
| POST | `/documents` | Create new document | Yes |
|
||
| PUT | `/documents/:id` | Update document metadata | Yes |
|
||
| DELETE | `/documents/:id` | Delete document | Yes |
|
||
| PUT | `/documents/:id/content` | Update document content (pages/sentences) | Yes |
|
||
| POST | `/documents/:id/share` | Generate share link | Yes |
|
||
|
||
### 4.5 Sentence Routes (`/api/sentences`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| GET | `/sentences/:id` | Get sentence with tokens | Yes |
|
||
| POST | `/sentences` | Create new sentence | Yes |
|
||
| PUT | `/sentences/:id` | Update sentence | Yes |
|
||
| DELETE | `/sentences/:id` | Delete sentence | Yes |
|
||
| POST | `/sentences/:id/tokens` | Add token to sentence | Yes |
|
||
| PUT | `/sentences/:id/tokens/:tokenId` | Update token | Yes |
|
||
| DELETE | `/sentences/:id/tokens/:tokenId` | Remove token | Yes |
|
||
| PUT | `/sentences/:id/reorder` | Reorder tokens | Yes |
|
||
|
||
### 4.6 Playlist Routes (`/api/playlists`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| POST | `/playlists/generate` | Generate video playlist from sentence | No |
|
||
|
||
**Request Body:**
|
||
```json
|
||
{
|
||
"sentenceId": "uuid",
|
||
"tokenIds": ["uuid1", "uuid2", "uuid3"]
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"playlist": [
|
||
{
|
||
"tokenId": "uuid1",
|
||
"termId": "uuid",
|
||
"displayText": "Dobar",
|
||
"videoUrl": "/uploads/videos/dobar.mp4",
|
||
"durationMs": 2500,
|
||
"thumbnailUrl": "/uploads/videos/dobar-thumb.jpg"
|
||
}
|
||
],
|
||
"totalDuration": 7500
|
||
}
|
||
```
|
||
|
||
### 4.7 Community Routes (`/api/community`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| GET | `/community/comments` | List comments | No |
|
||
| POST | `/community/comments` | Create comment | Yes |
|
||
| DELETE | `/community/comments/:id` | Delete own comment | Yes |
|
||
|
||
### 4.8 Bug Report Routes (`/api/bugs`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| GET | `/bugs` | List bug reports | Yes (Admin) |
|
||
| GET | `/bugs/:id` | Get bug report details | Yes |
|
||
| POST | `/bugs` | Submit bug report | Yes |
|
||
| PUT | `/bugs/:id` | Update bug status (admin) | Yes (Admin) |
|
||
|
||
### 4.9 Upload Routes (`/api/uploads`)
|
||
|
||
| Method | Endpoint | Description | Auth Required |
|
||
|--------|----------|-------------|---------------|
|
||
| POST | `/uploads/video` | Upload video file | Yes (Admin) |
|
||
| POST | `/uploads/icon` | Upload icon/image | Yes (Admin) |
|
||
|
||
### 4.10 Static File Serving
|
||
|
||
| Path | Description |
|
||
|------|-------------|
|
||
| `/uploads/videos/*` | Video files |
|
||
| `/uploads/icons/*` | Icon/image files |
|
||
| `/uploads/documents/*` | Document exports |
|
||
|
||
---
|
||
|
||
## 5. UI/UX Specifications (Exact Replication)
|
||
|
||
### 5.1 Design System (From Screenshots)
|
||
|
||
**Color Palette:**
|
||
- Primary Blue: `#2563eb` (buttons, active states)
|
||
- Sidebar Background: `#1e293b` (dark slate)
|
||
- Card Colors (word difficulty):
|
||
- Green: `#10b981` (A1-A2)
|
||
- Yellow: `#f59e0b` (B1-B2)
|
||
- Orange: `#f97316` (C1-C2)
|
||
- Background: `#f8fafc` (light gray)
|
||
- Text Primary: `#0f172a`
|
||
- Text Secondary: `#64748b`
|
||
- Border: `#e2e8f0`
|
||
|
||
**Typography:**
|
||
- Font Family: Inter, system-ui, sans-serif
|
||
- Headings: 600-700 weight
|
||
- Body: 400-500 weight
|
||
- Sizes: 14px (body), 16px (large), 12px (small)
|
||
|
||
**Spacing:**
|
||
- Base unit: 4px (Tailwind default)
|
||
- Card padding: 16px
|
||
- Section gaps: 24px
|
||
- Grid gap: 16px
|
||
|
||
**Border Radius:**
|
||
- Cards: 8px
|
||
- Buttons: 6px
|
||
- Inputs: 6px
|
||
|
||
### 5.2 Layout Components
|
||
|
||
**Sidebar (Left Navigation):**
|
||
- Width: 240px
|
||
- Fixed position
|
||
- Dark background (#1e293b)
|
||
- White text with hover states
|
||
- Logo at top
|
||
- Navigation items with icons
|
||
- Auth section at bottom
|
||
|
||
**Main Content Area:**
|
||
- Margin-left: 240px (sidebar width)
|
||
- Padding: 24px
|
||
- Max-width: 1400px (for large screens)
|
||
|
||
**Filter Bar (Dictionary Page):**
|
||
- Sticky top position
|
||
- White background with shadow
|
||
- Flex layout: inputs on left, actions on right
|
||
- Input fields: text search, dropdowns for filters
|
||
- Action buttons: Search (blue), Text toggle, Reset
|
||
|
||
**Word Card Grid:**
|
||
- CSS Grid: 4-5 columns on desktop, responsive
|
||
- Gap: 16px
|
||
- Card structure:
|
||
- Icon area (top, colored background)
|
||
- Word label (bottom, white background)
|
||
- Action buttons (overlay on hover)
|
||
|
||
### 5.3 Page-Specific Layouts
|
||
|
||
**Home (Početna):**
|
||
- Hero section with large heading
|
||
- 2x2 grid of feature cards
|
||
- Each card: icon, title, description, link
|
||
|
||
**Dictionary (Riječi):**
|
||
- Filter bar (sticky)
|
||
- Word card grid
|
||
- Pagination at bottom
|
||
- Word detail modal (overlay)
|
||
|
||
**Znakopis (Sentence Builder):**
|
||
- Two-column layout:
|
||
- Left: Token tray (current sentence)
|
||
- Right: Document panel (sentence list, page controls)
|
||
- Drag-and-drop interface for tokens
|
||
- Save/Load buttons in header
|
||
|
||
**Video Rečenica (Video Sentence):**
|
||
- Two-column layout:
|
||
- Left: Large video player (60% width)
|
||
- Right: Sentence panel (40% width)
|
||
- Video controls below player
|
||
- Highlighted current token in sentence list
|
||
|
||
**Oblak (Cloud):**
|
||
- Document list with cards
|
||
- Upload/Create buttons
|
||
- Document metadata display
|
||
- Load/Delete actions per document
|
||
|
||
### 5.4 Component Library (shadcn/ui)
|
||
|
||
Components to install:
|
||
- `button` - All action buttons
|
||
- `input` - Text inputs
|
||
- `select` - Dropdowns
|
||
- `card` - Word cards, document cards
|
||
- `dialog` - Modals (word detail, confirmations)
|
||
- `dropdown-menu` - User menu
|
||
- `tabs` - Znakopis/Oblak tabs
|
||
- `toast` - Notifications
|
||
- `avatar` - User avatar
|
||
- `badge` - CEFR level badges
|
||
- `separator` - Dividers
|
||
- `scroll-area` - Scrollable lists
|
||
|
||
### 5.5 Croatian Text (Exact Labels)
|
||
|
||
**Navigation:**
|
||
- Početna (Home)
|
||
- Riječi (Words/Dictionary)
|
||
- Znakopis (Sentence Builder)
|
||
- Video rečenica (Video Sentence)
|
||
- Oblak (Cloud)
|
||
- Korištenje aplikacije (Help)
|
||
- Zajednica (Community)
|
||
- Komentari (Comments)
|
||
- Prijavi grešku (Bug Report)
|
||
- Admin Panel (Admin only - User Management)
|
||
|
||
**Dictionary Page:**
|
||
- Riječ (Word) - search input label
|
||
- Tip riječi (Word Type) - dropdown label
|
||
- CEFR razina (CEFR Level) - dropdown label
|
||
- Traži (Search) - button
|
||
- Text - toggle button
|
||
- Reset - button
|
||
- Dodaj (Add) - card action
|
||
- Info - card action
|
||
|
||
**Znakopis Page:**
|
||
- Popis rečenica (Sentence List)
|
||
- stranica (page)
|
||
- Učitajte dokument (Load Document)
|
||
- Spremi (Save)
|
||
- Nova rečenica (New Sentence)
|
||
|
||
**Auth:**
|
||
- Prijavi se (Sign In)
|
||
- Odjavi se (Sign Out)
|
||
- Email adresa (Email Address)
|
||
- Lozinka (Password)
|
||
|
||
**Admin Panel:**
|
||
- Korisnici (Users)
|
||
- Dodaj korisnika (Add User)
|
||
- Uredi korisnika (Edit User)
|
||
- Obriši korisnika (Delete User)
|
||
- Resetiraj lozinku (Reset Password)
|
||
- Aktiviraj/Deaktiviraj (Activate/Deactivate)
|
||
- Uloga (Role)
|
||
- Aktivan (Active)
|
||
|
||
---
|
||
|
||
## 6. Implementation Milestones
|
||
|
||
**Note on Authentication Strategy:**
|
||
For the initial phases, we will implement a simple admin user with local user management. OAuth integration (Google, Microsoft) will be deferred to later phases (Phase 8+) to focus on core functionality first. The admin user will be able to create and manage local users through an admin panel.
|
||
|
||
### Phase 0: Project Setup (Week 1)
|
||
**Goal:** Initialize project structure and development environment
|
||
|
||
**Tasks:**
|
||
1. Create monorepo structure with pnpm workspaces
|
||
2. Initialize frontend (Vite + React + TypeScript)
|
||
3. Initialize backend (Express + TypeScript)
|
||
4. Set up Prisma with MySQL
|
||
5. Configure Tailwind CSS and shadcn/ui
|
||
6. Set up ESLint, Prettier, and TypeScript configs
|
||
7. Create `.env.example` files
|
||
8. Set up Git repository and `.gitignore`
|
||
9. Create initial README with setup instructions
|
||
|
||
**Deliverables:**
|
||
- ✅ Project compiles and runs (frontend + backend)
|
||
- ✅ Database connection works
|
||
- ✅ Basic "Hello World" on both ends
|
||
|
||
---
|
||
|
||
### Phase 1: Core Infrastructure (Week 2)
|
||
**Goal:** Build basic authentication and layout with admin user
|
||
|
||
**Backend Tasks:**
|
||
1. Implement Prisma schema (User model with role and isActive fields)
|
||
2. Run initial migration
|
||
3. Create seed script with default admin user
|
||
4. Implement simple session-based authentication
|
||
5. Create auth routes (login, logout, me)
|
||
6. Create auth middleware (isAuthenticated, isAdmin)
|
||
7. Set up CORS and security headers
|
||
8. Create admin user management routes (CRUD)
|
||
|
||
**Frontend Tasks:**
|
||
1. Create layout components (Sidebar, Header, Layout)
|
||
2. Set up React Router with routes
|
||
3. Create login page (simple email/password)
|
||
4. Implement auth store (Zustand)
|
||
5. Create auth API client
|
||
6. Implement protected routes
|
||
7. Create admin panel page for user management
|
||
8. Build user list, create, edit, delete components
|
||
|
||
**Deliverables:**
|
||
- ✅ Admin can login with default credentials
|
||
- ✅ Session persists across page reloads
|
||
- ✅ Sidebar navigation works
|
||
- ✅ Protected routes redirect to login
|
||
- ✅ Admin can create/edit/delete local users
|
||
- ✅ Admin can reset user passwords
|
||
- ✅ Admin can activate/deactivate users
|
||
|
||
---
|
||
|
||
### Phase 2: Dictionary Module (Week 3-4)
|
||
**Goal:** Complete dictionary browsing and search
|
||
|
||
**Backend Tasks:**
|
||
1. Implement Term, TermMedia, TermExample models
|
||
2. Run migrations
|
||
3. Create seed script with sample terms
|
||
4. Implement term routes (list, get, search)
|
||
5. Add filtering logic (wordType, cefrLevel, query)
|
||
6. Add pagination
|
||
7. Implement full-text search
|
||
8. Set up file upload for videos/icons
|
||
9. Create static file serving
|
||
|
||
**Frontend Tasks:**
|
||
1. Create Dictionary page layout
|
||
2. Build FilterBar component
|
||
3. Build WordCard component
|
||
4. Build WordGrid component
|
||
5. Implement term API client
|
||
6. Add search and filter functionality
|
||
7. Create WordDetailModal component
|
||
8. Integrate video player (Plyr)
|
||
9. Add pagination controls
|
||
10. Implement "Text" toggle view
|
||
|
||
**Deliverables:**
|
||
- ✅ Dictionary page displays word cards
|
||
- ✅ Search and filters work
|
||
- ✅ Word detail modal shows video
|
||
- ✅ Pagination works
|
||
- ✅ UI matches screenshots exactly
|
||
|
||
---
|
||
|
||
### Phase 3: Sentence Builder (Znakopis) (Week 5-6)
|
||
**Goal:** Build sentence composition workspace
|
||
|
||
**Backend Tasks:**
|
||
1. Implement Document, DocumentPage, Sentence, SentenceToken models
|
||
2. Run migrations
|
||
3. Create document routes (CRUD)
|
||
4. Create sentence routes (CRUD)
|
||
5. Implement token management (add, remove, reorder)
|
||
6. Add document content update endpoint
|
||
7. Implement document listing for user
|
||
|
||
**Frontend Tasks:**
|
||
1. Create Znakopis page layout
|
||
2. Build sentence store (Zustand)
|
||
3. Implement "Add to sentence" from Dictionary
|
||
4. Create TokenTray component
|
||
5. Implement drag-and-drop reordering (@dnd-kit)
|
||
6. Create SentenceList component
|
||
7. Build DocumentPanel component
|
||
8. Implement save/load document
|
||
9. Add page management (multi-page documents)
|
||
10. Create new sentence functionality
|
||
|
||
**Deliverables:**
|
||
- ✅ Users can add words from dictionary to sentence
|
||
- ✅ Tokens can be reordered via drag-and-drop
|
||
- ✅ Sentences can be saved to documents
|
||
- ✅ Documents can be loaded from cloud
|
||
- ✅ Multi-page documents work
|
||
- ✅ UI matches screenshots exactly
|
||
|
||
---
|
||
|
||
### Phase 4: Video Sentence Player (Week 7)
|
||
**Goal:** Implement video playback of sentences
|
||
|
||
**Backend Tasks:**
|
||
1. Create playlist generation endpoint
|
||
2. Implement playlist resolver (tokens → videos)
|
||
3. Handle missing media gracefully
|
||
4. Add video metadata to responses
|
||
|
||
**Frontend Tasks:**
|
||
1. Create VideoSentence page layout
|
||
2. Build VideoPlayer component with Plyr
|
||
3. Implement playlist logic
|
||
4. Add playback controls (play, pause, next, prev)
|
||
5. Implement token highlighting sync
|
||
6. Add speed control
|
||
7. Add loop functionality
|
||
8. Implement preloading of next video
|
||
9. Create sentence panel (right side)
|
||
|
||
**Deliverables:**
|
||
- ✅ Sentences play as sequential videos
|
||
- ✅ Playback controls work
|
||
- ✅ Current token is highlighted
|
||
- ✅ Videos transition smoothly
|
||
- ✅ UI matches screenshots exactly
|
||
|
||
---
|
||
|
||
### Phase 5: Cloud Documents (Week 8)
|
||
**Goal:** Complete document management
|
||
|
||
**Backend Tasks:**
|
||
1. Implement document visibility settings
|
||
2. Add share link generation
|
||
3. Create document export functionality
|
||
4. Add document search/filtering
|
||
|
||
**Frontend Tasks:**
|
||
1. Create Oblak (Cloud) page
|
||
2. Build DocumentBrowser component
|
||
3. Implement document list with cards
|
||
4. Add upload document functionality
|
||
5. Create document metadata editor
|
||
6. Implement load document into Znakopis
|
||
7. Add delete document with confirmation
|
||
8. Implement document sharing UI
|
||
|
||
**Deliverables:**
|
||
- ✅ Users can view all their documents
|
||
- ✅ Documents can be loaded into editor
|
||
- ✅ Documents can be deleted
|
||
- ✅ Share links work
|
||
- ✅ UI matches screenshots exactly
|
||
|
||
---
|
||
|
||
### Phase 6: Community & Support (Week 9)
|
||
**Goal:** Add community features
|
||
|
||
**Backend Tasks:**
|
||
1. Implement Comment and BugReport models
|
||
2. Run migrations
|
||
3. Create community routes
|
||
4. Create bug report routes
|
||
5. Add admin endpoints for bug management
|
||
|
||
**Frontend Tasks:**
|
||
1. Create Help page (Korištenje aplikacije)
|
||
2. Create Community page (Zajednica)
|
||
3. Create Comments page (Komentari)
|
||
4. Build comment form and list
|
||
5. Create BugReport page (Prijavi grešku)
|
||
6. Build bug report form
|
||
7. Add toast notifications
|
||
|
||
**Deliverables:**
|
||
- ✅ Users can post comments
|
||
- ✅ Users can submit bug reports
|
||
- ✅ Help page has documentation
|
||
- ✅ UI matches screenshots exactly
|
||
|
||
---
|
||
|
||
### Phase 7: Polish & Testing (Week 10)
|
||
**Goal:** Final refinements and quality assurance
|
||
|
||
**Tasks:**
|
||
1. Responsive design testing (mobile, tablet, desktop)
|
||
2. Cross-browser testing (Chrome, Firefox, Safari, Edge)
|
||
3. Accessibility audit (keyboard nav, ARIA labels, screen readers)
|
||
4. Performance optimization (lazy loading, code splitting)
|
||
5. Error handling improvements
|
||
6. Loading states and skeletons
|
||
7. Form validation refinements
|
||
8. Video loading optimization
|
||
9. Database query optimization
|
||
10. Security audit
|
||
11. Write E2E tests (Playwright)
|
||
12. Write unit tests for critical functions
|
||
13. Documentation updates
|
||
14. Deployment preparation
|
||
|
||
**Deliverables:**
|
||
- ✅ App works on all major browsers
|
||
- ✅ Mobile responsive
|
||
- ✅ Accessible (WCAG 2.1 AA)
|
||
- ✅ Fast loading times
|
||
- ✅ No critical bugs
|
||
- ✅ Tests pass
|
||
- ✅ Ready for deployment
|
||
|
||
---
|
||
|
||
### Phase 8: OAuth Integration (Future Phase)
|
||
**Goal:** Add OAuth authentication providers (deferred from Phase 1)
|
||
|
||
**Backend Tasks:**
|
||
1. Set up Passport.js with Google OAuth strategy
|
||
2. Set up Passport.js with Microsoft OAuth strategy
|
||
3. Update User model to support authProvider and providerId fields
|
||
4. Create OAuth callback routes
|
||
5. Implement account linking (OAuth to existing local accounts)
|
||
6. Update auth middleware to support OAuth sessions
|
||
|
||
**Frontend Tasks:**
|
||
1. Add OAuth buttons to login page
|
||
2. Create OAuth callback handling
|
||
3. Add account linking UI
|
||
4. Update user profile to show auth provider
|
||
|
||
**Deliverables:**
|
||
- ✅ Users can sign in with Google
|
||
- ✅ Users can sign in with Microsoft
|
||
- ✅ OAuth accounts can be linked to existing local accounts
|
||
- ✅ Session management works with OAuth
|
||
|
||
---
|
||
|
||
## 7. Environment Configuration
|
||
|
||
### Backend `.env` (packages/backend/.env)
|
||
|
||
```env
|
||
# Server
|
||
NODE_ENV=development
|
||
PORT=3000
|
||
FRONTEND_URL=http://localhost:5173
|
||
|
||
# Database
|
||
DATABASE_URL="mysql://user:password@localhost:3306/znakovni"
|
||
|
||
# Session
|
||
SESSION_SECRET=your-super-secret-session-key-change-in-production
|
||
|
||
# Default Admin User (created on first run)
|
||
ADMIN_EMAIL=admin@znakovni.hr
|
||
ADMIN_PASSWORD=change-this-password-immediately
|
||
|
||
# File Upload
|
||
UPLOAD_DIR=./uploads
|
||
MAX_FILE_SIZE=104857600
|
||
# 100MB in bytes
|
||
|
||
# CORS
|
||
CORS_ORIGIN=http://localhost:5173
|
||
```
|
||
|
||
### Frontend `.env` (packages/frontend/.env)
|
||
|
||
```env
|
||
VITE_API_URL=http://localhost:3000/api
|
||
VITE_UPLOADS_URL=http://localhost:3000/uploads
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Key Features & User Flows
|
||
|
||
### CORE FUNCTIONALITY OVERVIEW (Based on Screenshots 1, 2, 3)
|
||
|
||
**The application has THREE main interconnected screens that form the primary workflow:**
|
||
|
||
#### Screen 1: Riječi (Dictionary) - "1 dodaj rijeci.png"
|
||
**Purpose:** Browse pre-recorded sign language words and add them to your sentence.
|
||
|
||
**Key Elements:**
|
||
- Grid of word cards with icons/illustrations
|
||
- Each card has two buttons:
|
||
- **"Dodaj"** (Add) - Adds the word to the current sentence being built
|
||
- **"Info"** - Opens a modal/detail view showing the sign video for that word
|
||
- Filter bar at top with search and CEFR level filters
|
||
- Words are color-coded by difficulty level (green/yellow/orange)
|
||
|
||
**User Actions:**
|
||
1. Browse or search for words
|
||
2. Click "Dodaj" to add words to the sentence (words accumulate at the top of the page)
|
||
3. Click "Info" to watch the sign video demonstration for any word
|
||
4. Continue adding multiple words to build a complete sentence
|
||
|
||
---
|
||
|
||
#### Screen 2: Znakopis (Sentence Builder) - "2 dodaj rijeci znakopis.png"
|
||
**Purpose:** Organize the words you've added into a proper sentence order and save as a document.
|
||
|
||
**Key Elements:**
|
||
- **Top area:** Shows the current sentence with all added words as tokens/chips
|
||
- **Right panel:** "Popis rečenica" (Sentence List) showing:
|
||
- All sentences in the current document
|
||
- Page navigation (e.g., "1 / 2 stranica")
|
||
- Document management controls
|
||
- **Main workspace:** Area to reorder words via drag-and-drop
|
||
- **Action buttons:**
|
||
- "Spremi" (Save) - Saves the document to cloud storage under your user account
|
||
- "Učitajte dokument" (Load Document) - Loads a previously saved document
|
||
- "Nova rečenica" (New Sentence) - Creates a new sentence on a new page
|
||
|
||
**User Actions:**
|
||
1. View all words added from the Dictionary screen
|
||
2. Drag and drop words to reorder them into the correct sentence structure
|
||
3. Remove unwanted words
|
||
4. Create multiple sentences across multiple pages
|
||
5. Save the complete document to the cloud (stored under user's account)
|
||
6. Load previously saved documents for editing
|
||
|
||
---
|
||
|
||
#### Screen 3: Video Rečenica (Video Sentence Player) - "3 dodaj rijeci recenica.png"
|
||
**Purpose:** Play all the sign videos for your sentence in sequence, with visual highlighting.
|
||
|
||
**Key Elements:**
|
||
- **Left side (60%):** Large video player showing the current word's sign video
|
||
- **Right side (40%):** Sentence panel showing:
|
||
- Complete sentence with all words listed
|
||
- Current word being played is **highlighted/marked**
|
||
- Sentence navigation controls
|
||
- **Playback controls:**
|
||
- Play/Pause
|
||
- Next/Previous word
|
||
- Speed control
|
||
- Loop options
|
||
|
||
**User Actions:**
|
||
1. Load a sentence from Znakopis or Cloud
|
||
2. Click play to start sequential video playback
|
||
3. Watch as each word's sign video plays in order
|
||
4. See visual highlighting on the current word being signed
|
||
5. Control playback (pause, skip, adjust speed)
|
||
6. Navigate between different sentences/pages in the document
|
||
|
||
---
|
||
|
||
### 8.1 Complete User Workflow (End-to-End)
|
||
|
||
**Step 1: Build a Sentence (Riječi → Znakopis)**
|
||
1. User navigates to **"Riječi"** (Dictionary)
|
||
2. User searches/browses for words they want to use
|
||
3. User clicks **"Dodaj"** on each word card to add it to their sentence
|
||
- Words accumulate in a sentence builder area (visible at top)
|
||
4. User can click **"Info"** on any word to preview its sign video
|
||
5. After adding all desired words, user navigates to **"Znakopis"**
|
||
6. In Znakopis, user sees all added words as draggable tokens
|
||
7. User drags and drops words to arrange them in correct order
|
||
8. User can remove unwanted words
|
||
9. User can create additional sentences on new pages
|
||
10. User clicks **"Spremi"** (Save) to save the document to cloud
|
||
- Document is stored under the user's account in the portal
|
||
|
||
**Step 2: Review and Play (Znakopis → Video Rečenica)**
|
||
1. User navigates to **"Video rečenica"** (Video Sentence)
|
||
2. User loads their saved sentence/document
|
||
3. Sentence appears in the right panel with all words listed
|
||
4. User clicks **Play**
|
||
5. Video player shows each word's sign video in sequence
|
||
6. Current word is **highlighted** in the sentence list
|
||
7. User can control playback, adjust speed, or navigate between sentences
|
||
|
||
**Step 3: Manage Documents (Oblak)**
|
||
1. User navigates to **"Oblak"** (Cloud)
|
||
2. User sees all their saved documents
|
||
3. User can:
|
||
- Load a document back into Znakopis for editing
|
||
- Delete documents
|
||
- Share documents (generate link)
|
||
- View document metadata (creation date, page count, etc.)
|
||
|
||
---
|
||
|
||
### 8.2 Critical Implementation Requirements
|
||
|
||
**Sentence State Management:**
|
||
- Sentence state must persist across all three screens (Riječi → Znakopis → Video Rečenica)
|
||
- When user adds words in Riječi, they must appear in Znakopis
|
||
- When user saves in Znakopis, document must be available in Video Rečenica and Oblak
|
||
- Use Zustand store to maintain sentence/document state globally
|
||
|
||
**Video Synchronization:**
|
||
- In Video Rečenica, video playback must be synchronized with word highlighting
|
||
- When video for word N finishes, automatically start video for word N+1
|
||
- Highlight must move to the current word being played
|
||
- Smooth transitions between videos (preload next video)
|
||
|
||
**Document Structure:**
|
||
- Documents can have multiple pages
|
||
- Each page can have multiple sentences
|
||
- Each sentence is a sequence of word tokens
|
||
- Tokens maintain reference to original Term (for video lookup)
|
||
- Token order is preserved and editable
|
||
|
||
**Cloud Storage:**
|
||
- All saved documents are associated with the logged-in user
|
||
- Documents persist in database (Document → DocumentPage → Sentence → SentenceToken)
|
||
- Users can only see/edit their own documents (unless shared)
|
||
- Documents can be loaded back into Znakopis for editing
|
||
|
||
---
|
||
|
||
## 9. Technical Implementation Details
|
||
|
||
### 9.1 Video Playlist Resolution Algorithm
|
||
|
||
```typescript
|
||
// Pseudocode for playlist generation
|
||
async function generatePlaylist(sentenceId: string) {
|
||
// 1. Fetch sentence with tokens
|
||
const sentence = await prisma.sentence.findUnique({
|
||
where: { id: sentenceId },
|
||
include: {
|
||
tokens: {
|
||
include: { term: { include: { media: true } } },
|
||
orderBy: { tokenIndex: 'asc' }
|
||
}
|
||
}
|
||
});
|
||
|
||
// 2. Map tokens to video URLs
|
||
const playlist = sentence.tokens.map(token => {
|
||
if (!token.term) {
|
||
return null; // Skip punctuation or free-text
|
||
}
|
||
|
||
// Find primary video media
|
||
const video = token.term.media.find(m => m.kind === 'VIDEO');
|
||
|
||
if (!video) {
|
||
console.warn(`No video for term ${token.term.id}`);
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
tokenId: token.id,
|
||
termId: token.term.id,
|
||
displayText: token.displayText,
|
||
videoUrl: video.url,
|
||
durationMs: video.durationMs,
|
||
thumbnailUrl: video.url.replace('.mp4', '-thumb.jpg')
|
||
};
|
||
}).filter(Boolean);
|
||
|
||
return {
|
||
playlist,
|
||
totalDuration: playlist.reduce((sum, item) => sum + item.durationMs, 0)
|
||
};
|
||
}
|
||
```
|
||
|
||
### 9.2 Full-Text Search Implementation
|
||
|
||
```typescript
|
||
// MySQL full-text search query
|
||
async function searchTerms(query: string, filters: Filters) {
|
||
const where = {
|
||
AND: [
|
||
query ? {
|
||
OR: [
|
||
{ wordText: { contains: query } },
|
||
{ normalizedText: { contains: query } }
|
||
]
|
||
} : {},
|
||
filters.wordType ? { wordType: filters.wordType } : {},
|
||
filters.cefrLevel ? { cefrLevel: filters.cefrLevel } : {}
|
||
]
|
||
};
|
||
|
||
return await prisma.term.findMany({
|
||
where,
|
||
include: { media: true },
|
||
orderBy: { wordText: 'asc' },
|
||
skip: (filters.page - 1) * filters.limit,
|
||
take: filters.limit
|
||
});
|
||
}
|
||
```
|
||
|
||
### 9.3 Token Reordering Logic
|
||
|
||
```typescript
|
||
// Frontend: Drag-and-drop reorder
|
||
function handleDragEnd(event: DragEndEvent) {
|
||
const { active, over } = event;
|
||
|
||
if (active.id !== over.id) {
|
||
const oldIndex = tokens.findIndex(t => t.id === active.id);
|
||
const newIndex = tokens.findIndex(t => t.id === over.id);
|
||
|
||
const reordered = arrayMove(tokens, oldIndex, newIndex);
|
||
|
||
// Update local state
|
||
setSentenceTokens(reordered);
|
||
|
||
// Sync to backend
|
||
await api.put(`/sentences/${sentenceId}/reorder`, {
|
||
tokenIds: reordered.map(t => t.id)
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Deployment Guide (PoC)
|
||
|
||
### 10.1 Prerequisites
|
||
- Ubuntu 20.04+ or similar Linux server
|
||
- Node.js 20 LTS installed
|
||
- MySQL 8.0+ installed and running
|
||
- pnpm installed globally
|
||
- nginx installed (optional, for reverse proxy)
|
||
|
||
### 10.2 Deployment Steps
|
||
|
||
**1. Clone and Install**
|
||
```bash
|
||
git clone <your-repo-url>
|
||
cd turkshop
|
||
pnpm install
|
||
```
|
||
|
||
**2. Configure Environment**
|
||
```bash
|
||
# Backend
|
||
cd packages/backend
|
||
cp .env.example .env
|
||
# Edit .env with production values
|
||
nano .env
|
||
|
||
# Frontend
|
||
cd ../frontend
|
||
cp .env.example .env
|
||
# Edit .env with production API URL
|
||
nano .env
|
||
```
|
||
|
||
**3. Database Setup**
|
||
```bash
|
||
cd packages/backend
|
||
npx prisma migrate deploy
|
||
npx prisma db seed
|
||
```
|
||
|
||
**4. Build Applications**
|
||
```bash
|
||
# From root
|
||
pnpm build
|
||
```
|
||
|
||
**5. Start with PM2**
|
||
```bash
|
||
# Install PM2
|
||
npm install -g pm2
|
||
|
||
# Start backend
|
||
cd packages/backend
|
||
pm2 start dist/server.js --name znakovni-api
|
||
|
||
# Serve frontend (with nginx or serve)
|
||
cd ../frontend
|
||
pm2 serve dist 5173 --name znakovni-frontend
|
||
```
|
||
|
||
**6. Configure nginx (Optional)**
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name yourdomain.com;
|
||
|
||
# Frontend
|
||
location / {
|
||
root /path/to/turkshop/packages/frontend/dist;
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
|
||
# Backend API
|
||
location /api {
|
||
proxy_pass http://localhost:3000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection 'upgrade';
|
||
proxy_set_header Host $host;
|
||
proxy_cache_bypass $http_upgrade;
|
||
}
|
||
|
||
# Uploads
|
||
location /uploads {
|
||
proxy_pass http://localhost:3000;
|
||
}
|
||
}
|
||
```
|
||
|
||
**7. Set up SSL (Production)**
|
||
```bash
|
||
sudo apt install certbot python3-certbot-nginx
|
||
sudo certbot --nginx -d yourdomain.com
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Development Workflow
|
||
|
||
### 11.1 Initial Setup
|
||
|
||
```bash
|
||
# Clone repo
|
||
git clone <repo-url>
|
||
cd turkshop
|
||
|
||
# Install dependencies
|
||
pnpm install
|
||
|
||
# Set up environment files
|
||
cp packages/backend/.env.example packages/backend/.env
|
||
cp packages/frontend/.env.example packages/frontend/.env
|
||
|
||
# Edit .env files with your local MySQL credentials and OAuth keys
|
||
|
||
# Run database migrations
|
||
cd packages/backend
|
||
npx prisma migrate dev
|
||
npx prisma db seed
|
||
|
||
# Return to root
|
||
cd ../..
|
||
```
|
||
|
||
### 11.2 Running Development Servers
|
||
|
||
```bash
|
||
# From root directory
|
||
pnpm dev
|
||
|
||
# This runs both frontend (port 5173) and backend (port 3000) concurrently
|
||
```
|
||
|
||
### 11.3 Database Management
|
||
|
||
```bash
|
||
# Create new migration
|
||
cd packages/backend
|
||
npx prisma migrate dev --name description_of_change
|
||
|
||
# Reset database (WARNING: deletes all data)
|
||
npx prisma migrate reset
|
||
|
||
# Open Prisma Studio (GUI for database)
|
||
npx prisma studio
|
||
|
||
# Generate Prisma Client after schema changes
|
||
npx prisma generate
|
||
```
|
||
|
||
### 11.4 Adding shadcn/ui Components
|
||
|
||
```bash
|
||
cd packages/frontend
|
||
npx shadcn-ui@latest add button
|
||
npx shadcn-ui@latest add input
|
||
npx shadcn-ui@latest add card
|
||
# etc.
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Success Criteria
|
||
|
||
### Core Functionality Requirements (CRITICAL) ✅
|
||
|
||
**Screen 1: Riječi (Dictionary)**
|
||
- [ ] Dictionary displays grid of word cards with icons
|
||
- [ ] Each card has "Dodaj" (Add) and "Info" buttons
|
||
- [ ] Clicking "Dodaj" adds word to current sentence (visible at top of page)
|
||
- [ ] Clicking "Info" opens modal with sign video that auto-plays
|
||
- [ ] Search and filter controls work (word type, CEFR level)
|
||
- [ ] Words are color-coded by difficulty level
|
||
|
||
**Screen 2: Znakopis (Sentence Builder)**
|
||
- [ ] All words added from Dictionary appear as draggable tokens
|
||
- [ ] Tokens can be reordered via drag-and-drop
|
||
- [ ] Tokens can be removed
|
||
- [ ] Right panel shows "Popis rečenica" (Sentence List)
|
||
- [ ] Users can create multiple sentences across multiple pages
|
||
- [ ] "Spremi" (Save) button saves document to cloud under user's account
|
||
- [ ] "Učitajte dokument" (Load Document) loads previously saved documents
|
||
- [ ] Page navigation works (e.g., "1 / 2 stranica")
|
||
|
||
**Screen 3: Video Rečenica (Video Player)**
|
||
- [ ] Left side shows large video player (60% width)
|
||
- [ ] Right side shows sentence panel with all words (40% width)
|
||
- [ ] Clicking Play starts sequential video playback
|
||
- [ ] Videos play in order, one after another
|
||
- [ ] Current word being signed is **highlighted** in the sentence list
|
||
- [ ] Highlighting moves automatically as videos progress
|
||
- [ ] Playback controls work (play, pause, next, previous)
|
||
- [ ] Speed control and loop options work
|
||
- [ ] Videos transition smoothly (preload next video)
|
||
|
||
**Cross-Screen Integration**
|
||
- [ ] Sentence state persists from Riječi → Znakopis → Video Rečenica
|
||
- [ ] Documents saved in Znakopis appear in Oblak (Cloud)
|
||
- [ ] Documents loaded from Oblak can be edited in Znakopis
|
||
- [ ] Documents loaded from Oblak can be played in Video Rečenica
|
||
|
||
**Authentication & User Management**
|
||
- [ ] Admin can login with credentials
|
||
- [ ] Admin can create/edit/delete local users
|
||
- [ ] Admin can reset user passwords
|
||
- [ ] Admin can activate/deactivate users
|
||
- [ ] Regular users can login with their credentials
|
||
- [ ] Documents are associated with logged-in user
|
||
- [ ] Users can only see their own documents (unless shared)
|
||
|
||
**Cloud Document Management (Oblak)**
|
||
- [ ] Users can view all their saved documents
|
||
- [ ] Documents show metadata (title, creation date, page count)
|
||
- [ ] Users can load documents into Znakopis
|
||
- [ ] Users can delete documents
|
||
- [ ] Users can share documents (generate link)
|
||
|
||
**Additional Features**
|
||
- [ ] Comments and bug reports can be submitted
|
||
- [ ] Help page displays usage documentation
|
||
- [ ] Community features work
|
||
|
||
### Visual Requirements ✅
|
||
- [ ] UI matches screenshots pixel-perfect
|
||
- [ ] Croatian text labels are exact
|
||
- [ ] Color scheme matches original
|
||
- [ ] Typography matches original
|
||
- [ ] Layout and spacing match original
|
||
- [ ] Icons and graphics match original
|
||
|
||
### Performance Requirements ✅
|
||
- [ ] Dictionary search returns results < 500ms
|
||
- [ ] Video playback starts < 1s
|
||
- [ ] Page load time < 2s
|
||
- [ ] Smooth animations (60fps)
|
||
|
||
### Quality Requirements ✅
|
||
- [ ] TypeScript strict mode with no errors
|
||
- [ ] All forms have validation
|
||
- [ ] Error handling on all API calls
|
||
- [ ] Loading states for async operations
|
||
- [ ] Responsive design (desktop, tablet, mobile)
|
||
- [ ] Accessible (keyboard navigation, ARIA labels)
|
||
|
||
---
|
||
|
||
## 13. Next Steps
|
||
|
||
1. **Review and approve this plan**
|
||
2. **Prepare MySQL database** (local or cloud)
|
||
3. **Begin Phase 0: Project Setup**
|
||
4. **Iterate through phases sequentially**
|
||
5. **Test continuously during development**
|
||
6. **Deploy to production server**
|
||
7. **Phase 8+ (Future): Set up OAuth credentials** (Google Cloud Console, Microsoft Azure) when ready to add OAuth
|
||
|
||
---
|
||
|
||
## 14. Resources & References
|
||
|
||
### Documentation
|
||
- React: https://react.dev
|
||
- Vite: https://vitejs.dev
|
||
- TypeScript: https://www.typescriptlang.org
|
||
- Tailwind CSS: https://tailwindcss.com
|
||
- shadcn/ui: https://ui.shadcn.com
|
||
- Prisma: https://www.prisma.io
|
||
- Express: https://expressjs.com
|
||
- Passport.js: https://www.passportjs.org
|
||
- Plyr: https://plyr.io
|
||
- @dnd-kit: https://dndkit.com
|
||
|
||
### Original Specifications
|
||
- `/usr/src/znakovni/original/project1.md` - Detailed functional spec
|
||
- `/usr/src/znakovni/original/project2.md` - Replication requirements
|
||
- `/usr/src/znakovni/original/*.png` - UI screenshots
|
||
|
||
---
|
||
|
||
**END OF PLAN**
|
||
|