diff --git a/packages/web/docs/src/components/book-icon.tsx b/packages/web/docs/src/components/book-icon.tsx new file mode 100644 index 000000000..fd4b6c2de --- /dev/null +++ b/packages/web/docs/src/components/book-icon.tsx @@ -0,0 +1,15 @@ +export const BookIcon = (props: { size: number }) => ( + + + +); diff --git a/packages/web/docs/src/components/hero.tsx b/packages/web/docs/src/components/hero.tsx new file mode 100644 index 000000000..08c441d52 --- /dev/null +++ b/packages/web/docs/src/components/hero.tsx @@ -0,0 +1,33 @@ +import { ReactNode } from 'react'; + +export function HeroTitle(props: { children: ReactNode }) { + return ( +

+ {props.children} +

+ ); +} + +export function HeroSubtitle(props: { children: ReactNode }) { + return ( +

+ {props.children} +

+ ); +} + +export function HeroLinks(props: { children: ReactNode }) { + return ( +
+ {props.children} +
+ ); +} + +export function Hero(props: { children: ReactNode }) { + return ( +
+
{props.children}
+
+ ); +} diff --git a/packages/web/docs/src/components/highlights.tsx b/packages/web/docs/src/components/highlights.tsx new file mode 100644 index 000000000..551ea647c --- /dev/null +++ b/packages/web/docs/src/components/highlights.tsx @@ -0,0 +1,54 @@ +import { ReactNode } from 'react'; +import Link from 'next/link'; +import clsx from 'clsx'; +import { BookIcon } from './book-icon'; + +const classes = { + root: clsx('flex flex-1 flex-row gap-6 md:flex-col lg:flex-row'), + content: clsx('flex flex-col text-black dark:text-white'), + title: clsx('text-xl font-semibold'), + description: clsx('text-gray-600 dark:text-gray-400'), +}; + +export function HighlightTextLink(props: { href: string; children: ReactNode }) { + return ( + + {props.children} + + ); +} + +export function Highlights(props: { + items: Array<{ + title: string; + description: ReactNode; + icon: ReactNode; + documentationLink: string; + }>; +}) { + return ( +
+ {props.items.map(({ title, description, icon, documentationLink }, i) => ( +
+
{icon}
+
+

{title}

+

{description}

+ +
+ +
+
Learn more
+ +
+
+ ))} +
+ ); +} diff --git a/packages/web/docs/src/components/landing-page.tsx b/packages/web/docs/src/components/landing-page.tsx index 172ee18fe..844cca267 100644 --- a/packages/web/docs/src/components/landing-page.tsx +++ b/packages/web/docs/src/components/landing-page.tsx @@ -1,14 +1,15 @@ -import { ReactElement, ReactNode, useCallback, useState } from 'react'; -import Head from 'next/head'; +import { ReactElement, ReactNode } from 'react'; import Image, { StaticImageData } from 'next/image'; import Link from 'next/link'; import clsx from 'clsx'; -import CountUp from 'react-countup'; -import { FiGithub, FiGlobe, FiRadio, FiServer } from 'react-icons/fi'; +import { FiGithub, FiGlobe, FiPackage, FiServer, FiTruck } from 'react-icons/fi'; import * as Tooltip from '@radix-ui/react-tooltip'; -import { useMounted } from '@theguild/components'; +import { BookIcon } from './book-icon'; +import { Hero, HeroLinks, HeroSubtitle, HeroTitle } from './hero'; +import { Highlights, HighlightTextLink } from './highlights'; +import { Page } from './page'; import { Pricing } from './pricing'; -import cicdImage from '../../public/any-ci-cd.svg'; +import { StatsItem, StatsList } from './stats'; import monitoringImage from '../../public/features/new/monitoring-preview.png'; import schemaHistoryImage from '../../public/features/new/schema-history.png'; @@ -28,58 +29,6 @@ const classes = { description: clsx('text-gray-600 dark:text-gray-400'), }; -const BookIcon = (props: { size: number }) => ( - - - -); - -const CookiesConsent = (): ReactElement => { - const [show, setShow] = useState(() => localStorage.getItem('cookies') !== 'true'); - - const accept = useCallback(() => { - setShow(false); - localStorage.setItem('cookies', 'true'); - }, []); - - if (!show) { - return null; - } - - return ( -
-
-

This website uses cookies to analyze site usage and improve your experience.

-

If you continue to use our services, you are agreeing to the use of such cookies.

-
-
- - Privacy Policy - - -
-
- ); -}; - const gradients: [string, string][] = [ ['#ff9472', '#f2709c'], ['#4776e6', '#8e54e9'], @@ -98,66 +47,44 @@ function pickGradient(i: number): [string, string] { return gradient; } -const renderFeatures = ({ title, description }) => ( +const renderFeatures = ({ + title, + description, + documentationLink, +}: { + title: string; + description: ReactNode; + documentationLink?: string; +}) => (

{title}

{description}

+ {documentationLink ? ( + +
+ +
+
Learn more
+ + ) : null}
); -function Hero() { - return ( -
-
-

- Full Control Over GraphQL -

-

- Prevent breaking changes, monitor performance of your GraphQL API, and manage your API - gateway -

-
- - Start for free - - - Documentation - - - Star on GitHub - -
-
-
- ); -} - function Feature(props: { title: string; description: ReactNode; - highlights?: { - title: string; - description: ReactNode; - icon?: ReactNode; - }[]; - image: StaticImageData; + image?: StaticImageData; + highlights?: { title: string; description: string; documentationLink?: string }[]; gradient: number; documentationLink?: string; flipped?: boolean; }) { - const { title, description, highlights, image, gradient, flipped, documentationLink } = props; + const { title, description, image, gradient, flipped, documentationLink, highlights } = props; const [start, end] = pickGradient(gradient); return ( @@ -171,7 +98,7 @@ function Feature(props: { >

{title} @@ -179,7 +106,7 @@ function Feature(props: {
{description}
{documentationLink ? (
-
Learn more
-
+

) : null} -
- {title} -
+ + {highlights ? ( +
+
{highlights.map(renderFeatures)}
+
+ ) : null} + + {image ? ( +
+ {title} +
+ ) : null} - {Array.isArray(highlights) && highlights.length > 0 && ( -
- {highlights.map(({ title, description, icon }) => ( -
-
{icon}
-
-

{title}

-

{description}

-
-
- ))} -
- )} - - - ); -} - -function StatsItem(props: { label: string; value: number; suffix: string; decimal?: boolean }) { - return ( -
-
- - {props.suffix}+ -
-
- {props.label} -
-
- ); -} - -function Stats() { - return ( -
-
- - - -
); } export function IndexPage(): ReactElement { - const mounted = useMounted(); return ( - - - - -
- - + + + Full Control Over GraphQL + + Prevent breaking changes, monitor performance of your GraphQL API, and manage your API + gateway + + + <> + + Start for free + + + Documentation + + + Star on GitHub + + + + +
+ + + + + + +
-

Push GraphQL schema to the registry and track the history of changes.

-

All your GraphQL services in one place.

+
+
+

Push GraphQL schema to the registry and track the history of changes.

+

All your GraphQL services in one place.

+
+
+ {[ + { + title: 'Version control system for GraphQL', + description: + 'Track every modification of your GraphQL API across different environments, such as staging and production.', + }, + { + title: 'Schema checks', + description: + 'Detect breaking changes and composition errors, prevent them from being deployed.', + }, + { + title: 'Schema Explorer', + description: + 'Navigate through your GraphQL schema and understand which types and fields are referenced from which subgraphs.', + }, + ].map(renderFeatures)} +
} - highlights={[ - { - title: 'Manage your Gateway', - description: 'Connect to Apollo Federation, GraphQL Mesh, Stitching and more.', - icon: , - }, - { - title: 'Global Edge Network', - description: 'Access the registry from any place on earth within milliseconds.', - icon: , - }, - { - title: 'Make it smarter', - description: 'Detect unused parts of Schema thanks to GraphQL analytics.', - icon: , - }, - ]} image={schemaHistoryImage} gradient={0} /> +
+ + Connect to{' '} + + Apollo Federation + + ,{' '} + + GraphQL Mesh + + ,{' '} + + Stitching + {' '} + and more. + + ), + icon: , + documentationLink: '/docs/get-started/apollo-federation', + }, + { + title: 'Global Edge Network', + description: 'Access the registry from any place on earth within milliseconds.', + icon: , + documentationLink: '/docs/features/high-availability-cdn', + }, + { + title: 'Apollo GraphOS alternative', + description: 'GraphQL Hive is a drop-in replacement for Apollo GraphOS.', + icon: , + documentationLink: '/docs/get-started/apollo-federation', + }, + ]} + /> +
@@ -316,7 +278,7 @@ export function IndexPage(): ReactElement { }, { title: 'Overall performance', - description: 'Get a global overview of the usage of your GraphQL API.', + description: 'Get a global overview of the usage of GraphQL API.', }, { title: 'Query performance', @@ -331,35 +293,61 @@ export function IndexPage(): ReactElement { flipped />
-

Maintain your GraphQL API across many teams without concerns.

-
-
- {[ - { - title: 'Prevent Breaking Changes', - description: - 'Combination of Schema Registry and GraphQL Monitoring helps you evolve your GraphQL API.', - }, - { - title: 'Detect unused fields', - description: - 'Helps you understand the coverage of GraphQL schema and safely remove the unused part.', - }, - { - title: 'Alerts and notifications', - description: 'Stay on top of everything with Slack notifications.', - }, - ].map(renderFeatures)} +

Maintain GraphQL API across many teams without concerns.

} - image={cicdImage} gradient={2} + highlights={[ + { + title: 'Prevent Breaking Changes', + description: + 'Combination of Schema Registry and GraphQL Monitoring helps you evolve GraphQL API with confidence.', + documentationLink: '/docs/management/targets#conditional-breaking-changes', + }, + { + title: 'Detect unused fields', + description: + 'Helps you understand the coverage of GraphQL schema and safely remove the unused part.', + documentationLink: '/docs/features/usage-reporting', + }, + { + title: 'Schema Policy', + description: + 'Lint, verify, and enforce best practices across the entire federated graph.', + documentationLink: '/docs/features/schema-policy', + }, + ]} /> +
+ , + documentationLink: '/docs/integrations/ci-cd#github-check-suites', + }, + { + title: 'Works with every CI/CD', + description: 'Connect GraphQL Hive CLI to CI/CD of your choice.', + icon: , + documentationLink: '/docs/integrations/ci-cd', + }, + { + title: 'On-premise or Cloud', + description: + 'GraphQL Hive is MIT licensed, you can host it on your own infrastructure.', + icon: , + documentationLink: '/docs/self-hosting/get-started', + }, + ]} + /> +
@@ -399,8 +387,7 @@ export function IndexPage(): ReactElement {
-
- {mounted && } +
); } diff --git a/packages/web/docs/src/components/page.tsx b/packages/web/docs/src/components/page.tsx new file mode 100644 index 000000000..8035124e1 --- /dev/null +++ b/packages/web/docs/src/components/page.tsx @@ -0,0 +1,54 @@ +import { ReactElement, ReactNode, useCallback, useState } from 'react'; +import Head from 'next/head'; +import { useMounted } from '@theguild/components'; + +const CookiesConsent = (): ReactElement => { + const [show, setShow] = useState(() => localStorage.getItem('cookies') !== 'true'); + + const accept = useCallback(() => { + setShow(false); + localStorage.setItem('cookies', 'true'); + }, []); + + if (!show) { + return null; + } + + return ( +
+
+

This website uses cookies to analyze site usage and improve your experience.

+

If you continue to use our services, you are agreeing to the use of such cookies.

+
+
+ + Privacy Policy + + +
+
+ ); +}; + +export function Page(props: { children: ReactNode }) { + const mounted = useMounted(); + + return ( + <> + + + + +
{props.children}
+ {mounted && } + + ); +} diff --git a/packages/web/docs/src/components/stats.tsx b/packages/web/docs/src/components/stats.tsx new file mode 100644 index 000000000..a2cded36b --- /dev/null +++ b/packages/web/docs/src/components/stats.tsx @@ -0,0 +1,38 @@ +import { ReactNode } from 'react'; +import CountUp from 'react-countup'; + +export function StatsItem(props: { + label: string; + value: number; + suffix: string; + decimal?: boolean; +}) { + return ( +
+
+ + {props.suffix}+ +
+
+ {props.label} +
+
+ ); +} + +export function StatsList(props: { children: ReactNode }) { + return ( +
+ {props.children} +
+ ); +}