diff --git a/packages/web/docs/src/app/compare/gateway/[comparison]/apollo-router.json b/packages/web/docs/src/app/compare/gateway/[comparison]/apollo-router.json new file mode 100644 index 0000000000..0e3a62158c --- /dev/null +++ b/packages/web/docs/src/app/compare/gateway/[comparison]/apollo-router.json @@ -0,0 +1,22 @@ +{ + "name": "Apollo Router", + "logo": "apollo", + "sections": [ + { + "title": "Features Set 1", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "rows": [ + { "feature": "Feature 1", "values": [false, true] }, + { "feature": "Feature 2", "values": [false, true] }, + { "feature": "Feature 3", "values": [false, true] }, + { "feature": "Feature 4", "values": [true, false] }, + { "feature": "Feature ...", "values": [true, false] } + ] + }, + { + "title": "Features Set 2", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "rows": [{ "feature": "Feature 1", "values": [false, true] }] + } + ] +} diff --git a/packages/web/docs/src/app/compare/gateway/[comparison]/page.tsx b/packages/web/docs/src/app/compare/gateway/[comparison]/page.tsx new file mode 100644 index 0000000000..7a09ab71e1 --- /dev/null +++ b/packages/web/docs/src/app/compare/gateway/[comparison]/page.tsx @@ -0,0 +1,141 @@ +import { readdir, readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { CommunitySection } from '#components/community-section'; +import { CompanyTestimonialsSection } from '#components/company-testimonials'; +import { ErrorBoundary } from '#components/error-boundary'; +import { LandingPageContainer } from '#components/landing-page-container'; +import { TrustedBySection } from '#components/trusted-by-section'; +import { + CallToAction, + GetYourAPIGameRightSection, + Hero, + HeroLogo, + HiveGatewayIcon, + NextPageProps, +} from '@theguild/components'; +import { FederationCompatibleBenchmarksSection } from '../../../gateway/federation-compatible-benchmarks'; +import { GatewayHeroDecoration } from '../../../gateway/gateway-hero-decoration'; +import { otherLogos } from '../../../gateway/other-logos'; +import { metadata as rootMetadata } from '../../../layout'; +import { ComparisonSection, ComparisonTable } from '../comparison-table'; + +const __dirname = dirname(new URL(import.meta.url).pathname) + .replace('%5B', '[') + .replace('%5D', ']'); + +const DESCRIPTION = + 'See why teams choose a fully open-source gateway instead of other closed solutions'; + +export default async function ComparisonPage(props: NextPageProps<'comparison'>) { + const comparison = JSON.parse( + await readFile(join(__dirname, `${(await props.params).comparison}.json`), 'utf-8'), + ) as Comparison; /* we don't really need to parse this because it's a static build */ + + const Logo = otherLogos[comparison.logo as keyof typeof otherLogos]; + + return ( + + + + + } + className="bg-beige-100 mx-4 max-sm:mt-2 md:mx-6" + heading={`Hive Gateway vs. ${comparison.name}`} + checkmarks={['Fully open source', 'No vendor lock-in', 'Can be self-hosted!']} + text={DESCRIPTION} + > + + Get Started + + + GitHub + + + + + + + + + + + + + + + + , + }, + { + name: 'Hive Gateway', + icon: , + }, + ]} + sections={comparison.sections} + /> + + + + + {/* todo: smaller Community-driven open-source section */} + + + + ); +} + +export async function generateStaticParams() { + const dir = await readdir(__dirname); + const jsonFiles = dir.filter(file => file.endsWith('.json')); + return jsonFiles.map(file => ({ comparison: file.replace('.json', '') })); +} + +export async function generateMetadata({ params }: NextPageProps<'comparison'>) { + const file = join(__dirname, `${(await params).comparison}.json`); + + const comparison = JSON.parse( + await readFile(file, 'utf-8'), + ) as Comparison; /* we don't really need to parse this because it's a static build */ + + return { + title: `Hive Gateway vs. ${comparison.name}`, + description: DESCRIPTION, + alternates: { + // to remove leading slash + canonical: '.', + }, + openGraph: rootMetadata!.openGraph, + }; +} + +interface Comparison { + name: string; + logo: string; + sections: ComparisonSection[]; +} diff --git a/packages/web/docs/src/app/compare/gateway/comparison-table.tsx b/packages/web/docs/src/app/compare/gateway/comparison-table.tsx new file mode 100644 index 0000000000..16c3616b52 --- /dev/null +++ b/packages/web/docs/src/app/compare/gateway/comparison-table.tsx @@ -0,0 +1,118 @@ +'use client'; + +import { Fragment, ReactNode } from 'react'; +import { cn, Heading, ComparisonTable as Table } from '@theguild/components'; +import { CheckmarkIcon, XIcon } from '../../../components/icons'; +import { NestedSticky } from '../../../components/nested-sticky'; +import { TableSubheaderRow } from '../../../components/pricing/table-subheader-row'; + +const NO = ; +const YES = ; + +export type ComparisonColumn = { + name: string; + icon?: ReactNode; +}; + +export type ComparisonSection = { + title: string; + description: ReactNode; + icon?: ReactNode; + rows: ComparisonRow[]; +}; + +export type ComparisonRow = { + feature: string; + values: (ReactNode | boolean)[]; +}; + +export type ComparisonTableProps = { + className?: string; + columns: ComparisonColumn[]; + sections: ComparisonSection[]; +}; + +export function ComparisonTable({ className, columns, sections }: ComparisonTableProps) { + return ( +
+ + Hive Gateway allows you to do so much more. On your own terms. + +

+ Part of the Hive ecosystem, Hive Gateway is a fully-fledged solution that you can easily + tailor to your needs. +

+ +
+ +
+
+ Compare features +
+ {columns.map(column => ( +
+
+ {column.icon &&
{column.icon}
} +
{column.name}
+
+
+ ))} +
+
+ + + + + + {columns.map((column, i) => ( + + ))} + + + + {sections.map((section, sectionIndex) => ( + + + {section.rows.map((row, rowIndex) => ( + + + {row.feature} + + {row.values.map((value, columnIndex) => ( + + {typeof value === 'boolean' ? (value ? YES : NO) : value} + + ))} + + ))} + + ))} + +
Compare features{column.name}
+
+
+ ); +} + +function ComparisonTableCell({ children, className }: { children: ReactNode; className?: string }) { + return ( + &:last-child]:rounded-tr-3xl [.subheader+tr>&]:border-t [.subheader+tr>&]:first:rounded-tl-3xl [tr:is(:has(+.subheader),:last-child)>&:last-child]:rounded-br-3xl [tr:is(:last-child,:has(+.subheader))>&]:first:rounded-bl-3xl', + className, + )} + > + {children} + + ); +} diff --git a/packages/web/docs/src/app/gateway/federation-compatible-benchmarks/index.tsx b/packages/web/docs/src/app/gateway/federation-compatible-benchmarks/index.tsx index c39793a9fe..fe1bcc74f7 100644 --- a/packages/web/docs/src/app/gateway/federation-compatible-benchmarks/index.tsx +++ b/packages/web/docs/src/app/gateway/federation-compatible-benchmarks/index.tsx @@ -39,7 +39,7 @@ export function FederationCompatibleBenchmarksSection({
- +
diff --git a/packages/web/docs/src/app/gateway/gateway-hero-decoration.tsx b/packages/web/docs/src/app/gateway/gateway-hero-decoration.tsx new file mode 100644 index 0000000000..07ccba9caa --- /dev/null +++ b/packages/web/docs/src/app/gateway/gateway-hero-decoration.tsx @@ -0,0 +1,19 @@ +import { ReactNode } from 'react'; +import { DecorationIsolation, HiveGatewayIcon } from '@theguild/components'; + +export function GatewayHeroDecoration({ children }: { children: ReactNode }) { + return ( + + + + + {children} + + + ); +} diff --git a/packages/web/docs/src/app/gateway/orchestrate-your-way.tsx b/packages/web/docs/src/app/gateway/orchestrate-your-way.tsx index 5a572a5ade..636e7e8957 100644 --- a/packages/web/docs/src/app/gateway/orchestrate-your-way.tsx +++ b/packages/web/docs/src/app/gateway/orchestrate-your-way.tsx @@ -1,5 +1,6 @@ import { ReactNode } from 'react'; import { Anchor, Heading, InfoCard } from '@theguild/components'; +import { ApolloLogo } from './other-logos'; export function OrchestrateYourWay({ className, ...rest }: React.HTMLAttributes) { return ( @@ -39,8 +40,16 @@ export function OrchestrateYourWay({ className, ...rest }: React.HTMLAttributes<
    - } text="Apollo Federation V1" /> - } text="Apollo Federation V2" /> + } + text="Apollo Federation V1" + /> + } + text="Apollo Federation V2" + /> } @@ -97,21 +106,6 @@ function LinkCard({ href, text, logo }: { href: string; text: ReactNode; logo: R ); } -function ApolloLogo() { - return ( - - - - - ); -} - function StitchingLogo() { return ( diff --git a/packages/web/docs/src/app/gateway/other-logos.tsx b/packages/web/docs/src/app/gateway/other-logos.tsx new file mode 100644 index 0000000000..d34e32b531 --- /dev/null +++ b/packages/web/docs/src/app/gateway/other-logos.tsx @@ -0,0 +1,25 @@ +export function ApolloLogo(props: React.SVGProps) { + return ( + + + + + ); +} + +export const otherLogos = { + apollo: ApolloLogo, +}; diff --git a/packages/web/docs/src/app/gateway/page.tsx b/packages/web/docs/src/app/gateway/page.tsx index 96763df545..c823ec6d78 100644 --- a/packages/web/docs/src/app/gateway/page.tsx +++ b/packages/web/docs/src/app/gateway/page.tsx @@ -1,7 +1,6 @@ import { Metadata } from 'next'; import { CallToAction, - DecorationIsolation, ExploreMainProductCards, FrequentlyAskedQuestions, Hero, @@ -15,6 +14,7 @@ import { metadata as rootMetadata } from '../layout'; import { CloudNativeSection } from './cloud-native-section'; import { FederationCompatibleBenchmarksSection } from './federation-compatible-benchmarks'; import { GatewayFeatureTabs } from './gateway-feature-tabs'; +import { GatewayHeroDecoration } from './gateway-hero-decoration'; import GatewayLandingFAQ from './gateway-landing-faq.mdx'; import { LetsGetAdvancedSection } from './lets-get-advanced-section'; import { OrchestrateYourWay } from './orchestrate-your-way'; @@ -50,7 +50,25 @@ export default function HiveGatewayPage() { GitHub - + + + + + + + + + + + + @@ -75,36 +93,3 @@ export default function HiveGatewayPage() { ); } - -function GatewayHeroDecoration() { - return ( - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/web/docs/src/app/layout.tsx b/packages/web/docs/src/app/layout.tsx index f6ae1f45a3..6d4f91e201 100644 --- a/packages/web/docs/src/app/layout.tsx +++ b/packages/web/docs/src/app/layout.tsx @@ -68,6 +68,7 @@ export default async function HiveDocsLayout({ children }: { children: ReactNode '/ecosystem', '/partners', '/gateway', + '/compare/gateway/apollo-router', ]; return ( diff --git a/packages/web/docs/src/components/pricing/plans-table.tsx b/packages/web/docs/src/components/pricing/plans-table.tsx index 6ec07cb48e..257bb93953 100644 --- a/packages/web/docs/src/components/pricing/plans-table.tsx +++ b/packages/web/docs/src/components/pricing/plans-table.tsx @@ -17,6 +17,7 @@ import { SSOIcon, UsageIcon, } from './icons'; +import { TableSubheaderRow } from './table-subheader-row'; type PlanName = 'Hobby' | 'Pro' | 'Enterprise'; interface PricingPlan { @@ -420,22 +421,3 @@ function PlansTableCell({ ); } - -interface TableSubheaderRowProps { - icon: ReactNode; - title: string; - description: ReactNode; -} -function TableSubheaderRow({ icon, title, description }: TableSubheaderRowProps) { - return ( -
    - -
    - {icon} - {title} -
    -

    {description}

    - - - ); -} diff --git a/packages/web/docs/src/components/pricing/table-subheader-row.tsx b/packages/web/docs/src/components/pricing/table-subheader-row.tsx new file mode 100644 index 0000000000..ccc68b6e78 --- /dev/null +++ b/packages/web/docs/src/components/pricing/table-subheader-row.tsx @@ -0,0 +1,22 @@ +'use client'; + +import { ReactNode } from 'react'; + +export interface TableSubheaderRowProps { + icon: ReactNode; + title: string; + description: ReactNode; +} +export function TableSubheaderRow({ icon, title, description }: TableSubheaderRowProps) { + return ( +
+ + + ); +}
+
+ {icon} + {title} +
+

{description}

+