refactor landing page (#624)

Co-authored-by: Kamil Kisiela <kamil.kisiela@gmail.com>
This commit is contained in:
Dimitri POSTOLOV 2022-11-10 11:29:52 +01:00 committed by GitHub
parent dcc970ee78
commit 1097791db3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1079 additions and 1414 deletions

View file

@ -19,3 +19,4 @@ integration-tests/fixtures/init-invalid-schema.graphql
tmp
pnpm-lock.yaml
.bob/

View file

@ -72,8 +72,8 @@
"eslint-plugin-import": "2.26.0",
"eslint-plugin-simple-import-sort": "8.0.0",
"fs-extra": "10.1.0",
"graphql": "16.5.0",
"glob": "8.0.3",
"graphql": "16.5.0",
"husky": "7.0.4",
"jest": "29.2.2",
"lint-staged": "11.2.6",

View file

@ -15,6 +15,6 @@
"jsx": "preserve",
"incremental": false
},
"include": ["next-env.d.ts", "src", "pages"],
"include": ["next-env.d.ts", "src"],
"exclude": ["node_modules"]
}

View file

@ -1,4 +0,0 @@
module.exports = {
presets: [['next/babel', { 'preset-react': { runtime: 'automatic' } }]],
plugins: ['babel-plugin-macros', ['styled-components', { ssr: true }]],
};

View file

@ -1,34 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
# local env files
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# Graphql
lib/graphql-operations.ts

View file

