chore(website): make biome happy

This commit is contained in:
JiPai
2024-09-06 15:42:50 +08:00
parent 7acb86a83e
commit c482230995
23 changed files with 396 additions and 472 deletions

View File

@@ -1,5 +1,5 @@
import { notFound } from 'next/navigation'
import { notFound } from "next/navigation";
export default function CatchAll() {
notFound()
notFound();
}

View File

@@ -1,94 +1,90 @@
import clsx from 'clsx'
import { Inter, Lexend } from 'next/font/google'
import '@/styles/tailwind.css'
import GoogleAnalytics from '@/components/analitycs/google'
import clsx from "clsx";
import { Inter, Lexend } from "next/font/google";
import "@/styles/tailwind.css";
import GoogleAnalytics from "@/components/analitycs/google";
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import type { Metadata } from 'next'
import type { Metadata } from "next";
export const metadata: Metadata = {
title: {
default: 'Dokploy - Effortless Deployment Solutions',
template: '%s | Simplify Your DevOps',
default: "Dokploy - Effortless Deployment Solutions",
template: "%s | Simplify Your DevOps",
},
alternates: {
canonical: 'https://dokploy.com',
canonical: "https://dokploy.com",
languages: {
en: 'https://dokploy.com',
en: "https://dokploy.com",
},
},
description:
'Streamline your deployment process with Dokploy. Effortlessly manage applications and databases on any VPS using Docker and Traefik for improved performance and security.',
applicationName: 'Dokploy',
"Streamline your deployment process with Dokploy. Effortlessly manage applications and databases on any VPS using Docker and Traefik for improved performance and security.",
applicationName: "Dokploy",
keywords: [
'Dokploy',
'Docker',
'Traefik',
'deployment',
'VPS',
'application management',
'database management',
'DevOps',
'cloud infrastructure',
'UI Self hosted',
"Dokploy",
"Docker",
"Traefik",
"deployment",
"VPS",
"application management",
"database management",
"DevOps",
"cloud infrastructure",
"UI Self hosted",
],
referrer: 'origin',
robots: 'index, follow',
referrer: "origin",
robots: "index, follow",
openGraph: {
type: 'website',
url: 'https://dokploy.com',
title: 'Dokploy - Effortless Deployment Solutions',
type: "website",
url: "https://dokploy.com",
title: "Dokploy - Effortless Deployment Solutions",
description:
'Simplify your DevOps with Dokploy. Deploy applications and manage databases efficiently on any VPS.',
siteName: 'Dokploy',
"Simplify your DevOps with Dokploy. Deploy applications and manage databases efficiently on any VPS.",
siteName: "Dokploy",
images: [
{
url: 'http://dokploy.com/og.png',
url: "http://dokploy.com/og.png",
},
],
},
twitter: {
card: 'summary_large_image',
site: '@Dokploy',
creator: '@Dokploy',
title: 'Dokploy - Simplify Your DevOps',
card: "summary_large_image",
site: "@Dokploy",
creator: "@Dokploy",
title: "Dokploy - Simplify Your DevOps",
description:
'Deploy applications and manage databases with ease using Dokploy. Learn how our platform can elevate your infrastructure management.',
images: 'https://dokploy.com/og.png',
"Deploy applications and manage databases with ease using Dokploy. Learn how our platform can elevate your infrastructure management.",
images: "https://dokploy.com/og.png",
},
}
};
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
});
const lexend = Lexend({
subsets: ['latin'],
display: 'swap',
variable: '--font-lexend',
})
subsets: ["latin"],
display: "swap",
variable: "--font-lexend",
});
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode
params: { locale: string }
children: React.ReactNode;
params: { locale: string };
}) {
const { locale } = params
const messages = await getMessages()
const { locale } = params;
const messages = await getMessages();
return (
<html
lang={locale}
className={clsx(
'h-full scroll-smooth',
inter.variable,
lexend.variable,
)}
className={clsx("h-full scroll-smooth", inter.variable, lexend.variable)}
>
<GoogleAnalytics />
<body className="flex h-full flex-col">
@@ -97,5 +93,5 @@ export default async function RootLayout({
</NextIntlClientProvider>
</body>
</html>
)
);
}

View File

@@ -1,5 +1,5 @@
import { SlimLayout } from '@/components/SlimLayout'
import { SlimLayout } from "@/components/SlimLayout";
export default function NotFound() {
return <SlimLayout />
return <SlimLayout />;
}

View File

@@ -1,11 +1,11 @@
import { CallToAction } from '@/components/CallToAction'
import { Faqs } from '@/components/Faqs'
import { Footer } from '@/components/Footer'
import { Header } from '@/components/Header'
import { Hero } from '@/components/Hero'
import { PrimaryFeatures } from '@/components/PrimaryFeatures'
import { SecondaryFeatures } from '@/components/SecondaryFeatures'
import { Testimonials } from '../../components/Testimonials'
import { CallToAction } from "@/components/CallToAction";
import { Faqs } from "@/components/Faqs";
import { Footer } from "@/components/Footer";
import { Header } from "@/components/Header";
import { Hero } from "@/components/Hero";
import { PrimaryFeatures } from "@/components/PrimaryFeatures";
import { SecondaryFeatures } from "@/components/SecondaryFeatures";
import { Testimonials } from "../../components/Testimonials";
export default function Home() {
return (
@@ -21,5 +21,5 @@ export default function Home() {
<Footer />
</main>
</div>
)
);
}

View File

@@ -1,11 +1,11 @@
import { ReactNode } from 'react'
import type { ReactNode } from "react";
type Props = {
children: ReactNode
}
children: ReactNode;
};
// Since we have a `not-found.tsx` page on the root, a layout file
// is required, even if it's just passing children through.
export default function RootLayout({ children }: Props) {
return children
return children;
}

