- 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
50 KiB
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 (
/uploadsdirectory) - 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/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 textwordType(enum): Filter by word typecefrLevel(enum): Filter by CEFR levelpage(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:
{
"sentenceId": "uuid",
"tokenIds": ["uuid1", "uuid2", "uuid3"]
}
Response:
{
"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)
- Green:
- 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 buttonsinput- Text inputsselect- Dropdownscard- Word cards, document cardsdialog- Modals (word detail, confirmations)dropdown-menu- User menutabs- Znakopis/Oblak tabstoast- Notificationsavatar- User avatarbadge- CEFR level badgesseparator- Dividersscroll-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:
- Create monorepo structure with pnpm workspaces
- Initialize frontend (Vite + React + TypeScript)
- Initialize backend (Express + TypeScript)
- Set up Prisma with MySQL
- Configure Tailwind CSS and shadcn/ui
- Set up ESLint, Prettier, and TypeScript configs
- Create
.env.examplefiles - Set up Git repository and
.gitignore - 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:
- Implement Prisma schema (User model with role and isActive fields)
- Run initial migration
- Create seed script with default admin user
- Implement simple session-based authentication
- Create auth routes (login, logout, me)
- Create auth middleware (isAuthenticated, isAdmin)
- Set up CORS and security headers
- Create admin user management routes (CRUD)
Frontend Tasks:
- Create layout components (Sidebar, Header, Layout)
- Set up React Router with routes
- Create login page (simple email/password)
- Implement auth store (Zustand)
- Create auth API client
- Implement protected routes
- Create admin panel page for user management
- 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:
- Implement Term, TermMedia, TermExample models
- Run migrations
- Create seed script with sample terms
- Implement term routes (list, get, search)
- Add filtering logic (wordType, cefrLevel, query)
- Add pagination
- Implement full-text search
- Set up file upload for videos/icons
- Create static file serving
Frontend Tasks:
- Create Dictionary page layout
- Build FilterBar component
- Build WordCard component
- Build WordGrid component
- Implement term API client
- Add search and filter functionality
- Create WordDetailModal component
- Integrate video player (Plyr)
- Add pagination controls
- 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:
- Implement Document, DocumentPage, Sentence, SentenceToken models
- Run migrations
- Create document routes (CRUD)
- Create sentence routes (CRUD)
- Implement token management (add, remove, reorder)
- Add document content update endpoint
- Implement document listing for user
Frontend Tasks:
- Create Znakopis page layout
- Build sentence store (Zustand)
- Implement "Add to sentence" from Dictionary
- Create TokenTray component
- Implement drag-and-drop reordering (@dnd-kit)
- Create SentenceList component
- Build DocumentPanel component
- Implement save/load document
- Add page management (multi-page documents)
- 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:
- Create playlist generation endpoint
- Implement playlist resolver (tokens → videos)
- Handle missing media gracefully
- Add video metadata to responses
Frontend Tasks:
- Create VideoSentence page layout
- Build VideoPlayer component with Plyr
- Implement playlist logic
- Add playback controls (play, pause, next, prev)
- Implement token highlighting sync
- Add speed control
- Add loop functionality
- Implement preloading of next video
- 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:
- Implement document visibility settings
- Add share link generation
- Create document export functionality
- Add document search/filtering
Frontend Tasks:
- Create Oblak (Cloud) page
- Build DocumentBrowser component
- Implement document list with cards
- Add upload document functionality
- Create document metadata editor
- Implement load document into Znakopis
- Add delete document with confirmation
- 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:
- Implement Comment and BugReport models
- Run migrations
- Create community routes
- Create bug report routes
- Add admin endpoints for bug management
Frontend Tasks:
- Create Help page (Korištenje aplikacije)
- Create Community page (Zajednica)
- Create Comments page (Komentari)
- Build comment form and list
- Create BugReport page (Prijavi grešku)
- Build bug report form
- 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:
- Responsive design testing (mobile, tablet, desktop)
- Cross-browser testing (Chrome, Firefox, Safari, Edge)
- Accessibility audit (keyboard nav, ARIA labels, screen readers)
- Performance optimization (lazy loading, code splitting)
- Error handling improvements
- Loading states and skeletons
- Form validation refinements
- Video loading optimization
- Database query optimization
- Security audit
- Write E2E tests (Playwright)
- Write unit tests for critical functions
- Documentation updates
- 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:
- Set up Passport.js with Google OAuth strategy
- Set up Passport.js with Microsoft OAuth strategy
- Update User model to support authProvider and providerId fields
- Create OAuth callback routes
- Implement account linking (OAuth to existing local accounts)
- Update auth middleware to support OAuth sessions
Frontend Tasks:
- Add OAuth buttons to login page
- Create OAuth callback handling
- Add account linking UI
- 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)
# 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)
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:
- Browse or search for words
- Click "Dodaj" to add words to the sentence (words accumulate at the top of the page)
- Click "Info" to watch the sign video demonstration for any word
- 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:
- View all words added from the Dictionary screen
- Drag and drop words to reorder them into the correct sentence structure
- Remove unwanted words
- Create multiple sentences across multiple pages
- Save the complete document to the cloud (stored under user's account)
- 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:
- Load a sentence from Znakopis or Cloud
- Click play to start sequential video playback
- Watch as each word's sign video plays in order
- See visual highlighting on the current word being signed
- Control playback (pause, skip, adjust speed)
- Navigate between different sentences/pages in the document
8.1 Complete User Workflow (End-to-End)
Step 1: Build a Sentence (Riječi → Znakopis)
- User navigates to "Riječi" (Dictionary)
- User searches/browses for words they want to use
- User clicks "Dodaj" on each word card to add it to their sentence
- Words accumulate in a sentence builder area (visible at top)
- User can click "Info" on any word to preview its sign video
- After adding all desired words, user navigates to "Znakopis"
- In Znakopis, user sees all added words as draggable tokens
- User drags and drops words to arrange them in correct order
- User can remove unwanted words
- User can create additional sentences on new pages
- 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)
- User navigates to "Video rečenica" (Video Sentence)
- User loads their saved sentence/document
- Sentence appears in the right panel with all words listed
- User clicks Play
- Video player shows each word's sign video in sequence
- Current word is highlighted in the sentence list
- User can control playback, adjust speed, or navigate between sentences
Step 3: Manage Documents (Oblak)
- User navigates to "Oblak" (Cloud)
- User sees all their saved documents
- 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
// 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
// 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
// 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
git clone <your-repo-url>
cd turkshop
pnpm install
2. Configure Environment
# 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
cd packages/backend
npx prisma migrate deploy
npx prisma db seed
4. Build Applications
# From root
pnpm build
5. Start with PM2
# 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)
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)
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
11. Development Workflow
11.1 Initial Setup
# 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
# From root directory
pnpm dev
# This runs both frontend (port 5173) and backend (port 3000) concurrently
11.3 Database Management
# 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
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
- Review and approve this plan
- Prepare MySQL database (local or cloud)
- Begin Phase 0: Project Setup
- Iterate through phases sequentially
- Test continuously during development
- Deploy to production server
- 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