@ -1,119 +0,0 @@
import React from 'react';
export const Logo: React.FC<{ className?: string }> = ({ className }) => {
return (
<svg
className={className}
width="139"
height="61"
viewBox="0 0 139 61"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.29999 20.1C0.899988 19.3 0 17.8 0 16.1C0 13.6 2.10004 11.5 4.60004 11.5C5.30004 11.5 5.90002 11.6 6.40002 11.9L25.7 0.800049C26.6 0.300049 27.7 0 28.7 0C29.8 0 30.8 0.300049 31.7 0.800049L48.1 10.3C46.9 11.1 46 12.3 45.4 13.7L29.5 4.5C29.2 4.3 28.9 4.30005 28.6 4.30005C28.3 4.30005 28 4.4 27.7 4.5L8.90002 15.3C8.90002 15.6 9 15.8 9 16.1C9 18.1 7.7 19.7 6 20.4C5.9 20.4 5.70004 20.5 5.60004 20.5H5.5C5.4 20.5 5.20004 20.6 5.10004 20.6H5C4.8 20.6 4.7 20.6 4.5 20.6C4.3 20.6 4.10002 20.6 3.90002 20.6H3.79999C3.69999 20.6 3.50002 20.6 3.40002 20.5H3.29999C2.99999 20.4 2.59999 20.3 2.29999 20.1ZM56.7 16.1C56.7 17.5 56.1 18.7 55.1 19.5V41.2C55.1 43.4 53.9 45.4 52.1 46.5L35.7 55.9C35.7 54.4 35.2 52.9 34.3 51.8L49.9 42.8C50.5 42.5 50.8 41.9 50.8 41.2V20.5C48.9 19.9 47.5 18.2 47.5 16.1C47.5 15.1 47.8 14.2 48.4 13.4C48.5 13.3 48.6 13.1 48.7 13C49 12.7 49.2 12.5 49.5 12.3C49.5 12.3 49.6 12.3 49.6 12.2C49.7 12.1 49.8 12.1 50 12C50 12 50.1 12 50.1 11.9C50.3 11.8 50.4 11.8 50.6 11.7C51 11.6 51.5 11.5 52 11.5C54.6 11.5 56.7 13.6 56.7 16.1ZM33.3 56.1C33.3 56.3 33.3 56.6 33.2 56.8V56.9C32.8 59 31 60.7 28.7 60.7C26.7 60.7 25 59.4 24.4 57.6L5.40002 46.6C3.50002 45.5 2.40002 43.5 2.40002 41.3V22.8C3.10002 23 3.90004 23.2 4.60004 23.2C5.30004 23.2 6.00004 23.1 6.60004 22.9V41.3C6.60004 42 7 42.6 7.5 42.9L25.3 53.2C26.1 52.2 27.4 51.6 28.8 51.6C30.3 51.6 31.6 52.3 32.5 53.5C32.5 53.5 32.5 53.5 32.5 53.6C32.6 53.7 32.6 53.8 32.7 53.9C32.7 53.9 32.7 54 32.8 54C32.8 54.1 32.9 54.2 32.9 54.2C32.9 54.2 32.9 54.3 33 54.3C33 54.4 33.1 54.5 33.1 54.5C33.1 54.6 33.1 54.6 33.2 54.7C33.2 54.7999 33.3 54.8 33.3 54.9C33.3 55 33.3 55 33.4 55.1C33.4 55.2 33.4 55.2001 33.4 55.3C33.4 55.4 33.4 55.5 33.4 55.6C33.4 55.7 33.4 55.7001 33.4 55.8C33.3 55.8 33.3 55.9 33.3 56.1Z"
fill="#eab308"
/>
<path
d="M41.345 29.2535L43.8319 24.9475C44.056 24.5594 44.056 24.0813 43.8319 23.6933L40.9828 18.7602C40.7587 18.3721 40.3446 18.1331 39.8963 18.1331H34.9225L32.4356 13.827C32.2115 13.439 31.7973 13.2 31.3491 13.2H25.6509C25.2027 13.2 24.7885 13.439 24.5644 13.827L22.0775 18.1331H17.1037C16.6554 18.1331 16.2413 18.3721 16.0172 18.7602L13.1681 23.6933C12.944 24.0814 12.944 24.5595 13.1681 24.9475L15.655 29.2535L13.1681 33.5596C12.944 33.9476 12.944 34.4257 13.1681 34.8138L16.0172 39.7469C16.2413 40.135 16.6554 40.374 17.1037 40.374H22.0775L24.5644 44.68C24.7885 45.068 25.2027 45.3071 25.6509 45.3071H31.3491C31.7973 45.3071 32.2115 45.068 32.4356 44.68L34.9225 40.3739H39.8963C40.3446 40.3739 40.7587 40.1349 40.9828 39.7469L43.8319 34.8137C44.056 34.4257 44.056 33.9476 43.8319 33.5595L41.345 29.2535ZM22.0775 37.8656H17.828L15.7033 34.1866L17.828 30.5077H22.0775L24.2023 34.1866C23.9443 34.6334 22.3364 37.4173 22.0775 37.8656ZM22.0775 27.9993H17.828L15.7033 24.3204L17.828 20.6414H22.0775C22.3355 21.0881 23.9433 23.8721 24.2023 24.3204L22.0775 27.9993ZM30.6248 42.7987H26.3753L24.2505 39.1198C24.5085 38.6731 26.1163 35.8892 26.3753 35.4409H30.6248C30.8828 35.8876 32.4906 38.6715 32.7496 39.1198L30.6248 42.7987ZM24.2505 29.2535L26.3752 25.5746H30.6248L32.7495 29.2535L30.6248 32.9325H26.3752L24.2505 29.2535ZM30.6248 23.0662H26.3753C26.1173 22.6194 24.5095 19.8355 24.2505 19.3872L26.3752 15.7083H30.6247L32.7495 19.3872C32.4915 19.834 30.8837 22.6179 30.6248 23.0662ZM39.172 37.8656H34.9225C34.6645 37.4188 33.0567 34.6349 32.7977 34.1866L34.9225 30.5077H39.172L41.2967 34.1867L39.172 37.8656ZM39.172 27.9993H34.9225L32.7977 24.3204C33.0557 23.8737 34.6635 21.0898 34.9225 20.6415H39.172L41.2967 24.3204L39.172 27.9993Z"
fill="#FFB21D"
/>
<path
d="M78.074 14.3199V20.9859H71.078V14.3199H66.7V31.4799H71.078V25.0999H78.074V31.4799H82.474V14.3199H78.074Z"
fill="#0B0D11"
/>
<path d="M90.1824 14.3199V31.4799H94.5824V14.3199H90.1824Z" fill="#0B0D11" />
<path
d="M112.244 31.4799L119.196 14.3199H114.4L110.066 25.8479L105.754 14.3199H100.958L107.888 31.4799H112.244Z"
fill="#0B0D11"
/>
<path
d="M125.567 31.4799H138.745V27.2339H129.967V25.0339H137.183V21.0519H129.967V18.5879H138.745V14.3199H125.567V31.4799Z"
fill="#0B0D11"
/>
<path
d="M70.7 39.88H74.4V44.38H72.7999V43.6801C72.3999 44.2801 71.5999 44.5801 70.7999 44.5801C68.5999 44.5801 66.7 42.7801 66.7 40.5801C66.7 38.3801 68.5999 36.5801 70.7999 36.5801C72.0999 36.5801 73.3 37.1801 74.1 38.1801L72.4999 39.28C72.0999 38.78 71.4999 38.48 70.7999 38.48C69.5999 38.48 68.7 39.3801 68.7 40.5801C68.7 41.7801 69.5999 42.6801 70.7999 42.6801C71.4999 42.6801 72.0999 42.38 72.4999 41.78V41.5801H70.7V39.88Z"
fill="#C4C4C4"
/>
<path
d="M80.1 36.6801C82.1 36.6801 82.9999 37.98 82.9999 39.48C82.9999 40.58 82.4999 41.5801 81.4999 42.0801L83.2 44.38H80.7999L79.2999 42.28H78.2999V44.38H76.2999V36.6801H80.1ZM78.2999 38.48V40.5801H79.9999C80.7999 40.5801 81.1 40.0801 81.1 39.5801C81.1 38.9801 80.7999 38.5801 79.9999 38.5801H78.2999V38.48Z"
fill="#C4C4C4"
/>
<path
d="M89.2999 36.6801L92.4 44.38H90.2L89.7999 43.28H86.7999L86.4 44.38H84.2L87.2999 36.6801H89.2999ZM88.2999 39.1801L87.4 41.48H89.1L88.2999 39.1801Z"
fill="#C4C4C4"
/>
<path
d="M93.8999 36.6801H97.2999C99.3999 36.6801 100.3 38.0801 100.3 39.6801C100.3 41.2801 99.2999 42.6801 97.2999 42.6801H95.8999V44.48H93.8999V36.6801ZM95.8999 38.48V40.88H97.1999C97.9999 40.88 98.2999 40.3801 98.2999 39.6801C98.2999 39.0801 97.9999 38.48 97.1999 38.48H95.8999Z"
fill="#C4C4C4"
/>
<path
d="M109.2 36.6801V44.38H107.2V41.48H104V44.38H102V36.6801H104V39.6801H107.2V36.6801H109.2Z"
fill="#C4C4C4"
/>
<path
d="M115 36.48C117.3 36.48 119.1 38.28 119.1 40.48C119.1 41.98 118.3 43.18 117.1 43.98C117.5 44.18 117.8 44.38 118.1 44.38C118.2 44.38 118.6 44.3801 118.8 44.0801L120.3 45.28C119.7 46.08 118.8 46.38 118.1 46.38C116.5 46.38 115.6 44.9801 114.3 44.5801C112.4 44.2801 110.9 42.5801 110.9 40.5801C110.9 38.2801 112.7 36.48 115 36.48ZM115 42.5801C116.2 42.5801 117.1 41.68 117.1 40.48C117.1 39.28 116.2 38.38 115 38.38C113.8 38.38 112.9 39.28 112.9 40.48C112.9 41.68 113.8 42.5801 115 42.5801Z"
fill="#C4C4C4"
/>
<path d="M120.8 36.6801H122.8V42.48H126.5V44.38H120.8V36.6801Z" fill="#C4C4C4" />
</svg>
);
// return (
// <svg
// className={className}
// width="70"
// height="32"
// viewBox="0 0 70 32"
// fill="none"
// xmlns="http://www.w3.org/2000/svg"
// >
// <path
// d="M12.8116 12.8333V20.2075H4.93136V12.8333H0V31.8164H4.93136V24.7586H12.8116V31.8164H17.7677V12.8333H12.8116Z"
// fill="#eab308"
// />
// <path
// d="M22.7334 12.8333V31.8164H27.6895V12.8333H22.7334Z"
// fill="#eab308"
// />
// <path
// d="M43.8665 31.8164L51.6971 12.8333H46.295L41.4132 25.586L36.5562 12.8333H31.154L38.9599 31.8164H43.8665Z"
// fill="#eab308"
// />
// <path
// d="M55.1564 31.8164H70V27.1193H60.1125V24.6856H68.2406V20.2805H60.1125V17.5547H70V12.8333H55.1564V31.8164Z"
// fill="#eab308"
// />
// <path
// d="M4.57333 5.90333H6.77833V6.16C6.34667 6.79 5.64667 7.19833 4.83 7.19833C3.45333 7.19833 2.35667 6.10167 2.35667 4.725C2.35667 3.34833 3.45333 2.25167 4.83 2.25167C5.62333 2.25167 6.32333 2.61333 6.77833 3.185L8.68 1.90167C7.805 0.758334 6.39333 0 4.83 0C2.19333 0 0 2.12333 0 4.725C0 7.32667 2.19333 9.45 4.83 9.45C5.76333 9.45 6.685 9.1 7.12833 8.42333V9.275H8.96V3.96667H4.57333V5.90333Z"
// fill="#000"
// />
// <path
// d="M11.0661 0.175V9.275H13.3878V6.83667H14.5778L16.3044 9.275H19.0928L17.1211 6.52167C18.3461 5.96167 18.9178 4.77167 18.9178 3.5C18.9178 1.69167 17.8444 0.175 15.5111 0.175H11.0661ZM15.3828 2.29833C16.3161 2.29833 16.6661 2.82333 16.6661 3.52333C16.6661 4.15333 16.3161 4.725 15.3828 4.725H13.3878V2.29833H15.3828Z"
// fill="#000"
// />
// <path
// d="M23.753 0.175L20.0663 9.275H22.6096L23.1113 7.945H26.693L27.1946 9.275H29.738L26.063 0.175H23.753ZM25.8996 5.83333H23.9046L24.908 3.16167L25.8996 5.83333Z"
// fill="#000"
// />
// <path
// d="M31.3666 9.275H33.6999V7.21H35.3799C37.7949 7.21 38.9499 5.58833 38.9499 3.66333C38.9499 1.83167 37.7949 0.175 35.3799 0.175H31.3666V9.275ZM35.2516 2.29833C36.2316 2.29833 36.5932 2.95167 36.5932 3.675C36.5932 4.48 36.2316 5.08667 35.2516 5.08667H33.6999V2.29833H35.2516Z"
// fill="#000"
// />
// <path
// d="M46.8136 0.175V3.71H43.1036V0.175H40.7819V9.275H43.1036V5.89167H46.8136V9.275H49.1469V0.175H46.8136Z"
// fill="#000"
// />
// <path
// d="M51.0531 4.725C51.0531 7.05833 52.8264 9.01833 55.0897 9.38C56.6647 9.905 57.7264 11.5383 59.5581 11.5383C60.3864 11.5383 61.4481 11.2 62.1014 10.3017L60.3397 8.87833C60.0947 9.25167 59.6747 9.28667 59.5464 9.28667C59.1497 9.28667 58.7764 9.07667 58.3564 8.785C59.7681 7.95667 60.7131 6.45167 60.7131 4.725C60.7131 2.12333 58.5431 0 55.8831 0C53.2581 0 51.0531 2.12333 51.0531 4.725ZM53.4097 4.725C53.4097 3.34833 54.5181 2.25167 55.8831 2.25167C57.2831 2.25167 58.3564 3.34833 58.3564 4.725C58.3564 6.10167 57.2831 7.19833 55.8831 7.19833C54.5181 7.19833 53.4097 6.10167 53.4097 4.725Z"
// fill="#000"
// />
// <path
// d="M62.6319 9.275H69.3169V7.02333H64.9535V0.175H62.6319V9.275Z"
// fill="#000"
// />
// </svg>
// );
};

