Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8a6eac8
refactor(admin): migrate dashboard page layout to design-system templ…
bychrisr Feb 12, 2026
5728a96
fix(ds/header): align admin header with standard netflix top navigation
bychrisr Feb 12, 2026
1b9b3c7
refactor(ds/admin): introduce full-width admin page header component
bychrisr Feb 12, 2026
9a3121b
refactor(ds): unify public/private dashboards with standard header an…
bychrisr Feb 12, 2026
e87178e
fix(web/header): keep only course navigation items in learner hero
bychrisr Feb 12, 2026
f710b7d
refactor(web/catalog): render event-driven hero banner with real cata…
bychrisr Feb 12, 2026
8318d92
feat(web/catalog): overlay hero banner on autoplay background video
bychrisr Feb 12, 2026
ef52e76
feat(api): add highlight video field for events
bychrisr Feb 12, 2026
b657fc1
feat(apps): wire event highlight video in admin and catalog
bychrisr Feb 12, 2026
1287319
feat(design-system): implement resilient catalog hero video and legac…
bychrisr Feb 12, 2026
3c082a9
feat(api): include lesson video metadata in public catalog
bychrisr Feb 12, 2026
defdcbf
feat(web): align catalog hero layout with netflix composition
bychrisr Feb 12, 2026
11f927a
refactor(design-system): tune home header spacing typography and hove…
bychrisr Feb 12, 2026
ca0977d
feat(design-system): polish learner catalog hero, header and rail fid…
bychrisr Feb 12, 2026
8170bea
chore(api): update mock public event highlight video url
bychrisr Feb 12, 2026
503982b
feat(design-system): add hero audio toggle and align rating dock
bychrisr Feb 12, 2026
27589fb
fix(design-system): stabilize hero 16:9 layout and rail overflow
bychrisr Feb 12, 2026
cdd8f3b
style(design-system): crop desktop hero video and add bottom transiti…
bychrisr Feb 12, 2026
ef97627
fix(design-system): polish hero action buttons and pt-BR labels
bychrisr Feb 12, 2026
afab6a6
feat(web): route hero watch action to first event lesson
bychrisr Feb 12, 2026
401055c
feat(api): add event and lesson descriptions to catalog data
bychrisr Feb 12, 2026
2092e8d
feat(admin): support short and long descriptions in dashboard forms
bychrisr Feb 12, 2026
2cb1178
feat(web): polish hero rail cards and lesson navigation behavior
bychrisr Feb 12, 2026
f33f2b3
fix(design-system): remove unsupported youtube autoplay playerVar
bychrisr Feb 12, 2026
c2a8b23
test(e2e): update smoke selectors for new catalog and event forms
bychrisr Feb 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 146 additions & 97 deletions apps/admin/src/pages/DashboardPage.jsx

Large diffs are not rendered by default.

67 changes: 63 additions & 4 deletions apps/web/src/pages/CatalogPage.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { useEffect, useMemo, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Card, LearnerCatalogPage, Text } from '@flix/design-system/components';
import { fetchCatalog } from '../services/api.js';

const eventKey = (eventSlug) => `flix.web.eventKey.${eventSlug}`;

const getLessonThumbnail = (lesson) => {
if (lesson?.videoProvider === 'youtube' && lesson?.videoId) {
return `https://i.ytimg.com/vi/${lesson.videoId}/hqdefault.jpg`;
}
if (lesson?.videoProvider === 'vimeo' && lesson?.videoId) {
return `https://vumbnail.com/${lesson.videoId}.jpg`;
}
return '';
};

