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-hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/app/public/portal-showcase.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { ScorecardPage } from './components/scorecard';
import { PulseCheckPage } from './components/pulse-check';
import { ExplorePage } from './components/explore';
import { ShowcasePage } from './components/showcase';
import { LandingPage } from './components/landing';

const app = createApp({
apis,
Expand Down Expand Up @@ -202,6 +203,7 @@ const routes = (
<Route path="/pulse-check" element={<PulseCheckPage />} />
<Route path="/explore" element={<ExplorePage />} />
<Route path="/showcase" element={<ShowcasePage />} />
<Route path="/landing" element={<LandingPage />} />
</FlatRoutes>
);

Expand Down
46 changes: 46 additions & 0 deletions packages/app/src/components/landing/LandingPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { screen } from '@testing-library/react';
import { renderInTestApp } from '@backstage/test-utils';
import { LandingPage } from './LandingPage';

describe('LandingPage', () => {
it('renders the hero headline and value prop', async () => {
await renderInTestApp(<LandingPage />);

expect(
screen.getByText('Everything your developers need, in one portal'),
).toBeInTheDocument();
expect(
screen.getByText(/Software templates that publish to GitHub/),
).toBeInTheDocument();
expect(
screen.getByText(/Try it as a guest, no account needed/),
).toBeInTheDocument();
});

it('links the primary call to action into the showcase', async () => {
await renderInTestApp(<LandingPage />);

const cta = screen.getByRole('link', { name: 'Explore the showcase' });
expect(cta).toHaveAttribute('href', '/showcase');
});

it('links the secondary call to action into the catalog', async () => {
await renderInTestApp(<LandingPage />);

const cta = screen.getByRole('link', { name: 'Browse the catalog' });
expect(cta).toHaveAttribute('href', '/catalog');
});

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

expect(
screen.getByAltText(
'The plugin showcase in this portal, listing templates, GitHub integration and the answer engine',
),
).toBeInTheDocument();
expect(
screen.getByText('The plugin showcase running live in this portal.'),
).toBeInTheDocument();
});
});
113 changes: 113 additions & 0 deletions packages/app/src/components/landing/LandingPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import { styled, useTheme } from '@mui/material/styles';
import { Link as RouterLink } from 'react-router-dom';
import { Page, Content } from '@backstage/core-components';

const HeroSection = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(3),
padding: theme.spacing(8, 4),
borderRadius: theme.spacing(1),
border: `3px solid ${theme.palette.text.primary}`,
boxShadow: `10px 10px 0 ${theme.palette.text.primary}`,
backgroundColor: theme.palette.background.paper,
[theme.breakpoints.down('md')]: {
padding: theme.spacing(5, 2),
boxShadow: `6px 6px 0 ${theme.palette.text.primary}`,
},
}));

const ScreenshotFrame = styled('figure')(({ theme }) => ({
margin: 0,
padding: theme.spacing(1.5),
borderRadius: theme.spacing(1),
border: `3px solid ${theme.palette.text.primary}`,
boxShadow: `10px 10px 0 ${theme.palette.text.primary}`,
backgroundColor: theme.palette.background.paper,
[theme.breakpoints.down('md')]: {
boxShadow: `6px 6px 0 ${theme.palette.text.primary}`,
},
}));

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

return (
<Page themeId="tool">
<Content>
<HeroSection data-testid="landing-hero">
<Typography
variant="h2"
sx={{
fontWeight: 800,
fontSize: { xs: '2.25rem', md: '3.25rem' },
lineHeight: 1.1,
maxWidth: 800,
}}
>
Everything your developers need, in one portal
</Typography>
<Typography
variant="h6"
color="text.secondary"
sx={{ fontWeight: 400, maxWidth: 640 }}
>
Software templates that publish to GitHub, a catalog that keeps
services findable, and an answer engine grounded in your own docs.
Try it as a guest, no account needed.
</Typography>
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
<Button
component={RouterLink}
to="/showcase"
variant="contained"
size="large"
sx={{
fontWeight: 700,
borderWidth: 2,
boxShadow: `4px 4px 0 ${theme.palette.text.primary}`,
'&:hover': { borderWidth: 2 },
}}
>
Explore the showcase
</Button>
<Button
component={RouterLink}
to="/catalog"
variant="outlined"
size="large"
sx={{ fontWeight: 700, borderWidth: 2, '&:hover': { borderWidth: 2 } }}
>
Browse the catalog
</Button>
</Box>
</HeroSection>

<ScreenshotFrame data-testid="landing-screenshot">
<Box
component="img"
src="/portal-showcase.png"
alt="The plugin showcase in this portal, listing templates, GitHub integration and the answer engine"
sx={{
display: 'block',
width: '100%',
height: 'auto',
borderRadius: theme.spacing(0.5),
}}
/>
<Typography
component="figcaption"
variant="body2"
color="text.secondary"
sx={{ mt: 1.5 }}
>
The plugin showcase running live in this portal.
</Typography>
</ScreenshotFrame>
</Content>
</Page>
);
};
1 change: 1 addition & 0 deletions packages/app/src/components/landing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LandingPage } from './LandingPage';
Loading