View file

@ -1,144 +0,0 @@
import React from 'react';
import 'twin.macro';
import * as RadixTooltip from '@radix-ui/react-tooltip';
function Tooltip(
props: React.PropsWithChildren<{
content: string;
}>
) {
return (
<RadixTooltip.Root>
<RadixTooltip.Trigger>{props.children}</RadixTooltip.Trigger>
<RadixTooltip.Content sideOffset={5} tw="p-2 text-xs rounded-sm bg-white shadow">
{props.content}
<RadixTooltip.Arrow tw="fill-current text-white" />
</RadixTooltip.Content>
</RadixTooltip.Root>
);
}
function Plan(plan: {
name: string;
description: string;
price: React.ReactNode | string;
features: Array<React.ReactNode | string>;
footer?: React.ReactNode;
}) {
return (
<div tw="flex w-full md:w-1/3 flex-col items-start rounded-md border border-gray-700 p-4 hover:border-gray-600">
<div tw="flex h-full flex-col justify-between">
<div>
<h2 tw="text-base text-white font-bold flex items-center justify-between">{plan.name}</h2>
<div tw="text-3xl font-bold text-white">{plan.price}</div>
<div tw="text-sm text-gray-500 mt-3">{plan.description}</div>
<div>
<ul tw="mt-6 list-disc px-5 text-gray-500">
{plan.features.map((feature, i) => {
return (
<li key={i} tw="box-border mb-2">
<div tw="text-sm text-gray-300 flex items-center">{feature}</div>
</li>
);
})}
</ul>
</div>
</div>
{plan.footer && (
<div>
<div tw="mx-auto my-4 w-9/12 border-b border-gray-800" />
<div tw="text-xs text-gray-300">{plan.footer}</div>
</div>
)}
</div>
</div>
);
}
const usageDataRetentionExplainer = 'How long to store GraphQL requests reported to GraphQL Hive';
const operationsExplainer = 'GraphQL requests reported to GraphQL Hive';
export function Pricing({ gradient }: { gradient: [string, string] }) {
return (
<div style={{ backgroundColor: 'rgb(23, 23, 23)' }} tw="w-full">
<div tw="max-width[1024px] w-full px-6 box-border mx-auto my-12">
<h2
tw="md:text-3xl text-2xl text-white font-bold bg-clip-text text-transparent dark:text-transparent leading-normal"
style={{
backgroundImage: `linear-gradient(-70deg, ${gradient[1]}, ${gradient[0]})`,
}}
>
Pricing
</h2>
<p tw="text-gray-400">All features are available in every plan</p>
<div tw="flex flex-col md:flex-row content-start items-stretch justify-start justify-items-start gap-4 mt-6">
<Plan
name="Hobby"
description="For personal or small projects"
price="Free"
features={[
'Unlimited seats',
'Unlimited schema pushes',
<Tooltip content={operationsExplainer}>Limit of 1M operations monthly</Tooltip>,
<Tooltip content={usageDataRetentionExplainer}>7 days of usage data retention</Tooltip>,
]}
/>
<Plan
name="Pro"
description="For scaling API"
price={
<Tooltip content="Base price charged monthly">
$10<span tw="text-sm text-gray-500">/mo</span>
</Tooltip>
}
features={[
'Unlimited seats',
'Unlimited schema pushes',
<Tooltip content={operationsExplainer}>$10 per 1M operations monthly</Tooltip>,
<Tooltip content={usageDataRetentionExplainer}>90 days of usage data retention</Tooltip>,
]}
footer={
<>
<div tw="mb-2 text-sm font-bold">Free 30 days trial period</div>
</>
}
/>
<Plan
name="Enterprise"
description="Custom plan for large companies"
price={
<span
tw="cursor-pointer"
onClick={() => {
if (typeof window !== 'undefined' && (window as any).$crisp) {
(window as any).$crisp.push(['do', 'chat:open']);
}
}}
>
Contact us
</span>
}
features={[
'Unlimited seats',
'Unlimited schema pushes',
<Tooltip content={operationsExplainer}>Unlimited operations</Tooltip>,
<Tooltip content={usageDataRetentionExplainer}>12 months of usage data retention</Tooltip>,
<span tw="flex gap-1">
Support from
<a
href="https://the-guild.dev"
target="_blank"
rel="noreferrer"
tw="font-medium transition-colors text-orange-500 hover:underline"
>
The Guild
</a>
</span>,
]}
footer={<>Shape a custom plan for your business</>}
/>
</div>
</div>
</div>
);
}

View file