View File

@@ -1,13 +1,13 @@
'use client'
"use client";
import Error from 'next/error'
import NextError from "next/error";
export default function NotFound() {
return (
<html lang="en">
<body>
<Error statusCode={404} />
<NextError statusCode={404} />
</body>
</html>
)
);
}

File diff suppressed because one or more lines are too long

View File

@@ -1,58 +1,57 @@
import { useTranslations } from 'next-intl'
import { Container } from './Container'
import { useTranslations } from "next-intl";
import { Container } from "./Container";
const faqs = [
[
{
question: 'faq.q1',
answer: 'faq.a1',
question: "faq.q1",
answer: "faq.a1",
},
{
question: 'faq.q2',
answer: 'faq.a2',
question: "faq.q2",
answer: "faq.a2",
},
{
question: 'faq.q3',
answer: 'faq.a3',
question: "faq.q3",
answer: "faq.a3",
},
{
question: 'faq.q4',
answer: 'faq.a4',
question: "faq.q4",
answer: "faq.a4",
},
],
[
{
question: 'faq.q5',
answer: 'faq.a5',
question: "faq.q5",
answer: "faq.a5",
},
{
question: 'faq.q6',
answer: 'faq.a6',
question: "faq.q6",
answer: "faq.a6",
},
{
question: 'faq.q7',
question: "faq.q7",
answer: "faq.a7",
},
],
[
{
question:
'faq.q8',
answer: 'faq.a8',
question: "faq.q8",
answer: "faq.a8",
},
{
question: 'faq.q9',
answer: 'faq.a9',
question: "faq.q9",
answer: "faq.a9",
},
{
question: 'faq.q10',
answer: 'faq.a10',
question: "faq.q10",
answer: "faq.a10",
},
],
]
];
export function Faqs() {
const t = useTranslations('HomePage')
const t = useTranslations("HomePage");
return (
<section
id="faqs"
@@ -65,10 +64,10 @@ export function Faqs() {
id="faq-title"
className="font-display text-3xl tracking-tight text-primary sm:text-4xl"
>
{t('faq.title')}
{t("faq.title")}
</h2>
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
{t('faq.des')}
{t("faq.des")}
</p>
</div>
<ul className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 lg:max-w-none lg:grid-cols-3">
@@ -91,5 +90,5 @@ export function Faqs() {
</ul>
</Container>
</section>
)
);
}

View File

@@ -1,13 +1,13 @@
import Link from 'next/link'
import Link from "next/link";
import { Container } from './Container'
import { NavLink } from './NavLink'
import { Logo } from './shared/Logo'
import { useTranslations } from 'next-intl'
import { useTranslations } from "next-intl";
import { Container } from "./Container";
import { NavLink } from "./NavLink";
import { Logo } from "./shared/Logo";
export function Footer() {
const t = useTranslations('HomePage')
const linkT = useTranslations('Link')
const t = useTranslations("HomePage");
const linkT = useTranslations("Link");
return (
<footer className="bg-black">
@@ -22,14 +22,10 @@ export function Footer() {
<nav className="mt-10 text-sm" aria-label="quick links">
<div className="-my-1 flex flex-wrap justify-center gap-6">
<NavLink href="/#features">
{t('navigation.features')}
</NavLink>
<NavLink href="/#faqs">
{t('navigation.faqs')}
</NavLink>
<NavLink href={linkT('docs.intro')} target="_blank">
{t('navigation.docs')}
<NavLink href="/#features">{t("navigation.features")}</NavLink>
<NavLink href="/#faqs">{t("navigation.faqs")}</NavLink>
<NavLink href={linkT("docs.intro")} target="_blank">
{t("navigation.docs")}
</NavLink>
</div>
</nav>
@@ -62,12 +58,12 @@ export function Footer() {
</Link>
</div>
<p className="mt-6 text-sm text-muted-foreground sm:mt-0">
{t('footer.copyright', {
{t("footer.copyright", {
year: new Date().getFullYear(),
})}
</p>
</div>
</Container>
</footer>
)
);
}

View File

@@ -1,40 +1,40 @@
'use client'
"use client";
import { cn } from '@/lib/utils'
import { Popover, Transition } from '@headlessui/react'
import { HeartIcon } from 'lucide-react'
import { Fragment, JSX, SVGProps } from 'react'
import { Container } from './Container'
import { NavLink } from './NavLink'
import { trackGAEvent } from './analitycs'
import { Logo } from './shared/Logo'
import { Button, buttonVariants } from './ui/button'
import { useLocale, useTranslations } from 'next-intl'
import { Link, useRouter } from '@/i18n/routing'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from '@/components/ui/select'
} from "@/components/ui/select";
import { Link, useRouter } from "@/i18n/routing";
import { cn } from "@/lib/utils";
import { Popover, Transition } from "@headlessui/react";
import { HeartIcon } from "lucide-react";
import { useLocale, useTranslations } from "next-intl";
import { Fragment, type JSX, type SVGProps } from "react";
import { Container } from "./Container";
import { NavLink } from "./NavLink";
import { trackGAEvent } from "./analitycs";
import { Logo } from "./shared/Logo";
import { Button, buttonVariants } from "./ui/button";
function MobileNavLink({
href,
children,
target,
}: {
href: string
children: React.ReactNode
target?: string
href: string;
children: React.ReactNode;
target?: string;
}) {
return (
<Popover.Button
onClick={() => {
trackGAEvent({
action: 'Nav Link Clicked',
category: 'Navigation',
action: "Nav Link Clicked",
category: "Navigation",
label: href,
})
});
}}
as={Link}
href={href}
@@ -43,7 +43,7 @@ function MobileNavLink({
>
{children}
</Popover.Button>
)
);
}
function MobileNavIcon({ open }: { open: boolean }) {
@@ -57,20 +57,17 @@ function MobileNavIcon({ open }: { open: boolean }) {
>
<path
d="M0 1H14M0 7H14M0 13H14"
className={cn(
'origin-center transition',
open && 'scale-90 opacity-0',
)}
className={cn("origin-center transition", open && "scale-90 opacity-0")}
/>
<path
d="M2 2L12 12M12 2L2 12"
className={cn(
'origin-center transition',
!open && 'scale-90 opacity-0',
"origin-center transition",
!open && "scale-90 opacity-0",
)}
/>
</svg>
)
);
}
const I18nIcon = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
@@ -89,11 +86,11 @@ const I18nIcon = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
d="m478.33 433.6-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362 368 281.65 401.17 362zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73 39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93.92 1.19 1.83 2.35 2.74 3.51-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59 22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z"
/>
</svg>
)
);
function MobileNavigation() {
const t = useTranslations('HomePage')
const linkT = useTranslations('Link')
const t = useTranslations("HomePage");
const linkT = useTranslations("Link");
return (
<Popover>
<Popover.Button
@@ -129,30 +126,25 @@ function MobileNavigation() {
className="absolute inset-x-0 top-full mt-4 flex origin-top flex-col rounded-2xl border border-border bg-background p-4 text-lg tracking-tight text-primary shadow-xl ring-1 ring-border/5"
>
<MobileNavLink href="/#features">
{t('navigation.features')}
{t("navigation.features")}
</MobileNavLink>
{/* <MobileNavLink href="/#testimonials">Testimonials</MobileNavLink> */}
<MobileNavLink href="/#faqs">
{t('navigation.faqs')}
</MobileNavLink>
<MobileNavLink
href={linkT('docs.intro')}
target="_blank"
>
{t('navigation.docs')}
<MobileNavLink href="/#faqs">{t("navigation.faqs")}</MobileNavLink>
<MobileNavLink href={linkT("docs.intro")} target="_blank">
{t("navigation.docs")}
</MobileNavLink>
</Popover.Panel>
</Transition.Child>
</Transition.Root>
</Popover>
)
);
}
export function Header() {
const router = useRouter()
const locale = useLocale()
const t = useTranslations('HomePage')
const linkT = useTranslations('Link')
const router = useRouter();
const locale = useLocale();
const t = useTranslations("HomePage");
const linkT = useTranslations("Link");
return (
<header className="bg-background py-10">
@@ -163,57 +155,50 @@ export function Header() {
<Logo className="h-10 w-auto" />
</Link>
<div className="hidden md:flex md:gap-x-6">
<NavLink href="/#features">
{t('navigation.features')}
</NavLink>
<NavLink href="/#features">{t("navigation.features")}</NavLink>
{/* <NavLink href="/#testimonials">Testimonials</NavLink> */}
<NavLink href="/#faqs">
{t('navigation.faqs')}
</NavLink>
<NavLink href={linkT('docs.intro')} target="_blank">
{t('navigation.docs')}
<NavLink href="/#faqs">{t("navigation.faqs")}</NavLink>
<NavLink href={linkT("docs.intro")} target="_blank">
{t("navigation.docs")}
</NavLink>
</div>
</div>
<div className="flex items-center gap-x-2 md:gap-x-5">
<Select
onValueChange={(locale) => {
router.replace('/', {
locale: locale as 'en' | 'zh-Hans',
})
router.replace("/", {
locale: locale as "en" | "zh-Hans",
});
}}
value={locale}
>
<SelectTrigger
className={buttonVariants({
variant: 'outline',
variant: "outline",
className:
' flex items-center gap-2 !rounded-full visited:outline-none focus-within:outline-none focus:outline-none',
" flex items-center gap-2 !rounded-full visited:outline-none focus-within:outline-none focus:outline-none",
})}
>
<I18nIcon width={20} height={20} />
</SelectTrigger>
<SelectContent>
<SelectItem value="en">
{t('navigation.i18nEn')}
</SelectItem>
<SelectItem value="en">{t("navigation.i18nEn")}</SelectItem>
<SelectItem value="zh-Hans">
{t('navigation.i18nZh-Hans')}
{t("navigation.i18nZh-Hans")}
</SelectItem>
</SelectContent>
</Select>
<Link
className={buttonVariants({
variant: 'outline',
className:
' flex items-center gap-2 !rounded-full',
variant: "outline",
className: " flex items-center gap-2 !rounded-full",
})}
href="https://opencollective.com/dokploy"
target="_blank"
>
<span className="text-sm font-semibold">
{t('navigation.support')}{' '}
{t("navigation.support")}{" "}
</span>
<HeartIcon className="animate-heartbeat size-4 fill-red-600 text-red-500 " />
</Link>
@@ -236,7 +221,7 @@ export function Header() {
>
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
</svg>
{t('navigation.discord')}
{t("navigation.discord")}
</Link>
</Button>
<div className="-mr-1 md:hidden">
@@ -246,5 +231,5 @@ export function Header() {
</nav>
</Container>
</header>
)
);
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
"use client";
import { Link } from "@/i18n/routing";
import { trackGAEvent } from "./analitycs";
import { Link } from '@/i18n/routing'
export function NavLink({
href,

View File

@@ -1,73 +1,73 @@
'use client'
"use client";
import { Tab } from '@headlessui/react'
import { AnimatePresence, motion } from 'framer-motion'
import { useEffect, useState } from 'react'
import { Tab } from "@headlessui/react";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useState } from "react";
import { cn } from '@/lib/utils'
import { Container } from './Container'
import { useTranslations } from 'next-intl'
import { cn } from "@/lib/utils";
import { useTranslations } from "next-intl";
import { Container } from "./Container";
const features = [
{
title: 'primaryFeatures.projects',
description: 'primaryFeatures.projectsDes',
image: '/primary/projects.png',
title: "primaryFeatures.projects",
description: "primaryFeatures.projectsDes",
image: "/primary/projects.png",
},
{
title: 'primaryFeatures.applications',
description: 'primaryFeatures.applicationsDes',
image: '/primary/applications.png',
title: "primaryFeatures.applications",
description: "primaryFeatures.applicationsDes",
image: "/primary/applications.png",
},
{
title: 'primaryFeatures.compose',
description: 'primaryFeatures.composeDes',
image: '/primary/compose.png',
title: "primaryFeatures.compose",
description: "primaryFeatures.composeDes",
image: "/primary/compose.png",
},
{
title: 'primaryFeatures.multinode',
description: 'primaryFeatures.multinodeDes',
image: '/primary/multinode.png',
title: "primaryFeatures.multinode",
description: "primaryFeatures.multinodeDes",
image: "/primary/multinode.png",
},
{
title: 'primaryFeatures.monitoring',
description: 'primaryFeatures.monitoringDes',
image: '/primary/monitoring.png',
title: "primaryFeatures.monitoring",
description: "primaryFeatures.monitoringDes",
image: "/primary/monitoring.png",
},
{
title: 'primaryFeatures.backups',
description: 'primaryFeatures.backupsDes',
image: '/primary/backups.png',
title: "primaryFeatures.backups",
description: "primaryFeatures.backupsDes",
image: "/primary/backups.png",
},
]
];
export function PrimaryFeatures() {
const t = useTranslations('HomePage')
const t = useTranslations("HomePage");
const [tabOrientation, setTabOrientation] = useState<
'horizontal' | 'vertical'
>('horizontal')
"horizontal" | "vertical"
>("horizontal");
useEffect(() => {
const lgMediaQuery = window.matchMedia('(min-width: 1024px)')
const lgMediaQuery = window.matchMedia("(min-width: 1024px)");
function onMediaQueryChange({ matches }: { matches: boolean }) {
setTabOrientation(matches ? 'vertical' : 'horizontal')
setTabOrientation(matches ? "vertical" : "horizontal");
}
onMediaQueryChange(lgMediaQuery)
lgMediaQuery.addEventListener('change', onMediaQueryChange)
onMediaQueryChange(lgMediaQuery);
lgMediaQuery.addEventListener("change", onMediaQueryChange);
return () => {
lgMediaQuery.removeEventListener('change', onMediaQueryChange)
}
}, [])
lgMediaQuery.removeEventListener("change", onMediaQueryChange);
};
}, []);
const [isMounted, setIsMounted] = useState(false)
const [isMounted, setIsMounted] = useState(false);
// Cambiar isMounted a true después del primer render
useEffect(() => {
setIsMounted(true)
}, [])
setIsMounted(true);
}, []);
return (
<section
@@ -88,16 +88,16 @@ export function PrimaryFeatures() {
<Container className="relative">
<div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl md:text-5xl">
{t('primaryFeatures.title')}
{t("primaryFeatures.title")}
</h2>
<p className="mt-6 text-lg tracking-tight text-muted-foreground">
{t('primaryFeatures.des')}
{t("primaryFeatures.des")}
</p>
</div>
<Tab.Group
as="div"
className="mt-16 grid grid-cols-1 items-center gap-y-2 pt-10 sm:gap-y-6 md:mt-20 lg:grid-cols-12 lg:pt-0"
vertical={tabOrientation === 'vertical'}
vertical={tabOrientation === "vertical"}
>
{({ selectedIndex }) => (
<>
@@ -113,12 +113,11 @@ export function PrimaryFeatures() {
initial={false}
key={`feature-${featureIndex}`}
className={cn(
'group relative rounded-full px-4 py-1 transition-colors lg:rounded-l-xl lg:rounded-r-none lg:p-6 ',
"group relative rounded-full px-4 py-1 transition-colors lg:rounded-l-xl lg:rounded-r-none lg:p-6 ",
)}
>
<AnimatePresence>
{selectedIndex ===
featureIndex && (
{selectedIndex === featureIndex && (
<motion.span
layoutId="tab"
className="absolute inset-0 z-10 rounded-full bg-white/5 mix-blend-difference lg:rounded-l-xl lg:rounded-r-none"
@@ -126,7 +125,7 @@ export function PrimaryFeatures() {
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
type: 'spring',
type: "spring",
bounce: 0.2,
duration: 0.5,
}}
@@ -136,7 +135,7 @@ export function PrimaryFeatures() {
<h3>
<Tab
className={cn(
'font-display text-lg text-primary ui-not-focus-visible:outline-none',
"font-display text-lg text-primary ui-not-focus-visible:outline-none",
)}
>
<span className="absolute inset-0 rounded-full lg:rounded-l-xl lg:rounded-r-none" />
@@ -145,7 +144,7 @@ export function PrimaryFeatures() {
</h3>
<p
className={cn(
'mt-2 hidden text-sm text-muted-foreground lg:block',
"mt-2 hidden text-sm text-muted-foreground lg:block",
)}
>
{t(feature.description)}
@@ -166,19 +165,11 @@ export function PrimaryFeatures() {
<motion.div
key={feature.title}
initial={
isMounted
? { opacity: 0.8, x: 50 }
: {}
}
animate={
isMounted
? { opacity: 1, x: 0 }
: {}
}
initial={isMounted ? { opacity: 0.8, x: 50 } : {}}
animate={isMounted ? { opacity: 1, x: 0 } : {}}
exit={{ opacity: 0, x: -50 }}
transition={{
type: 'spring',
type: "spring",
bounce: 0.2,
duration: 0.6,
}}
@@ -199,5 +190,5 @@ export function PrimaryFeatures() {
</Tab.Group>
</Container>
</section>
)
);
}

View File

@@ -1,38 +1,38 @@
'use client'
"use client";
import { cn } from '@/lib/utils'
import { Tab } from '@headlessui/react'
import { motion } from 'framer-motion'
import { Layers, Terminal, Users } from 'lucide-react'
import { Container } from './Container'
import { useTranslations } from 'next-intl'
import { cn } from "@/lib/utils";
import { Tab } from "@headlessui/react";
import { motion } from "framer-motion";
import { Layers, Terminal, Users } from "lucide-react";
import { useTranslations } from "next-intl";
import { Container } from "./Container";
interface Feature {
name: React.ReactNode
summary: string
description: string
image: string
icon: React.ComponentType
name: React.ReactNode;
summary: string;
description: string;
image: string;
icon: React.ComponentType;
}
const features: Array<Feature> = [
{
name: 'secondaryFeatures.templates',
summary: 'secondaryFeatures.templatesSummary',
description: 'secondaryFeatures.templatesDes',
image: '/secondary/templates.png',
name: "secondaryFeatures.templates",
summary: "secondaryFeatures.templatesSummary",
description: "secondaryFeatures.templatesDes",
image: "/secondary/templates.png",
icon: function ReportingIcon() {
return (
<>
<Layers className="size-5 text-primary" />
</>
)
);
},
},
{
name: 'secondaryFeatures.traefik',
summary: 'secondaryFeatures.traefikSummary',
description: 'secondaryFeatures.traefikDes',
image: '/secondary/traefik.png',
name: "secondaryFeatures.traefik",
summary: "secondaryFeatures.traefikSummary",
description: "secondaryFeatures.traefikDes",
image: "/secondary/traefik.png",
icon: function ReportingIcon() {
return (
<>
@@ -82,12 +82,7 @@ const features: Array<Feature> = [
d="M299.847 285.567c10.027 58.288 105.304 42.877 91.619-15.91-12.271-52.716-94.951-38.124-91.619 15.91m-113.855 9.427c12.996 50.745 94.24 37.753 91.178-13.149-3.669-60.964-103.603-49.2-91.178 13.149m132.351 58.517c.044 7.79 1.843 15.403.289 24.148-1.935 3.656-5.729 4.043-9.001 5.52-4.524-.71-8.328-3.68-10.143-7.912-1.161-9.202.433-18.111.726-27.316l18.129 5.56z"
fill="#fff"
/>
<ellipse
cx="208.4"
cy="286.718"
rx="13.719"
ry="14.86"
/>
<ellipse cx="208.4" cy="286.718" rx="13.719" ry="14.86" />
<ellipse
cx="214.64"
cy="290.071"
@@ -95,19 +90,9 @@ const features: Array<Feature> = [
ry="3.777"
fill="#fff"
/>
<ellipse
cx="323.348"
cy="283.017"
rx="13.491"
ry="14.86"
/>
<ellipse cx="323.348" cy="283.017" rx="13.491" ry="14.86" />
<g fill="#fff">
<ellipse
cx="329.485"
cy="286.371"
rx="3.181"
ry="3.777"
/>
<ellipse cx="329.485" cy="286.371" rx="3.181" ry="3.777" />
<path d="M279.137 354.685c-5.986 14.507 3.338 43.515 19.579 22.119-1.161-9.202.433-18.111.726-27.316l-20.305 5.197z" />
</g>
<path
@@ -226,60 +211,60 @@ const features: Array<Feature> = [
</g>
</svg>
</>
)
);
},
},
{
name: 'secondaryFeatures.users',
summary: 'secondaryFeatures.usersSummary',
description: 'secondaryFeatures.usersDes',
image: '/secondary/users.png',
name: "secondaryFeatures.users",
summary: "secondaryFeatures.usersSummary",
description: "secondaryFeatures.usersDes",
image: "/secondary/users.png",
icon: function InventoryIcon() {
return (
<>
<Users className="size-5 text-primary" />
</>
)
);
},
},
{
name: 'secondaryFeatures.terminal',
summary: 'secondaryFeatures.terminalSummary',
description: 'secondaryFeatures.terminalDes',
image: '/secondary/terminal.png',
name: "secondaryFeatures.terminal",
summary: "secondaryFeatures.terminalSummary",
description: "secondaryFeatures.terminalDes",
image: "/secondary/terminal.png",
icon: function ContactsIcon() {
return (
<>
<Terminal className="size-5 text-primary" />
</>
)
);
},
},
]
];
function Feature({
feature,
isActive,
className,
...props
}: React.ComponentPropsWithoutRef<'div'> & {
feature: Feature
isActive: boolean
}: React.ComponentPropsWithoutRef<"div"> & {
feature: Feature;
isActive: boolean;
}) {
const t = useTranslations('HomePage')
const t = useTranslations("HomePage");
return (
<div
className={cn(
className,
!isActive ? 'opacity-75 hover:opacity-100 ' : 'rounded-xl',
' relative p-4',
!isActive ? "opacity-75 hover:opacity-100 " : "rounded-xl",
" relative p-4",
)}
{...props}
>
<div
className={cn(
'flex size-9 items-center justify-center rounded-lg',
isActive ? 'bg-border' : 'bg-muted',
"flex size-9 items-center justify-center rounded-lg",
isActive ? "bg-border" : "bg-muted",
)}
>
<feature.icon />
@@ -289,7 +274,7 @@ function Feature({
layoutId="bubble"
className="absolute inset-0 z-10 rounded-xl bg-white/5 mix-blend-difference"
transition={{
type: 'spring',
type: "spring",
bounce: 0.2,
duration: 0.6,
}}
@@ -297,8 +282,8 @@ function Feature({
)}
<h3
className={cn(
'mt-6 text-sm font-medium',
isActive ? 'text-primary' : 'text-primary/85',
"mt-6 text-sm font-medium",
isActive ? "text-primary" : "text-primary/85",
)}
>
{feature.name}
@@ -310,7 +295,7 @@ function Feature({
{t(feature.description)}
</p>
</div>
)
);
}
function FeaturesMobile() {
@@ -318,11 +303,7 @@ function FeaturesMobile() {
<div className="-mx-4 mt-20 flex flex-col gap-y-10 overflow-hidden px-4 sm:-mx-6 sm:px-6 lg:hidden">
{features.map((feature) => (
<div key={feature.summary}>
<Feature
feature={feature}
className="mx-auto max-w-2xl"
isActive
/>
<Feature feature={feature} className="mx-auto max-w-2xl" isActive />
<div className="relative mt-10 pb-10">
<div className="absolute -inset-x-4 bottom-0 top-8 bg-muted sm:-inset-x-6" />
<div className="relative mx-auto w-[52.75rem] overflow-hidden rounded-xl bg-white shadow-lg shadow-slate-900/5 ring-1 ring-slate-500/10">
@@ -337,11 +318,11 @@ function FeaturesMobile() {
</div>
))}
</div>
)
);
}
function FeaturesDesktop() {
const t = useTranslations('HomePage')
const t = useTranslations("HomePage");
return (
<Tab.Group as="div" className="hidden lg:mt-20 lg:block">
{({ selectedIndex }) => (
@@ -371,9 +352,8 @@ function FeaturesDesktop() {
static
key={feature.summary}
className={cn(
'px-5 transition duration-500 ease-in-out ui-not-focus-visible:outline-none',
featureIndex !== selectedIndex &&
'opacity-60',
"px-5 transition duration-500 ease-in-out ui-not-focus-visible:outline-none",
featureIndex !== selectedIndex && "opacity-60",
)}
style={{
transform: `translateX(-${selectedIndex * 100}%)`,
@@ -396,11 +376,11 @@ function FeaturesDesktop() {
</>
)}
</Tab.Group>
)
);
}
export function SecondaryFeatures() {
const t = useTranslations('HomePage')
const t = useTranslations("HomePage");
return (
<section
id="secondary-features"
@@ -410,15 +390,15 @@ export function SecondaryFeatures() {
<Container className="max-w-[95rem]">
<div className="mx-auto max-w-2xl md:text-center">
<h2 className="font-display text-3xl tracking-tight text-primary sm:text-4xl">
{t('secondaryFeatures.title')}
{t("secondaryFeatures.title")}
</h2>
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
{t('secondaryFeatures.des')}
{t("secondaryFeatures.des")}
</p>
</div>
<FeaturesMobile />
<FeaturesDesktop />
</Container>
</section>
)
);
}

View File

@@ -1,10 +1,10 @@
import { Link } from '@/i18n/routing'
import { Footer } from './Footer'
import { Header } from './Header'
import { useTranslations } from 'next-intl'
import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { Footer } from "./Footer";
import { Header } from "./Header";
export function SlimLayout() {
const t = useTranslations('404')
const t = useTranslations("404");
return (
<>
<div>
@@ -12,16 +12,12 @@ export function SlimLayout() {
</div>
<main className="flex flex-auto items-center justify-center text-center">
<div>
<h1 className="mb-4 text-6xl font-semibold text-primary">
404
</h1>
<p className="mb-4 text-lg text-muted-foreground">
{t('title')}
</p>
<h1 className="mb-4 text-6xl font-semibold text-primary">404</h1>
<p className="mb-4 text-lg text-muted-foreground">{t("title")}</p>
<p className="mt-4 text-muted-foreground">
{t('des')}{' '}
{t("des")}{" "}
<Link href="/" className="text-primary">
{t('action')}
{t("action")}
</Link>
</p>
</div>
@@ -30,5 +26,5 @@ export function SlimLayout() {
<Footer />
</div>
</>
)
);
}

View File

@@ -1,16 +1,16 @@
'use client'
"use client";
import * as React from 'react'
import * as SelectPrimitive from '@radix-ui/react-select'
import { Check, ChevronDown, ChevronUp } from 'lucide-react'
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown, ChevronUp } from "lucide-react";
import * as React from "react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
const Select = SelectPrimitive.Root
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
@@ -29,8 +29,8 @@ const SelectTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
@@ -39,15 +39,15 @@ const SelectScrollUpButton = React.forwardRef<
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
@@ -56,28 +56,28 @@ const SelectScrollDownButton = React.forwardRef<
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
));
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = 'popper', ...props }, ref) => (
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
@@ -86,9 +86,9 @@ const SelectContent = React.forwardRef<
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
@@ -96,8 +96,8 @@ const SelectContent = React.forwardRef<
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
@@ -105,11 +105,11 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
@@ -118,7 +118,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
@@ -131,8 +131,8 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
@@ -140,11 +140,11 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
@@ -157,4 +157,4 @@ export {
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
};

View File

@@ -1,12 +1,12 @@
import { notFound } from 'next/navigation'
import { getRequestConfig } from 'next-intl/server'
import { routing } from './routing'
import { getRequestConfig } from "next-intl/server";
import { notFound } from "next/navigation";
import { routing } from "./routing";
export default getRequestConfig(async ({ locale }) => {
// Validate that the incoming `locale` parameter is valid
if (!routing.locales.includes(locale as any)) notFound()
// Validate that the incoming `locale` parameter is valid
if (!routing.locales.includes(locale as any)) notFound();
return {
messages: (await import(`../locales/${locale}.json`)).default,
}
})
return {
messages: (await import(`../locales/${locale}.json`)).default,
};
});

View File

@@ -1,16 +1,16 @@
import { defineRouting } from 'next-intl/routing'
import { createSharedPathnamesNavigation } from 'next-intl/navigation'
import { createSharedPathnamesNavigation } from "next-intl/navigation";
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
// A list of all locales that are supported
locales: ['en', 'zh-Hans'],
locales: ["en", "zh-Hans"],
// Used when no locale matches
defaultLocale: 'en',
localePrefix: 'as-needed',
})
defaultLocale: "en",
localePrefix: "as-needed",
});
// Lightweight wrappers around Next.js' navigation APIs
// that will consider the routing configuration
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation(routing)
createSharedPathnamesNavigation(routing);

View File

@@ -7,8 +7,8 @@
"support": "Support",
"discord": "Discord",
"i18nButtonPlaceholder": "Language",
"i18nEn":"English",
"i18nZh-Hans":"简体中文"
"i18nEn": "English",
"i18nZh-Hans": "简体中文"
},
"hero": {
"deploy": "Deploy",

View File

@@ -7,8 +7,8 @@
"support": "赞助",
"discord": "Discord",
"i18nButtonPlaceholder": "语言",
"i18nEn":"English",
"i18nZh-Hans":"简体中文"
"i18nEn": "English",
"i18nZh-Hans": "简体中文"
},
"hero": {
"deploy": "部署在",

View File

@@ -1,9 +1,9 @@
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing)
export default createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
matcher: ['/((?!_next|.*\\..*).*)']
}
matcher: ["/((?!_next|.*\\..*).*)"],
};

View File

@@ -1,15 +1,15 @@
const createNextIntlPlugin = require('next-intl/plugin')
const createNextIntlPlugin = require("next-intl/plugin");
const withNextIntl = createNextIntlPlugin()
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
}
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
};
module.exports = withNextIntl(nextConfig)
module.exports = withNextIntl(nextConfig);

View File

@@ -4,5 +4,5 @@ module.exports = {
semi: false,
tabWidth: 4,
useTabs: true,
plugins: ['prettier-plugin-tailwindcss'],
}
plugins: ["prettier-plugin-tailwindcss"],
};