Files
znakovni.hr/main-plan.md
johnny2211 e38194a44c Add comprehensive project documentation
- Add detailed implementation plan (main-plan.md)
- Add professional README with project overview
- Include original specifications and screenshots
- Document technology stack and architecture
- Provide setup and deployment instructions
2026-01-17 11:42:15 +01:00

38 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: Passport.js (Google OAuth, Microsoft OAuth, local email/password)
  • 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
│   │   │   ├── 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
│       │   │   ├── terms.ts
│       │   │   ├── documents.ts
│       │   │   ├── sentences.ts
│       │   │   ├── playlists.ts
│       │   │   ├── community.ts
│       │   │   └── index.ts
│       │   ├── controllers/     # Business logic
│       │   │   ├── authController.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
│       │   │   └── passport.ts  # Passport config
│       │   ├── 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")
  authProvider  String    @default("local") @map("auth_provider") // local, google, microsoft
  providerId    String?   @map("provider_id")
  createdAt     DateTime  @default(now()) @map("created_at")
  updatedAt     DateTime  @updatedAt @map("updated_at")

  documents     Document[]
  comments      Comment[]
  bugReports    BugReport[]

  @@map("users")
}

// ============================================
// 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/register Register new user with email/password No
POST /auth/login Login with email/password No
POST /auth/logout Logout current user Yes
GET /auth/me Get current user info Yes
GET /auth/google Initiate Google OAuth flow No
GET /auth/google/callback Google OAuth callback No
GET /auth/microsoft Initiate Microsoft OAuth flow No
GET /auth/microsoft/callback Microsoft OAuth callback No

4.2 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.3 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.4 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.5 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.6 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.7 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.8 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.9 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)

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)
  • Registriraj se (Sign Up)
  • Email adresa (Email Address)
  • Lozinka (Password)

6. Implementation Milestones

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 authentication and basic layout

Backend Tasks:

  1. Implement Prisma schema (User model)
  2. Run initial migration
  3. Set up Passport.js with local strategy
  4. Implement session management
  5. Create auth routes (register, login, logout, me)
  6. Set up Google OAuth
  7. Set up Microsoft OAuth
  8. Create auth middleware
  9. Set up CORS and security headers

Frontend Tasks:

  1. Create layout components (Sidebar, Header, Layout)
  2. Set up React Router with routes
  3. Create auth pages (Login, Register)
  4. Implement auth store (Zustand)
  5. Create auth API client
  6. Implement protected routes
  7. Add OAuth buttons

Deliverables:

  • Users can register and login
  • OAuth works (Google + Microsoft)
  • Session persists across page reloads
  • Sidebar navigation works
  • Protected routes redirect to login

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

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

# OAuth - Google
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_CALLBACK_URL=http://localhost:3000/api/auth/google/callback

# OAuth - Microsoft
MICROSOFT_CLIENT_ID=your-microsoft-client-id
MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret
MICROSOFT_CALLBACK_URL=http://localhost:3000/api/auth/microsoft/callback

# 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

  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

// 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
  • Authentication works (email + Google + Microsoft)
  • 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. Set up OAuth credentials (Google Cloud Console, Microsoft Azure)
  3. Prepare MySQL database (local or cloud)
  4. Begin Phase 0: Project Setup
  5. Iterate through phases sequentially
  6. Test continuously during development
  7. Deploy to production server

14. Resources & References

Documentation

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