@ -1,12 +0,0 @@
export function TheGuildLogo() {
return (
<svg width="28" height="30" viewBox="0 0 28 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.14552 11.2636C1.49179 11.3815 1.86112 11.4485 2.24663 11.4485C2.59289 11.4485 2.92688 11.3954 3.24229 11.2995V20.4156C3.24229 20.7378 3.41423 21.038 3.6916 21.199L12.4106 26.2686C12.823 25.7838 13.4338 25.4752 14.1158 25.4752C14.8581 25.4752 15.515 25.8408 15.9244 26.401C15.9316 26.4113 15.9394 26.4209 15.9466 26.4309C15.9754 26.4719 16.0023 26.5138 16.0284 26.557L16.0583 26.6061C16.0814 26.6459 16.1027 26.6864 16.123 26.7274C16.1341 26.7494 16.1446 26.7717 16.1551 26.7941C16.1725 26.8321 16.1889 26.8707 16.2045 26.9099C16.2156 26.9379 16.2255 26.966 16.2354 26.9944C16.2479 27.0302 16.2602 27.0664 16.2707 27.1029C16.2809 27.1376 16.289 27.1726 16.2977 27.2076L16.3201 27.3063C16.3288 27.35 16.3345 27.3943 16.3405 27.439C16.3438 27.4655 16.3489 27.4915 16.3513 27.518C16.3582 27.5904 16.3624 27.6634 16.3624 27.7376C16.3624 27.8559 16.3507 27.9708 16.333 28.0842L16.3267 28.1264C16.1431 29.1892 15.2226 30 14.1158 30C13.1288 30 12.2904 29.3548 11.9894 28.4619L2.64322 23.0279C1.71614 22.4892 1.14552 21.4931 1.14552 20.4156V11.2636ZM25.6106 5.7171C26.8495 5.7171 27.8571 6.73185 27.8571 7.97948C27.8571 8.6582 27.5576 9.26632 27.0861 9.6814V20.4156C27.0861 21.4931 26.5155 22.4892 25.5884 23.0279L17.5584 27.6966C17.5494 26.9328 17.293 26.2297 16.8677 25.6598L24.5401 21.199C24.8174 21.038 24.9894 20.7378 24.9894 20.4156V10.152C24.0521 9.87988 23.3641 9.00992 23.3641 7.97948C23.3641 7.47784 23.5291 7.01541 23.8047 6.63985C23.8089 6.63382 23.8134 6.62809 23.8176 6.62205C23.8697 6.55267 23.9254 6.48571 23.9847 6.42266L23.9928 6.41391C24.1159 6.28481 24.2537 6.17108 24.4041 6.07425C24.4196 6.064 24.4361 6.05465 24.452 6.04469C24.5116 6.0088 24.5724 5.97501 24.6353 5.94454C24.6536 5.9358 24.6719 5.92614 24.6904 5.9177C24.7692 5.8821 24.8495 5.84922 24.9328 5.82298C24.9331 5.82298 24.9331 5.82298 24.9331 5.82268L25.0961 5.77742C25.2614 5.73814 25.4336 5.7171 25.6106 5.7171ZM22.5404 10.5949V18.077C22.5404 18.9633 22.0668 19.7892 21.3048 20.2323V20.2326L15.2999 23.72L14.7458 24.0259L14.7524 23.3858V20.733L19.6948 17.8595V15.2279L15.4877 14.0222L22.5404 10.5949ZM5.69132 10.5904L8.53693 11.9961V17.8593L13.4044 20.6894V23.9985L6.92691 20.2327C6.16489 19.7892 5.69132 18.9633 5.69132 18.0771V10.5904ZM12.8802 5.53771C13.6305 5.10182 14.601 5.10182 15.3514 5.53771L21.9391 9.38647L21.3131 9.68481L18.8691 10.8923L14.1158 8.12859L9.3624 10.8923L6.30231 9.38014L6.89031 9.02359C6.8957 9.01876 6.91098 9.0085 6.92745 8.99915L12.8802 5.53771ZM14.1157 0C14.633 0 15.15 0.134536 15.6134 0.403911L23.6911 5.10062C23.0965 5.50422 22.6364 6.09094 22.3833 6.7781L14.565 2.23282C14.4288 2.15318 14.2733 2.11156 14.1157 2.11156C13.9582 2.11156 13.803 2.15318 13.6664 2.23282L4.45713 7.58743C4.47929 7.71502 4.49307 7.84534 4.49307 7.97957C4.49307 8.953 3.87842 9.78224 3.02024 10.1008C3.01365 10.1032 3.00676 10.1059 3.00017 10.108C2.93218 10.1325 2.86329 10.1536 2.79259 10.1714L2.75036 10.1822C2.68566 10.1973 2.61946 10.2091 2.55296 10.2184L2.49875 10.2266C2.41578 10.2359 2.3319 10.242 2.24654 10.242C2.15518 10.242 2.06532 10.2347 1.97635 10.2239C1.95239 10.2208 1.92903 10.2166 1.90536 10.213C1.83707 10.2024 1.76997 10.1889 1.70377 10.1723C1.6834 10.1671 1.66304 10.1623 1.64267 10.1566C1.46744 10.1068 1.30089 10.0377 1.14543 9.94935L1.01191 9.86708C0.403138 9.46213 0 8.76789 0 7.97957C0 6.73194 1.00795 5.71719 2.24654 5.71719C2.56794 5.71719 2.87287 5.78657 3.14934 5.90934L12.618 0.403911C13.0814 0.134536 13.5987 0 14.1157 0Z"
fill="#03a6a6"
/>
</svg>
);
}

View file

