Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions apps/web/e2e-dev/landing-navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,35 @@ const landingAnchors = [
{ label: 'Sản phẩm', id: 'flow' },
{ label: 'AI có kiểm chứng', id: 'intelligence' },
{ label: 'Chế độ dữ liệu', id: 'modes' },
{ label: 'Bảng giá', id: 'pricing' },
] as const;

test('landing navigation reveals and offsets each destination [WEB-013/WEB-014]', async ({
page,
}) => {
await page.goto('/vi-VN');

await expect
.poll(() =>
page
.locator('.interactive-surface')
.evaluateAll((surfaces) =>
surfaces.every(
(surface) => surface.querySelectorAll(':scope > .surface-glow').length === 1,
),
),
)
.toBe(true);

const headerHeight = await page
.locator('[data-header]')
.evaluate((element) => Math.round(element.getBoundingClientRect().height));
const visitedDestinationIds: string[] = [];

for (const { label, id } of landingAnchors) {
await page.getByRole('link', { name: label, exact: true }).click();
await expect(page).toHaveURL(new RegExp(`#${id}$`));
visitedDestinationIds.push(id);

const destination = page.locator(`#${id}`);
await expect(destination.locator('[data-reveal]').first()).toHaveClass(/is-visible/);
Expand All @@ -26,6 +41,16 @@ test('landing navigation reveals and offsets each destination [WEB-013/WEB-014]'
destination.evaluate((element) => Math.round(element.getBoundingClientRect().top)),
)
.toBeGreaterThanOrEqual(headerHeight);

for (const visitedId of visitedDestinationIds) {
await expect
.poll(() =>
page
.locator(`#${visitedId} [data-reveal]`)
.evaluateAll((items) => items.every((item) => item.classList.contains('is-visible'))),
)
.toBe(true);
}
}
});

Expand Down
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"scripts": {
"browser:install:ci": "playwright install --with-deps chromium",
"budget": "node scripts/check-bundle-budget.mjs",
"build": "tsc --build && vite build && corepack pnpm budget",
"build": "tsc --build && vite build && pnpm budget",
"dev": "vite",
"preview": "vite preview",
"test": "vitest run",
"test:e2e": "corepack pnpm test:e2e:preview && corepack pnpm test:e2e:dev",
"test:e2e": "pnpm test:e2e:preview && pnpm test:e2e:dev",
"test:e2e:dev": "playwright test --config playwright.dev.config.ts",
"test:e2e:preview": "playwright test",
"test:watch": "vitest",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const browserChannel = process.env['PLAYWRIGHT_BROWSER_CHANNEL'];
const browser = browserChannel === 'chrome' ? { channel: 'chrome' as const } : {};
const previewCommand =
process.env['PLAYWRIGHT_SKIP_BUILD'] === 'true'
? `corepack pnpm preview --host 127.0.0.1 --port ${previewPort} --strictPort`
: `corepack pnpm build && corepack pnpm preview --host 127.0.0.1 --port ${previewPort} --strictPort`;
? `pnpm preview --host 127.0.0.1 --port ${previewPort} --strictPort`
: `pnpm build && pnpm preview --host 127.0.0.1 --port ${previewPort} --strictPort`;

export default defineConfig({
timeout: 30_000,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/playwright.dev.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineConfig({
trace: 'retain-on-failure',
},
webServer: {
command: `corepack pnpm dev --host 127.0.0.1 --port ${devPort} --strictPort`,
command: `pnpm dev --host 127.0.0.1 --port ${devPort} --strictPort`,
env: { VITE_DATABREEZE_DEMO_MODE: 'true' },
reuseExistingServer: false,
timeout: 120_000,
Expand Down
16 changes: 11 additions & 5 deletions apps/web/src/features/landing/landing-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export function LandingPage({
}),
[locale],
);
// Keep the innerHTML prop stable while only the route hash changes. The
// landing script progressively adds reveal state and interaction layers to
// this DOM; replacing it on every navbar jump would discard that state.
const markupProperty = useMemo(() => ({ __html: markup }), [markup]);

useEffect(() => {
if (import.meta.env.MODE === 'test') return undefined;
Expand Down Expand Up @@ -79,10 +83,12 @@ export function LandingPage({

useEffect(() => {
const root = document.querySelector<HTMLElement>('.teammate-landing-root');
const pricingSection = root?.querySelector<HTMLElement>('[data-pricing-section]');
if (root === null || pricingSection === null) return;
if (root === null) return;

const pricingLocale = pricingSection.dataset.pricingLocale === 'en' ? 'en-US' : 'vi-VN';
const pricingSection = root.querySelector<HTMLElement>('[data-pricing-section]');
if (pricingSection === null) return;

const pricingLocale = pricingSection.dataset['pricingLocale'] === 'en' ? 'en-US' : 'vi-VN';
const formatPricingAmount = (value: number) =>
`${new Intl.NumberFormat(pricingLocale).format(value)} ₫`;

Expand All @@ -92,7 +98,7 @@ export function LandingPage({
const button = target.closest<HTMLButtonElement>('[data-pricing-cycle]');
if (button === null || !root.contains(button)) return;

const cycle = button.dataset.pricingCycle;
const cycle = button.dataset['pricingCycle'];
if (cycle !== 'monthly' && cycle !== 'annual') return;

root.querySelectorAll<HTMLButtonElement>('[data-pricing-cycle]').forEach((item) => {
Expand Down Expand Up @@ -129,7 +135,7 @@ export function LandingPage({
return (
<>
<link data-teammate-landing-stylesheet href={TEAMMATE_LANDING_STYLESHEET} rel="stylesheet" />
<div className="teammate-landing-root" dangerouslySetInnerHTML={{ __html: markup }} />
<div className="teammate-landing-root" dangerouslySetInnerHTML={markupProperty} />
</>
);
}
Expand Down
30 changes: 30 additions & 0 deletions apps/web/test/landing-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,36 @@ describe('landing page stylesheet [WEB-013]', () => {
},
);

it('preserves previously revealed sections across consecutive navbar hash navigation', async () => {
const router = createAppRouter({
authenticationState: 'signed-out',
initialEntries: ['/vi-VN'],
});

render(<ApplicationBoundary router={router} />);
await screen.findByRole('heading', { name: /DataBreeze Dữ liệu biết cất lời\./u });

await router.navigate('/vi-VN#flow');
const flowHeading = await waitFor(() => {
const heading = document.querySelector<HTMLElement>('#flow [data-reveal]');
expect(heading?.classList.contains('is-visible')).toBe(true);
return heading;
});

await router.navigate('/vi-VN#intelligence');
await waitFor(() => {
expect(
document
.querySelector<HTMLElement>('#intelligence [data-reveal]')
?.classList.contains('is-visible'),
).toBe(true);
});

const preservedFlowHeading = document.querySelector<HTMLElement>('#flow [data-reveal]');
expect(preservedFlowHeading).toBe(flowHeading);
expect(preservedFlowHeading?.classList.contains('is-visible')).toBe(true);
});

it('removes the landing stylesheet when navbar routing leaves the page', async () => {
const router = createAppRouter({
authenticationState: 'signed-out',
Expand Down
4 changes: 4 additions & 0 deletions prototypes/databreeze-landing/script.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
(() => {
'use strict';

const landingRoot = document.querySelector('.teammate-landing-root') ?? document.body;
if (landingRoot.dataset.landingEnhanced === 'true') return;
landingRoot.dataset.landingEnhanced = 'true';

const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const header = document.querySelector('[data-header]');
const revealItems = document.querySelectorAll('[data-reveal]');
Expand Down
Loading