export const CatalogPage = () => {
const { eventSlug } = useParams();
const navigate = useNavigate();
const [catalog, setCatalog] = useState(null);
const [accessKey, setAccessKey] = useState(() =>
eventSlug ? window.localStorage.getItem(eventKey(eventSlug)) ?? '' : '',
Expand Down Expand Up @@ -47,17 +58,60 @@ export const CatalogPage = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [eventSlug]);

const handleWatchFirstLesson = async () => {
if (!eventSlug) return;

const navigateToFirstLesson = (items = []) => {
const firstLesson = items.find((lesson) => lesson?.slug);
if (!firstLesson?.slug) return false;
navigate(`/events/${eventSlug}/lessons/${firstLesson.slug}`);
return true;
};

if (navigateToFirstLesson(catalog?.catalog?.items ?? [])) {
return;
}

setLoading(true);
setError('');

try {
const body = await fetchCatalog(eventSlug, accessKey || undefined);
setCatalog(body);
if (accessKey) {
window.localStorage.setItem(eventKey(eventSlug), accessKey);
}

if (!navigateToFirstLesson(body?.catalog?.items ?? [])) {
setError('Nenhuma aula disponível para este evento.');
}
} catch (caughtError) {
setCatalog(null);
setError(caughtError?.message ?? 'Catalog request failed');
} finally {
setLoading(false);
}
};

const releasedItems = releasedLessons.map((lesson) => ({
id: lesson.id,
title: lesson.title,
description: lesson.description ?? '',
status: lesson.status,
imageUrl: getLessonThumbnail(lesson),
presetIconName: 'presetRecentlyAdded',
onClick: () => navigate(`/events/${eventSlug}/lessons/${lesson.slug}`),
action: <Link to={`/events/${eventSlug}/lessons/${lesson.slug}`}>Open lesson</Link>,
}));

const gatedItems = gatedLessons.map((lesson) => ({
id: lesson.id,
title: lesson.title,
description: lesson.description ?? '',
status: lesson.status,
imageUrl: getLessonThumbnail(lesson),
presetIconName: lesson.status === 'locked' ? 'presetTop10' : 'presetLeavingSoon',
onClick: () => navigate(`/events/${eventSlug}/lessons/${lesson.slug}`),
action: <Link to={`/events/${eventSlug}/lessons/${lesson.slug}`}>Details</Link>,
}));

Expand All @@ -77,10 +131,15 @@ export const CatalogPage = () => {
eventSlug={eventSlug ?? ''}
accessKey={accessKey}
onAccessKeyChange={setAccessKey}
onLoad={loadCatalog}
onLoad={handleWatchFirstLesson}
loading={loading}
heroTitle={catalog?.event?.title ?? 'Flix'}
heroDescription={catalog?.event?.description ?? ''}
eventVisibility={catalog?.event?.visibility}
eventTitle={catalog?.event?.title ?? ''}
eventDescription={catalog?.event?.longDescription ?? catalog?.event?.description ?? ''}
highlightVideoUrl={catalog?.event?.highlightVideoUrl ?? ''}
heroTitle={catalog?.event?.hero?.title ?? catalog?.event?.title ?? 'Flix'}
heroDescription={catalog?.event?.shortDescription ?? catalog?.event?.hero?.subtitle ?? catalog?.event?.description ?? ''}
heroCtaLabel={catalog?.event?.hero?.ctaText ?? 'Load catalog'}
releasedItems={releasedItems}
gatedItems={gatedItems}
/>
Expand Down
6 changes: 5 additions & 1 deletion e2e/tests/admin-smoke.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ test('admin happy path smoke: login and create event', async ({ page }) => {
const randomSuffix = `${Date.now()}`;
await page.getByPlaceholder('Event title').fill(`E2E Event ${randomSuffix}`);
await page.getByRole('textbox', { name: 'Slug', exact: true }).fill(`e2e-event-${randomSuffix}`);
await page.getByPlaceholder('Description').fill('Smoke test event created via browser runtime');
await page
.getByPlaceholder('Description', { exact: true })
.fill('Smoke test event created via browser runtime');
await page.getByPlaceholder('Short description (Hero)').fill('Short hero copy for smoke validation');
await page.getByPlaceholder('Long description').fill('Long description for smoke validation');
await page.getByPlaceholder('Hero title').fill(`Hero ${randomSuffix}`);
await page.getByPlaceholder('Hero subtitle').fill('Subtitle for smoke validation');
await page.getByPlaceholder('Hero CTA text').fill('Start now');
Expand Down
20 changes: 3 additions & 17 deletions e2e/tests/learner-smoke.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,10 @@ import { expect, test } from '@playwright/test';
test('learner happy path smoke: catalog to playback', async ({ page }) => {
await page.goto('/events/flix-mvp-launch-event');

await expect(page.getByRole('heading', { name: 'Flix', exact: true })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Catalog Access' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Assistir Aula' })).toBeVisible();
await expect(page.getByText('Próximas Aulas')).toBeVisible();

const lessonLink = page.locator('a[href*="/events/flix-mvp-launch-event/lessons/"]').first();
for (let attempt = 1; attempt <= 3; attempt += 1) {
await page.getByRole('button', { name: 'Load catalog' }).click();
try {
await expect(lessonLink).toBeVisible({ timeout: 8_000 });
break;
} catch (error) {
if (attempt === 3) {
throw error;
}
await page.waitForTimeout(1_000);
}
}

await lessonLink.click();
await page.getByRole('button', { name: 'Assistir Aula' }).click();
await expect(page.getByRole('heading', { name: 'Lesson Playback' })).toBeVisible();

await expect(page.getByRole('heading', { name: 'Lesson Materials' })).toBeVisible();
Expand Down
Loading