A React front-end prototype inspired by Dailymotion: home feed, search, and video watch experience with vertical/horizontal orientation, EN/FR i18n, and dark/light theme.
Live demo: dailymotion-interview.vercel.app
This project implements two main routes:
- Home / search (
/) — discovery feed (“For You”), hero search, topic filters, user-aware search (handle matching + relevance ranking), orientation toggle, and load-more pagination. - Watch (
/videos/:videoId) — embedded player, author info, expandable description, comments, and related videos (layout adapts to vertical vs horizontal video).
The UI is built from scratch as a deliberate choice (no component library beyond Radix primitives for accessible menus, toasts, and tooltips). Data comes from the Dailymotion public API, with a few client-side layers on top where the API alone does not match the desired UX.
| Layer | Choice |
|---|---|
| UI | React 19, TypeScript |
| Build | Vite 5 |
| Routing | React Router 7 |
| Styling | Co-located CSS, design tokens (tokens.css), CSS variables for theming |
| Accessibility | Radix UI (Popover, Toast, Tooltip) |
| Icons | react-icons |
| i18n | JSON locale files bundled at build time (en / fr) |
| Tests | Vitest (unit), Playwright (e2e) |
| Lint | ESLint 9 + TypeScript ESLint |
Requirements: Node.js 20+ and npm.
npm install
npm run devOpen http://localhost:5173.
| Command | Description |
|---|---|
npm run dev |
Development server (includes comments proxy — see below) |
npm run build |
Type-check + production build |
npm run preview |
Preview production build locally |
npm run test |
Run unit tests once |
npm run test:watch |
Run unit tests in watch mode |
npm run test:e2e |
Run Playwright end-to-end tests |
npm run test:e2e:ui |
Run Playwright with interactive UI |
npm run lint |
ESLint |
No environment variables are required; the app calls the public Dailymotion API directly from the browser.
Dailymotion’s legacy /video/{id}/comments endpoint often returns empty results for newer “Neon” comments. In dev and preview, a Vite plugin (vite/dmCommentsPlugin.ts) fetches the watch page HTML server-side and exposes parsed comments at /api/dm/comments/:videoId.
In a static production deploy (dist/ only), that middleware is not available — comments fall back to the legacy API and may appear empty on some videos. This is documented as a known tradeoff below.
src/
├── app/ Route definitions
├── pages/ SearchPage, VideoPage (+ hooks)
├── components/ UI by domain (layout, home, video, search)
├── hooks/ Shared hooks (e.g. desktop carousel nav)
├── constants/ Layout breakpoints + E2E test IDs
├── lib/ API client, feed curation, search ranking
├── utils/ Pure helpers (video, comments parsing, text)
├── types/ Shared TypeScript types
├── i18n/ Provider + locale JSON (en/fr)
├── theme/ Dark/light mode
└── styles/ Global styles + design tokens (spacing, z-index, breakpoints)
e2e/ Playwright specs
test-fixtures/ Captured HTML for unit tests
vite/ Vite plugins (comments proxy)
Shared layout values live in src/styles/tokens.css (spacing, z-index scale, radii) and src/constants/layout.ts (901px desktop breakpoint for JS media queries and image sizes). CSS uses (width >= 901px) / (width <= 900px) consistently. Component styles are co-located — e.g. VideoPlayer.css and VideoRelated.css own their own classes instead of VideoPage.css styling child components.
All Dailymotion fetching and response normalization live in src/lib/ (dailymotionApi.ts, discoveryFeed.ts). Components and hooks consume typed data — they do not build URLs or parse dotted API field names.
Search goes beyond a single API call:
- User handle priority — resolves
@username, partial matches (e.g.nozman→ Dr Nozman), merges creator videos with keyword results. - Client-side relevance — when sort is “Most relevant”, results are re-ranked using title, description, and owner fields (
lib/searchRanking.ts). - Orientation filter — API returns both formats; vertical/horizontal filtering happens client-side via
aspect_ratio/ dimensions.
The raw “trending” public Dailymotion API seems to skew toward low-quality vertical channels. discoveryFeed.ts applies client-side curation: exclude noisy channels, minimum view thresholds and orientation filter across categories when the feed is thin.
- Pages wire hooks, URL state, and layout.
- Components handle presentation; shared search UI lives under
components/search/. - Hooks on SearchPage (
useSearchCatalog,useDisplayedVideos) isolate fetch/state from render.
Translations are the single source of truth in src/i18n/locales/{en,fr}/*.json, imported at build time (no runtime fetch, no flash of English). Theme uses data-theme on <html> with CSS variables.
| Decision | Why | Cost |
|---|---|---|
| No React Query / SWR | Scope fits local useState + useEffect; fewer dependencies |
Manual loading/error boilerplate; no cache/dedup |
| Client-side orientation filter | API has no reliable orientation param for all endpoints | Extra API rows fetched, filtered in browser |
| Client-side feed curation | Trending quality varies; no backend to curate | Heuristic tuning; not deterministic across time |
| HTML comment scraping | Legacy comments API empty for Neon content | Fragile if DOM changes but is only a problem on public API |
| Plain CSS vs CSS modules | Faster iteration, matches interview scope and easier to read code | Global class names; requires discipline |
| Local-only interactions | Like, save, follow are UI demos because of no API key | State resets on navigation |
| Strict TypeScript | Catches bad API shapes early | Slightly more typing around Dailymotion’s dotted fields |
Tests focus on code we own and failures that would be silent (wrong order, empty comments, bad parsing) — not on “does React render”.
| Area | File | Why |
|---|---|---|
| Comment HTML parsing | utils/parseWatchPageComments.test.ts |
DOM structure can change; fixture-based |
| Comment sort + French dates | utils/commentSort.test.ts |
Custom Top/Recent logic |
| API → watch fallback | lib/getVideoComments.test.ts |
Dual-fetch + abort path |
| Search relevance | lib/searchRanking.test.ts |
Client ranking has no API contract |
| Orientation + dedup | utils/video.test.ts |
User-facing filter + load-more merge |
| Description sanitization | utils/videoText.test.ts |
External HTML in descriptions |
Intentionally not tested (unit): full page smoke tests, Radix/component snapshots, live Dailymotion API calls in Vitest, discoveryFeed curation (depends on live trending data — would need heavy mocking for little confidence).
npm test # run once
npm run test:watch # watch modeFixtures live in test-fixtures/ (captured watch-page HTML for comment parsing).
Two specs cover the core journeys against a running dev server and the live Dailymotion API. Selectors use shared data-testid values from src/constants/testIds.ts.
| Spec | Flow |
|---|---|
e2e/happy-path.spec.ts |
Home feed → first video → watch page |
e2e/search-path.spec.ts |
Hero search (music) → results → watch page |
# First time only: install browser binaries
npx playwright install chromium
# Linux/WSL: install OS dependencies if the browser fails to launch
# npx playwright install-deps chromium
npm run test:e2ePlaywright starts npm run dev automatically (reuseExistingServer locally if one is already running). Requires network access for API calls.
Given more time or a production target:
- Comments — Move watch-page fetch to a small serverless/edge function so production gets the same comment path as dev.
- Data fetching — TanStack Query for caching, dedup, stale-while-revalidate, and simpler pagination.
- Search load-more — Paginate matched-user videos (carousel currently shows page 1 only).
- Feed curation — Extract pure curation functions from
discoveryFeed.tsand unit-test them; tune with recorded API fixtures. - Error boundaries — Graceful error UI instead of white screen on route/render failures.
- More E2E — Search flow, orientation toggle, i18n switch; run against Vercel preview in CI.
- CI — GitHub Actions:
lint,test,test:e2e,buildon every push. - Skeleton UI - Add skeleton UI to loading components so that layout does not shift when fetching content.
Built as a front-end technical exercise for Dailymotion.