diff --git a/.tmp/proof/landing-hero.png b/.tmp/proof/landing-hero.png new file mode 100644 index 0000000..f753509 Binary files /dev/null and b/.tmp/proof/landing-hero.png differ diff --git a/packages/app/public/portal-showcase.png b/packages/app/public/portal-showcase.png new file mode 100644 index 0000000..317ea3a Binary files /dev/null and b/packages/app/public/portal-showcase.png differ diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index ae1f34c..fe40ca1 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -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, @@ -202,6 +203,7 @@ const routes = ( } /> } /> } /> + } /> ); diff --git a/packages/app/src/components/landing/LandingPage.test.tsx b/packages/app/src/components/landing/LandingPage.test.tsx new file mode 100644 index 0000000..6381afe --- /dev/null +++ b/packages/app/src/components/landing/LandingPage.test.tsx @@ -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(); + + 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(); + + 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(); + + 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(); + + 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(); + }); +}); diff --git a/packages/app/src/components/landing/LandingPage.tsx b/packages/app/src/components/landing/LandingPage.tsx new file mode 100644 index 0000000..ddaba5a --- /dev/null +++ b/packages/app/src/components/landing/LandingPage.tsx @@ -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 ( + + + + + Everything your developers need, in one portal + + + 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. + + + + + + + + + + + The plugin showcase running live in this portal. + + + + + ); +}; diff --git a/packages/app/src/components/landing/index.ts b/packages/app/src/components/landing/index.ts new file mode 100644 index 0000000..a91a96a --- /dev/null +++ b/packages/app/src/components/landing/index.ts @@ -0,0 +1 @@ +export { LandingPage } from './LandingPage';