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
Binary file added .tmp/proof/landing-highlights.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions packages/app/src/components/landing/LandingPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,51 @@ describe('LandingPage', () => {
expect(cta).toHaveAttribute('href', '/catalog');
});

it('renders the four feature highlights', async () => {
await renderInTestApp(<LandingPage />);

expect(screen.getByText('What you can try')).toBeInTheDocument();
expect(screen.getByText('Software templates')).toBeInTheDocument();
expect(screen.getByText('Software catalog')).toBeInTheDocument();
expect(screen.getByText('GitHub integration')).toBeInTheDocument();
expect(screen.getByText('Answer engine')).toBeInTheDocument();
expect(
screen.getByText(/Search the catalog and TechDocs in one place/),
).toBeInTheDocument();
});

it('links each highlight to its live surface', async () => {
await renderInTestApp(<LandingPage />);

expect(
screen.getByRole('link', { name: 'Use a template' }),
).toHaveAttribute('href', '/self-service');
expect(
screen.getByRole('link', { name: 'Browse services' }),
).toHaveAttribute('href', '/catalog');
expect(
screen.getByRole('link', { name: 'See GitHub integration' }),
).toHaveAttribute('href', '/showcase');
expect(
screen.getByRole('link', { name: 'Search catalog and docs' }),
).toHaveAttribute('href', '/search');
});

it('renders the footer with a second path into the demo', async () => {
await renderInTestApp(<LandingPage />);

expect(screen.getByText('Ready to look around?')).toBeInTheDocument();
expect(
screen.getByText(/The demo runs as a guest with read-only access/),
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'Start with the showcase' }),
).toHaveAttribute('href', '/showcase');
expect(
screen.getByRole('link', { name: 'Open the catalog' }),
).toHaveAttribute('href', '/catalog');
});

it('shows the portal screenshot with alt text', async () => {
await renderInTestApp(<LandingPage />);

Expand Down
117 changes: 117 additions & 0 deletions packages/app/src/components/landing/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,43 @@ const ScreenshotFrame = styled('figure')(({ theme }) => ({
},
}));

const FeatureCard = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1.5),
padding: theme.spacing(3),
borderRadius: theme.spacing(1),
border: `3px solid ${theme.palette.text.primary}`,
backgroundColor: theme.palette.background.paper,
}));

const highlights = [
{
title: 'Software templates',
body: 'Start a service, docs site or AI worker from a template that publishes to GitHub and registers itself in the catalog.',
link: '/self-service',
linkLabel: 'Use a template',
},
{
title: 'Software catalog',
body: 'Every service, API and team in one searchable place, with ownership and lifecycle kept next to the code.',
link: '/catalog',
linkLabel: 'Browse services',
},
{
title: 'GitHub integration',
body: 'Templates open repositories and pull requests, and the catalog stays in sync with what is actually on GitHub.',
link: '/showcase',
linkLabel: 'See GitHub integration',
},
{
title: 'Answer engine',
body: 'Search the catalog and TechDocs in one place. The showcase explains how an answer engine can extend this search foundation.',
link: '/search',
linkLabel: 'Search catalog and docs',
},
];

export const LandingPage = () => {
const theme = useTheme();

Expand Down Expand Up @@ -107,6 +144,86 @@ export const LandingPage = () => {
The plugin showcase running live in this portal.
</Typography>
</ScreenshotFrame>

<Box
component="section"
data-testid="landing-highlights"
sx={{ mt: 6 }}
>
<Typography variant="h4" sx={{ fontWeight: 800, mb: 3 }}>
What you can try
</Typography>
<Box
sx={{
display: 'grid',
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
gap: 3,
}}
>
{highlights.map(highlight => (
<FeatureCard key={highlight.title}>
<Typography variant="h5" sx={{ fontWeight: 700 }}>
{highlight.title}
</Typography>
<Typography color="text.secondary" sx={{ flexGrow: 1 }}>
{highlight.body}
</Typography>
<Button
component={RouterLink}
to={highlight.link}
variant="text"
sx={{
alignSelf: 'flex-start',
fontWeight: 700,
px: 0,
'&:hover': { backgroundColor: 'transparent' },
}}
>
{highlight.linkLabel}
</Button>
</FeatureCard>
))}
</Box>
</Box>

<Box
component="footer"
data-testid="landing-footer"
sx={{
mt: 6,
pt: 4,
borderTop: `3px solid ${theme.palette.text.primary}`,
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Typography variant="h5" sx={{ fontWeight: 700 }}>
Ready to look around?
</Typography>
<Typography color="text.secondary" sx={{ maxWidth: 640 }}>
The demo runs as a guest with read-only access, so you can open
every page without signing up.
</Typography>
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
<Button
component={RouterLink}
to="/showcase"
variant="contained"
sx={{ fontWeight: 700 }}
>
Start with the showcase
</Button>
<Button
component={RouterLink}
to="/catalog"
variant="outlined"
sx={{ fontWeight: 700, borderWidth: 2, '&:hover': { borderWidth: 2 } }}
>
Open the catalog
</Button>
</Box>
</Box>
</Content>
</Page>
);
Expand Down
Loading