- Implement JWT-based authentication with login/logout - Add user management routes and middleware - Create admin panel for managing words and categories - Add authentication store and API client - Update database schema with User model - Configure CORS and authentication middleware - Add login page and protected routes
40 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.
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
8.1 Dictionary Search Flow
- User navigates to "Riječi" (Dictionary)
- User enters search term in "Riječ" input
- User optionally selects "Tip riječi" (word type) filter
- User optionally selects "CEFR razina" (level) filter
- User clicks "Traži" (Search)
- Grid updates with filtered results
- User clicks "Info" on a card
- Modal opens showing:
- Word details
- Sign video (auto-plays)
- Examples
- Metadata
- User can click "Dodaj" to add word to current sentence
8.2 Sentence Building Flow
- User searches for words in Dictionary
- User clicks "Dodaj" on multiple word cards
- Words are added to sentence store
- User navigates to "Znakopis"
- Tokens appear in TokenTray
- User drags tokens to reorder
- User can remove tokens
- User clicks "Spremi" (Save)
- Document is saved to cloud (if logged in) or local storage
- User can create multiple sentences across pages
8.3 Video Playback Flow
- User builds or loads a sentence in Znakopis
- User navigates to "Video rečenica"
- Sentence tokens appear in right panel
- User clicks play button
- Videos play sequentially for each token
- Current token is highlighted
- User can pause, skip, or adjust speed
- User can navigate between pages/sentences
8.4 Cloud Document Flow
- User logs in
- User creates sentences in Znakopis
- User saves document to cloud
- User navigates to "Oblak"
- Document appears in list
- User can:
- Load document into Znakopis
- Delete document
- Share document (get link)
- View document metadata
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
Functional Requirements ✅
- Users can browse and search dictionary with filters
- Word detail modal displays video and metadata
- Users can build sentences by adding words
- Tokens can be reordered via drag-and-drop
- Sentences can be saved to documents
- Documents can be loaded from cloud
- Video sentence player works with sequential playback
- 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
- Cloud document management works
- Comments and bug reports can be submitted
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