Files
znakovni.hr/main-plan.md
johnny2211 3275bc4a4f Add authentication system and admin panel
- 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
2026-01-17 14:30:22 +01:00

1409 lines
40 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.
---
## 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
### 8.1 Dictionary Search Flow
1. User navigates to "Riječi" (Dictionary)
2. User enters search term in "Riječ" input
3. User optionally selects "Tip riječi" (word type) filter
4. User optionally selects "CEFR razina" (level) filter
5. User clicks "Traži" (Search)
6. Grid updates with filtered results
7. User clicks "Info" on a card
8. Modal opens showing:
- Word details
- Sign video (auto-plays)
- Examples
- Metadata
9. User can click "Dodaj" to add word to current sentence
### 8.2 Sentence Building Flow
1. User searches for words in Dictionary
2. User clicks "Dodaj" on multiple word cards
3. Words are added to sentence store
4. User navigates to "Znakopis"
5. Tokens appear in TokenTray
6. User drags tokens to reorder
7. User can remove tokens
8. User clicks "Spremi" (Save)
9. Document is saved to cloud (if logged in) or local storage
10. User can create multiple sentences across pages
### 8.3 Video Playback Flow
1. User builds or loads a sentence in Znakopis
2. User navigates to "Video rečenica"
3. Sentence tokens appear in right panel
4. User clicks play button
5. Videos play sequentially for each token
6. Current token is highlighted
7. User can pause, skip, or adjust speed
8. User can navigate between pages/sentences
### 8.4 Cloud Document Flow
1. User logs in
2. User creates sentences in Znakopis
3. User saves document to cloud
4. User navigates to "Oblak"
5. Document appears in list
6. 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
```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
### 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
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**