docs(nx-dev): revamp the Nx Enterprise page on nx.dev (#29209)
This update introduces a revamp to the Nx Enterprise page. The code changes involved the addition of new image files, amending several components for improved UI/UX. This refactor also includes an alteration to the Call-to-Action section and an introduction of a new Carousel component for better navigation. These adjustments aim to provide an improved user experience and enhanced readability. --------- Co-authored-by: Isaac Mann <isaacplmann@users.noreply.github.com>
This commit is contained in:
parent
2e98918a3f
commit
cc1441170a
@ -3,29 +3,31 @@ import { NextSeo } from 'next-seo';
|
|||||||
import { DefaultLayout } from '@nx/nx-dev/ui-common';
|
import { DefaultLayout } from '@nx/nx-dev/ui-common';
|
||||||
import {
|
import {
|
||||||
CallToAction,
|
CallToAction,
|
||||||
DownloadEbook,
|
CustomerLogos,
|
||||||
EnterpriseAddons,
|
CustomerMetrics,
|
||||||
Hero,
|
Hero,
|
||||||
MetricsAndCustomers,
|
HetznerCloudTestimonial,
|
||||||
ScaleYourPeople,
|
MakeYourCiFast,
|
||||||
|
ScaleOrganizationIntro,
|
||||||
|
ScaleYourOrganization,
|
||||||
Security,
|
Security,
|
||||||
TrustedBy,
|
TestimonialCarousel,
|
||||||
SolveYourCi,
|
VmwareTestimonial,
|
||||||
} from '@nx/nx-dev/ui-enterprise';
|
} from '@nx/nx-dev/ui-enterprise';
|
||||||
import { TrialCallout } from '@nx/nx-dev/ui-pricing';
|
|
||||||
import { requestFreeTrial } from '../lib/components/headerCtaConfigs';
|
import { requestFreeTrial } from '../lib/components/headerCtaConfigs';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
export function Enterprise(): JSX.Element {
|
export function Enterprise(): ReactElement {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NextSeo
|
<NextSeo
|
||||||
title="Nx Enterprise"
|
title="Solving the Performance Paradox, get speed and scale"
|
||||||
description="Accelerate your organization's journey to tighter collaboration, better developer experience, and speed…lots of speed."
|
description="Accelerate your organization's journey to tighter collaboration, better developer experience, and speed…lots of speed."
|
||||||
openGraph={{
|
openGraph={{
|
||||||
url: 'https://nx.dev' + router.asPath,
|
url: 'https://nx.dev' + router.asPath,
|
||||||
title: 'Nx Enterprise',
|
title: 'Solving the Performance Paradox, get speed and scale',
|
||||||
description:
|
description:
|
||||||
"Accelerate your organization's journey to tighter collaboration, better developer experience, and speed…lots of speed.",
|
"Accelerate your organization's journey to tighter collaboration, better developer experience, and speed…lots of speed.",
|
||||||
images: [
|
images: [
|
||||||
@ -41,35 +43,32 @@ export function Enterprise(): JSX.Element {
|
|||||||
type: 'website',
|
type: 'website',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DefaultLayout headerCTAConfig={[requestFreeTrial]}>
|
<DefaultLayout headerCTAConfig={[requestFreeTrial]} isHome={true}>
|
||||||
<div>
|
<div>
|
||||||
<Hero />
|
<Hero />
|
||||||
|
<CustomerLogos />
|
||||||
|
</div>
|
||||||
|
<CustomerMetrics />
|
||||||
|
<div className="mt-32 lg:mt-40">
|
||||||
|
<MakeYourCiFast />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-32 lg:mt-40">
|
<div className="mt-32 lg:mt-40">
|
||||||
<MetricsAndCustomers />
|
<TestimonialCarousel />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-32 lg:mt-40">
|
<div className="mt-32 lg:mt-40">
|
||||||
<TrialCallout pageId="enterprise" />
|
<ScaleOrganizationIntro />
|
||||||
|
<ScaleYourOrganization />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-32 lg:mt-56">
|
<div className="mt-32 lg:mt-40">
|
||||||
<ScaleYourPeople />
|
<HetznerCloudTestimonial />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-32 lg:mt-56">
|
<div className="mt-32 lg:mt-56">
|
||||||
<Security />
|
<Security />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-32 lg:mt-56">
|
<div className="mt-32 lg:mt-56">
|
||||||
<SolveYourCi />
|
<VmwareTestimonial />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-32 lg:mt-56">
|
<div className="mt-32 lg:mt-40">
|
||||||
<DownloadEbook />
|
|
||||||
</div>
|
|
||||||
<div className="mt-32 lg:mt-56">
|
|
||||||
<EnterpriseAddons />
|
|
||||||
</div>
|
|
||||||
<div className="mt-32">
|
|
||||||
<TrustedBy />
|
|
||||||
</div>
|
|
||||||
<div className="mt-32 lg:mt-56">
|
|
||||||
<CallToAction />
|
<CallToAction />
|
||||||
</div>
|
</div>
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
|
|||||||
BIN
nx-dev/nx-dev/public/images/enterprise/nx-agents.avif
Normal file
BIN
nx-dev/nx-dev/public/images/enterprise/nx-agents.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
nx-dev/nx-dev/public/images/enterprise/nx-atomizer.avif
Normal file
BIN
nx-dev/nx-dev/public/images/enterprise/nx-atomizer.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
nx-dev/nx-dev/public/images/enterprise/nx-partnership.avif
Normal file
BIN
nx-dev/nx-dev/public/images/enterprise/nx-partnership.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
BIN
nx-dev/nx-dev/public/images/enterprise/nx-replay.avif
Normal file
BIN
nx-dev/nx-dev/public/images/enterprise/nx-replay.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@ -53,13 +53,16 @@ export function Marquee({
|
|||||||
.map((_, i) => (
|
.map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className={cx('flex shrink-0 justify-around [gap:var(--gap)]', {
|
className={cx(
|
||||||
'animate-marquee flex-row': !vertical,
|
'flex shrink-0 items-center justify-around [gap:var(--gap)]',
|
||||||
'animate-marquee-vertical flex-col': vertical,
|
{
|
||||||
'[animation-play-state:paused]': shouldReduceMotion,
|
'animate-marquee flex-row': !vertical,
|
||||||
'group-hover:[animation-play-state:paused]': pauseOnHover,
|
'animate-marquee-vertical flex-col': vertical,
|
||||||
'[animation-direction:reverse]': reverse,
|
'[animation-play-state:paused]': shouldReduceMotion,
|
||||||
})}
|
'group-hover:[animation-play-state:paused]': pauseOnHover,
|
||||||
|
'[animation-direction:reverse]': reverse,
|
||||||
|
}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export function DefaultLayout({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[46.125rem] -translate-x-1/2 rotate-[35deg] bg-gradient-to-tr from-[#9333ea] to-[#3b82f6] opacity-25 sm:left-[calc(70%-30rem)] sm:w-[92.1875rem] dark:from-[#3b82f6] dark:to-[#9333ea] dark:opacity-15"
|
className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[46.125rem] -translate-x-1/2 rotate-[35deg] bg-gradient-to-tr from-[#9333ea] to-[#3b82f6] opacity-10 sm:left-[calc(70%-30rem)] sm:w-[92.1875rem] dark:from-[#3b82f6] dark:to-[#9333ea] dark:opacity-15"
|
||||||
style={{
|
style={{
|
||||||
clipPath:
|
clipPath:
|
||||||
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 95.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 67.5% 76.7%, 0.1% 64.9%, 77.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 84.1% 44.1%)',
|
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 95.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 67.5% 76.7%, 0.1% 64.9%, 77.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 84.1% 44.1%)',
|
||||||
|
|||||||
@ -7,29 +7,19 @@ import {
|
|||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, ReactElement, useEffect, useState } from 'react';
|
||||||
import { ButtonLink, ButtonLinkProps } from '../button';
|
import { ButtonLink, ButtonLinkProps } from '../button';
|
||||||
import {
|
import { resourceMenuItems } from './menu-items';
|
||||||
companyItems,
|
|
||||||
eventItems,
|
|
||||||
featuresItems,
|
|
||||||
learnItems,
|
|
||||||
ossProducts,
|
|
||||||
resourceMenuItems,
|
|
||||||
productsMenuItems,
|
|
||||||
enterpriseResourcesMenuItems,
|
|
||||||
} from './menu-items';
|
|
||||||
import { MobileMenuItem } from './mobile-menu-item';
|
import { MobileMenuItem } from './mobile-menu-item';
|
||||||
import { SectionsMenu } from './sections-menu';
|
import { SectionsMenu } from './sections-menu';
|
||||||
import { TwoColumnsMenu } from './two-columns-menu';
|
|
||||||
import { AlgoliaSearch } from '@nx/nx-dev/feature-search';
|
import { AlgoliaSearch } from '@nx/nx-dev/feature-search';
|
||||||
import { GitHubIcon, NxCloudAnimatedIcon, NxIcon } from '@nx/nx-dev/ui-icons';
|
import { GitHubIcon, NxIcon } from '@nx/nx-dev/ui-icons';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
ctaButtons?: ButtonLinkProps[];
|
ctaButtons?: ButtonLinkProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Header({ ctaButtons }: HeaderProps): JSX.Element {
|
export function Header({ ctaButtons }: HeaderProps): ReactElement {
|
||||||
let [isOpen, setIsOpen] = useState(false);
|
let [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
// We need to close the popover if the route changes or the window is resized to prevent the popover from being stuck open.
|
// We need to close the popover if the route changes or the window is resized to prevent the popover from being stuck open.
|
||||||
@ -70,7 +60,7 @@ export function Header({ ctaButtons }: HeaderProps): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/*DESKTOP*/}
|
{/*DESKTOP*/}
|
||||||
<div className="mx-auto mt-2 hidden w-full max-w-7xl items-center justify-between space-x-10 rounded-xl border border-slate-200/40 bg-white/70 px-4 py-2 shadow-lg backdrop-blur-xl backdrop-saturate-150 lg:flex dark:border-slate-800/60 dark:bg-slate-950/40">
|
<div className="mx-auto mt-2 hidden w-full max-w-7xl items-center justify-between space-x-10 rounded-xl border border-slate-200/40 bg-white/70 px-4 py-2 shadow-sm backdrop-blur-xl backdrop-saturate-150 lg:flex dark:border-slate-800/60 dark:bg-slate-950/40">
|
||||||
{/*PRIMARY NAVIGATION*/}
|
{/*PRIMARY NAVIGATION*/}
|
||||||
<div className="flex flex-shrink-0 text-sm">
|
<div className="flex flex-shrink-0 text-sm">
|
||||||
{/*LOGO*/}
|
{/*LOGO*/}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
export * from './lib/call-to-action';
|
export * from './lib/call-to-action';
|
||||||
export * from './lib/download-case-study';
|
|
||||||
export * from './lib/download-ebook';
|
|
||||||
export * from './lib/enterprise-addons';
|
|
||||||
export * from './lib/hero';
|
export * from './lib/hero';
|
||||||
export * from './lib/metrics-and-customers';
|
|
||||||
export * from './lib/scale-your-people';
|
|
||||||
export * from './lib/security';
|
export * from './lib/security';
|
||||||
export * from './lib/trusted-by';
|
export * from './lib/customer-logos';
|
||||||
export * from './lib/solve-your-ci';
|
export * from './lib/customer-metrics';
|
||||||
|
export * from './lib/make-your-ci-fast';
|
||||||
|
export * from './lib/hetzner-cloud-testimonial';
|
||||||
|
export * from './lib/vmware-testimonial';
|
||||||
|
export * from './lib/scale-your-organization';
|
||||||
|
export * from './lib/testimonial-carousel';
|
||||||
|
export * from './lib/download-case-study';
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
import { ReactNode } from 'react';
|
|
||||||
import { cx } from '@nx/nx-dev/ui-primitives';
|
|
||||||
|
|
||||||
export const BentoGrid = ({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
'mx-auto grid max-w-7xl grid-cols-1 gap-4 md:auto-rows-[24rem] md:grid-cols-2 lg:grid-cols-3 ',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BentoGridItem = ({
|
|
||||||
className,
|
|
||||||
title = null,
|
|
||||||
description = null,
|
|
||||||
header,
|
|
||||||
url = null,
|
|
||||||
icon,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
title?: string | ReactNode | null;
|
|
||||||
description?: string | ReactNode | null;
|
|
||||||
header: ReactNode;
|
|
||||||
icon?: ReactNode;
|
|
||||||
url?: string | null;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
'group/bento shadow-input relative row-span-1 flex flex-col justify-between space-y-4 overflow-hidden rounded-xl border border-slate-200 bg-white p-4 transition duration-200 dark:border-slate-800 dark:bg-slate-950 dark:shadow-none',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{header}
|
|
||||||
<div className="mt-2 flex items-start">
|
|
||||||
<div className="grow">
|
|
||||||
<div className="flex items-center gap-2 font-sans font-bold text-slate-800 transition duration-200 group-hover/bento:text-blue-500 dark:text-slate-200 group-hover/bento:dark:text-sky-500">
|
|
||||||
{icon} {title}
|
|
||||||
</div>
|
|
||||||
{description && (
|
|
||||||
<div className="mt-2 font-sans text-sm font-normal text-slate-600 transition duration-200 group-hover/bento:text-blue-500 dark:text-slate-400 group-hover/bento:dark:text-sky-500">
|
|
||||||
{description}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{url ? (
|
|
||||||
<Link
|
|
||||||
href={url}
|
|
||||||
title="Learn more"
|
|
||||||
className="float-right text-sm font-medium transition duration-200 group-hover/bento:text-blue-500 group-hover/bento:dark:text-sky-500"
|
|
||||||
prefetch={false}
|
|
||||||
>
|
|
||||||
<span className="group absolute inset-0" />
|
|
||||||
|
|
||||||
<span
|
|
||||||
className="inline-block transition duration-200 group-hover/bento:translate-x-2"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
→
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,11 +1,17 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
import { sendCustomEvent } from '@nx/nx-dev/feature-analytics';
|
||||||
|
|
||||||
export function CallToAction(): JSX.Element {
|
export function CallToAction(): ReactElement {
|
||||||
return (
|
return (
|
||||||
<section className="relative isolate px-6 py-32 sm:py-40 lg:px-8">
|
<section
|
||||||
|
className="relative isolate px-6 py-32 sm:py-40 lg:px-8"
|
||||||
|
aria-labelledby="section-cta-heading"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
className="absolute inset-0 -z-10 h-full w-full stroke-black/10 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-white/10"
|
className="absolute inset-0 -z-10 h-full w-full rotate-180 stroke-black/10 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-white/10"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<pattern
|
<pattern
|
||||||
@ -41,7 +47,7 @@ export function CallToAction(): JSX.Element {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-[#80caff] to-[#4f46e5] opacity-20"
|
className="aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-[#80caff] to-[#4f46e5] opacity-10"
|
||||||
style={{
|
style={{
|
||||||
clipPath:
|
clipPath:
|
||||||
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
|
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
|
||||||
@ -50,31 +56,44 @@ export function CallToAction(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mx-auto max-w-2xl text-center">
|
<div className="mx-auto max-w-2xl text-center">
|
||||||
<h2
|
<h2
|
||||||
id="cta"
|
id="section-cta-heading"
|
||||||
className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white"
|
className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white"
|
||||||
>
|
>
|
||||||
Your organization's transformation
|
Get your hands dirty and try it out for yourself.
|
||||||
<br />
|
|
||||||
starts now
|
|
||||||
</h2>
|
</h2>
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<Link
|
<Link
|
||||||
href="/contact/engineering"
|
aria-label="Request a free trial"
|
||||||
title="Talk to the engineering team"
|
href="/contact/sales"
|
||||||
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
|
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
|
title="Request a free trial"
|
||||||
|
onClick={() =>
|
||||||
|
sendCustomEvent(
|
||||||
|
'request-trial-click',
|
||||||
|
'enterprise-bottom-cta',
|
||||||
|
'enterprise'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
|
||||||
>
|
>
|
||||||
Talk to engineering
|
Request a free trial
|
||||||
</Link>
|
</Link>
|
||||||
<p className="mt-6 italic">
|
<p className="mt-6 italic">
|
||||||
Ready to talk terms?{' '}
|
Want to talk terms or see a demo?{' '}
|
||||||
<Link
|
<Link
|
||||||
href="/contact/sales"
|
href="/contact/sales"
|
||||||
title="Talk to the sales team"
|
title="Talk to the team"
|
||||||
className="font-semibold underline"
|
className="font-semibold underline"
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
|
onClick={() =>
|
||||||
|
sendCustomEvent(
|
||||||
|
'contact-team',
|
||||||
|
'enterprise-bottom-cta',
|
||||||
|
'enterprise'
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Speak directly to sales
|
Reach out to our team
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
309
nx-dev/ui-enterprise/src/lib/carousel.tsx
Normal file
309
nx-dev/ui-enterprise/src/lib/carousel.tsx
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
forwardRef,
|
||||||
|
ReactElement,
|
||||||
|
ReactNode,
|
||||||
|
Ref,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { motion, MotionConfig, useAnimation } from 'framer-motion';
|
||||||
|
import { cx } from '@nx/nx-dev/ui-primitives';
|
||||||
|
|
||||||
|
export interface CarouselHandle {
|
||||||
|
/**
|
||||||
|
* Navigate to a specific slide index
|
||||||
|
* @param index The zero-based index of the target slide
|
||||||
|
*/
|
||||||
|
goToSlide: (index: number) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the next slide
|
||||||
|
*/
|
||||||
|
goToNext: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the previous slide
|
||||||
|
*/
|
||||||
|
goToPrevious: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current slide index
|
||||||
|
*/
|
||||||
|
getCurrentIndex: () => number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total number of slides
|
||||||
|
*/
|
||||||
|
getTotalSlides: () => number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause the autoplay if it's enabled
|
||||||
|
*/
|
||||||
|
pauseAutoPlay: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume the autoplay if it's enabled
|
||||||
|
*/
|
||||||
|
resumeAutoPlay: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CarouselContextValue {
|
||||||
|
currentIndex: number;
|
||||||
|
totalSlides: number;
|
||||||
|
goToSlide: (index: number) => void;
|
||||||
|
goToNext: () => void;
|
||||||
|
goToPrevious: () => void;
|
||||||
|
pauseAutoPlay: () => void;
|
||||||
|
resumeAutoPlay: () => void;
|
||||||
|
registerSlide: (id: string) => void;
|
||||||
|
unregisterSlide: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CarouselRootProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
autoPlayInterval?: number;
|
||||||
|
animationDuration?: number;
|
||||||
|
onSlideChange?: (index: number) => void;
|
||||||
|
enableKeyboardNavigation?: boolean;
|
||||||
|
ref?: Ref<CarouselHandle>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SPRING_CONFIG = {
|
||||||
|
type: 'spring' as const,
|
||||||
|
damping: 30,
|
||||||
|
stiffness: 300,
|
||||||
|
mass: 0.2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const CarouselContext = createContext<CarouselContextValue | null>(null);
|
||||||
|
|
||||||
|
export const useCarousel = (): CarouselContextValue => {
|
||||||
|
const context = useContext(CarouselContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Carousel components must be used within a CarouselRoot');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CarouselRoot = forwardRef<CarouselHandle, CarouselRootProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
autoPlayInterval,
|
||||||
|
animationDuration = 0.5,
|
||||||
|
onSlideChange,
|
||||||
|
enableKeyboardNavigation = true,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
): ReactElement => {
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [slideIds, setSlideIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const autoPlayTimeoutRef = useRef<NodeJS.Timeout>();
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const goToSlide = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
// Ensure the index is within bounds
|
||||||
|
const safeIndex = Math.max(0, Math.min(index, slideIds.length - 1));
|
||||||
|
setCurrentIndex(safeIndex);
|
||||||
|
|
||||||
|
// Call the onSlideChange callback if provided
|
||||||
|
onSlideChange?.(safeIndex);
|
||||||
|
},
|
||||||
|
[slideIds.length, onSlideChange]
|
||||||
|
);
|
||||||
|
const goToNext = useCallback(() => {
|
||||||
|
goToSlide((currentIndex + 1) % slideIds.length);
|
||||||
|
}, [currentIndex, slideIds.length, goToSlide]);
|
||||||
|
const goToPrevious = useCallback(() => {
|
||||||
|
goToSlide((currentIndex - 1 + slideIds.length) % slideIds.length);
|
||||||
|
}, [currentIndex, slideIds.length, goToSlide]);
|
||||||
|
|
||||||
|
// Implement keyboard navigation
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enableKeyboardNavigation) return;
|
||||||
|
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
// Ignore keyboard navigation when user is typing in an input
|
||||||
|
if (
|
||||||
|
event.target instanceof HTMLInputElement ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
event.altKey ||
|
||||||
|
event.shiftKey ||
|
||||||
|
event.metaKey
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowLeft':
|
||||||
|
event.preventDefault();
|
||||||
|
goToPrevious();
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
event.preventDefault();
|
||||||
|
goToNext();
|
||||||
|
break;
|
||||||
|
case 'Home':
|
||||||
|
event.preventDefault();
|
||||||
|
goToSlide(0);
|
||||||
|
break;
|
||||||
|
case 'End':
|
||||||
|
event.preventDefault();
|
||||||
|
goToSlide(slideIds.length - 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [
|
||||||
|
enableKeyboardNavigation,
|
||||||
|
goToNext,
|
||||||
|
goToPrevious,
|
||||||
|
goToSlide,
|
||||||
|
slideIds.length,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Autoplay functionality
|
||||||
|
const pauseAutoPlay = useCallback(() => {
|
||||||
|
if (autoPlayTimeoutRef.current) {
|
||||||
|
clearInterval(autoPlayTimeoutRef.current);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
const resumeAutoPlay = useCallback(() => {
|
||||||
|
if (autoPlayInterval) {
|
||||||
|
pauseAutoPlay();
|
||||||
|
autoPlayTimeoutRef.current = setInterval(goToNext, autoPlayInterval);
|
||||||
|
}
|
||||||
|
}, [autoPlayInterval, goToNext, pauseAutoPlay]);
|
||||||
|
|
||||||
|
// Set up and clean up autoplay
|
||||||
|
useEffect(() => {
|
||||||
|
if (autoPlayInterval) {
|
||||||
|
resumeAutoPlay();
|
||||||
|
}
|
||||||
|
return pauseAutoPlay;
|
||||||
|
}, [autoPlayInterval, resumeAutoPlay, pauseAutoPlay]);
|
||||||
|
|
||||||
|
// Register and unregister slides
|
||||||
|
const registerSlide = useCallback((id: string) => {
|
||||||
|
setSlideIds((prev) => [...prev, id]);
|
||||||
|
}, []);
|
||||||
|
const unregisterSlide = useCallback((id: string) => {
|
||||||
|
setSlideIds((prev) => prev.filter((slideId) => slideId !== id));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const contextValue = {
|
||||||
|
currentIndex,
|
||||||
|
totalSlides: slideIds.length,
|
||||||
|
goToSlide,
|
||||||
|
goToNext,
|
||||||
|
goToPrevious,
|
||||||
|
pauseAutoPlay,
|
||||||
|
resumeAutoPlay,
|
||||||
|
registerSlide,
|
||||||
|
unregisterSlide,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expose methods through the ref
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
goToSlide,
|
||||||
|
goToNext,
|
||||||
|
goToPrevious,
|
||||||
|
getCurrentIndex: () => currentIndex,
|
||||||
|
getTotalSlides: () => slideIds.length,
|
||||||
|
pauseAutoPlay,
|
||||||
|
resumeAutoPlay,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
goToSlide,
|
||||||
|
goToNext,
|
||||||
|
goToPrevious,
|
||||||
|
currentIndex,
|
||||||
|
slideIds.length,
|
||||||
|
pauseAutoPlay,
|
||||||
|
resumeAutoPlay,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CarouselContext.Provider value={contextValue}>
|
||||||
|
<MotionConfig transition={{ duration: animationDuration }}>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
role="region"
|
||||||
|
aria-roledescription="carousel"
|
||||||
|
aria-label={`Carousel with ${slideIds.length} slides`}
|
||||||
|
tabIndex={enableKeyboardNavigation ? 0 : undefined}
|
||||||
|
className={cx('relative overflow-hidden', className)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</MotionConfig>
|
||||||
|
</CarouselContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export function CarouselViewport({
|
||||||
|
className = '',
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}): ReactElement {
|
||||||
|
const { currentIndex } = useCarousel();
|
||||||
|
const controls = useAnimation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
controls.start({
|
||||||
|
x: -currentIndex * 100 + '%',
|
||||||
|
transition: SPRING_CONFIG,
|
||||||
|
});
|
||||||
|
}, [currentIndex, controls]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div animate={controls} className={cx('flex h-full', className)}>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CarouselSlide({
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}): ReactElement {
|
||||||
|
const { registerSlide, unregisterSlide } = useCarousel();
|
||||||
|
const slideId = useRef(`slide-${Math.random()}`);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
registerSlide(slideId.current);
|
||||||
|
return () => unregisterSlide(slideId.current);
|
||||||
|
}, [registerSlide, unregisterSlide]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="group"
|
||||||
|
aria-roledescription="slide"
|
||||||
|
className={cx('w-full flex-shrink-0', className)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
114
nx-dev/ui-enterprise/src/lib/customer-logos.tsx
Normal file
114
nx-dev/ui-enterprise/src/lib/customer-logos.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
AmericanAirlinesIcon,
|
||||||
|
AwsIcon,
|
||||||
|
BillIcon,
|
||||||
|
CapitalOneIcon,
|
||||||
|
CaterpillarIcon,
|
||||||
|
CiscoIcon,
|
||||||
|
FicoIcon,
|
||||||
|
HiltonIcon,
|
||||||
|
ManIcon,
|
||||||
|
RoyalBankOfCanadaIcon,
|
||||||
|
SevenElevenIcon,
|
||||||
|
ShopifyIcon,
|
||||||
|
StorybookIcon,
|
||||||
|
VmwareIcon,
|
||||||
|
} from '@nx/nx-dev/ui-icons';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
import { Marquee } from '@nx/nx-dev/ui-animations';
|
||||||
|
import { cx } from '@nx/nx-dev/ui-primitives';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { ChevronRightIcon } from '@heroicons/react/20/solid';
|
||||||
|
|
||||||
|
export function CustomerLogos(): ReactElement {
|
||||||
|
const icons = [
|
||||||
|
{
|
||||||
|
Icon: AwsIcon,
|
||||||
|
className: 'size-14',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: ManIcon,
|
||||||
|
className: 'size-14',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: CapitalOneIcon,
|
||||||
|
className: 'size-28',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: ShopifyIcon,
|
||||||
|
className: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: RoyalBankOfCanadaIcon,
|
||||||
|
className: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: VmwareIcon,
|
||||||
|
className: 'size-24',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: StorybookIcon,
|
||||||
|
className: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: FicoIcon,
|
||||||
|
className: 'size-20',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: CaterpillarIcon,
|
||||||
|
className: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: CiscoIcon,
|
||||||
|
className: 'size-16',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: BillIcon,
|
||||||
|
className: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: SevenElevenIcon,
|
||||||
|
className: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: HiltonIcon,
|
||||||
|
className: 'size-24',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: AmericanAirlinesIcon,
|
||||||
|
className: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="customer-logos"
|
||||||
|
className="group/canvas relative mx-auto flex max-w-7xl overflow-hidden text-slate-950 dark:text-slate-100"
|
||||||
|
>
|
||||||
|
<Marquee className="w-full justify-center overflow-hidden [--duration:240s] [--gap:6rem]">
|
||||||
|
{icons.map((e, idx) => (
|
||||||
|
<e.Icon
|
||||||
|
key={'icon-' + idx}
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cx('mx-auto size-12', e.className)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Marquee>
|
||||||
|
<div className="absolute bottom-0 left-0 top-0 w-1/3 max-w-60 bg-gradient-to-r from-white to-white/0 dark:from-slate-950 dark:to-slate-950/0"></div>
|
||||||
|
<div className="absolute bottom-0 right-0 top-0 w-1/3 max-w-60 bg-gradient-to-r from-white/0 to-white dark:from-slate-950/0 dark:to-slate-950"></div>
|
||||||
|
<div className="absolute inset-0 grid grid-cols-1 place-items-center bg-white/60 opacity-0 backdrop-blur-sm transition-all duration-500 group-hover/canvas:opacity-100 dark:bg-slate-950/60">
|
||||||
|
<Link
|
||||||
|
href="/customers"
|
||||||
|
title="See our customers"
|
||||||
|
className="group/link relative flex items-center gap-2 text-base font-semibold drop-shadow"
|
||||||
|
>
|
||||||
|
<span>See our customers</span>{' '}
|
||||||
|
<ChevronRightIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-5 transform transition-all group-hover/link:translate-x-1"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
90
nx-dev/ui-enterprise/src/lib/customer-metrics.tsx
Normal file
90
nx-dev/ui-enterprise/src/lib/customer-metrics.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
import { HetznerCloudIcon } from '@nx/nx-dev/ui-icons';
|
||||||
|
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { sendCustomEvent } from '@nx/nx-dev/feature-analytics';
|
||||||
|
|
||||||
|
export function CustomerMetrics(): ReactElement {
|
||||||
|
return (
|
||||||
|
<section className="border-b border-t border-slate-200 bg-slate-50 py-24 sm:py-32 dark:border-slate-800 dark:bg-slate-900">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-none">
|
||||||
|
<div className="mt-6 flex flex-col-reverse gap-x-8 gap-y-20 lg:flex-row lg:items-center">
|
||||||
|
<div className="lg:w-full lg:max-w-2xl lg:flex-auto">
|
||||||
|
<figure className="rounded-2xl bg-white shadow-lg ring-1 ring-slate-900/5 sm:col-span-2 xl:col-start-2 xl:row-end-1 dark:bg-slate-800">
|
||||||
|
<blockquote className="p-6 text-lg font-semibold tracking-tight text-black dark:text-white">
|
||||||
|
<p>
|
||||||
|
Engineers will run a test command and expect it to run for
|
||||||
|
20 mins, they start it and see it finishes in a few seconds,
|
||||||
|
then they ask “Did I start it wrong? Why is it so fast?”
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
<figcaption className="flex flex-wrap items-center gap-x-4 gap-y-4 border-t border-slate-900/10 px-6 py-4 sm:flex-nowrap dark:border-slate-100/10">
|
||||||
|
<img
|
||||||
|
alt="pavlo grosse"
|
||||||
|
src="https://avatars.githubusercontent.com/u/2219064?v=4"
|
||||||
|
className="size-10 flex-none rounded-full bg-slate-50"
|
||||||
|
/>
|
||||||
|
<div className="flex-auto">
|
||||||
|
<div className="font-semibold">Pavlo Grosse</div>
|
||||||
|
<div className="text-slate-600 dark:text-slate-500">
|
||||||
|
Senior Software Engineer, Hetzner Cloud
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<HetznerCloudIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="mx-auto size-10 flex-none bg-white text-[#D50C2D]"
|
||||||
|
/>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<SectionHeading
|
||||||
|
as="p"
|
||||||
|
variant="subtitle"
|
||||||
|
className="mt-8 text-center italic"
|
||||||
|
>
|
||||||
|
Want to see it in action?{' '}
|
||||||
|
<Link
|
||||||
|
href="/contact/sales"
|
||||||
|
title="Request Nx Enterprise demo"
|
||||||
|
prefetch={false}
|
||||||
|
onClick={() =>
|
||||||
|
sendCustomEvent(
|
||||||
|
'request-trial-click',
|
||||||
|
'enterprise-customer-metrics',
|
||||||
|
'enterprise'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="font-semibold underline"
|
||||||
|
>
|
||||||
|
Request a demo now <span aria-hidden="true">→</span>
|
||||||
|
</Link>
|
||||||
|
</SectionHeading>
|
||||||
|
</div>
|
||||||
|
<div className="lg:flex lg:flex-auto lg:justify-center">
|
||||||
|
<dl className="flex flex-col justify-center gap-8 sm:flex-row lg:flex-col xl:w-80">
|
||||||
|
<div className="flex flex-col-reverse gap-y-1 border-slate-200 pl-4 text-center sm:border-l sm:text-left dark:border-slate-800">
|
||||||
|
<dt className="text-base/7">faster CI</dt>
|
||||||
|
<dd className="[text-shadow:1px 1px 0 white;] te text-5xl font-semibold tracking-tight text-black dark:text-white">
|
||||||
|
70%
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col-reverse gap-y-1 border-slate-200 pl-4 text-center sm:border-l sm:text-left dark:border-slate-800">
|
||||||
|
<dt className="text-base/7">less compute used</dt>
|
||||||
|
<dd className="text-5xl font-semibold tracking-tight text-black dark:text-white">
|
||||||
|
60%
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col-reverse gap-y-1 border-slate-200 pl-4 text-center sm:border-l sm:text-left dark:border-slate-800">
|
||||||
|
<dt className="text-base/7">reduction in infra costs</dt>
|
||||||
|
<dd className="text-5xl font-semibold tracking-tight text-black dark:text-white">
|
||||||
|
75%
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { ButtonLink } from '@nx/nx-dev/ui-common';
|
import { ButtonLink } from '@nx/nx-dev/ui-common';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
export interface DownloadCaseStudyProps {
|
export interface DownloadCaseStudyProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -12,7 +13,7 @@ export function DownloadCaseStudy({
|
|||||||
description,
|
description,
|
||||||
buttonHref,
|
buttonHref,
|
||||||
buttonText = 'Download (pdf)',
|
buttonText = 'Download (pdf)',
|
||||||
}: DownloadCaseStudyProps): JSX.Element {
|
}: DownloadCaseStudyProps): ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className="border border-slate-100 bg-white shadow-lg sm:rounded-lg dark:border-slate-800/60 dark:bg-slate-950">
|
<div className="border border-slate-100 bg-white shadow-lg sm:rounded-lg dark:border-slate-800/60 dark:bg-slate-950">
|
||||||
<div className="px-4 py-5 sm:p-6">
|
<div className="px-4 py-5 sm:p-6">
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
import { ButtonLink } from '@nx/nx-dev/ui-common';
|
|
||||||
import { ReactElement } from 'react';
|
|
||||||
|
|
||||||
export function DownloadEbook(): ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
||||||
<div className="relative isolate overflow-hidden bg-slate-50/70 px-6 pb-4 pt-16 shadow-2xl ring-1 ring-slate-200 sm:rounded-3xl sm:px-16 sm:pb-0 md:pt-24 lg:flex lg:gap-x-20 lg:px-24 lg:pt-0 dark:border dark:border-slate-800/60 dark:bg-slate-950 dark:ring-slate-800/60">
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
className="absolute left-1/2 top-1/2 -z-10 h-[64rem] w-[64rem] -translate-y-1/2 [mask-image:radial-gradient(closest-side,white,transparent)] sm:left-full sm:-ml-80 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2 lg:translate-y-0"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
cx={512}
|
|
||||||
cy={512}
|
|
||||||
r={512}
|
|
||||||
fill="url(#759c1415-0410-454c-8f7c-9a820de03641)"
|
|
||||||
fillOpacity="0.7"
|
|
||||||
/>
|
|
||||||
<defs>
|
|
||||||
<radialGradient id="759c1415-0410-454c-8f7c-9a820de03641">
|
|
||||||
<stop stopColor="#7775D6" />
|
|
||||||
<stop offset={1} stopColor="#a855f7" />
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<div className="mx-auto max-w-md text-center lg:mx-0 lg:flex-auto lg:py-12 lg:text-left">
|
|
||||||
<h2 className="text-3xl font-bold tracking-tight text-slate-950 sm:text-4xl dark:text-slate-50">
|
|
||||||
Download our ebook
|
|
||||||
</h2>
|
|
||||||
<p className="mt-6 text-lg leading-8">
|
|
||||||
Discover how to scale your organization without feeling the pain of
|
|
||||||
CI, while having a better developer experience and fitting your
|
|
||||||
requirements.
|
|
||||||
</p>
|
|
||||||
<div className="mt-10 flex items-center justify-center gap-x-6 lg:justify-start">
|
|
||||||
<ButtonLink
|
|
||||||
href="https://go.nx.dev/ci-ebook"
|
|
||||||
title="Download Fast CI whitepaper"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<span>Download</span>{' '}
|
|
||||||
<span className="text-xs italic">(pdf)</span>
|
|
||||||
</ButtonLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative mt-16 hidden h-72 sm:block lg:mt-8">
|
|
||||||
<img
|
|
||||||
className="absolute left-0 top-0 w-[42rem] max-w-none rounded-md bg-white/5 ring-1 ring-white/10"
|
|
||||||
src="images/white-paper-ebook.avif"
|
|
||||||
alt="App screenshot"
|
|
||||||
width={1200}
|
|
||||||
height={675}
|
|
||||||
/>
|
|
||||||
<span className="absolute left-4 top-4 inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
|
|
||||||
ebook
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
import {
|
|
||||||
BoltIcon,
|
|
||||||
ChevronDoubleRightIcon,
|
|
||||||
UsersIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
|
||||||
|
|
||||||
export function EnterpriseAddons(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<section id="partner-with-the-nx-team">
|
|
||||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
|
||||||
<div className="mx-auto max-w-2xl text-center">
|
|
||||||
<SectionHeading as="h2" variant="title">
|
|
||||||
Partner with the Nx team
|
|
||||||
</SectionHeading>
|
|
||||||
</div>
|
|
||||||
<div className="mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-none">
|
|
||||||
<dl className="grid max-w-xl grid-cols-1 gap-x-8 gap-y-16 lg:max-w-none lg:grid-cols-3">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<dt className="text-base font-semibold leading-7 text-black dark:text-white">
|
|
||||||
<div className="mb-6 flex h-10 w-10 items-center justify-center rounded-lg bg-black dark:bg-white">
|
|
||||||
<BoltIcon
|
|
||||||
className="h-6 w-6 text-white dark:text-black"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
Move fast, move together
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 flex flex-auto flex-col text-base leading-7">
|
|
||||||
<p className="flex-auto">
|
|
||||||
We know Nx, you know your code. Together, we can build your
|
|
||||||
ultimate developer platform, tailored to your team.
|
|
||||||
</p>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<dt className="text-base font-semibold leading-7 text-black dark:text-white">
|
|
||||||
<div className="mb-6 flex h-10 w-10 items-center justify-center rounded-lg bg-black dark:bg-white">
|
|
||||||
<UsersIcon
|
|
||||||
className="h-6 w-6 text-white dark:text-black"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
No trial and error necessary
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 flex flex-auto flex-col text-base leading-7">
|
|
||||||
<p className="flex-auto">
|
|
||||||
With the help of the developers of Nx, you'll use Nx and Nx
|
|
||||||
Cloud to its full potential, the first time. No matter how
|
|
||||||
long you've been using Nx, we'll find ways to make it more
|
|
||||||
powerful.
|
|
||||||
</p>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<dt className="text-base font-semibold leading-7 text-black dark:text-white">
|
|
||||||
<div className="mb-6 flex h-10 w-10 items-center justify-center rounded-lg bg-black dark:bg-white">
|
|
||||||
<ChevronDoubleRightIcon
|
|
||||||
className="h-6 w-6 text-white dark:text-black"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
Migrate to Nx & Nx Cloud
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 flex flex-auto flex-col text-base leading-7">
|
|
||||||
<p className="flex-auto">
|
|
||||||
Wherever your team is at in their Nx journey, our experts will
|
|
||||||
make it easy to move ahead and capture the full value of Nx
|
|
||||||
and Nx Cloud.
|
|
||||||
</p>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,42 +1,104 @@
|
|||||||
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
|
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
|
||||||
import Link from 'next/link';
|
import { ReactElement } from 'react';
|
||||||
|
import { ChevronRightIcon } from '@heroicons/react/20/solid';
|
||||||
|
import { sendCustomEvent } from '@nx/nx-dev/feature-analytics';
|
||||||
|
|
||||||
export function Hero(): JSX.Element {
|
export function Hero(): ReactElement {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="relative isolate overflow-hidden">
|
||||||
<div className="relative mx-auto max-w-7xl px-6 lg:px-8">
|
<svg
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
focusable="false"
|
||||||
<SectionHeading id="fast-ci-for-monorepo" as="h1" variant="display">
|
aria-hidden="true"
|
||||||
Nx Enterprise
|
role="presentation"
|
||||||
|
className="absolute inset-0 -z-10 size-full stroke-slate-200 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-slate-800"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<pattern
|
||||||
|
x="50%"
|
||||||
|
y={-1}
|
||||||
|
id="0787a7c5-978c-4f66-83c7-11c213f99cb7"
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
patternUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<path d="M.5 200V.5H200" fill="none" />
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect
|
||||||
|
fill="url(#0787a7c5-978c-4f66-83c7-11c213f99cb7)"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
strokeWidth={0}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div className="mx-auto max-w-7xl px-6 pb-24 pt-32 lg:flex lg:px-8 lg:pt-56">
|
||||||
|
<div className="mx-auto max-w-2xl lg:mx-0 lg:-mt-12 lg:shrink-0">
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://bit.ly/3B0Ebfe"
|
||||||
|
title="See live event in details"
|
||||||
|
className="group/event-link inline-flex space-x-6"
|
||||||
|
>
|
||||||
|
<span className="rounded-full bg-blue-600/10 px-3 py-1 text-sm/6 font-semibold text-blue-600 ring-1 ring-inset ring-blue-600/10 dark:bg-cyan-600/10 dark:text-cyan-600 dark:ring-cyan-600/10">
|
||||||
|
Live event
|
||||||
|
</span>
|
||||||
|
<span className="inline-flex items-center space-x-2 text-sm/6 font-medium">
|
||||||
|
<span>Webinar + live Q&A on Dec 10th</span>
|
||||||
|
<ChevronRightIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-5 transform transition-all group-hover/event-link:translate-x-1"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<SectionHeading
|
||||||
|
id="get-speed-and-scale"
|
||||||
|
as="h1"
|
||||||
|
variant="display"
|
||||||
|
className="mt-8 text-pretty tracking-tight"
|
||||||
|
>
|
||||||
|
Solving the Performance Paradox,{' '}
|
||||||
|
<span className="rounded-lg bg-gradient-to-r from-pink-500 to-fuchsia-500 bg-clip-text text-transparent">
|
||||||
|
get speed and scale
|
||||||
|
</span>
|
||||||
</SectionHeading>
|
</SectionHeading>
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
as="p"
|
as="p"
|
||||||
variant="subtitle"
|
variant="subtitle"
|
||||||
className="mx-auto mt-6 max-w-3xl "
|
className="mx-auto mt-6 max-w-3xl lg:pr-20"
|
||||||
>
|
>
|
||||||
Accelerate your organization's journey to tighter collaboration,
|
Accelerate your organization's journey to tighter collaboration,
|
||||||
better developer experience, and speed…lots of speed.
|
better developer experience, and speed…lots of speed.
|
||||||
</SectionHeading>
|
</SectionHeading>
|
||||||
<div className="mt-10 items-center justify-center gap-x-6">
|
<div className="mt-8 flex items-center gap-x-6">
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
href="/contact/sales"
|
href="/contact/sales"
|
||||||
title="Request a free trial"
|
title="Request a free trial"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="default"
|
size="default"
|
||||||
|
onClick={() =>
|
||||||
|
sendCustomEvent(
|
||||||
|
'request-trial-click',
|
||||||
|
'enterprise-hero',
|
||||||
|
'enterprise'
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Request a free trial
|
Request a free trial
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
<p className="mt-6 italic">
|
</div>
|
||||||
Got questions?{' '}
|
</div>
|
||||||
<Link
|
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-8 lg:max-w-none lg:flex-none xl:ml-32">
|
||||||
href="/contact/engineering"
|
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
|
||||||
title="Talk to the sales team"
|
<div className="-m-2 rounded-xl bg-slate-900/5 p-2 ring-1 ring-inset ring-slate-900/10 lg:-m-4 lg:rounded-2xl lg:p-4 dark:bg-slate-100/5 dark:ring-slate-100/10">
|
||||||
className="font-semibold underline"
|
<img
|
||||||
prefetch={false}
|
alt="nx cloud application dashboard screenshot"
|
||||||
>
|
src="/images/home/nx-app-dashboard.avif"
|
||||||
Talk to an engineer.
|
width={2500}
|
||||||
</Link>
|
height={1616}
|
||||||
</p>
|
className="w-[764px] rounded-md shadow-2xl ring-1 ring-slate-900/10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
246
nx-dev/ui-enterprise/src/lib/hetzner-cloud-testimonial.tsx
Normal file
246
nx-dev/ui-enterprise/src/lib/hetzner-cloud-testimonial.tsx
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import { ComponentProps, ReactElement } from 'react';
|
||||||
|
import { Button, ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
|
||||||
|
import { HetznerCloudIcon } from '@nx/nx-dev/ui-icons';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { cx } from '@nx/nx-dev/ui-primitives';
|
||||||
|
import { MovingBorder } from '@nx/nx-dev/ui-animations';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
ArrowDownTrayIcon,
|
||||||
|
PlayIcon,
|
||||||
|
VideoCameraIcon,
|
||||||
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
|
function PlayButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: ComponentProps<'div'>): ReactElement {
|
||||||
|
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">
|
||||||
|
<MovingBorder duration={5000} rx="5%" ry="5%">
|
||||||
|
<div className="size-20 bg-[radial-gradient(var(--blue-500)_40%,transparent_60%)] opacity-[0.8] dark:bg-[radial-gradient(var(--pink-500)_40%,transparent_60%)]" />
|
||||||
|
</MovingBorder>
|
||||||
|
</div>
|
||||||
|
<motion.div
|
||||||
|
initial="initial"
|
||||||
|
whileHover="hover"
|
||||||
|
variants={parent}
|
||||||
|
className="relative isolate flex size-20 cursor-pointer items-center justify-center gap-6 rounded-full border-2 border-slate-100 bg-white/10 p-6 text-sm text-white antialiased backdrop-blur-xl"
|
||||||
|
>
|
||||||
|
<PlayIcon aria-hidden="true" className="absolute left-6 top-6 size-8" />
|
||||||
|
<motion.div variants={child} className="absolute left-20 top-4 w-48">
|
||||||
|
<p className="text-base font-medium">Watch the interview</p>
|
||||||
|
<p># customer story</p>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HetznerCloudTestimonial(): ReactElement {
|
||||||
|
return (
|
||||||
|
<div className="border-b border-t border-slate-200 bg-slate-50 py-24 sm:py-32 dark:border-slate-800 dark:bg-slate-900">
|
||||||
|
<section
|
||||||
|
id="hetzner-cloud-testimonial"
|
||||||
|
className="z-0 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"
|
||||||
|
>
|
||||||
|
<SectionHeading as="h2" variant="title" id="hetzner-cloud-testimonial">
|
||||||
|
Nx Enterprise{' '}
|
||||||
|
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
|
||||||
|
speeds build and test times
|
||||||
|
</span>{' '}
|
||||||
|
<br />
|
||||||
|
as Hetzner Cloud{' '}
|
||||||
|
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
|
||||||
|
scales up
|
||||||
|
</span>{' '}
|
||||||
|
product offering
|
||||||
|
</SectionHeading>
|
||||||
|
<div className="mt-8 md:grid md:grid-cols-2 md:items-center md:gap-10 lg:gap-12">
|
||||||
|
<div className="mb-24 hidden sm:px-6 md:mb-0 md:block">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute bottom-0 start-0 -translate-x-14 translate-y-10">
|
||||||
|
<svg
|
||||||
|
className="h-auto max-w-40 text-slate-200 dark:text-slate-800"
|
||||||
|
width="696"
|
||||||
|
height="653"
|
||||||
|
viewBox="0 0 696 653"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="72.5" cy="29.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="171.5" cy="29.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="270.5" cy="29.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="369.5" cy="29.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="468.5" cy="29.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="567.5" cy="29.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="666.5" cy="29.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="29.5" cy="128.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="128.5" cy="128.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="227.5" cy="128.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="326.5" cy="128.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="425.5" cy="128.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="524.5" cy="128.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="623.5" cy="128.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="72.5" cy="227.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="171.5" cy="227.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="270.5" cy="227.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="369.5" cy="227.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="468.5" cy="227.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="567.5" cy="227.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="666.5" cy="227.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="29.5" cy="326.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="128.5" cy="326.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="227.5" cy="326.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="326.5" cy="326.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="425.5" cy="326.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="524.5" cy="326.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="623.5" cy="326.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="72.5" cy="425.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="171.5" cy="425.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="270.5" cy="425.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="369.5" cy="425.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="468.5" cy="425.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="567.5" cy="425.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="666.5" cy="425.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="29.5" cy="524.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="128.5" cy="524.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="227.5" cy="524.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="326.5" cy="524.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="425.5" cy="524.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="524.5" cy="524.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="623.5" cy="524.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="72.5" cy="623.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="171.5" cy="623.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="270.5" cy="623.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="369.5" cy="623.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="468.5" cy="623.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="567.5" cy="623.5" r="29.5" fill="currentColor" />
|
||||||
|
<circle cx="666.5" cy="623.5" r="29.5" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
src="/images/enterprise/video-story-pavlo-grosse.avif"
|
||||||
|
alt="Avatar"
|
||||||
|
className="relative rounded-xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 grid h-full w-full items-center justify-center">
|
||||||
|
{/*<PlayButton*/}
|
||||||
|
{/*onClick={() => setIsOpen(true)}*/}
|
||||||
|
{/*/>*/}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<blockquote className="relative">
|
||||||
|
<svg
|
||||||
|
className="absolute start-0 top-0 size-24 -translate-x-8 -translate-y-4 transform text-slate-200 dark:text-slate-800"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7.39762 10.3C7.39762 11.0733 7.14888 11.7 6.6514 12.18C6.15392 12.6333 5.52552 12.86 4.76621 12.86C3.84979 12.86 3.09047 12.5533 2.48825 11.94C1.91222 11.3266 1.62421 10.4467 1.62421 9.29999C1.62421 8.07332 1.96459 6.87332 2.64535 5.69999C3.35231 4.49999 4.33418 3.55332 5.59098 2.85999L6.4943 4.25999C5.81354 4.73999 5.26369 5.27332 4.84476 5.85999C4.45201 6.44666 4.19017 7.12666 4.05926 7.89999C4.29491 7.79332 4.56983 7.73999 4.88403 7.73999C5.61716 7.73999 6.21938 7.97999 6.69067 8.45999C7.16197 8.93999 7.39762 9.55333 7.39762 10.3ZM14.6242 10.3C14.6242 11.0733 14.3755 11.7 13.878 12.18C13.3805 12.6333 12.7521 12.86 11.9928 12.86C11.0764 12.86 10.3171 12.5533 9.71484 11.94C9.13881 11.3266 8.85079 10.4467 8.85079 9.29999C8.85079 8.07332 9.19117 6.87332 9.87194 5.69999C10.5789 4.49999 11.5608 3.55332 12.8176 2.85999L13.7209 4.25999C13.0401 4.73999 12.4903 5.27332 12.0713 5.85999C11.6786 6.44666 11.4168 7.12666 11.2858 7.89999C11.5215 7.79332 11.7964 7.73999 12.1106 7.73999C12.8437 7.73999 13.446 7.97999 13.9173 8.45999C14.3886 8.93999 14.6242 9.55333 14.6242 10.3Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div className="relative z-10">
|
||||||
|
<p className="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-neutral-200">
|
||||||
|
Featured client
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-xl font-medium italic text-slate-800 md:text-2xl md:leading-normal xl:text-3xl xl:leading-normal dark:text-neutral-200">
|
||||||
|
Nx is speed and scalability. Before we only had a few features
|
||||||
|
and CI was slow and now it’s fast with way more features.
|
||||||
|
That’s a huge win for us.”.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<figcaption className="mt-6 flex flex-wrap items-center gap-4 sm:flex-nowrap">
|
||||||
|
<img
|
||||||
|
alt="pavlo grosse"
|
||||||
|
src="https://avatars.githubusercontent.com/u/2219064?v=4"
|
||||||
|
className="size-12 flex-none rounded-full bg-gray-50"
|
||||||
|
/>
|
||||||
|
<div className="flex-auto">
|
||||||
|
<div className="text-base font-semibold">Pavlo Grosse</div>
|
||||||
|
<div className="text-xs text-slate-600 dark:text-slate-500">
|
||||||
|
Senior Software Engineer, Hetzner Cloud
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<HetznerCloudIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="mx-auto size-10 flex-none bg-white text-[#D50C2D]"
|
||||||
|
/>
|
||||||
|
</figcaption>
|
||||||
|
|
||||||
|
<footer className="mt-8 flex items-center gap-6">
|
||||||
|
{/*<Button*/}
|
||||||
|
{/* title="Watch the customer story"*/}
|
||||||
|
{/* variant="secondary"*/}
|
||||||
|
{/* size="small"*/}
|
||||||
|
{/*>*/}
|
||||||
|
{/* <PlayIcon aria-hidden="true" className="size-5 shrink-0" />*/}
|
||||||
|
{/* <span>Watch the customer story</span>*/}
|
||||||
|
{/*</Button>*/}
|
||||||
|
<Link
|
||||||
|
href="/customers"
|
||||||
|
prefetch={false}
|
||||||
|
className="text-sm/6 font-semibold"
|
||||||
|
>
|
||||||
|
See our customers <span aria-hidden="true">→</span>
|
||||||
|
</Link>
|
||||||
|
</footer>
|
||||||
|
</blockquote>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
143
nx-dev/ui-enterprise/src/lib/make-your-ci-fast.tsx
Normal file
143
nx-dev/ui-enterprise/src/lib/make-your-ci-fast.tsx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
||||||
|
|
||||||
|
export function MakeYourCiFast(): ReactElement {
|
||||||
|
return (
|
||||||
|
<section className="make-your-ci-fast-section">
|
||||||
|
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
|
||||||
|
<SectionHeading id="make-your-ci-fast" as="h1" variant="title">
|
||||||
|
Make your CI fast,{' '}
|
||||||
|
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
|
||||||
|
really fast
|
||||||
|
</span>
|
||||||
|
</SectionHeading>
|
||||||
|
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6">
|
||||||
|
<div className="relative lg:col-span-2">
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] bg-white shadow ring-1 ring-black/5 lg:rounded-tl-[calc(2rem+1px)] dark:bg-slate-950 dark:ring-white/10">
|
||||||
|
<img
|
||||||
|
alt="Nx Replay: remote caching"
|
||||||
|
src="/images/enterprise/nx-replay.avif"
|
||||||
|
className="h-80 object-cover object-left"
|
||||||
|
/>
|
||||||
|
<div className="relative p-10 pt-4">
|
||||||
|
<div className="absolute -top-10 left-10">
|
||||||
|
<span className="inline-flex items-center rounded-md bg-slate-400/10 px-2 py-1 text-xs font-medium text-slate-400 ring-1 ring-inset ring-slate-400/20">
|
||||||
|
Nx Replay
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-medium tracking-tight text-slate-950 dark:text-white">
|
||||||
|
Never build the same code twice
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 max-w-lg text-sm/6">
|
||||||
|
Remote caching ensures that tasks are never rebuilt twice,
|
||||||
|
significantly reducing build times and resource usage. Share
|
||||||
|
cached results across your team and CI pipelines.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative lg:col-span-2">
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] bg-white shadow ring-1 ring-black/5 dark:bg-slate-950 dark:ring-white/10">
|
||||||
|
<img
|
||||||
|
alt="Nx flaky task detection & rerun"
|
||||||
|
src="/images/enterprise/nx-flaky-tasks-detection.avif"
|
||||||
|
className="h-80 object-cover"
|
||||||
|
/>
|
||||||
|
<div className="relative p-10 pt-4">
|
||||||
|
<div className="absolute -top-10 left-10">
|
||||||
|
<span className="inline-flex items-center rounded-md bg-slate-400/10 px-2 py-1 text-xs font-medium text-slate-400 ring-1 ring-inset ring-slate-400/20">
|
||||||
|
Flaky task retries
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-medium tracking-tight text-slate-950 dark:text-white">
|
||||||
|
One less thing to debug
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 max-w-lg text-sm/6">
|
||||||
|
Automatically detect and re-run flaky tasks, enhancing the
|
||||||
|
reliability of your CI processes and minimizing time spent
|
||||||
|
debugging.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative lg:col-span-2">
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] bg-white shadow ring-1 ring-black/5 max-lg:rounded-t-[calc(2rem+1px)] lg:rounded-tr-[calc(2rem+1px)] dark:bg-slate-950 dark:ring-white/10">
|
||||||
|
<img
|
||||||
|
alt="Nx Atomizer: split large tasks & E2E in chunks"
|
||||||
|
src="/images/enterprise/nx-atomizer.avif"
|
||||||
|
className="h-80 object-cover object-left"
|
||||||
|
/>
|
||||||
|
<div className="relative p-10 pt-4">
|
||||||
|
<div className="absolute -top-10 left-10">
|
||||||
|
<span className="inline-flex items-center rounded-md bg-slate-400/10 px-2 py-1 text-xs font-medium text-slate-400 ring-1 ring-inset ring-slate-400/20">
|
||||||
|
Atomizer
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-medium tracking-tight text-slate-950 dark:text-white">
|
||||||
|
Break tasks down, to speed tests up
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 max-w-lg text-sm/6">
|
||||||
|
<span className="font-semibold">Atomizer</span> automatically
|
||||||
|
splits large e2e tests into smaller, atomized tasks, enabling
|
||||||
|
lightning fast testing. Parallelize your tests to reduce
|
||||||
|
bottlenecks and keep your pipelines fast.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative lg:col-span-3">
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] bg-white shadow ring-1 ring-black/5 max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-bl-[calc(2rem+1px)] dark:bg-slate-950 dark:ring-white/10">
|
||||||
|
<img
|
||||||
|
alt="Nx Agents: simple & fast task distribution"
|
||||||
|
src="/images/enterprise/nx-agents.avif"
|
||||||
|
className="h-80 object-cover object-left"
|
||||||
|
/>
|
||||||
|
<div className="relative p-10 pt-4">
|
||||||
|
<div className="absolute -top-10 left-10">
|
||||||
|
<span className="inline-flex items-center rounded-md bg-slate-400/10 px-2 py-1 text-xs font-medium text-slate-400 ring-1 ring-inset ring-slate-400/20">
|
||||||
|
Nx Agents
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-medium tracking-tight text-slate-950 dark:text-white">
|
||||||
|
Machines, make it fast!
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 max-w-lg text-sm/6">
|
||||||
|
<span className="font-semibold">Nx Agents</span> intelligently
|
||||||
|
distribute tasks across multiple machines, significantly
|
||||||
|
reducing build times. Dynamically allocate agents based on PR
|
||||||
|
size to balance speed and cost.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative lg:col-span-3">
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] bg-white shadow ring-1 ring-black/5 max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)] dark:bg-slate-950 dark:ring-white/10">
|
||||||
|
<img
|
||||||
|
alt="Partner with the Nx Team for guidances"
|
||||||
|
src="/images/enterprise/nx-partnership.avif"
|
||||||
|
className="h-80 object-cover object-left lg:object-right"
|
||||||
|
/>
|
||||||
|
<div className="relative p-10 pt-4">
|
||||||
|
<div className="absolute -top-10 left-10">
|
||||||
|
<span className="inline-flex items-center rounded-md bg-slate-400/10 px-2 py-1 text-xs font-medium text-slate-400 ring-1 ring-inset ring-slate-400/20">
|
||||||
|
Partnership
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-medium tracking-tight text-slate-950 dark:text-white">
|
||||||
|
Always thinking a step ahead for you
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 max-w-lg text-sm/6">
|
||||||
|
With Nx, you receive expert guidance from day one, ensuring
|
||||||
|
your setup is optimized for maximum efficiency. Whether you're
|
||||||
|
starting fresh, migrating, or scaling your developer platform,
|
||||||
|
we'll work with you to tailor the perfect solution for your
|
||||||
|
team.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,185 +0,0 @@
|
|||||||
import { motion, Variants } from 'framer-motion';
|
|
||||||
import { DownloadCaseStudy } from './download-case-study';
|
|
||||||
import {
|
|
||||||
BillColoredIcon,
|
|
||||||
CapitalOneIcon,
|
|
||||||
CaterpillarIcon,
|
|
||||||
CiscoIcon,
|
|
||||||
FicoIcon,
|
|
||||||
HiltonIcon,
|
|
||||||
ManIcon,
|
|
||||||
RoyalBankOfCanadaColoredIcon,
|
|
||||||
SevenElevenColoredIcon,
|
|
||||||
ShopifyIcon,
|
|
||||||
StorybookIcon,
|
|
||||||
VmwareIcon,
|
|
||||||
} from '@nx/nx-dev/ui-icons';
|
|
||||||
|
|
||||||
export function MetricsAndCustomers(): JSX.Element {
|
|
||||||
const downloadElement: Variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
translateY: 90,
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
translateY: 0,
|
|
||||||
transition: {
|
|
||||||
duration: 1,
|
|
||||||
ease: 'easeInOut',
|
|
||||||
type: 'tween',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className="relative isolate pb-24 pt-16">
|
|
||||||
<svg
|
|
||||||
className="absolute inset-0 -z-10 h-full w-full rotate-180 transform stroke-slate-100 [mask-image:radial-gradient(100%_100%_at_top,white,transparent)] dark:stroke-slate-800/60 dark:[mask-image:radial-gradient(100%_100%_at_top,black,transparent)]"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<pattern
|
|
||||||
id="83dwp7e5a-9d52-45fc-17c6-718e5d7fe918"
|
|
||||||
width={200}
|
|
||||||
height={200}
|
|
||||||
x="50%"
|
|
||||||
y={-1}
|
|
||||||
patternUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<path d="M100 200V.5M.5 .5H200" fill="none" />
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<svg
|
|
||||||
x="50%"
|
|
||||||
y={-1}
|
|
||||||
className="overflow-visible fill-slate-50/15 dark:fill-slate-900/10"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M-100.5 0h201v201h-201Z M699.5 0h201v201h-201Z M499.5 400h201v201h-201Z M-300.5 600h201v201h-201Z"
|
|
||||||
strokeWidth={0}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<rect
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
strokeWidth={0}
|
|
||||||
fill="url(#83dwp7e5a-9d52-45fc-17c6-718e5d7fe918)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
|
||||||
<dl className="grid grid-cols-1 gap-4 text-center md:grid-cols-3">
|
|
||||||
<div className="p-4 text-xl">
|
|
||||||
<dt className="text-center text-xs uppercase">Speed</dt>
|
|
||||||
<dd className="mt-2 ">
|
|
||||||
<span className="text-3xl font-semibold text-slate-950 dark:text-slate-50">
|
|
||||||
30-70% Faster
|
|
||||||
</span>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div className="p-4 text-xl">
|
|
||||||
<dt className="text-center text-xs uppercase">Infra Cost</dt>
|
|
||||||
<dd className="mt-2 ">
|
|
||||||
<span className="text-3xl font-semibold text-slate-950 dark:text-slate-50">
|
|
||||||
40-75% Cheaper
|
|
||||||
</span>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div className="p-4 text-xl">
|
|
||||||
<dt className="text-center text-xs uppercase">Compute</dt>
|
|
||||||
<dd className="mt-2 ">
|
|
||||||
<span className="text-3xl font-semibold text-slate-950 dark:text-slate-50">
|
|
||||||
30-60% Less
|
|
||||||
</span>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
<div className="mt-12 grid grid-cols-2 justify-between gap-2 md:mt-0 md:flex">
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<ManIcon aria-hidden="true" className="h-14 w-14 text-[#E40045]" />
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
|
|
||||||
<CapitalOneIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-28 w-28 text-black dark:text-white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<ShopifyIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-12 w-12 text-[#7AB55C]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
|
|
||||||
<RoyalBankOfCanadaColoredIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-14 w-14"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
|
|
||||||
<VmwareIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-28 w-28 text-black dark:text-white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<StorybookIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-12 w-12 text-[#FF4785]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
|
|
||||||
<FicoIcon aria-hidden="true" className="h-28 w-28 text-[#0A6DE6]" />
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<CaterpillarIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-14 w-14 text-[#FFCD11]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative mt-12 flex">
|
|
||||||
<div className="hidden w-1/4 items-center gap-20 lg:flex">
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<CiscoIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-16 w-16 text-[#1BA0D7]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<BillColoredIcon aria-hidden="true" className="h-14 w-14" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grow lg:w-1/2">
|
|
||||||
<motion.div
|
|
||||||
initial="hidden"
|
|
||||||
animate="visible"
|
|
||||||
variants={downloadElement}
|
|
||||||
whileInView="visible"
|
|
||||||
className="mx-auto max-w-xl"
|
|
||||||
>
|
|
||||||
<DownloadCaseStudy
|
|
||||||
title="Banking Case Study"
|
|
||||||
description="See how a $7B bank saved money, reduced CI times by 62% and improved developer productivity."
|
|
||||||
buttonHref="https://go.nx.dev/banking-case-study"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
<div className="hidden w-1/4 items-center justify-end gap-20 lg:flex">
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<SevenElevenColoredIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-14 w-14"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1 hidden h-14 items-center justify-center lg:flex lg:h-28">
|
|
||||||
<HiltonIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-20 w-20 text-black dark:text-white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
319
nx-dev/ui-enterprise/src/lib/scale-your-organization.tsx
Normal file
319
nx-dev/ui-enterprise/src/lib/scale-your-organization.tsx
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
import { SectionHeading, Strong } from '@nx/nx-dev/ui-common';
|
||||||
|
import {
|
||||||
|
AcademicCapIcon,
|
||||||
|
ArrowPathIcon,
|
||||||
|
CheckBadgeIcon,
|
||||||
|
CodeBracketSquareIcon,
|
||||||
|
DocumentMagnifyingGlassIcon,
|
||||||
|
DocumentTextIcon,
|
||||||
|
EyeIcon,
|
||||||
|
ShieldExclamationIcon,
|
||||||
|
UsersIcon,
|
||||||
|
} from '@heroicons/react/24/outline';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export function ScaleYourOrganization(): ReactElement {
|
||||||
|
return (
|
||||||
|
<section className="overflow-hidden">
|
||||||
|
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||||
|
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-x-8 gap-y-16 sm:gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-2 lg:items-start">
|
||||||
|
<div className="flex items-start justify-end lg:order-first">
|
||||||
|
<img
|
||||||
|
alt="Nx Polygraph"
|
||||||
|
src="/images/enterprise/graphs.jpg"
|
||||||
|
width={3405}
|
||||||
|
height={1493}
|
||||||
|
className="w-[48rem] max-w-none rounded-xl shadow-xl ring-1 ring-slate-400/10 sm:w-[57rem] md:-mr-4 lg:mr-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="lg:ml-auto lg:pl-4 lg:pt-4">
|
||||||
|
<div className="lg:max-w-lg">
|
||||||
|
<SectionHeading as="p" variant="subtitle" className="mt-6">
|
||||||
|
Get to market faster by avoiding the most common pitfall of
|
||||||
|
growing teams: silos. Silos create waste, have poorly defined
|
||||||
|
ownership, lack visibility - and slow everything down.
|
||||||
|
</SectionHeading>
|
||||||
|
<SectionHeading as="p" variant="subtitle" className="mt-4">
|
||||||
|
Polygraph is a set of Nx Enterprise features built to help large
|
||||||
|
organizations{' '}
|
||||||
|
<Strong>
|
||||||
|
see across workspaces and take action to accelerate
|
||||||
|
time-to-market
|
||||||
|
</Strong>
|
||||||
|
.
|
||||||
|
</SectionHeading>
|
||||||
|
<figure className="mt-12 border-l border-slate-200 pl-8 dark:border-slate-800">
|
||||||
|
<blockquote className="text-base/7">
|
||||||
|
<p>
|
||||||
|
“Nx means tooling and efficiency around our software
|
||||||
|
development lifecycle that empowers us to move a lot faster,
|
||||||
|
ship code faster and more reliably.”
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
<figcaption className="mt-6 flex items-center gap-x-4 text-sm/6">
|
||||||
|
<img
|
||||||
|
alt="Justin Schwartzenberger"
|
||||||
|
src="https://avatars.githubusercontent.com/u/1243236?v=4"
|
||||||
|
className="size-8 flex-none rounded-full"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold">Justin Schwartzenberger</div>
|
||||||
|
<div className="text-slate-500">
|
||||||
|
Principle Software Engineer, SiriusXM
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto mt-16 max-w-4xl sm:mt-20 lg:mt-24">
|
||||||
|
<dl className="mx-auto mt-10 flex flex-col">
|
||||||
|
<div className="group/section flex items-center gap-16">
|
||||||
|
<div className="hidden shrink-0 p-6 sm:p-12 lg:block">
|
||||||
|
<div className="relative grid size-12 place-items-center">
|
||||||
|
<EyeIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-8 transition-all group-hover/section:-translate-x-2"
|
||||||
|
/>
|
||||||
|
<DocumentMagnifyingGlassIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 size-8 opacity-0 transition-all group-hover/section:-translate-y-2 group-hover/section:translate-x-8 group-hover/section:opacity-100"
|
||||||
|
/>
|
||||||
|
<CodeBracketSquareIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 size-8 opacity-0 transition-all group-hover/section:translate-x-7 group-hover/section:translate-y-6 group-hover/section:opacity-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex gap-x-4">
|
||||||
|
<div className="absolute -bottom-6 left-0 top-0 flex w-6 justify-center">
|
||||||
|
<div className="w-px bg-slate-200 dark:bg-slate-800" />
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-1 flex size-6 flex-none items-center justify-center bg-white dark:bg-slate-950">
|
||||||
|
<div className="size-1.5 rounded-full bg-slate-100 ring-1 ring-slate-300 dark:bg-slate-800 dark:ring-slate-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col pb-10">
|
||||||
|
<dt className="text-base font-semibold leading-7 text-black dark:text-white">
|
||||||
|
<div className="mb-6 flex">
|
||||||
|
<div className="rounded-lg bg-blue-500 px-2 py-0.5 font-semibold text-white dark:bg-white dark:text-black">
|
||||||
|
Visibility
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
See dependencies across teams and repos
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 flex flex-auto flex-col text-base leading-7">
|
||||||
|
<p className="flex-auto">
|
||||||
|
In an organization with hundreds of repos it can be hard
|
||||||
|
to understand the relationships between different parts of
|
||||||
|
your ever-growing codebases. With the Workspace Graph, see
|
||||||
|
dependencies across all your repos to understand which
|
||||||
|
projects and teams are affected by changes in a single
|
||||||
|
repo.
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="group/section flex items-center gap-16">
|
||||||
|
<div className="hidden shrink-0 p-6 sm:p-12 lg:block">
|
||||||
|
<div className="relative grid size-12 place-items-center">
|
||||||
|
<CheckBadgeIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-8 transition-all group-hover/section:-translate-x-2"
|
||||||
|
/>
|
||||||
|
<DocumentTextIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 size-8 opacity-0 transition-all group-hover/section:-translate-y-2 group-hover/section:translate-x-8 group-hover/section:opacity-100"
|
||||||
|
/>
|
||||||
|
<ArrowPathIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 size-8 opacity-0 transition-all group-hover/section:translate-x-7 group-hover/section:translate-y-6 group-hover/section:opacity-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="group/section relative flex gap-x-4">
|
||||||
|
<div className="absolute -bottom-6 left-0 top-0 flex w-6 justify-center">
|
||||||
|
<div className="w-px bg-slate-200 dark:bg-slate-800" />
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-1 flex size-6 flex-none items-center justify-center bg-white dark:bg-slate-950">
|
||||||
|
<div className="size-1.5 rounded-full bg-slate-100 ring-1 ring-slate-300 dark:bg-slate-800 dark:ring-slate-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col pb-10">
|
||||||
|
<dt className="text-base font-semibold leading-7 text-black dark:text-white">
|
||||||
|
<div className="mb-6 flex">
|
||||||
|
<div className="rounded-lg bg-blue-500 px-2 py-0.5 font-semibold text-white dark:bg-white dark:text-black">
|
||||||
|
Conformance
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
Streamline onboarding and governance
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 flex flex-auto flex-col text-base leading-7">
|
||||||
|
<p className="flex-auto">
|
||||||
|
Platform teams can easily publish and enforce coding
|
||||||
|
standards across an entire organization. Write and publish
|
||||||
|
rules, refactorings, and generators, then quickly deploy
|
||||||
|
them across all repos without involving individual teams.
|
||||||
|
Ensure security vulnerabilities are always addressed and
|
||||||
|
third-party dependencies stay up to date.{' '}
|
||||||
|
<i>
|
||||||
|
Included in{' '}
|
||||||
|
<Link
|
||||||
|
href="/powerpack"
|
||||||
|
title="Nx Powerpack"
|
||||||
|
prefetch={false}
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Nx Powerpack
|
||||||
|
</Link>
|
||||||
|
, and available complimentary to all Enterprise
|
||||||
|
customers.
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
|
<ul className="mt-6 space-y-2 pl-4">
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
href="/ci/recipes/enterprise/conformance/configure-conformance-rules-in-nx-cloud"
|
||||||
|
prefetch={false}
|
||||||
|
title="Configure Conformance Rules"
|
||||||
|
className="text-sm/6 font-semibold"
|
||||||
|
>
|
||||||
|
Configure Conformance Rules{' '}
|
||||||
|
<span aria-hidden="true">→</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
href="/ci/recipes/enterprise/conformance/publish-conformance-rules-to-nx-cloud"
|
||||||
|
prefetch={false}
|
||||||
|
title="Publish Conformance Rules"
|
||||||
|
className="text-sm/6 font-semibold"
|
||||||
|
>
|
||||||
|
Publish Conformance Rules{' '}
|
||||||
|
<span aria-hidden="true">→</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="group/section flex items-center gap-16">
|
||||||
|
<div className="hidden shrink-0 p-6 sm:p-12 lg:block">
|
||||||
|
<div className="relative grid size-12 place-items-center">
|
||||||
|
<UsersIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-8 transition-all group-hover/section:-translate-x-2"
|
||||||
|
/>
|
||||||
|
<AcademicCapIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 size-8 opacity-0 transition-all group-hover/section:-translate-y-2 group-hover/section:translate-x-8 group-hover/section:opacity-100"
|
||||||
|
/>
|
||||||
|
<ShieldExclamationIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 size-8 opacity-0 transition-all group-hover/section:translate-x-8 group-hover/section:translate-y-6 group-hover/section:opacity-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex gap-x-4">
|
||||||
|
<div className="absolute -bottom-6 left-0 top-0 flex w-6 justify-center">
|
||||||
|
<div className="w-px bg-slate-200 dark:bg-slate-800" />
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-1 flex size-6 flex-none items-center justify-center bg-white dark:bg-slate-950">
|
||||||
|
<div className="size-1.5 rounded-full bg-slate-100 ring-1 ring-slate-300 dark:bg-slate-800 dark:ring-slate-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col pb-10">
|
||||||
|
<dt className="text-base font-semibold leading-7 text-black dark:text-white">
|
||||||
|
<div className="mb-6 flex">
|
||||||
|
<div className="rounded-lg bg-blue-500 px-2 py-0.5 font-semibold text-white dark:bg-white dark:text-black">
|
||||||
|
Ownership
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
Clear, expressive ownership
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 flex flex-auto flex-col text-base leading-7">
|
||||||
|
<p className="flex-auto">
|
||||||
|
Unlike the code ownership tools from VCS providers, Nx
|
||||||
|
Owners is built for monorepos. Define ownership at the
|
||||||
|
project level. Nx will compile it back to the file-based
|
||||||
|
rules your VCS providers understand.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Polygraph lets you see owners across all your repositories
|
||||||
|
and plan out multi repo refactorings and migrations.{' '}
|
||||||
|
<i>
|
||||||
|
Included in{' '}
|
||||||
|
<Link
|
||||||
|
href="/powerpack"
|
||||||
|
title="Nx Powerpack"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Nx Powerpack
|
||||||
|
</Link>
|
||||||
|
, and available complimentary to all Enterprise
|
||||||
|
customers.
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScaleOrganizationIntro(): ReactElement {
|
||||||
|
return (
|
||||||
|
<section className="relative isolate px-6 py-24 sm:py-32 lg:px-8">
|
||||||
|
<svg
|
||||||
|
focusable={false}
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 -z-10 h-full w-full stroke-black/10 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-white/20"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<pattern
|
||||||
|
id="1d4240dd-898f-445f-932d-e2872fd12de3"
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
x="50%"
|
||||||
|
y={0}
|
||||||
|
patternUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<path d="M.5 200V.5H200" fill="none" />
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<svg
|
||||||
|
x="50%"
|
||||||
|
y={0}
|
||||||
|
className="overflow-visible fill-slate-200/20 dark:fill-slate-800/60"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
|
||||||
|
strokeWidth={0}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<rect
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
strokeWidth={0}
|
||||||
|
fill="url(#1d4240dd-898f-445f-932d-e2872fd12de3)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div className="mx-auto max-w-5xl text-center">
|
||||||
|
<SectionHeading as="h2" variant="title" id="scale-your-organization">
|
||||||
|
Don’t lose velocity as your organization grows{' '}
|
||||||
|
<br className="xl:block" />
|
||||||
|
<span className="line-through">10x developer</span>{' '}
|
||||||
|
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
|
||||||
|
Nx developers are even more efficient at scale
|
||||||
|
</span>
|
||||||
|
</SectionHeading>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,255 +0,0 @@
|
|||||||
import {
|
|
||||||
BuildingOffice2Icon,
|
|
||||||
Cog6ToothIcon,
|
|
||||||
CubeTransparentIcon,
|
|
||||||
IdentificationIcon,
|
|
||||||
SquaresPlusIcon,
|
|
||||||
UserGroupIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
|
||||||
|
|
||||||
export function ScaleYourPeople(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<section id="scale-your-people">
|
|
||||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
|
||||||
<SectionHeading as="h2" variant="title">
|
|
||||||
Scale your people
|
|
||||||
</SectionHeading>
|
|
||||||
</div>
|
|
||||||
<SectionHeading as="p" variant="subtitle" className="mt-6">
|
|
||||||
Build big things with the efficiency of a small team by increasing
|
|
||||||
collaboration and developer mobility, reducing wait time and
|
|
||||||
duplication, and establishing clear ownership.
|
|
||||||
</SectionHeading>
|
|
||||||
<div className="mx-auto mt-12 grid max-w-2xl grid-cols-1 gap-2 md:gap-y-16 lg:max-w-none lg:grid-cols-4">
|
|
||||||
<div className="relative rounded-md bg-slate-100 px-4 py-3 text-slate-900 dark:bg-slate-800 dark:text-slate-100">
|
|
||||||
<div className="flex items-center gap-3 text-lg font-medium leading-6">
|
|
||||||
<CubeTransparentIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-5 w-5 flex-none"
|
|
||||||
/>
|
|
||||||
Visibility
|
|
||||||
</div>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 4 12"
|
|
||||||
fill="currentColor"
|
|
||||||
className="absolute right-0 top-1/2 hidden h-6 w-2 -translate-y-1/2 translate-x-full transform text-slate-100 lg:block dark:text-slate-800"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3.26 4.9a2 2 0 0 1 0 2.2L0 12V0l3.26 4.9z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="relative rounded-md bg-slate-100 px-4 py-3 text-slate-900 dark:bg-slate-800 dark:text-slate-100">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 4 12"
|
|
||||||
fill="currentColor"
|
|
||||||
className="absolute left-0 top-1/2 hidden h-6 w-2 -translate-y-1/2 transform text-white lg:block dark:text-slate-950"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3.26 4.9a2 2 0 0 1 0 2.2L0 12V0l3.26 4.9z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div className="flex items-center gap-3 text-lg font-medium leading-6">
|
|
||||||
<IdentificationIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-5 w-5 flex-none"
|
|
||||||
/>
|
|
||||||
Ownership
|
|
||||||
</div>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 4 12"
|
|
||||||
fill="currentColor"
|
|
||||||
className="absolute right-0 top-1/2 hidden h-6 w-2 -translate-y-1/2 translate-x-full transform text-slate-100 lg:block dark:text-slate-800"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3.26 4.9a2 2 0 0 1 0 2.2L0 12V0l3.26 4.9z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="relative rounded-md bg-slate-100 px-4 py-3 text-slate-900 dark:bg-slate-800 dark:text-slate-100">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 4 12"
|
|
||||||
fill="currentColor"
|
|
||||||
className="absolute left-0 top-1/2 hidden h-6 w-2 -translate-y-1/2 transform text-white lg:block dark:text-slate-950"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3.26 4.9a2 2 0 0 1 0 2.2L0 12V0l3.26 4.9z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div className="flex items-center gap-3 text-lg font-medium leading-6">
|
|
||||||
<UserGroupIcon aria-hidden="true" className="h-5 w-5 flex-none" />
|
|
||||||
Control
|
|
||||||
</div>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 4 12"
|
|
||||||
fill="currentColor"
|
|
||||||
className="absolute right-0 top-1/2 hidden h-6 w-2 -translate-y-1/2 translate-x-full transform text-slate-100 lg:block dark:text-slate-800"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3.26 4.9a2 2 0 0 1 0 2.2L0 12V0l3.26 4.9z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="relative rounded-md bg-slate-100 px-4 py-3 text-slate-900 dark:bg-slate-800 dark:text-slate-100">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 4 12"
|
|
||||||
fill="currentColor"
|
|
||||||
className="absolute left-0 top-1/2 hidden h-6 w-2 -translate-y-1/2 transform text-white lg:block dark:text-slate-950"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3.26 4.9a2 2 0 0 1 0 2.2L0 12V0l3.26 4.9z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div className="flex items-center gap-3 text-lg font-medium leading-6">
|
|
||||||
<Cog6ToothIcon aria-hidden="true" className="h-5 w-5 flex-none" />
|
|
||||||
Automation
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<picture className="block py-12">
|
|
||||||
<img
|
|
||||||
src="/images/enterprise/graphs.jpg"
|
|
||||||
alt="Product screenshot"
|
|
||||||
className="mx-auto max-w-full rounded-xl shadow-xl ring-1 ring-slate-400/10"
|
|
||||||
width={2500}
|
|
||||||
height={1616}
|
|
||||||
/>
|
|
||||||
</picture>
|
|
||||||
<div className="relative mx-auto mt-16 max-w-2xl space-y-12 sm:mt-20">
|
|
||||||
<svg
|
|
||||||
className="absolute left-0 top-0 -z-10 -ml-20 hidden -translate-x-full -translate-y-1/2 transform lg:block"
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 200 400"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<pattern
|
|
||||||
id="de316486-4a29-4312-bdfc-fbce2132a2c1"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
patternUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={4}
|
|
||||||
height={4}
|
|
||||||
className="text-slate-100 dark:text-slate-800/60"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<rect
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="url(#de316486-4a29-4312-bdfc-fbce2132a2c1)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
className="absolute bottom-0 right-0 -z-10 -mr-20 hidden translate-x-full translate-y-1/2 transform lg:block"
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 200 400"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<pattern
|
|
||||||
id="de316486-4a29-4312-bdfc-fbce2132a2c1"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
patternUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={4}
|
|
||||||
height={4}
|
|
||||||
className="text-slate-100 dark:text-slate-800/60"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<rect
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="url(#de316486-4a29-4312-bdfc-fbce2132a2c1)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div className="space-y-6 lg:space-y-12">
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<span className="inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30">
|
|
||||||
coming soon
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="rounded-full p-3 shadow-sm ring-1 ring-slate-200 dark:ring-slate-800/60">
|
|
||||||
<SquaresPlusIcon className="h-5 w-5 text-slate-900 dark:text-slate-100" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
|
||||||
Monorepo, polyrepo, multi-monorepo?
|
|
||||||
</h4>
|
|
||||||
<p className="mt-2">
|
|
||||||
Whatever you’re working with, Nx Enterprise will give you the
|
|
||||||
visibility you need to understand what they have in common,
|
|
||||||
how they relate, and how they differ.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="rounded-full p-3 shadow-sm ring-1 ring-slate-200 dark:ring-slate-800/60">
|
|
||||||
<Cog6ToothIcon className="h-5 w-5 text-slate-900 dark:text-slate-100" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
|
||||||
Monorepo experience in a polyrepo environment
|
|
||||||
</h4>
|
|
||||||
<p className="mt-2">
|
|
||||||
Nx Enterprise will support optional monorepo-like constraints
|
|
||||||
to be applied across Nx Workspace boundaries in a seamless and
|
|
||||||
flexible way. Move fast with confidence.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="rounded-full p-3 shadow-sm ring-1 ring-slate-200 dark:ring-slate-800/60">
|
|
||||||
<BuildingOffice2Icon className="h-5 w-5 text-slate-900 dark:text-slate-100" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
|
||||||
Automation over coordination
|
|
||||||
</h4>
|
|
||||||
<p className="mt-2">
|
|
||||||
Testing and cross-repo coordination can be left to Nx
|
|
||||||
Enterprise tooling instead of manual human intervention to
|
|
||||||
test and enforce cross-repo dependencies & constraints.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,10 +1,16 @@
|
|||||||
|
import {
|
||||||
|
GlobeAmericasIcon,
|
||||||
|
ServerStackIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
} from '@heroicons/react/24/outline';
|
||||||
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
export function Security(): JSX.Element {
|
export function Security(): ReactElement {
|
||||||
return (
|
return (
|
||||||
<section id="nx-enterprise-security" className="relative isolate">
|
<section id="nx-enterprise-security" className="relative isolate">
|
||||||
<div className="mx-auto max-w-7xl sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mx-auto flex max-w-2xl flex-col gap-16 bg-white/70 px-6 py-16 ring-1 ring-slate-200 sm:rounded-3xl sm:p-8 lg:mx-0 lg:max-w-none lg:flex-row lg:items-center lg:py-20 xl:gap-x-20 xl:px-20 dark:bg-white/5 dark:ring-white/10">
|
<div className="flex flex-col gap-16 lg:flex-row lg:gap-20">
|
||||||
<div className="max-w-md">
|
<div className="max-w-md">
|
||||||
<SectionHeading as="h2" variant="title">
|
<SectionHeading as="h2" variant="title">
|
||||||
Security
|
Security
|
||||||
@ -62,35 +68,49 @@ export function Security(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex-auto">
|
<div className="w-full flex-auto">
|
||||||
<div className="mt-6">
|
<div className="mt-8 flex gap-4">
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
<ServerStackIcon aria-hidden="true" className="size-6 shrink-0" />
|
||||||
Dedicated infrastructure
|
<div>
|
||||||
</h4>
|
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
||||||
<p className="mt-2">
|
Dedicated infrastructure
|
||||||
We can support you to self-host Nx Cloud within your own
|
</h4>
|
||||||
infrastructure or, depending on your needs, run Nx Cloud on
|
<p className="mt-2">
|
||||||
managed hosts within our cloud.
|
We can support you to self-host Nx Cloud within your own
|
||||||
</p>
|
infrastructure or, depending on your needs, run Nx Cloud on
|
||||||
|
managed hosts within our cloud.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6">
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
<div className="mt-8 flex gap-4">
|
||||||
Application security
|
<ShieldCheckIcon aria-hidden="true" className="size-6 shrink-0" />
|
||||||
</h4>
|
<div>
|
||||||
<p className="mt-2">
|
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
||||||
We consistently review our security policies and collaborate
|
Application security
|
||||||
with third parties for penetration testing to promptly identify
|
</h4>
|
||||||
and mitigate potential risks.
|
<p className="mt-2">
|
||||||
</p>
|
We consistently review our security policies and collaborate
|
||||||
|
with third parties for penetration testing to promptly
|
||||||
|
identify and mitigate potential risks.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6">
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
<div className="mt-8 flex gap-4">
|
||||||
US & EU instances available
|
<GlobeAmericasIcon
|
||||||
</h4>
|
aria-hidden="true"
|
||||||
<p className="mt-2">
|
className="size-6 shrink-0"
|
||||||
We support region specific hosting of Nx Cloud in the event IT
|
/>
|
||||||
security or data protection policies restrict international
|
<div>
|
||||||
transfers.
|
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
||||||
</p>
|
US & EU instances available
|
||||||
|
</h4>
|
||||||
|
<p className="mt-2">
|
||||||
|
We support region specific hosting of Nx Cloud in the event IT
|
||||||
|
security or data protection policies restrict international
|
||||||
|
transfers.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -100,7 +120,7 @@ export function Security(): JSX.Element {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="aspect-[1318/752] w-[82.375rem] flex-none bg-gradient-to-r from-[#80caff] to-[#4f46e5] opacity-25"
|
className="aspect-[1318/752] w-[82.375rem] flex-none bg-gradient-to-r from-[#80caff] to-[#4f46e5] opacity-5"
|
||||||
style={{
|
style={{
|
||||||
clipPath:
|
clipPath:
|
||||||
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
|
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
|
||||||
|
|||||||
@ -1,849 +0,0 @@
|
|||||||
import {
|
|
||||||
ArrowsRightLeftIcon,
|
|
||||||
BanknotesIcon,
|
|
||||||
BoltIcon,
|
|
||||||
ChartBarSquareIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
ClipboardDocumentIcon,
|
|
||||||
CloudArrowDownIcon,
|
|
||||||
CursorArrowRaysIcon,
|
|
||||||
LightBulbIcon,
|
|
||||||
SparklesIcon,
|
|
||||||
Square3Stack3DIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import { animate, motion, useMotionValue, useTransform } from 'framer-motion';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { BentoGrid, BentoGridItem } from './bento-grid';
|
|
||||||
import { cx } from '@nx/nx-dev/ui-primitives';
|
|
||||||
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
export function SolveYourCi(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<section id="solve-your-ci">
|
|
||||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
|
||||||
<div className="mx-auto max-w-2xl text-center">
|
|
||||||
<SectionHeading as="h2" variant="title">
|
|
||||||
Solve your CI
|
|
||||||
</SectionHeading>
|
|
||||||
</div>
|
|
||||||
<SectionHeading as="p" variant="subtitle" className="mt-6">
|
|
||||||
Monorepos help you scale your people but they can also make CI a
|
|
||||||
challenge. Nx Enterprise solves it by providing efficient, fast and
|
|
||||||
reliable CI that can handle workflows of any size.
|
|
||||||
</SectionHeading>
|
|
||||||
{/*FEATURES CONTAINER*/}
|
|
||||||
<BentoGrid className="mx-auto mt-20 w-full md:auto-rows-[22rem]">
|
|
||||||
{items.map((item, i) => (
|
|
||||||
<BentoGridItem
|
|
||||||
key={i}
|
|
||||||
title={item.title}
|
|
||||||
description={item.description}
|
|
||||||
header={item.header}
|
|
||||||
className={cx('[&>p:text-lg]', item.className)}
|
|
||||||
icon={item.icon}
|
|
||||||
url={item.url}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</BentoGrid>
|
|
||||||
|
|
||||||
{/*TEXT*/}
|
|
||||||
<div className="relative mx-auto mt-16 max-w-2xl space-y-12 sm:my-32">
|
|
||||||
<svg
|
|
||||||
className="absolute left-0 top-0 -z-10 -ml-20 hidden -translate-x-full -translate-y-1/2 transform lg:block"
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 200 400"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<pattern
|
|
||||||
id="de316486-4a29-4312-bdfc-fbce2132a2c1"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
patternUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={4}
|
|
||||||
height={4}
|
|
||||||
className="text-slate-100 dark:text-slate-800/60"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</pattern>
|
|
||||||
<pattern
|
|
||||||
id="de316486-4a29-4312-bdfc-fbce2132a2c1"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
patternUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={4}
|
|
||||||
height={4}
|
|
||||||
className="text-pink-200 dark:text-slate-800/60"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<rect
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="url(#de316486-4a29-4312-bdfc-fbce2132a2c1)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
className="absolute bottom-0 right-0 -z-10 -mr-20 hidden translate-x-full translate-y-1/2 transform lg:block"
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 200 400"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<pattern
|
|
||||||
id="de316486-4a29-4312-bdfc-fbce2132a2c1"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
patternUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={4}
|
|
||||||
height={4}
|
|
||||||
className="text-slate-100 dark:text-slate-800/60"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<rect
|
|
||||||
width={200}
|
|
||||||
height={400}
|
|
||||||
fill="url(#de316486-4a29-4312-bdfc-fbce2132a2c1)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<div className="space-y-6 lg:space-y-12">
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="rounded-full p-3 shadow-sm ring-1 ring-slate-200 dark:ring-slate-800/60">
|
|
||||||
<BanknotesIcon className="h-5 w-5 text-slate-900 dark:text-slate-100" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
|
||||||
Both faster and cheaper
|
|
||||||
</h4>
|
|
||||||
<p className="mt-2">
|
|
||||||
Current CI providers aren’t made for monorepos. They’re
|
|
||||||
low-level and static in their configuration. The combination
|
|
||||||
of Nx Agents and Nx Replay lets you reuse computation from
|
|
||||||
other runs and utilizes the allocated VMs in the most optimal
|
|
||||||
way.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="rounded-full p-3 shadow-sm ring-1 ring-slate-200 dark:ring-slate-800/60">
|
|
||||||
<CursorArrowRaysIcon className="h-5 w-5 text-slate-900 dark:text-slate-100" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
|
||||||
Solving E2E tests
|
|
||||||
</h4>
|
|
||||||
<p className="mt-2">
|
|
||||||
Atomizer splits large e2e projects into fine-grained test
|
|
||||||
runs, enabling more efficient distribution and dramatically
|
|
||||||
reducing CI times. It also identifies flaky e2e tests at the
|
|
||||||
file level and re-runs those specific tests.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="rounded-full p-3 shadow-sm ring-1 ring-slate-200 dark:ring-slate-800/60">
|
|
||||||
<LightBulbIcon className="h-5 w-5 text-slate-900 dark:text-slate-100" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
|
||||||
What, not how
|
|
||||||
</h4>
|
|
||||||
<p className="mt-2">
|
|
||||||
Nx Enterprise simplifies CI configuration, emphasizing which
|
|
||||||
tasks to execute over how with no need to tweak your CI
|
|
||||||
scripts as your monorepo evolves. This simplified
|
|
||||||
configuration cuts down on CI maintenance and increases
|
|
||||||
stability.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="rounded-full p-3 shadow-sm ring-1 ring-slate-200 dark:ring-slate-800/60">
|
|
||||||
<BoltIcon className="h-5 w-5 text-slate-900 dark:text-slate-100" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="relative text-base font-medium leading-6 text-slate-900 dark:text-slate-100">
|
|
||||||
Nx Powerpack included
|
|
||||||
</h4>
|
|
||||||
<p className="mt-2">
|
|
||||||
A suite of paid extensions for the Nx CLI specifically
|
|
||||||
designed for enterprises, built and supported by the Nx core
|
|
||||||
team.{' '}
|
|
||||||
<Link
|
|
||||||
href="/powerpack"
|
|
||||||
title="Learn more about Nx Powerpack"
|
|
||||||
className="font-semibold underline"
|
|
||||||
>
|
|
||||||
Learn more about Nx Powerpack →
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Caching = () => {
|
|
||||||
const variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'build',
|
|
||||||
project: 'website',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'test',
|
|
||||||
project: 'express',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'build',
|
|
||||||
project: 'eslint',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'lint',
|
|
||||||
project: 'autoloan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'test',
|
|
||||||
project: 'website',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'lint',
|
|
||||||
project: 'website',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'build-base',
|
|
||||||
project: 'express',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'build',
|
|
||||||
project: 'express',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'lint',
|
|
||||||
project: 'express',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cache: 'remote',
|
|
||||||
target: 'test',
|
|
||||||
project: 'autoloan',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial="hidden"
|
|
||||||
variants={variants}
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="flex h-full min-h-[12rem] w-full flex-1 flex-col gap-2"
|
|
||||||
>
|
|
||||||
<motion.div className="flex items-center gap-1.5 text-center text-sm italic">
|
|
||||||
<SparklesIcon aria-hidden="true" className="h-4 w-4" />
|
|
||||||
<span className="font-semibold">
|
|
||||||
<Counter value={items.length + 123} duration={5} />
|
|
||||||
</span>
|
|
||||||
tasks replayed instantly with cache
|
|
||||||
</motion.div>
|
|
||||||
<div className="flex flex-1 flex-col divide-y divide-slate-100 overflow-hidden rounded-lg border border-slate-100 dark:divide-slate-700 dark:border-slate-700 dark:bg-slate-950">
|
|
||||||
{items.map((i, idx) => (
|
|
||||||
<div
|
|
||||||
key={`project-${i}-${idx}`}
|
|
||||||
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
|
|
||||||
>
|
|
||||||
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-emerald-500" />
|
|
||||||
<div className="flex min-w-[4rem]">
|
|
||||||
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-500/10 dark:bg-slate-400/10 dark:text-slate-400 dark:ring-slate-400/20">
|
|
||||||
{i.cache}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="grow truncate text-left text-sm font-medium"
|
|
||||||
data-testid="task-target"
|
|
||||||
>
|
|
||||||
{i.project}:{i.target}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs">< 1s</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Atomizer = () => {
|
|
||||||
const variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const itemVariants = {
|
|
||||||
visible: (i: number) => ({
|
|
||||||
opacity: 1,
|
|
||||||
x: 0,
|
|
||||||
transition: {
|
|
||||||
delay: i * 0.2,
|
|
||||||
duration: 0.275,
|
|
||||||
ease: 'easeOut',
|
|
||||||
when: 'beforeChildren',
|
|
||||||
staggerChildren: 0.3,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
x: -100,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const uiDialogsTests = ['e2e-ui-cdk:e2e', 'e2e-ui-layout:e2e'];
|
|
||||||
const loansTests = [
|
|
||||||
'loans-front-store:e2e',
|
|
||||||
'loans-loans:e2e',
|
|
||||||
'loans-credit-card:e2e',
|
|
||||||
'loans-workflows:e2e',
|
|
||||||
'loans-mortgage:e2e',
|
|
||||||
'loans-submission:e2e',
|
|
||||||
];
|
|
||||||
const DefaultConnector = () => (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className="absolute left-4 top-0 -ml-px -mt-px h-[105%] w-0.5 bg-slate-100 dark:bg-slate-700"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="absolute left-4 top-1/2 -ml-px h-0.5 w-3 bg-slate-100 dark:bg-slate-700"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
const BottomConnector = () => (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className="absolute left-4 top-0 -ml-px -mt-px h-[55%] w-0.5 bg-slate-100 dark:bg-slate-700"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="absolute left-4 top-1/2 -ml-px h-0.5 w-3 bg-slate-100 dark:bg-slate-700"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial="hidden"
|
|
||||||
variants={variants}
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="flex h-full min-h-[12rem] w-full flex-1 flex-col gap-2"
|
|
||||||
>
|
|
||||||
<motion.div className="flex items-center gap-1.5 text-center text-sm">
|
|
||||||
Running
|
|
||||||
<ChevronRightIcon aria-hidden="true" className="h-3 w-3" />
|
|
||||||
<code className="text-xs font-medium">nx affected --targets=e2e</code>
|
|
||||||
</motion.div>
|
|
||||||
<div className="flex flex-1 flex-col divide-y divide-slate-100 overflow-hidden rounded-lg border border-slate-100 bg-white dark:divide-slate-700 dark:border-slate-700 dark:bg-slate-950">
|
|
||||||
<div className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-yellow-500" />
|
|
||||||
</div>
|
|
||||||
<div className="flex min-w-[5rem]">
|
|
||||||
<span className="whitespace-nowrap text-sm font-medium">
|
|
||||||
In progress
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grow truncate text-left text-sm font-medium">
|
|
||||||
E2E {'>'} CI
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{uiDialogsTests.map((i, idx) => (
|
|
||||||
<div
|
|
||||||
key={`${i}-${idx}`}
|
|
||||||
className="relative flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
|
|
||||||
>
|
|
||||||
{uiDialogsTests.length === idx + 1 ? (
|
|
||||||
<BottomConnector />
|
|
||||||
) : (
|
|
||||||
<DefaultConnector />
|
|
||||||
)}
|
|
||||||
<span aria-hidden="true" className="h-4 w-4" />
|
|
||||||
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-yellow-500" />
|
|
||||||
</div>
|
|
||||||
<div className="grow truncate text-left text-sm">{i}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<motion.div
|
|
||||||
custom={1}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
|
|
||||||
>
|
|
||||||
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-emerald-500" />
|
|
||||||
<div className="flex min-w-[5rem]">
|
|
||||||
<span className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-500/10 dark:bg-slate-400/10 dark:text-slate-400 dark:ring-slate-400/20">
|
|
||||||
miss
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="grow truncate text-left text-sm font-medium">
|
|
||||||
website:e2e
|
|
||||||
</div>
|
|
||||||
<motion.span
|
|
||||||
custom={3}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="inline-flex cursor-default items-center rounded-md bg-yellow-400/10 px-2 py-1 text-xs font-medium text-yellow-500 ring-1 ring-inset ring-yellow-400/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20"
|
|
||||||
>
|
|
||||||
flaky
|
|
||||||
</motion.span>
|
|
||||||
<motion.span
|
|
||||||
custom={2}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="inline-flex cursor-default items-center rounded-md bg-slate-50 px-2 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-500/10 dark:bg-slate-400/10 dark:text-slate-400 dark:ring-slate-400/20"
|
|
||||||
>
|
|
||||||
1 retry
|
|
||||||
</motion.span>
|
|
||||||
</motion.div>
|
|
||||||
<div className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-yellow-500" />
|
|
||||||
</div>
|
|
||||||
<div className="flex min-w-[5rem]">
|
|
||||||
<span className="whitespace-nowrap text-sm font-medium">
|
|
||||||
In progress
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grow truncate text-left text-sm font-medium">
|
|
||||||
loans {'>'} e2e
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{loansTests.map((i, idx) => (
|
|
||||||
<div
|
|
||||||
key={`${i}-${idx}`}
|
|
||||||
className="relative flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-900/40"
|
|
||||||
>
|
|
||||||
{loansTests.length === idx + 1 ? (
|
|
||||||
<BottomConnector />
|
|
||||||
) : (
|
|
||||||
<DefaultConnector />
|
|
||||||
)}
|
|
||||||
<span aria-hidden="true" className="h-4 w-4" />
|
|
||||||
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-yellow-500" />
|
|
||||||
</div>
|
|
||||||
<div className="grow truncate text-left text-sm">{i}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TaskDistribution = () => {
|
|
||||||
const variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const itemVariants = {
|
|
||||||
visible: (i: number) => ({
|
|
||||||
opacity: 1,
|
|
||||||
x: 0,
|
|
||||||
transition: {
|
|
||||||
delay: i * 0.2,
|
|
||||||
duration: 0.275,
|
|
||||||
ease: 'easeOut',
|
|
||||||
when: 'beforeChildren',
|
|
||||||
staggerChildren: 0.3,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
x: -100,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const agent1Items = ['website:build-base', 'website:build'];
|
|
||||||
const agent2Items = ['docs:lint', 'express:test', 'website:lint'];
|
|
||||||
const agent3Items = ['graph-client:build', 'plugin:test'];
|
|
||||||
const nxReplayItems = [
|
|
||||||
'graph-client:lint',
|
|
||||||
'plugin:lint',
|
|
||||||
'website:test',
|
|
||||||
'vite:test',
|
|
||||||
'vite:build',
|
|
||||||
];
|
|
||||||
|
|
||||||
const notStartedTasks = ['js:build', 'js:lint'];
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial="hidden"
|
|
||||||
variants={variants}
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="relative flex h-full min-h-[12rem] w-full flex-1 flex-col gap-2"
|
|
||||||
>
|
|
||||||
<div className="grid max-h-full grid-cols-2 items-stretch gap-8 overflow-hidden p-1 lg:grid-cols-3">
|
|
||||||
<motion.div
|
|
||||||
custom={2}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="relative overflow-x-hidden rounded-lg bg-white p-2 ring-1 ring-slate-100 dark:bg-slate-950 dark:ring-slate-700"
|
|
||||||
>
|
|
||||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-400">
|
|
||||||
Main Workflow
|
|
||||||
</p>
|
|
||||||
<div className="mt-2 flex flex-col gap-1">
|
|
||||||
<p className="overflow-x-auto truncate py-2 font-mono text-xs font-medium text-slate-900 dark:text-slate-400">
|
|
||||||
nx affected --target=build,lint,test
|
|
||||||
</p>
|
|
||||||
<div className="flex h-1.5 w-full flex-row rounded-full">
|
|
||||||
<div
|
|
||||||
title="2 tasks in completed"
|
|
||||||
className="cursor-pointer rounded-l-full bg-green-400 dark:bg-green-600"
|
|
||||||
style={{ flexGrow: 8 }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
title="12 tasks in progress"
|
|
||||||
className="cursor-pointer bg-yellow-400 dark:bg-yellow-600"
|
|
||||||
style={{ flexGrow: 12 }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
title="24 tasks not started"
|
|
||||||
className="cursor-pointer rounded-r-full bg-slate-100 dark:bg-slate-600"
|
|
||||||
style={{ flexGrow: 24 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 flex flex-1 flex-col divide-y divide-slate-100 overflow-auto rounded-lg border border-slate-100 dark:divide-slate-700 dark:border-slate-700">
|
|
||||||
{notStartedTasks.map((i, idx) => (
|
|
||||||
<motion.div
|
|
||||||
key={`${i}-${idx}`}
|
|
||||||
custom={idx + 3}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="flex w-full flex-row items-center gap-2 p-2 transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-800/40"
|
|
||||||
>
|
|
||||||
<div className="text-xs">{i}</div>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={3}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="relative flex flex-col gap-2 overflow-x-hidden rounded-lg bg-white p-2 ring-1 ring-slate-100 dark:bg-slate-950 dark:ring-slate-700"
|
|
||||||
>
|
|
||||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-400">
|
|
||||||
Nx Agents
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-1 flex-col overflow-hidden">
|
|
||||||
<motion.div custom={4} variants={itemVariants} className="relative">
|
|
||||||
<div className="sticky top-0 z-10 mt-1 rounded-md border border-slate-100 bg-slate-50 p-2 transition hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800">
|
|
||||||
<div className="flex items-center gap-x-2 text-xs font-medium">
|
|
||||||
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-yellow-500"></div>
|
|
||||||
</div>
|
|
||||||
Agent 1<span className="flex-grow"></span>
|
|
||||||
<span className="mr-1 opacity-80">{agent1Items.length}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul className="my-2 overflow-y-auto overflow-x-hidden">
|
|
||||||
{agent1Items.map((i, idx) => (
|
|
||||||
<motion.li
|
|
||||||
key={`agent-${i}-${idx}`}
|
|
||||||
custom={idx + 5}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="truncate p-1 pl-4 text-xs"
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</motion.li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div custom={6} variants={itemVariants} className="relative">
|
|
||||||
<div className="sticky top-0 z-10 mt-1 rounded-md border border-slate-100 bg-slate-50 p-2 transition hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800">
|
|
||||||
<div className="flex items-center gap-x-2 text-xs font-medium">
|
|
||||||
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-yellow-500"></div>
|
|
||||||
</div>
|
|
||||||
Agent 2<span className="flex-grow"></span>
|
|
||||||
<span className="mr-1 opacity-80">{agent2Items.length}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul className="my-2 overflow-y-auto overflow-x-hidden">
|
|
||||||
{agent2Items.map((i, idx) => (
|
|
||||||
<motion.li
|
|
||||||
key={`agent-${i}-${idx}`}
|
|
||||||
custom={idx + 7}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="truncate p-1 pl-4 text-xs"
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</motion.li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={8}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="relative hidden lg:block"
|
|
||||||
>
|
|
||||||
<div className="sticky top-0 z-10 mt-1 rounded-md border border-slate-100 bg-slate-50 p-2 transition hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800">
|
|
||||||
<div className="flex items-center gap-x-2 text-xs font-medium">
|
|
||||||
<div className="flex-none animate-pulse rounded-full bg-yellow-500/20 p-1">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-yellow-500"></div>
|
|
||||||
</div>
|
|
||||||
Agent 3<span className="flex-grow"></span>
|
|
||||||
<span className="mr-1 opacity-80">{agent3Items.length}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul className="my-2 overflow-y-auto overflow-x-hidden">
|
|
||||||
{agent3Items.map((i, idx) => (
|
|
||||||
<motion.li
|
|
||||||
key={`agent-${i}-${idx}`}
|
|
||||||
custom={idx + 9}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="truncate p-1 pl-4 text-xs"
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</motion.li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={4}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="relative hidden flex-col gap-2 overflow-x-hidden rounded-lg bg-white p-2 ring-1 ring-slate-100 lg:flex dark:bg-slate-950 dark:ring-slate-700"
|
|
||||||
>
|
|
||||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-400">
|
|
||||||
Nx Replay
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-1 flex-col divide-y divide-slate-100 overflow-auto rounded-lg border border-slate-100 dark:divide-slate-800 dark:border-slate-800">
|
|
||||||
{nxReplayItems.map((i, idx) => (
|
|
||||||
<motion.div
|
|
||||||
key={`replay-${i}-${idx}`}
|
|
||||||
custom={idx + 10}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="flex w-full flex-row items-center gap-2 p-2 text-xs transition-colors ease-out hover:bg-slate-50/40 dark:hover:bg-slate-800/40"
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const IncreasedVisibility = () => {
|
|
||||||
const variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial="hidden"
|
|
||||||
variants={variants}
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="flex h-full min-h-[12rem] w-full flex-1 flex-col gap-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
{/*STATUS*/}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="m-1 h-2.5 w-2.5 flex-none rounded-full bg-red-500"></div>
|
|
||||||
<span
|
|
||||||
className="whitespace-nowrap text-lg font-medium"
|
|
||||||
data-testid="status-label"
|
|
||||||
>
|
|
||||||
Failed
|
|
||||||
</span>
|
|
||||||
with code: 1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/*COMPARE & FLAKY*/}
|
|
||||||
{/*<div className="flex items-center justify-end gap-1.5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="cursor-default rounded bg-white px-2 py-1 text-xs font-semibold text-slate-900 shadow-sm ring-1 ring-inset ring-slate-300 dark:bg-white/10 dark:text-slate-400 dark:ring-slate-700"
|
|
||||||
>
|
|
||||||
Compare to similar tasks
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="cursor-default rounded bg-white px-2 py-1 text-xs font-semibold text-slate-900 shadow-sm ring-1 ring-inset ring-slate-300 dark:bg-white/10 dark:text-slate-400 dark:ring-slate-700"
|
|
||||||
>
|
|
||||||
Set as "not flaky"
|
|
||||||
</button>
|
|
||||||
</div>*/}
|
|
||||||
<div>
|
|
||||||
<div className="border-b border-slate-100 dark:border-slate-700">
|
|
||||||
<div className="-mb-px flex space-x-4">
|
|
||||||
<span className="cursor-default whitespace-nowrap border-b-2 border-transparent px-0.5 py-2 text-xs font-medium text-slate-500 dark:text-slate-400">
|
|
||||||
Attempt 1
|
|
||||||
</span>
|
|
||||||
<span className="cursor-default whitespace-nowrap border-b-2 border-blue-500 px-0.5 py-2 text-xs font-medium text-blue-500 dark:border-sky-600 dark:text-sky-600">
|
|
||||||
Attempt 2
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs">Feb 23, 2024 08:57:49 - 08:57:54 (4s)</p>
|
|
||||||
<motion.div className="terminal-output flex max-h-full min-h-[2rem] flex-col overflow-visible rounded-lg border border-slate-200 bg-slate-50 font-mono text-xs leading-normal text-slate-800 subpixel-antialiased dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200">
|
|
||||||
<div className="flex items-center justify-between gap-4 rounded-t-lg border-b border-slate-200 bg-slate-100 px-2 py-1 dark:border-slate-700 dark:bg-slate-800">
|
|
||||||
<div className="font-sans text-sm font-medium">
|
|
||||||
<span className="group relative flex cursor-pointer items-center overflow-hidden whitespace-nowrap subpixel-antialiased dark:text-slate-300">
|
|
||||||
<span>nx run nx-dev:build</span>
|
|
||||||
<span className="transform opacity-0 transition-all">
|
|
||||||
<ClipboardDocumentIcon className="h-4 w-4" />
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="overflow-hidden">
|
|
||||||
<pre className="overflow-x-hidden p-1 dark:text-slate-400">
|
|
||||||
{`nx run nx-dev:sitemap ✨ [next-sitemap]
|
|
||||||
Loading next-sitemap config:file:///home/workflows/workspace/nx-dev/nx-dev/next-sitemap.config.js
|
|
||||||
❌ [next-sitemap] Unable to find export-maker.
|
|
||||||
Make sure to build the project using "next build" command
|
|
||||||
node:internal/process/promises:289
|
|
||||||
triggerUncaughtException(err, true /* fromPromise */); ^
|
|
||||||
[Error:ENOENT: no such file or directory, stat '/home/workflows/workspace/dist/nx-dev/nx-dev/.next/export-marker.json']
|
|
||||||
errno: -2, code: 'ENOENT', syscall: 'stat', path: '/home/workflows/workspace/dist/nx-dev/nx-dev/.next/export-marker.json'
|
|
||||||
Node.js v20.9.0 Warning: command "pnpm next-sitemap --config
|
|
||||||
./nx-dev/nx-dev/next-sitemap.config.js" exited with non-zero status code`}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Counter({
|
|
||||||
value,
|
|
||||||
duration = 2,
|
|
||||||
}: {
|
|
||||||
value: number;
|
|
||||||
duration?: number;
|
|
||||||
}) {
|
|
||||||
const count = useMotionValue(0);
|
|
||||||
const rounded = useTransform(count, Math.round);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const animation = animate(count, value, {
|
|
||||||
type: 'tween',
|
|
||||||
ease: 'easeOut',
|
|
||||||
duration,
|
|
||||||
});
|
|
||||||
|
|
||||||
return animation.stop;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <motion.span>{rounded}</motion.span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
title: 'Nx Agents: seamless task distribution',
|
|
||||||
header: <TaskDistribution />,
|
|
||||||
className: 'md:col-span-2',
|
|
||||||
icon: <ArrowsRightLeftIcon className="h-4 w-4 text-slate-500" />,
|
|
||||||
url: '/ci/features/distribute-task-execution',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Nx Replay: secure computation cache',
|
|
||||||
header: <Caching />,
|
|
||||||
className: 'md:col-span-1',
|
|
||||||
icon: <CloudArrowDownIcon className="h-4 w-4 text-slate-500" />,
|
|
||||||
url: '/ci/features/remote-cache',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Increased visibility',
|
|
||||||
header: <IncreasedVisibility />,
|
|
||||||
className: 'md:col-span-1',
|
|
||||||
icon: <ChartBarSquareIcon className="h-4 w-4 text-slate-500" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Atomizer: task splitting & flaky rerun',
|
|
||||||
description: null,
|
|
||||||
header: <Atomizer />,
|
|
||||||
className: 'md:col-span-2',
|
|
||||||
icon: <Square3Stack3DIcon className="h-4 w-4 text-slate-500" />,
|
|
||||||
url: '/ci/features/split-e2e-tasks',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
196
nx-dev/ui-enterprise/src/lib/testimonial-carousel.tsx
Normal file
196
nx-dev/ui-enterprise/src/lib/testimonial-carousel.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { ReactElement, ReactNode, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
CarouselHandle,
|
||||||
|
CarouselRoot,
|
||||||
|
CarouselSlide,
|
||||||
|
CarouselViewport,
|
||||||
|
} from './carousel';
|
||||||
|
import { PayfitIcon, UkgIcon } from '@nx/nx-dev/ui-icons';
|
||||||
|
|
||||||
|
export function Carousel({
|
||||||
|
items,
|
||||||
|
}: {
|
||||||
|
items: { element: ReactNode; innerButtonElement: ReactNode }[];
|
||||||
|
}): ReactElement {
|
||||||
|
const carouselRef = useRef<CarouselHandle>(null);
|
||||||
|
const iterableItems = items.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
}));
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const handleSlideChange = (index: number) => setCurrentIndex(index);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
{/* Main carousel section */}
|
||||||
|
<CarouselRoot
|
||||||
|
className="overflow-hidden rounded-lg xl:[box-shadow:0_50px_100px_-20px_rgba(50,50,93,0.25),_0_30px_60px_-30px_rgba(0,0,0,0.3)]"
|
||||||
|
onSlideChange={handleSlideChange}
|
||||||
|
enableKeyboardNavigation={true}
|
||||||
|
autoPlayInterval={6000}
|
||||||
|
ref={carouselRef}
|
||||||
|
>
|
||||||
|
<CarouselViewport>
|
||||||
|
{iterableItems.map((item, index) => (
|
||||||
|
<CarouselSlide key={item.id}>{item.element}</CarouselSlide>
|
||||||
|
))}
|
||||||
|
</CarouselViewport>
|
||||||
|
|
||||||
|
{/* Custom line-style indicators */}
|
||||||
|
<div className="absolute bottom-8 left-1/2 flex -translate-x-1/2 gap-2">
|
||||||
|
{iterableItems.map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`h-1 w-8 rounded-full transition-colors duration-300 ${
|
||||||
|
index === currentIndex ? 'bg-white' : 'bg-white/30'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CarouselRoot>
|
||||||
|
|
||||||
|
{/* Partner logos section - now linked to carousel items */}
|
||||||
|
<div className="mt-12 flex items-center justify-center divide-x divide-slate-200 dark:divide-slate-700">
|
||||||
|
{iterableItems.map((item, index) => (
|
||||||
|
<button
|
||||||
|
key={item.id + '-logo'}
|
||||||
|
onClick={() => carouselRef.current?.goToSlide(index)}
|
||||||
|
className={`px-8 py-4 transition-all duration-300 ${
|
||||||
|
index === currentIndex
|
||||||
|
? 'underline opacity-100 grayscale-0'
|
||||||
|
: 'opacity-50 grayscale'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.innerButtonElement}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TestimonialCarousel(): ReactElement {
|
||||||
|
return (
|
||||||
|
<section className="">
|
||||||
|
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||||
|
<Carousel
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
element: (
|
||||||
|
<div className="relative overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-opacity-75 bg-contain bg-right bg-no-repeat"
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
"url('https://images.unsplash.com/photo-1511376868136-742c0de8c9a8?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-[#0F6FDE] via-[#0F6FDE] via-70% to-[#0F6FDE]/40" />
|
||||||
|
<div className="relative mx-auto grid max-w-2xl grid-cols-1 px-12 py-16 text-white lg:mx-0 lg:max-w-none lg:grid-cols-4">
|
||||||
|
<div className="col-span-3 flex flex-col">
|
||||||
|
<figure className="flex flex-auto flex-col justify-between">
|
||||||
|
<blockquote className="text-pretty text-xl/8">
|
||||||
|
<p>
|
||||||
|
“The number of hours we spent trying to manage CI
|
||||||
|
before, trying to load balance in CircleCI, the
|
||||||
|
number of agents that we run ourselves by hand and
|
||||||
|
try to distribute ourselves manually - it was
|
||||||
|
painful, we’d spend hours and days trying to do
|
||||||
|
that.{' '}
|
||||||
|
<span className="font-semibold">
|
||||||
|
With Nx Cloud we don’t need to think about that,
|
||||||
|
here is my task, deal with it and make it fast
|
||||||
|
</span>
|
||||||
|
.”
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
<figcaption className="mt-10 flex items-center gap-x-6">
|
||||||
|
<img
|
||||||
|
alt="avatar"
|
||||||
|
src="https://avatars.githubusercontent.com/u/7281023?v=4"
|
||||||
|
className="size-14 rounded-full bg-slate-50"
|
||||||
|
/>
|
||||||
|
<div className="text-base">
|
||||||
|
<div className="font-semibold">
|
||||||
|
Nicolas Beaussart
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 ">
|
||||||
|
Staff Platform Engineer, Payfit
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div className="grid-col-1 grid place-items-center p-4">
|
||||||
|
<PayfitIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-12 lg:size-20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
// innerButtonElement: (
|
||||||
|
// <PayfitIcon
|
||||||
|
// aria-hidden="true"
|
||||||
|
// className="h-10 self-start text-[#0F6FDE]"
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
innerButtonElement: (
|
||||||
|
<span className="text-2xl">Increase speed</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: (
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-opacity-75 bg-contain bg-right bg-no-repeat"
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
"url('https://images.unsplash.com/photo-1552664730-d307ca884978?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-[#005151] via-[#005151] via-55% to-[#005151]/40" />
|
||||||
|
<div className="relative mx-auto grid max-w-2xl grid-cols-1 px-12 py-16 text-white lg:mx-0 lg:max-w-none lg:grid-cols-4">
|
||||||
|
<div className="col-span-2 flex flex-col">
|
||||||
|
<figure className="flex flex-auto flex-col justify-between">
|
||||||
|
<blockquote className="text-pretty text-xl/8">
|
||||||
|
<p>
|
||||||
|
“I really like the Nx check-ins - Nx people are very
|
||||||
|
well prepared for how to help their team grow and
|
||||||
|
scale and to help us spot some of our challenges. I
|
||||||
|
can’t see a future where we don’t have Nx.”
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
<figcaption className="mt-10 flex items-center gap-x-6">
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src="https://avatars.githubusercontent.com/u/6657673?v=4"
|
||||||
|
className="size-14 rounded-full bg-slate-50"
|
||||||
|
/>
|
||||||
|
<div className="text-base">
|
||||||
|
<div className="font-semibold">Sid Govindaraju</div>
|
||||||
|
<div className="mt-1">Engineering Manager, UKG</div>
|
||||||
|
</div>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div className="grid-col-1 grid place-items-center p-4">
|
||||||
|
<UkgIcon aria-hidden="true" className="h-6 lg:h-12" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
// innerButtonElement: (
|
||||||
|
// <UkgIcon aria-hidden="true" className="h-8 text-[#005151]" />
|
||||||
|
// ),
|
||||||
|
innerButtonElement: (
|
||||||
|
<span className="text-2xl">Proactive partnership</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,220 +0,0 @@
|
|||||||
import {
|
|
||||||
AwsIcon,
|
|
||||||
BillColoredIcon,
|
|
||||||
CapitalOneIcon,
|
|
||||||
CaterpillarIcon,
|
|
||||||
CiscoIcon,
|
|
||||||
FicoIcon,
|
|
||||||
HiltonIcon,
|
|
||||||
ManIcon,
|
|
||||||
ReactQueryIcon,
|
|
||||||
RedwoodJsIcon,
|
|
||||||
RoyalBankOfCanadaColoredIcon,
|
|
||||||
SevenElevenColoredIcon,
|
|
||||||
ShopifyIcon,
|
|
||||||
StorybookIcon,
|
|
||||||
VmwareIcon,
|
|
||||||
} from '@nx/nx-dev/ui-icons';
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
|
|
||||||
export function TrustedBy(): JSX.Element {
|
|
||||||
const variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
visible: (i: number) => ({
|
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
|
||||||
delay: i || 0,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
const itemVariants = {
|
|
||||||
visible: (i: number) => ({
|
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
|
||||||
transition: {
|
|
||||||
delay: i * 0.25,
|
|
||||||
duration: 0.65,
|
|
||||||
ease: 'easeOut',
|
|
||||||
when: 'beforeChildren',
|
|
||||||
staggerChildren: 0.3,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
y: 4,
|
|
||||||
transition: {
|
|
||||||
when: 'afterChildren',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="">
|
|
||||||
<div className="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8 lg:pb-16">
|
|
||||||
{/*<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-xl">*/}
|
|
||||||
{/* <SectionHeading as="h2" variant="title" id="trusted-by">*/}
|
|
||||||
{/* Trusted by startups and Fortune 500 companies*/}
|
|
||||||
{/* </SectionHeading>*/}
|
|
||||||
{/*</div>*/}
|
|
||||||
<motion.dl
|
|
||||||
initial="hidden"
|
|
||||||
variants={variants}
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="mt-4 grid grid-cols-2 gap-0.5 md:grid-cols-5"
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
custom={1}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<AwsIcon className="h-14 w-14 text-black dark:text-white" />
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={2}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<ManIcon aria-hidden="true" className="h-14 w-14 text-[#E40045]" />
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={3}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<CapitalOneIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-28 w-28 text-black dark:text-white"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={4}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<ShopifyIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-12 w-12 text-[#7AB55C]"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={5}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<RoyalBankOfCanadaColoredIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-14 w-14"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={6}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<VmwareIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-28 w-28 text-black dark:text-white"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={7}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<StorybookIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-12 w-12 text-[#FF4785]"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={8}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<FicoIcon aria-hidden="true" className="h-28 w-28 text-[#0A6DE6]" />
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={9}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex h-14 items-center justify-center lg:h-28"
|
|
||||||
>
|
|
||||||
<CaterpillarIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-14 w-14 text-[#FFCD11]"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={10}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<CiscoIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-20 w-20 text-[#1BA0D7]"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={11}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<BillColoredIcon aria-hidden="true" className="h-14 w-14" />
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={12}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<SevenElevenColoredIcon aria-hidden="true" className="h-14 w-14" />
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={13}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<HiltonIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-20 w-20 text-black dark:text-white"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={14}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<RedwoodJsIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-14 w-14 text-[#BF4722]"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
custom={14}
|
|
||||||
variants={itemVariants}
|
|
||||||
className="col-span-1 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<ReactQueryIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="h-14 w-14 text-[#FF4154]"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
{/*<motion.div*/}
|
|
||||||
{/* custom={11}*/}
|
|
||||||
{/* variants={itemVariants}*/}
|
|
||||||
{/* className="col-span-1 flex items-center justify-center"*/}
|
|
||||||
{/*>*/}
|
|
||||||
{/* <AmericanAirlinesIcon*/}
|
|
||||||
{/* aria-hidden="true"*/}
|
|
||||||
{/* className="h-12 w-12 text-[#0071CE]"*/}
|
|
||||||
{/* />*/}
|
|
||||||
{/*</motion.div>*/}
|
|
||||||
</motion.dl>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
48
nx-dev/ui-enterprise/src/lib/vmware-testimonial.tsx
Normal file
48
nx-dev/ui-enterprise/src/lib/vmware-testimonial.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
import { VmwareIcon } from '@nx/nx-dev/ui-icons';
|
||||||
|
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
|
||||||
|
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
|
export function VmwareTestimonial(): ReactElement {
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
<div className="mx-auto max-w-7xl rounded-lg bg-black px-4 py-12 sm:px-6 lg:px-8 lg:py-24">
|
||||||
|
<blockquote className="mx-auto max-w-4xl">
|
||||||
|
<VmwareIcon aria-hidden="true" className="size-20 text-white" />
|
||||||
|
|
||||||
|
<p className="text-xl text-white sm:text-2xl md:text-3xl md:leading-normal">
|
||||||
|
Since using Nx Cloud, we saw our CI times reduced by 83%! That means
|
||||||
|
our teams are not waiting hours for their PR to be merged anymore,
|
||||||
|
we reclaimed our productivity and are pretty happy about it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="mt-4 text-right md:text-lg">
|
||||||
|
<span className="font-semibold text-sky-500">
|
||||||
|
Laurent Delamare,
|
||||||
|
</span>{' '}
|
||||||
|
<span className="text-neutral-500">Senior Engineer</span>
|
||||||
|
</p>
|
||||||
|
<footer className="mt-6 md:mt-10">
|
||||||
|
<div className="flex items-center justify-center gap-8">
|
||||||
|
<SectionHeading as="p" variant="subtitle" className="text-white">
|
||||||
|
Curious how they did it?
|
||||||
|
</SectionHeading>
|
||||||
|
<ButtonLink
|
||||||
|
href="https://go.nx.dev/ci-ebook"
|
||||||
|
title="Download the ebook"
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ArrowDownTrayIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-5 shrink-0"
|
||||||
|
/>
|
||||||
|
<span>Download the ebook</span>
|
||||||
|
</ButtonLink>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import { FC, SVGProps } from 'react';
|
import { FC, SVGProps } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `#D50C2D` for a colored version.
|
||||||
|
*/
|
||||||
export const HetznerCloudIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
|
export const HetznerCloudIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
|
||||||
//Color: #D50C2D
|
|
||||||
<svg
|
<svg
|
||||||
role="img"
|
role="img"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { FC, SVGProps } from 'react';
|
import { FC, SVGProps } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `#0F6FDE` for a colored version.
|
||||||
|
*/
|
||||||
export const PayfitIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
|
export const PayfitIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
|
||||||
// Color: #0F6FDE
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user