@ -1,5 +1,4 @@
/* eslint-disable no-undef */
module.exports = {
export default {
poweredByHeader: false,
eslint: {
ignoreDuringBuilds: true,

View file

@ -2,32 +2,25 @@
"name": "@hive/landing-page",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "next dev",
"dev": "next",
"build": "bob runify --single"
},
"dependencies": {
"@radix-ui/react-tooltip": "0.1.7",
"@theguild/components": "1.12.1-alpha-bbdd479.0",
"framer-motion": "^6.3.3",
"next": "12.3.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-icons": "4.4.0",
"react-use": "^17.3.2",
"styled-components": "5.3.5",
"twin.macro": "2.8.2"
"@radix-ui/react-tooltip": "1.0.2",
"@theguild/components": "4.3.2",
"clsx": "1.2.1",
"next": "12.3.3",
"next-themes": "*",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "4.6.0"
},
"devDependencies": {
"tslib": "2.4.0",
"tailwindcss": "2.2.19",
"@types/react": "17.0.45",
"@types/react-dom": "17.0.17"
},
"babelMacros": {
"twin": {
"preset": "styled-components"
}
"@theguild/tailwind-config": "0.2.1",
"@types/react": "18.0.25",
"tailwindcss": "3.2.3"
},
"buildOptions": {
"runify": true,

View file

@ -1,428 +0,0 @@
import React from 'react';
import tw from 'twin.macro';
import Head from 'next/head';
import { GlobalStyles } from 'twin.macro';
import {
Header,
FooterExtended,
ThemeProvider,
GlobalStyles as TGCStyles,
useThemeContext,
} from '@theguild/components';
import * as Tooltip from '@radix-ui/react-tooltip';
import { FiServer, FiGlobe, FiRadio, FiGithub } from 'react-icons/fi';
import { Pricing } from '../components/pricing';
const PrimaryLink = tw.a`
inline-block
bg-yellow-500 hover:bg-opacity-75
dark:bg-yellow-600 dark:hover:bg-opacity-100 dark:hover:bg-yellow-500
text-white px-6 py-3 rounded-lg font-medium
shadow-sm
`;
const SecondaryLink = tw.a`
inline-block
bg-gray-100 hover:bg-gray-200
dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700
text-gray-600 px-6 py-3 rounded-lg font-medium
shadow-sm
`;
const FeatureWrapper = tw.div`
w-full py-24
odd:bg-gray-50
odd:dark:bg-gray-900
even:bg-white
even:dark:bg-black
`;
const CookiesConsent: React.FC = () => {
const [show, setShow] = React.useState(typeof window !== 'undefined' && localStorage.getItem('cookies') === null);
const accept = React.useCallback(() => {
setShow(false);
localStorage.setItem('cookies', 'true');
}, [setShow]);
if (!show) {
return null;
}
return (
<div tw="w-full fixed bg-gray-100 px-5 py-7 bottom-0 flex gap-4 flex-wrap lg:flex-nowrap text-center lg:text-left items-center justify-center lg:justify-between">
<div tw="w-full text-sm">
<p>This website uses cookies to analyze site usage and improve your experience. </p>
<p>If you continue to use our services, you are agreeing to the use of such cookies. </p>
</div>
<div tw="flex gap-4 items-center flex-shrink-0 lg:pr-24">
<a href="/privacy-policy.pdf" tw="text-yellow-600 whitespace-nowrap hover:underline">
Privacy Policy
</a>
<button
tw="bg-yellow-500 px-5 py-2 text-white rounded-md hover:bg-yellow-700 focus:outline-none"
onClick={accept}
>
Allow Cookies
</button>
</div>
</div>
);
};
const gradients: [string, string][] = [
['#ff9472', '#f2709c'],
['#4776e6', '#8e54e9'],
['#f857a6', '#ff5858'],
['#4AC29A', '#BDFFF3'],
['#00c6ff', '#0072ff'],
];
function pickGradient(i: number) {
const gradient = gradients[i % gradients.length];
if (!gradient) {
throw new Error('No gradient found');
}
return gradient;
}
function GuildHeader() {
const { isDarkTheme } = useThemeContext();
const color = isDarkTheme ? '#000' : '#f9fafb';
return (
<Header
wrapperProps={{
style: {
backgroundColor: color,
},
}}
navigationProps={{
style: {
backgroundColor: color,
},
}}
accentColor="#D49605"
activeLink=""
themeSwitch
disableSearch
/>
);
}
function Hero() {
return (
<div tw="w-full">
<div tw="py-20 sm:py-24 lg:py-32 my-6">
<h1 tw="max-w-screen-md mx-auto font-extrabold text-5xl sm:text-5xl lg:text-6xl text-center bg-gradient-to-r from-yellow-500 to-orange-600 dark:from-yellow-400 dark:to-orange-500 bg-clip-text text-transparent">
Take full control of your GraphQL API
</h1>
<p tw="max-w-screen-sm mx-auto mt-6 text-2xl text-gray-600 text-center dark:text-gray-400">
Prevent breaking changes, monitor performance of your GraphQL API, and manage your API gateway
</p>
<div tw="mt-10 flex flex-col sm:flex-row items-center justify-center gap-4">
<PrimaryLink href="https://app.graphql-hive.com">Sign up for free</PrimaryLink>
<SecondaryLink href="https://docs.graphql-hive.com">Documentation</SecondaryLink>
<SecondaryLink tw="flex flex-row gap-2 items-center" href="https://github.com/kamilkisiela/graphql-hive">
<FiGithub /> GitHub
</SecondaryLink>
</div>
</div>
</div>
);
}
const Highlight = {
Root: tw.div`flex flex-row md:flex-col lg:flex-row flex-1 gap-6`,
Icon: tw.div`w-16 h-16 text-yellow-500 flex-shrink-0`,
Content: tw.div`flex flex-col text-black dark:text-white`,
Title: tw.h3`text-xl font-semibold`,
Description: tw.p`text-gray-600 dark:text-gray-400`,
};
function Feature(props: {
title: string;
description: React.ReactNode;
highlights?: Array<{
title: string;
description: React.ReactNode;
icon?: React.ReactNode;
}>;
image: string;
gradient: number;
flipped?: boolean;
}) {
const { title, description, highlights, image, gradient, flipped } = props;
const [start, end] = pickGradient(gradient);
return (
<FeatureWrapper>
<div tw="container box-border px-6 mx-auto flex flex-col gap-y-24">
<div
tw="flex flex-col gap-24 md:gap-12 lg:gap-24 items-start"
css={[flipped ? tw`md:flex-row-reverse` : tw`md:flex-row`]}
>
<div tw="flex flex-col gap-4 w-full md:w-2/5 lg:w-1/3 flex-shrink-0">
<h2
tw="font-semibold text-5xl bg-clip-text text-transparent dark:text-transparent leading-normal"
style={{
backgroundImage: `linear-gradient(-70deg, ${end}, ${start})`,
}}
>
{title}
</h2>
<div tw="text-lg text-gray-600 dark:text-gray-400 leading-7">{description}</div>
</div>
<div
tw="rounded-3xl overflow-hidden p-8 flex-grow flex flex-col justify-center items-center relative"
style={{
backgroundImage: `linear-gradient(70deg, ${start}, ${end})`,
}}
>
<img src={image} tw="rounded-2xl" alt={title} />
</div>
</div>
{Array.isArray(highlights) && highlights.length > 0 ? (
<div tw="flex flex-col md:flex-row gap-12 justify-between">
{highlights.map(({ title, description, icon }, i) => (
<Highlight.Root key={i}>
<Highlight.Icon>{icon}</Highlight.Icon>
<Highlight.Content>
<Highlight.Title>{title}</Highlight.Title>
<Highlight.Description>{description}</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
))}
</div>
) : null}
</div>
</FeatureWrapper>
);
}
export default function Index() {
const title = 'GraphQL Hive - Schema Registry and Monitoring';
const fullDescription =
'Prevent breaking changes, monitor performance of your GraphQL API, and manage your API gateway (Federation, Stitching) with the Schema Registry. GraphQL Hive is a SAAS solution that is also 100% open source and can be self-hosted.';
return (
<ThemeProvider>
<Head>
<meta charSet="utf-8" />
<title>{title}</title>
<meta property="og:title" content={title} key="title" />
<meta name="description" content={fullDescription} key="description" />
<meta name="og:description" content={fullDescription} key="og:description" />
<meta property="og:url" key="og:url" content="https://graphql-hive.com" />
<meta property="og:type" key="og:type" content="website" />
<meta
property="og:image"
key="og:image"
content="https://og-image-guild.vercel.app/**Manage%20your%20GraphQL%20APIs**.png?theme=light&md=1&fontSize=100px&images=https://graphql-hive.com/logo.svg&widths=800&heights=400"
/>
<meta property="og:type" content="website" />
<meta property="og:locale" content="en" />
<meta name="twitter:card" key="twitter:card" content="summary_large_image" />
<meta name="twitter:site" key="twitter:site" content="@TheGuildDev" />
<link rel="canonical" href="https://graphql-hive.com" />
</Head>
<Tooltip.Provider>
<div tw="flex flex-col h-full">
<style global jsx>{`
* {
font-family: Inter;
}
.dark {
background-color: #0b0d11;
}
html,
body,
#__next {
height: 100vh;
}
body {
margin: 0;
}
`}</style>
<GlobalStyles />
<TGCStyles includeFonts={false} />
<GuildHeader />
<Hero />
<div tw="flex flex-col">
<Feature
title="Schema Registry"
description={
<div tw="space-y-2">
<p>Push GraphQL schema to the registry and track the history of changes.</p>
<p>All your GraphQL services in one place.</p>
</div>
}
highlights={[
{
title: 'Manage your Gateway',
description: 'Connect to Apollo Federation, GraphQL Mesh, Stitching and more.',
icon: <FiServer tw="w-full h-full" />,
},
{
title: 'Global Edge Network',
description: 'Access the registry from any place on earth within milliseconds.',
icon: <FiGlobe tw="w-full h-full" />,
},
{
title: 'Make it smarter',
description: 'Detect unused parts of Schema thanks to GraphQL analytics.',
icon: <FiRadio tw="w-full h-full" />,
},
]}
image="/features/schema-history.png"
gradient={0}
/>
<Feature
title="Monitoring"
description={
<div tw="flex flex-col gap-y-24">
<div>
<p>Be aware of how your GraphQL API is used and what is the experience of its final users.</p>
</div>
<div tw="flex flex-col gap-y-12">
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">GraphQL Consumers</Highlight.Title>
<Highlight.Description tw="text-sm">
Track every source of GraphQL requests and see how the API is consumed.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Overall performance</Highlight.Title>
<Highlight.Description tw="text-sm">
Get a global overview of the usage of your GraphQL API.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Query performance</Highlight.Title>
<Highlight.Description tw="text-sm">
Detect slow GraphQL Operations and identify the culprits.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
</div>
</div>
}
image="/features/monitoring-preview.png"
gradient={1}
flipped
/>
<Feature
title="Analytics"
description={
<div tw="flex flex-col gap-y-12">
<div>
<p>Maintain your GraphQL API across many teams without concerns.</p>
</div>
<div tw="flex flex-col gap-y-12">
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Prevent Breaking Changes</Highlight.Title>
<Highlight.Description tw="text-sm">
Combination of Schema Registry and GraphQL Monitoring helps you evolve your GraphQL API.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Detect unused fields</Highlight.Title>
<Highlight.Description tw="text-sm">
Helps you understand the coverage of GraphQL schema and safely remove the unused part.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Alerts and notifications</Highlight.Title>
<Highlight.Description tw="text-sm">
Stay on top of everything with Slack notifications.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
</div>
</div>
}
image="/any-ci-cd.svg"
gradient={2}
/>
<FeatureWrapper>
<div tw="container box-border px-6 mx-auto flex flex-col gap-y-24">
<div tw="text-center">
<h2
tw="font-semibold text-5xl mb-6 bg-clip-text text-transparent dark:text-transparent leading-normal"
style={{
backgroundImage: `linear-gradient(-70deg, ${gradients[3][1]}, ${gradients[3][0]})`,
}}
>
Open-Source
</h2>
<p tw="text-lg text-gray-600 dark:text-gray-400 leading-7">Built entirely in public.</p>
</div>
<div tw="max-w-screen-lg px-6 box-border mx-auto grid grid-cols-2 gap-12">
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Public roadmap</Highlight.Title>
<Highlight.Description tw="text-sm">Influence the future of GraphQL Hive.</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Cloud and Self-Hosted</Highlight.Title>
<Highlight.Description tw="text-sm">
MIT licensed, host it on your own infrastructure.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Available for free</Highlight.Title>
<Highlight.Description tw="text-sm">
Free Hobby plan that fits perfectly for most side projects.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
<Highlight.Root>
<Highlight.Content>
<Highlight.Title tw="text-lg">Community</Highlight.Title>
<Highlight.Description tw="text-sm">
Implement your own features with our help.
</Highlight.Description>
</Highlight.Content>
</Highlight.Root>
</div>
</div>
</FeatureWrapper>
</div>
<Pricing gradient={gradients[4]} />
<FooterExtended
resources={[
{
title: 'Privacy Policy',
href: '/privacy-policy.pdf',
children: 'Privacy Policy',
},
{
title: 'Terms of Use',
href: '/terms-of-use.pdf',
children: 'Terms of Use',
},
]}
/>
</div>
<CookiesConsent />
</Tooltip.Provider>
</ThemeProvider>
);
}

View file

@ -0,0 +1 @@
module.exports = require('@theguild/tailwind-config/postcss.config');

View file

@ -0,0 +1,47 @@
import { ReactElement } from 'react';
import { AppProps } from 'next/app';
import Head from 'next/head';
import { FooterExtended, Header } from '@theguild/components';
import { ThemeProvider } from 'next-themes';
import '@theguild/components/style.css';
const TITLE = 'GraphQL Hive - Schema Registry and Monitoring';
const DESCRIPTION =
'Prevent breaking changes, monitor performance of your GraphQL API, and manage your API gateway (Federation, Stitching) with the Schema Registry. GraphQL Hive is a SAAS solution that is also 100% open source and can be self-hosted.';
export default function App({ Component, pageProps }: AppProps): ReactElement {
return (
<ThemeProvider attribute="class" disableTransitionOnChange>
<Head>
<meta charSet="utf-8" />
<title>{TITLE}</title>
<meta property="og:title" content={TITLE} key="title" />
<meta name="description" content={DESCRIPTION} key="description" />
<meta name="og:description" content={DESCRIPTION} key="og:description" />
<meta property="og:url" key="og:url" content="https://graphql-hive.com" />
<meta property="og:type" key="og:type" content="website" />
<meta
property="og:image"
key="og:image"
content="https://og-image-guild.vercel.app/**Manage%20your%20GraphQL%20APIs**.png?theme=light&md=1&fontSize=100px&images=https://graphql-hive.com/logo.svg&widths=800&heights=400"
/>
<meta property="og:type" content="website" />
<meta property="og:locale" content="en" />
<meta name="twitter:card" key="twitter:card" content="summary_large_image" />
<meta name="twitter:site" key="twitter:site" content="@TheGuildDev" />
<link rel="canonical" href="https://graphql-hive.com" />
</Head>
<Header accentColor="#d49605" search={false} />
<Component {...pageProps} />
<FooterExtended
resources={[
{ title: 'Privacy Policy', href: '/privacy-policy.pdf', children: 'Privacy Policy' },
{ title: 'Terms of Use', href: '/terms-of-use.pdf', children: 'Terms of Use' },
]}
/>
</ThemeProvider>
);
}

View file

@ -1,36 +1,10 @@
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
// eslint-disable-next-line no-process-env
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_TRACKING_ID;
// eslint-disable-next-line no-process-env
/* eslint-disable no-process-env */
const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_TRACKING_ID;
const CRISP_WEBSITE_ID = process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID;
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
const richData = {
'@context': 'https://schema.org',
@ -44,18 +18,16 @@ export default class MyDocument extends Document {
return (
<Html>
<Head>
{GA_TRACKING_ID && <script async src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`} />}
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(richData) }} />
{GA_TRACKING_ID && <script async src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`} />}
{GA_TRACKING_ID && (
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', { page_path: location.pathname });
`,
}}
/>

View file

@ -1,5 +1,5 @@
import { NextApiRequest, NextApiResponse } from 'next';
export default async function graphql(req: NextApiRequest, res: NextApiResponse) {
export default (req: NextApiRequest, res: NextApiResponse): void => {
res.status(200).json({});
}
};

View file

@ -0,0 +1,308 @@
import { ReactElement, useState, useCallback, ReactNode } from 'react';
import { useMounted } from '@theguild/components';
import * as Tooltip from '@radix-ui/react-tooltip';
import clsx from 'clsx';
import { FiServer, FiGlobe, FiRadio, FiGithub } from 'react-icons/fi';
import { Pricing } from '../pricing';
const classes = {
link: clsx(
'inline-block bg-gray-100 hover:bg-gray-200 text-gray-600 px-6 py-3 rounded-lg font-medium shadow-sm',
'dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700'
),
feature: clsx('w-full py-24', 'odd:bg-gray-50 odd:dark:bg-gray-900', 'even:bg-white even:dark:bg-black'),
root: clsx('flex flex-row md:flex-col lg:flex-row flex-1 gap-6'),
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'),
};
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 (
<div className="fixed bottom-0 flex w-full flex-wrap items-center justify-center gap-4 bg-gray-100 px-5 py-7 text-center lg:flex-nowrap lg:justify-between lg:text-left">
<div className="text-dark w-full text-sm">
<p>This website uses cookies to analyze site usage and improve your experience.</p>
<p>If you continue to use our services, you are agreeing to the use of such cookies.</p>
</div>
<div className="flex flex-shrink-0 items-center gap-4 lg:pr-24">
<a href="/privacy-policy.pdf" className="whitespace-nowrap text-yellow-600 hover:underline">
Privacy Policy
</a>
<button
className="focus:outline-none rounded-md bg-yellow-500 px-5 py-2 text-white hover:bg-yellow-700"
onClick={accept}
>
Allow Cookies
</button>
</div>
</div>
);
};
const gradients: [string, string][] = [
['#ff9472', '#f2709c'],
['#4776e6', '#8e54e9'],
['#f857a6', '#ff5858'],
['#4ac29a', '#bdfff3'],
['#00c6ff', '#0072ff'],
];
function pickGradient(i: number): [string, string] {
const gradient = gradients[i % gradients.length];
if (!gradient) {
throw new Error('No gradient found');
}
return gradient;
}
const renderFeatures = ({ title, description }) => (
<div className={classes.root} key={title}>
<div className={classes.content}>
<h3 className={clsx(classes.title, 'text-lg')}>{title}</h3>
<p className={clsx(classes.description, 'text-sm')}>{description}</p>
</div>
</div>
);
function Hero() {
return (
<div className="w-full">
<div className="my-6 py-20 px-2 sm:py-24 lg:py-32">
<h1 className="to-orange-600 dark:to-orange-500 mx-auto max-w-screen-md bg-gradient-to-r from-yellow-500 bg-clip-text text-center text-5xl font-extrabold text-transparent dark:from-yellow-400 sm:text-5xl lg:text-6xl">
Take full control of your GraphQL API
</h1>
<p className="mx-auto mt-6 max-w-screen-sm text-center text-2xl text-gray-600 dark:text-gray-400">
Prevent breaking changes, monitor performance of your GraphQL API, and manage your API gateway
</p>
<div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
<a
href="https://app.graphql-hive.com"
className={clsx(
'inline-block rounded-lg bg-yellow-500 px-6 py-3 font-medium text-white shadow-sm hover:bg-opacity-75',
'dark:bg-yellow-600 dark:hover:bg-yellow-500 dark:hover:bg-opacity-100'
)}
>
Sign up for free
</a>
<a href="https://docs.graphql-hive.com" className={classes.link}>
Documentation
</a>
<a
className={clsx(classes.link, 'flex flex-row items-center gap-2')}
href="https://github.com/kamilkisiela/graphql-hive"
>
<FiGithub /> GitHub
</a>
</div>
</div>
</div>
);
}
function Feature(props: {
title: string;
description: ReactNode;
highlights?: {
title: string;
description: ReactNode;
icon?: ReactNode;
}[];
image: string;
gradient: number;
flipped?: boolean;
}) {
const { title, description, highlights, image, gradient, flipped } = props;
const [start, end] = pickGradient(gradient);
return (
<div className={classes.feature}>
<div className="container mx-auto box-border flex flex-col gap-y-24 px-6">
<div
className={clsx(
'flex flex-col items-start gap-24 md:gap-12 lg:gap-24',
flipped ? 'md:flex-row-reverse' : 'md:flex-row'
)}
>
<div className="flex w-full flex-shrink-0 flex-col gap-4 md:w-2/5 lg:w-1/3">
<h2
className="bg-clip-text text-5xl font-semibold leading-normal text-transparent dark:text-transparent"
style={{ backgroundImage: `linear-gradient(-70deg, ${end}, ${start})` }}
>
{title}
</h2>
<div className="text-lg leading-7 text-gray-600 dark:text-gray-400">{description}</div>
</div>
<div
className="relative flex flex-grow flex-col items-center justify-center overflow-hidden rounded-3xl p-8"
style={{ backgroundImage: `linear-gradient(70deg, ${start}, ${end})` }}
>
<img src={image} className="rounded-2xl" alt={title} />
</div>
</div>
{Array.isArray(highlights) && highlights.length > 0 && (
<div className="flex flex-col justify-between gap-12 md:flex-row">
{highlights.map(({ title, description, icon }) => (
<div className={classes.root} key={title}>
<div className="h-16 w-16 flex-shrink-0 text-yellow-500">{icon}</div>
<div className={classes.content}>
<h3 className={classes.title}>{title}</h3>
<p className={classes.description}>{description}</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
}
export default function IndexPage(): ReactElement {
const mounted = useMounted();
return (
<Tooltip.Provider>
<div className="flex h-full flex-col">
<Hero />
<div className="flex flex-col">
<Feature
title="Schema Registry"
description={
<div className="space-y-2">
<p>Push GraphQL schema to the registry and track the history of changes.</p>
<p>All your GraphQL services in one place.</p>
</div>
}
highlights={[
{
title: 'Manage your Gateway',
description: 'Connect to Apollo Federation, GraphQL Mesh, Stitching and more.',
icon: <FiServer className="h-full w-full" />,
},
{
title: 'Global Edge Network',
description: 'Access the registry from any place on earth within milliseconds.',
icon: <FiGlobe className="h-full w-full" />,
},
{
title: 'Make it smarter',
description: 'Detect unused parts of Schema thanks to GraphQL analytics.',
icon: <FiRadio className="h-full w-full" />,
},
]}
image="/features/schema-history.png"
gradient={0}
/>
<Feature
title="Monitoring"
description={
<div className="flex flex-col gap-y-24">
<div>
<p>Be aware of how your GraphQL API is used and what is the experience of its final users.</p>
</div>
<div className="flex flex-col gap-y-12">
{[
{
title: 'GraphQL Consumers',
description: 'Track every source of GraphQL requests and see how the API is consumed.',
},
{
title: 'Overall performance',
description: 'Get a global overview of the usage of your GraphQL API.',
},
{
title: 'Query performance',
description: 'Detect slow GraphQL Operations and identify the culprits.',
},
].map(renderFeatures)}
</div>
</div>
}
image="/features/monitoring-preview.png"
gradient={1}
flipped
/>
<Feature
title="Analytics"
description={
<div className="flex flex-col gap-y-12">
<div>
<p>Maintain your GraphQL API across many teams without concerns.</p>
</div>
<div className="flex flex-col gap-y-12">
{[
{
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)}
</div>
</div>
}
image="/any-ci-cd.svg"
gradient={2}
/>
<div className={classes.feature}>
<div className="container mx-auto box-border flex flex-col gap-y-24 px-6">
<div className="text-center">
<h2
className="mb-6 bg-clip-text text-5xl font-semibold leading-normal text-transparent dark:text-transparent"
style={{
backgroundImage: `linear-gradient(-70deg, ${gradients[3][1]}, ${gradients[3][0]})`,
}}
>
Open-Source
</h2>
<p className="text-lg leading-7 text-gray-600 dark:text-gray-400">Built entirely in public.</p>
</div>
<div className="mx-auto box-border grid max-w-screen-lg grid-cols-2 gap-12 px-6">
{[
{
title: 'Public roadmap',
description: 'Influence the future of GraphQL Hive.',
},
{
title: 'Cloud and Self-Hosted',
description: 'MIT licensed, host it on your own infrastructure.',
},
{
title: 'Available for free',
description: 'Free Hobby plan that fits perfectly for most side projects.',
},
{
title: 'Community',
description: 'Implement your own features with our help.',
},
].map(renderFeatures)}
</div>
</div>
</div>
</div>
<Pricing gradient={gradients[4]} />
</div>
{mounted && <CookiesConsent />}
</Tooltip.Provider>
);
}

View file

@ -0,0 +1,133 @@
import { ReactNode, ReactElement } from 'react';
import { Root, Trigger, Content, Arrow } from '@radix-ui/react-tooltip';
function Tooltip({ content, children }: { content: string; children: ReactNode }) {
return (
<Root>
<Trigger>{children}</Trigger>
<Content sideOffset={5} className="rounded-sm bg-white p-2 text-xs font-normal text-black shadow">
{content}
<Arrow className="fill-current text-white" />
</Content>
</Root>
);
}
function Plan(plan: {
name: string;
description: string;
price: ReactNode | string;
features: (ReactNode | string)[];
footer?: ReactNode;
}): ReactElement {
return (
<div className="flex w-full flex-col items-start rounded-md border border-gray-700 p-4 hover:border-gray-600 md:w-1/3">
<div className="flex h-full flex-col justify-between">
<div>
<h2 className="flex items-center justify-between text-base font-bold text-white">{plan.name}</h2>
<div className="text-3xl font-bold text-white">{plan.price}</div>
<div className="mt-3 text-sm text-gray-500">{plan.description}</div>
<div>
<ul className="mt-6 list-disc px-5 text-gray-500">
{plan.features.map((feature, i) => (
<li key={i} className="mb-2 box-border">
<div className="flex items-center text-sm text-gray-300">{feature}</div>
</li>
))}
</ul>
</div>
</div>
{plan.footer && (
<div>
<div className="mx-auto my-4 w-9/12 border-b border-gray-800" />
<div className="text-xs text-gray-300">{plan.footer}</div>
</div>
)}
</div>
</div>
);
}
const USAGE_DATA_RETENTION_EXPLAINER = 'How long to store GraphQL requests reported to GraphQL Hive';
const OPERATIONS_EXPLAINER = 'GraphQL requests reported to GraphQL Hive';
export function Pricing({ gradient }: { gradient: [string, string] }): ReactElement {
return (
<div className="bg-neutral-900 w-full">
<div className="mx-auto my-12 box-border w-full max-w-[1024px] px-6">
<h2
className="bg-clip-text text-2xl font-bold leading-normal text-white text-transparent dark:text-transparent md:text-3xl"
style={{
backgroundImage: `linear-gradient(-70deg, ${gradient[1]}, ${gradient[0]})`,
}}
>
Pricing
</h2>
<p className="text-gray-400">All features are available in every plan</p>
<div className="mt-6 flex flex-col content-start items-stretch justify-start justify-items-start gap-4 md:flex-row">
<Plan
name="Hobby"
description="For personal or small projects"
price="Free"
features={[
'Unlimited seats',
'Unlimited schema pushes',
<Tooltip content={OPERATIONS_EXPLAINER}>Limit of 1M operations monthly</Tooltip>,
<Tooltip content={USAGE_DATA_RETENTION_EXPLAINER}>7 days of usage data retention</Tooltip>,
]}
/>
<Plan
name="Pro"
description="For scaling API"
price={
<Tooltip content="Base price charged monthly">
$10<span className="text-sm text-gray-500">/mo</span>
</Tooltip>
}
features={[
'Unlimited seats',
'Unlimited schema pushes',
<Tooltip content={OPERATIONS_EXPLAINER}>$10 per 1M operations monthly</Tooltip>,
<Tooltip content={USAGE_DATA_RETENTION_EXPLAINER}>90 days of usage data retention</Tooltip>,
]}
footer={<div className="mb-2 text-sm font-bold">Free 30 days trial period</div>}
/>
<Plan
name="Enterprise"
description="Custom plan for large companies"
price={
<span
className="cursor-pointer"
onClick={() => {
if (typeof window !== 'undefined') {
(window as any).$crisp?.push(['do', 'chat:open']);
}
}}
>
Contact us
</span>
}
features={[
'Unlimited seats',
'Unlimited schema pushes',
<Tooltip content={OPERATIONS_EXPLAINER}>Unlimited operations</Tooltip>,
<Tooltip content={USAGE_DATA_RETENTION_EXPLAINER}>12 months of usage data retention</Tooltip>,
<span className="flex gap-1">
Support from
<a
href="https://the-guild.dev"
target="_blank"
rel="noreferrer"
className="text-orange-500 font-medium transition-colors hover:underline"
>
The Guild
</a>
</span>,
]}
footer="Shape a custom plan for your business"
/>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1 @@
module.exports = require('@theguild/tailwind-config');

View file

@ -1,22 +0,0 @@
// tailwind.config.js
const colors = require('tailwindcss/colors');
module.exports = {
darkMode: 'class',
theme: {
fontFamily: {
title: ['Inter', 'ui-sans-serif', 'system-ui'],
},
colors: {
transparent: 'transparent',
current: 'currentColor',
white: colors.white,
black: colors.black,
gray: colors.trueGray,
red: colors.red,
yellow: colors.yellow,
emerald: colors.emerald,
orange: colors.orange,
},
},
};

View file

@ -15,6 +15,6 @@
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "twin.d.ts", "pages", "components"],
"include": ["next-env.d.ts", "src"],
"exclude": ["node_modules"]
}

View file

@ -1,29 +0,0 @@
// twin.d.ts
import 'twin.macro';
import styledImport, { CSSProp, css as cssImport } from 'styled-components';
declare module 'twin.macro' {
// The styled and css imports
const styled: typeof styledImport;
const css: typeof cssImport;
}
declare module 'react' {
// The css prop
interface HTMLAttributes<T> extends DOMAttributes<T> {
css?: CSSProp;
}
// The inline svg css prop
interface SVGProps extends SVGProps<SVGSVGElement> {
css?: CSSProp;
}
}
// The 'as' prop on styled components
declare global {
namespace JSX {
interface IntrinsicAttributes<T> extends DOMAttributes<T> {
as?: string;
}
}
}

File diff suppressed because it is too large Load diff