nx/nx-dev/ui-home/src/lib/ci-for-monorepos.tsx

540 lines
19 KiB
TypeScript

import { Dialog, Transition } from '@headlessui/react';
import { PlayIcon } from '@heroicons/react/24/outline';
import { AnimateValue, Marquee } from '@nx/nx-dev/ui-animations';
import {
ButtonLink,
SectionHeading,
Strong,
TextLink,
} from '@nx/nx-dev/ui-common';
import {
AzureDevOpsIcon,
BitbucketIcon,
GitHubIcon,
GitlabIcon,
JenkinsIcon,
TravisCiIcon,
} from '@nx/nx-dev/ui-icons';
import { cx } from '@nx/nx-dev/ui-primitives';
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
import { ComponentProps, Fragment, ReactNode, useState } from 'react';
export function CiForMonorepos(): JSX.Element {
return (
<section className="bg-slate-50 py-32 shadow-inner sm:py-40 dark:bg-slate-900">
<article className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="max-w-5xl">
<SectionHeading
as="h2"
variant="title"
id="ci-for-monorepos"
className="scroll-mt-24"
>
Finally! CI that works for monorepos.
</SectionHeading>
<SectionHeading as="p" variant="subtitle" className="mt-6">
Current CI systems are <Strong>slow</Strong>,{' '}
<Strong>hard to maintain, and unreliable</Strong>. With Nx Cloud, we
introduce an{' '}
<TextLink
href="/blog/reliable-ci-a-new-execution-model-fixing-both-flakiness-and-slowness?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos"
title="Task-based CI by Nx"
>
innovative task-based approach
</TextLink>{' '}
to making CI for monorepos not just <Strong>fast</Strong>, but also{' '}
<Strong>cost-efficient</Strong>. It plugs right into your existing
CI setup, enabling features such as{' '}
<TextLink
href="/ci/features/remote-cache?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos"
title="Remote caching with Nx Replay"
>
remote caching
</TextLink>
,{' '}
<TextLink
href="/ci/features/distribute-task-execution?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos"
title="Distribute task execution with Nx Agents "
>
dynamically allocating machines to distribute tasks
</TextLink>
, providing{' '}
<TextLink
href="/ci/features/split-e2e-tasks?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos"
title="E2E test splitting with Atomizer"
>
fine-grained e2e test splitting
</TextLink>{' '}
and{' '}
<TextLink
href="/ci/features/flaky-tasks?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos"
title="Flakiness detection with Nx"
>
automated flakiness detection
</TextLink>
. <Strong>All with a single line of code!</Strong>
</SectionHeading>
</div>
<div className="mt-24 grid grid-cols-1 gap-4 lg:grid-cols-12">
<ApplicationCard />
<div className="grid grid-cols-2 gap-4 lg:col-span-8 lg:grid-cols-2">
<ProjectsCreatedEveryMonth />
<HalveYouBill />
<IntegratesToYouCurrentCiProvider />
</div>
<div className="lg:col-span-12">
<AiSection />
</div>
</div>
</article>
</section>
);
}
export function Card({
className,
children,
}: {
className?: string;
children?: ReactNode;
}): JSX.Element {
return (
<div
className={cx(
'relative h-full w-full overflow-hidden rounded-2xl border border-slate-100 bg-white p-6 dark:border-slate-800/60 dark:bg-slate-950',
className
)}
>
{children}
</div>
);
}
export function PulseLine({
className = '',
}: {
className?: string;
}): JSX.Element {
return (
<span
className={cx(
'absolute left-0 top-1/2 h-48 w-[1px] -translate-y-1/2 animate-pulse bg-gradient-to-b from-blue-500/0 via-blue-400 to-blue-500/0',
className
)}
/>
);
}
export function CornerBlur({
className = '',
}: {
className?: string;
}): JSX.Element {
return (
<div
className={cx(
'absolute bottom-0 left-0 z-0 size-72 -translate-x-1/2 translate-y-1/2 rounded-full bg-slate-50 blur-2xl dark:bg-slate-900',
className
)}
/>
);
}
export function ApplicationCard(): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="col-span-1 h-[600px] lg:col-span-4 lg:h-[600px]">
<Card>
<PulseLine />
<CornerBlur />
<p className="text-2xl text-slate-900 dark:text-slate-100">
Powerful and elegant UI
</p>
<p className="mt-2 text-slate-600 dark:text-slate-400">
An application built for monorepo CI, so you can quickly find what
failed, debug and move on.
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<a
href="https://staging.nx.app/orgs/62d013d4d26f260059f7765e/workspaces/62d013ea0852fe0a2df74438/overview??utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos_live_demo"
target="_blank"
rel="noopener"
title="See Nx Cloud live demo"
className="group font-semibold leading-6 text-slate-950 dark:text-white"
>
View a live demo{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</a>
</div>
<picture className="absolute bottom-0 left-4 h-[345px] w-full overflow-hidden rounded-xl border border-slate-200 dark:bg-slate-800">
<Image
src="/images/home/nx-app-dashboard.avif"
alt="App screenshot: overview"
width={534}
height={370}
loading={'eager'}
priority={true}
unoptimized
className="h-full w-full object-cover object-left-top"
/>
<div className="absolute inset-0 z-10 grid h-full w-full items-center justify-center">
<PlayButton onClick={() => setIsOpen(true)} />
</div>
</picture>
</Card>
{/*MODAL*/}
<Transition appear show={isOpen} as={Fragment}>
<Dialog
as="div"
open={isOpen}
onClose={() => setIsOpen(false)}
className="relative z-10"
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/25 backdrop-blur-sm" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative w-auto transform overflow-hidden rounded-2xl border border-slate-600 text-left align-middle shadow-xl transition-all focus:outline-none dark:border-slate-800">
<iframe
width="812"
height="468"
src="https://www.youtube.com/embed/4VI-q943J3o?si=3tR-EkCKLfLvHYzL"
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
className="max-w-full"
/>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
}
//
function PlayButton({
className,
...props
}: ComponentProps<'div'>): JSX.Element {
const parent = {
initial: {
width: 82,
transition: {
when: 'afterChildren',
},
},
hover: {
width: 296,
transition: {
duration: 0.125,
type: 'tween',
ease: 'easeOut',
},
},
};
const child = {
initial: {
opacity: 0,
x: -6,
},
hover: {
x: 0,
opacity: 1,
transition: {
duration: 0.015,
type: 'tween',
ease: 'easeOut',
},
},
};
return (
<div
className={cx(
'group relative overflow-hidden rounded-full bg-transparent p-[1px] shadow-md',
className
)}
{...props}
>
<div className="absolute inset-0">
<span className="absolute inset-[-1000%] animate-[spin_2s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#FFFFFF_0%,#3B82F6_50%,#FFFFFF_100%)] dark:bg-[conic-gradient(from_90deg_at_50%_50%,#FFFFFF_0%,#0EA5E9_50%,#FFFFFF_100%)]" />
</div>
<motion.div
initial="initial"
whileHover="hover"
variants={parent}
className="relative isolate flex h-20 w-20 cursor-pointer items-center justify-center gap-6 rounded-full border border-slate-100 bg-white/70 p-6 text-sm text-slate-950 antialiased backdrop-blur-xl"
>
<PlayIcon
aria-hidden="true"
className="absolute left-6 top-6 h-8 w-8"
/>
<motion.div variants={child} className="absolute left-20 top-4 w-48">
<p className="text-base font-medium">See how Nx Cloud works</p>
<p className="text-slate-700">In under 9 minutes</p>
</motion.div>
</motion.div>
</div>
);
}
//
export function ProjectsCreatedEveryMonth(): JSX.Element {
return (
<div className="relative col-span-2 h-[360px] md:col-span-1">
<Card className="relative">
<div className="flex flex-col justify-between text-center drop-shadow">
<div>
<span className="text-9xl font-bold text-slate-950 dark:text-white">
<AnimateValue num={10} suffix="k" once />
</span>
<br />
<span className="text-4xl font-semibold text-slate-950 dark:text-white">
new projects
</span>
</div>
<span className="text-2xl font-semibold text-slate-600 dark:text-slate-400">
every month
</span>
</div>
<div className="mt-8 text-center">
<ButtonLink
href="https://cloud.nx.app?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos_connect_now"
title="Get started"
variant="primary"
size="large"
target="_blank"
rel="nofollow"
>
Connect to Nx Cloud now!
</ButtonLink>
</div>
</Card>
</div>
);
}
//
//
export function HalveYouBill(): JSX.Element {
return (
<div className="col-span-2 h-[415px] sm:h-[360px] md:col-span-1">
<Card>
<p className="text-2xl text-slate-900 dark:text-slate-100">
Halve your CI bill
</p>
<p className="mt-2 text-slate-600 dark:text-slate-400">
Nx Cloud not only makes your runs{' '}
<span className="font-semibold text-slate-800 dark:text-slate-200">
30% to 70% faster
</span>
, but also significantly cheaper.
</p>
<div className="mt-6">
<div className="flex items-center">
<div className="w-28 shrink-0 border-r-2 border-slate-200 py-3 pr-2 text-right text-slate-700 transition duration-200 dark:border-slate-800 dark:text-slate-300">
CI
</div>
<div className="flex-grow py-1.5 font-semibold">
<div className="w-full flex-grow items-center justify-end rounded-r-lg border border-l-0 border-slate-200 bg-slate-100 px-4 py-2 text-right text-slate-900 transition duration-200 dark:border-slate-800 dark:bg-slate-700 dark:text-white">
<span className="drop-shadow-sm">$6k</span>
</div>
</div>
</div>
<div className="flex items-center">
<div className="w-28 shrink-0 border-r-2 border-slate-200 py-3 pr-2 text-right font-medium text-slate-700 transition duration-200 dark:border-slate-800 dark:text-slate-300">
CI + Nx Cloud
</div>
<div className="flex-grow py-1.5 font-semibold">
<div className="w-1/2 rounded-r-lg border border-l-0 border-slate-200 bg-gradient-to-r from-emerald-500 to-green-500 px-4 py-2 text-right text-white transition duration-200 dark:border-slate-800">
<span className="drop-shadow-sm">$3.2k</span>
</div>
</div>
</div>
</div>
<p className="z-10 mt-6 text-xs text-slate-400 transition duration-200 dark:text-slate-600">
<span className="underline">Cost per month for CI compute.</span> Data
collected based on a typical month of CI runs measured on the Nx OSS
monorepo.
</p>
</Card>
</div>
);
}
//
//
export function IntegratesToYouCurrentCiProvider(): JSX.Element {
return (
<div className="col-span-2 h-fit sm:h-[225px]">
<Card>
<div className="relative z-20">
<p className="text-2xl text-slate-900 dark:text-slate-100">
Works with your current CI
</p>
<p className="mt-2 max-w-md text-slate-600 dark:text-slate-400">
Use your current CI provider, export your compute and slash your CI
bill by using Nx Agents to save time, improve performance and
ramp-up your developer experience.
</p>
<div className="mt-4 flex items-center">
<Link
href="/ci/intro/connect-to-nx-cloud?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos"
title="Add Nx Cloud to your CI workflow"
prefetch={false}
className="group font-semibold leading-6 text-slate-950 dark:text-white"
>
Enable faster CI with a single line of code{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</Link>
</div>
</div>
<div className="hidden sm:block">
<CiProviderVerticalMarquees />
</div>
</Card>
</div>
);
}
export function CiProviderVerticalMarquees(): JSX.Element {
const ICON_DATA = [
{
Icon: GitHubIcon,
},
{
Icon: JenkinsIcon,
},
{
Icon: BitbucketIcon,
},
{
Icon: AzureDevOpsIcon,
},
{
Icon: TravisCiIcon,
},
{
Icon: GitlabIcon,
},
];
const ICON_DATA_REVERSED = ICON_DATA.reverse();
return (
<div className="bg-background absolute inset-y-0 right-0 flex h-full w-40 flex-col items-center justify-center gap-4 overflow-hidden">
<div className="flex flex-row gap-6 [perspective:300px]">
<Marquee
className="h-96 justify-center overflow-hidden [--duration:60s] [--gap:1rem]"
vertical
>
{ICON_DATA.map((data, idx) => (
<data.Icon
key={'ci-provider-icon-1-' + idx}
aria-hidden="true"
className="mx-auto size-8"
/>
))}
</Marquee>
<Marquee
className="hidden h-96 justify-center overflow-hidden [--duration:60s] [--gap:1rem] md:flex"
vertical
reverse
>
{ICON_DATA_REVERSED.map((data, idx) => (
<data.Icon
key={'ci-provider-icon-2-' + idx}
aria-hidden="true"
className="mx-auto size-8"
/>
))}
</Marquee>
</div>
</div>
);
}
export function AiSection(): JSX.Element {
return (
<div className="h-fit sm:h-[225px]">
<Card className="flex items-center gap-8">
<PulseLine className="left-full -translate-x-[1px]" />
<div className="relative hidden h-full w-64 shrink-0 overflow-hidden lg:block">
<img
src="/images/home/ci-with-ai-light.avif"
alt="ci with ai illustration"
className="absolute inset-0 block -translate-y-[85px] scale-150 transform dark:hidden"
/>
<img
src="/images/home/ci-with-ai-dark.avif"
alt="ci with ai illustration"
className="absolute inset-0 hidden -translate-y-[85px] scale-150 transform dark:block"
/>
</div>
<div className="grow">
<p className="text-2xl text-slate-900 dark:text-slate-100">
AI for your CI
</p>
<p className="mt-2text-slate-600 dark:text-slate-400">
Identify and <Strong> resolve task failures</Strong> instantly with
intelligent explanations and actionable solutions. Set your desired
CI run time, and Nx Cloud will match it. Our{' '}
<Strong>custom AI model</Strong> analyzes your previous runs, then{' '}
<Strong>dynamically predicts and allocates</Strong> the optimal
number of agents. The more you use it, the smarter it gets. Take the
guesswork out of your work.
</p>
<div className="mt-4 flex items-center">
<Link
href="/ci/concepts/nx-cloud-ai?utm_source=homepage&utm_medium=website&utm_campaign=homepage_links&utm_content=cta_ci_for_monorepos"
title="Add AI to your CI with Nx Cloud"
prefetch={false}
className="group font-semibold leading-6 text-slate-950 dark:text-white"
>
Learn more{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</Link>
</div>
</div>
</Card>
</div>
);
}