mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add translate for zh-Hans
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,63 +1,58 @@
|
||||
import { Container } from "./Container";
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Container } from './Container'
|
||||
|
||||
const faqs = [
|
||||
[
|
||||
{
|
||||
question: "What is dokploy?",
|
||||
answer:
|
||||
"Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as a free alternative self-hostable solution to platforms like Heroku, Vercel, and Netlify.",
|
||||
question: 'faq.q1',
|
||||
answer: 'faq.a2',
|
||||
},
|
||||
{
|
||||
question: "Why Choose Dokploy?",
|
||||
answer: "Simplicity, Flexibility, and Fast",
|
||||
question: 'faq.q2',
|
||||
answer: 'faq.a2',
|
||||
},
|
||||
{
|
||||
question: "Is free?",
|
||||
answer:
|
||||
"Yes, dokploy is totally free. You can use it for personal projects, small teams, or even for large-scale applications.",
|
||||
question: 'faq.q3',
|
||||
answer: 'faq.a3',
|
||||
},
|
||||
{
|
||||
question: "Is it open source?",
|
||||
answer: "Yes, dokploy is open source and free to use.",
|
||||
question: 'faq.q4',
|
||||
answer: 'faq.a4',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question: "What type of applications can i deploy with dokploy?",
|
||||
answer:
|
||||
"Dokploy is a great choice for any type of application. You can deploy your code to dokploy and manage it from the dashboard. We support a wide range of languages and frameworks, so you can choose the one that best fits your needs.",
|
||||
question: 'faq.q5',
|
||||
answer: 'faq.a5',
|
||||
},
|
||||
{
|
||||
question: "How do I request a feature or report a bug?",
|
||||
answer:
|
||||
"Currently we are working on fixing bug fixes, but we will be releasing new features soon. You can also request features or report bugs.",
|
||||
question: 'faq.q6',
|
||||
answer: 'faq.a6',
|
||||
},
|
||||
{
|
||||
question: "Do you track the usage of Dokploy?",
|
||||
answer: "No, we don't track any usage data.",
|
||||
question: 'faq.q7',
|
||||
answer: "faq.a7",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question:
|
||||
"Are there any user forums or communities where I can interact with other users?",
|
||||
answer:
|
||||
"Yes, we have active github discussions where you can share ideas, ask for help, and connect with other users.",
|
||||
'faq.q8',
|
||||
answer: 'faq.a8',
|
||||
},
|
||||
{
|
||||
question: "What types of applications can I deploy with Dokploy?",
|
||||
answer:
|
||||
"Dokploy supports a variety of applications, including those built with Docker, as well as applications from any Git repository, offering custom builds with Nixpacks, Dockerfiles, or Buildpacks like Heroku and Paketo.",
|
||||
question: 'faq.q9',
|
||||
answer: 'faq.a9',
|
||||
},
|
||||
{
|
||||
question: "How does Dokploy handle database management?",
|
||||
answer:
|
||||
"Dokploy supports multiple database systems including Postgres, MySQL, MariaDB, MongoDB, and Redis, providing tools for easy deployment and management directly from the dashboard.",
|
||||
question: 'faq.q10',
|
||||
answer: 'faq.a10',
|
||||
},
|
||||
],
|
||||
];
|
||||
]
|
||||
|
||||
export function Faqs() {
|
||||
const t = useTranslations('HomePage')
|
||||
return (
|
||||
<section
|
||||
id="faqs"
|
||||
@@ -70,11 +65,10 @@ export function Faqs() {
|
||||
id="faq-title"
|
||||
className="font-display text-3xl tracking-tight text-primary sm:text-4xl"
|
||||
>
|
||||
Frequently asked questions
|
||||
{t('faq.title')}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
If you can’t find what you’re looking for, email our support team
|
||||
and if you’re lucky someone will get back to you.
|
||||
{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">
|
||||
@@ -84,10 +78,10 @@ export function Faqs() {
|
||||
{column.map((faq, faqIndex) => (
|
||||
<li key={faqIndex}>
|
||||
<h3 className="font-display text-lg leading-7 text-primary">
|
||||
{faq.question}
|
||||
{t(faq.question)}
|
||||
</h3>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
{faq.answer}
|
||||
{t(faq.answer)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
@@ -97,5 +91,5 @@ export function Faqs() {
|
||||
</ul>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import Link from "next/link";
|
||||
import Link from 'next/link'
|
||||
|
||||
import { Container } from "./Container";
|
||||
import { NavLink } from "./NavLink";
|
||||
import { Logo } from "./shared/Logo";
|
||||
import { Container } from './Container'
|
||||
import { NavLink } from './NavLink'
|
||||
import { Logo } from './shared/Logo'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
export function Footer() {
|
||||
const t = useTranslations('HomePage')
|
||||
return (
|
||||
<footer className="bg-black">
|
||||
<Container>
|
||||
<div className="py-16">
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Logo className="mx-auto h-10 w-auto" />
|
||||
<span className="text-center text-sm font-medium text-primary">
|
||||
Dokploy
|
||||
@@ -17,14 +19,18 @@ export function Footer() {
|
||||
</div>
|
||||
|
||||
<nav className="mt-10 text-sm" aria-label="quick links">
|
||||
<div className="-my-1 flex justify-center gap-6 flex-wrap">
|
||||
<NavLink href="/#features">Features</NavLink>
|
||||
<NavLink href="/#faqs">Faqs</NavLink>
|
||||
<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="https://docs.dokploy.com/get-started/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
{t('navigation.docs')}
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -57,11 +63,12 @@ export function Footer() {
|
||||
</Link>
|
||||
</div>
|
||||
<p className="mt-6 text-sm text-muted-foreground sm:mt-0">
|
||||
Copyright © {new Date().getFullYear()} Dokploy. All rights
|
||||
reserved.
|
||||
{t('footer.copyright', {
|
||||
year: new Date().getFullYear(),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { HeartIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Fragment } 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 { cn } from '@/lib/utils'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { HeartIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Fragment } 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 { useTranslations } from 'next-intl'
|
||||
|
||||
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}
|
||||
@@ -36,7 +37,7 @@ function MobileNavLink({
|
||||
>
|
||||
{children}
|
||||
</Popover.Button>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function MobileNavIcon({ open }: { open: boolean }) {
|
||||
@@ -50,20 +51,24 @@ 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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function MobileNavigation() {
|
||||
const t = useTranslations('HomePage')
|
||||
return (
|
||||
<Popover>
|
||||
<Popover.Button
|
||||
@@ -96,27 +101,32 @@ function MobileNavigation() {
|
||||
>
|
||||
<Popover.Panel
|
||||
as="div"
|
||||
className="absolute inset-x-0 top-full mt-4 flex origin-top flex-col rounded-2xl bg-background border border-border p-4 text-lg tracking-tight text-primary shadow-xl ring-1 ring-border/5"
|
||||
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">Features</MobileNavLink>
|
||||
<MobileNavLink href="/#testimonials">Testimonials</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">Faqs</MobileNavLink>
|
||||
<MobileNavLink href="/#features">
|
||||
{t('navigation.features')}
|
||||
</MobileNavLink>
|
||||
{/* <MobileNavLink href="/#testimonials">Testimonials</MobileNavLink> */}
|
||||
<MobileNavLink href="/#faqs">
|
||||
{t('navigation.faqs')}
|
||||
</MobileNavLink>
|
||||
<MobileNavLink
|
||||
href="https://docs.dokploy.com/get-started/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
{t('navigation.docs')}
|
||||
</MobileNavLink>
|
||||
</Popover.Panel>
|
||||
</Transition.Child>
|
||||
</Transition.Root>
|
||||
</Popover>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function Header() {
|
||||
const t = useTranslations('HomePage')
|
||||
return (
|
||||
<header className="py-10 bg-background">
|
||||
<header className="bg-background py-10">
|
||||
<Container>
|
||||
<nav className="relative z-50 flex justify-between">
|
||||
<div className="flex items-center md:gap-x-12">
|
||||
@@ -124,28 +134,35 @@ export function Header() {
|
||||
<Logo className="h-10 w-auto" />
|
||||
</Link>
|
||||
<div className="hidden md:flex md:gap-x-6">
|
||||
<NavLink href="/#features">Features</NavLink>
|
||||
<NavLink href="/#features">
|
||||
{t('navigation.features')}
|
||||
</NavLink>
|
||||
{/* <NavLink href="/#testimonials">Testimonials</NavLink> */}
|
||||
<NavLink href="/#faqs">Faqs</NavLink>
|
||||
<NavLink href="/#faqs">
|
||||
{t('navigation.faqs')}
|
||||
</NavLink>
|
||||
<NavLink
|
||||
href="https://docs.dokploy.com/get-started/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
Docs
|
||||
{t('navigation.docs')}
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 md:gap-x-5">
|
||||
<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">Support </span>
|
||||
<HeartIcon className="size-4 text-red-500 fill-red-600 animate-heartbeat " />
|
||||
<span className="text-sm font-semibold">
|
||||
{t('navigation.support')}{' '}
|
||||
</span>
|
||||
<HeartIcon className="animate-heartbeat size-4 fill-red-600 text-red-500 " />
|
||||
</Link>
|
||||
{/* @ts-expect-error */}
|
||||
<Button
|
||||
@@ -156,7 +173,7 @@ export function Header() {
|
||||
href="https://discord.gg/2tBnJ3jDJc"
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row gap-2 items-center text-white"
|
||||
className="flex flex-row items-center gap-2 text-white"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
@@ -166,7 +183,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>
|
||||
Discord
|
||||
{t('navigation.discord')}
|
||||
</Link>
|
||||
</Button>
|
||||
<div className="-mr-1 md:hidden">
|
||||
@@ -176,5 +193,5 @@ export function Header() {
|
||||
</nav>
|
||||
</Container>
|
||||
</header>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,77 +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 { cn } from '@/lib/utils'
|
||||
import { Container } from './Container'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Projects",
|
||||
description:
|
||||
"Manage and organize all your projects in one place, keeping detailed track of progress and resource allocation.",
|
||||
image: "/primary/projects.png",
|
||||
title: 'primaryFeatures.projects',
|
||||
description: 'primaryFeatures.projectsDes',
|
||||
image: '/primary/projects.png',
|
||||
},
|
||||
{
|
||||
title: "Applications & Databases",
|
||||
description:
|
||||
"Centralize control over your applications and databases for enhanced security and efficiency, simplifying access and management across your infrastructure.",
|
||||
image: "/primary/applications.png",
|
||||
title: 'primaryFeatures.applications',
|
||||
description: 'primaryFeatures.applicationsDes',
|
||||
image: '/primary/applications.png',
|
||||
},
|
||||
{
|
||||
title: "Docker Compose",
|
||||
description:
|
||||
"Native Docker Compose support for manage complex applications and services with ease.",
|
||||
image: "/primary/compose.png",
|
||||
title: 'primaryFeatures.compose',
|
||||
description: 'primaryFeatures.composeDes',
|
||||
image: '/primary/compose.png',
|
||||
},
|
||||
{
|
||||
title: "Multi Node",
|
||||
description:
|
||||
"Scale applications to multiples nodes using docker swarm to manage the cluster.",
|
||||
image: "/primary/multinode.png",
|
||||
title: 'primaryFeatures.multinode',
|
||||
description: 'primaryFeatures.multinodeDes',
|
||||
image: '/primary/multinode.png',
|
||||
},
|
||||
{
|
||||
title: "Monitoring",
|
||||
description:
|
||||
"Monitor your systems' performance and health in real time, ensuring continuous and uninterrupted operation.",
|
||||
image: "/primary/monitoring.png",
|
||||
title: 'primaryFeatures.monitoring',
|
||||
description: 'primaryFeatures.monitoringDes',
|
||||
image: '/primary/monitoring.png',
|
||||
},
|
||||
{
|
||||
title: "Backups",
|
||||
description:
|
||||
"Implement automatic and secure backup solutions to protect your critical data and restore it quickly when necessary.",
|
||||
image: "/primary/backups.png",
|
||||
title: 'primaryFeatures.backups',
|
||||
description: 'primaryFeatures.backupsDes',
|
||||
image: '/primary/backups.png',
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
export function PrimaryFeatures() {
|
||||
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
|
||||
@@ -92,17 +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">
|
||||
Comprehensive Control for Your Digital Ecosystem
|
||||
{t('primaryFeatures.title')}
|
||||
</h2>
|
||||
<p className="mt-6 text-lg tracking-tight text-muted-foreground">
|
||||
Simplify your project and data management, ensure robust monitoring,
|
||||
and secure your backups—all without the fuss over minute details.
|
||||
{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 }) => (
|
||||
<>
|
||||
@@ -118,19 +113,20 @@ export function PrimaryFeatures() {
|
||||
initial={false}
|
||||
key={`feature-${featureIndex}`}
|
||||
className={cn(
|
||||
"group relative rounded-full px-4 py-1 lg:rounded-l-xl lg:rounded-r-none lg:p-6 transition-colors ",
|
||||
'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 bg-white/5 rounded-full mix-blend-difference lg:rounded-l-xl lg:rounded-r-none"
|
||||
className="absolute inset-0 z-10 rounded-full bg-white/5 mix-blend-difference lg:rounded-l-xl lg:rounded-r-none"
|
||||
initial={{ opacity: 1 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
type: 'spring',
|
||||
bounce: 0.2,
|
||||
duration: 0.5,
|
||||
}}
|
||||
@@ -140,19 +136,19 @@ export function PrimaryFeatures() {
|
||||
<h3>
|
||||
<Tab
|
||||
className={cn(
|
||||
"font-display text-lg ui-not-focus-visible:outline-none text-primary",
|
||||
'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" />
|
||||
{feature.title}
|
||||
{t(feature.title)}
|
||||
</Tab>
|
||||
</h3>
|
||||
<p
|
||||
className={cn(
|
||||
"mt-2 hidden text-sm lg:block text-muted-foreground",
|
||||
'mt-2 hidden text-sm text-muted-foreground lg:block',
|
||||
)}
|
||||
>
|
||||
{feature.description}
|
||||
{t(feature.description)}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
@@ -164,21 +160,29 @@ export function PrimaryFeatures() {
|
||||
<div className="relative sm:px-6 lg:hidden">
|
||||
<div className="absolute -inset-x-4 bottom-[-4.25rem] top-[-6.5rem] bg-white/10 ring-1 ring-inset ring-white/10 sm:inset-x-0 sm:rounded-t-xl" />
|
||||
<p className="relative mx-auto max-w-2xl text-base text-white sm:text-center">
|
||||
{feature.description}
|
||||
{t(feature.description)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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,
|
||||
}}
|
||||
className="mt-10 h-[24rem] lg:h-[40rem] w-[45rem] overflow-hidden rounded-xl shadow-xl border sm:w-auto lg:mt-0 lg:w-[67.8125rem]"
|
||||
className="mt-10 h-[24rem] w-[45rem] overflow-hidden rounded-xl border shadow-xl sm:w-auto lg:mt-0 lg:h-[40rem] lg:w-[67.8125rem]"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
@@ -195,5 +199,5 @@ export function PrimaryFeatures() {
|
||||
</Tab.Group>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,40 +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 { 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'
|
||||
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: "Open Source Templates",
|
||||
summary: "One click to deploy open source templates.",
|
||||
description:
|
||||
"Deploy open source templates with one click, powered by Docker Compose, (Plausible, Calcom, Pocketbase, etc.)",
|
||||
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: "Real-Time Traefik Configuration",
|
||||
summary:
|
||||
" Modify Traefik settings on-the-fly via a graphical interface or API.",
|
||||
description:
|
||||
"Users can adjust Traefik's configuration, including middleware, forwarding rules, and SSL certificates through an intuitive interface or API. This feature enables seamless traffic routing and security adjustments without the need to restart services",
|
||||
image: "/secondary/traefik.png",
|
||||
name: 'secondaryFeatures.traefik',
|
||||
summary: 'secondaryFeatures.traefikSummary',
|
||||
description: 'secondaryFeatures.traefikDes',
|
||||
image: '/secondary/traefik.png',
|
||||
icon: function ReportingIcon() {
|
||||
return (
|
||||
<>
|
||||
@@ -84,7 +82,12 @@ 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"
|
||||
@@ -92,9 +95,19 @@ 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
|
||||
@@ -213,63 +226,60 @@ const features: Array<Feature> = [
|
||||
</g>
|
||||
</svg>
|
||||
</>
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "User Permission Management",
|
||||
summary:
|
||||
"Detailed control over user permissions for accessing and managing projects and services.",
|
||||
description:
|
||||
"Allows administrators to define specific roles and permissions for each user, including the ability to create, modify, or delete applications and databases. This feature ensures secure and efficient management of large and diverse teams.",
|
||||
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: "Terminal Access",
|
||||
summary:
|
||||
"Direct access to each container's and server terminal for advanced management.",
|
||||
description:
|
||||
"Provides an interface to access the command line of any active container, allowing developers to execute commands, manage services, and troubleshoot directly from the dashboard",
|
||||
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')
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
className,
|
||||
!isActive ? "opacity-75 hover:opacity-100 " : "rounded-xl",
|
||||
" p-4 relative",
|
||||
!isActive ? 'opacity-75 hover:opacity-100 ' : 'rounded-xl',
|
||||
' relative p-4',
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"size-9 rounded-lg flex items-center justify-center",
|
||||
isActive ? "bg-border" : "bg-muted",
|
||||
'flex size-9 items-center justify-center rounded-lg',
|
||||
isActive ? 'bg-border' : 'bg-muted',
|
||||
)}
|
||||
>
|
||||
<feature.icon />
|
||||
@@ -277,9 +287,9 @@ function Feature({
|
||||
{isActive && (
|
||||
<motion.span
|
||||
layoutId="bubble"
|
||||
className="absolute inset-0 z-10 bg-white/5 mix-blend-difference rounded-xl"
|
||||
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,
|
||||
}}
|
||||
@@ -287,20 +297,20 @@ 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}
|
||||
</h3>
|
||||
<p className="mt-2 font-display text-xl text-foreground">
|
||||
{feature.summary}
|
||||
{t(feature.summary)}
|
||||
</p>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
{feature.description}
|
||||
{t(feature.description)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function FeaturesMobile() {
|
||||
@@ -308,7 +318,11 @@ 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">
|
||||
@@ -323,10 +337,11 @@ function FeaturesMobile() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function FeaturesDesktop() {
|
||||
const t = useTranslations('HomePage')
|
||||
return (
|
||||
<Tab.Group as="div" className="hidden lg:mt-20 lg:block">
|
||||
{({ selectedIndex }) => (
|
||||
@@ -340,7 +355,7 @@ function FeaturesDesktop() {
|
||||
name: (
|
||||
<Tab className="ui-not-focus-visible:outline-none">
|
||||
<span className="absolute inset-0" />
|
||||
{feature.name}
|
||||
{t(feature.name)}
|
||||
</Tab>
|
||||
),
|
||||
}}
|
||||
@@ -356,10 +371,13 @@ 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}%)` }}
|
||||
style={{
|
||||
transform: `translateX(-${selectedIndex * 100}%)`,
|
||||
}}
|
||||
aria-hidden={featureIndex !== selectedIndex}
|
||||
>
|
||||
<div className="w-[52.75rem] overflow-hidden rounded-xl bg-red-500 shadow-lg shadow-slate-900/5 ring-1 ring-slate-500/10">
|
||||
@@ -378,30 +396,29 @@ function FeaturesDesktop() {
|
||||
</>
|
||||
)}
|
||||
</Tab.Group>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function SecondaryFeatures() {
|
||||
const t = useTranslations('HomePage')
|
||||
return (
|
||||
<section
|
||||
id="secondary-features"
|
||||
aria-label="Features for simplifying everyday business tasks"
|
||||
className="pb-14 pt-20 sm:pb-20 sm:pt-32 lg:pb-32 bg-black"
|
||||
className="bg-black pb-14 pt-20 sm:pb-20 sm:pt-32 lg:pb-32"
|
||||
>
|
||||
<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">
|
||||
Advanced Management Tools
|
||||
{t('secondaryFeatures.title')}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
Elevate your infrastructure with tools that offer precise control,
|
||||
detailed monitoring, and enhanced security, ensuring seamless
|
||||
management and robust performance.
|
||||
{t('secondaryFeatures.des')}
|
||||
</p>
|
||||
</div>
|
||||
<FeaturesMobile />
|
||||
<FeaturesDesktop />
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { Footer } from "./Footer";
|
||||
import { Header } from "./Header";
|
||||
import Link from 'next/link'
|
||||
import { Footer } from './Footer'
|
||||
import { Header } from './Header'
|
||||
|
||||
export function SlimLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
@@ -8,14 +8,16 @@ export function SlimLayout({ children }: { children: React.ReactNode }) {
|
||||
<div>
|
||||
<Header />
|
||||
</div>
|
||||
<main className="text-center flex-auto items-center flex justify-center">
|
||||
<main className="flex flex-auto items-center justify-center text-center">
|
||||
<div>
|
||||
<h1 className="mb-4 text-6xl font-semibold text-primary">404</h1>
|
||||
<h1 className="mb-4 text-6xl font-semibold text-primary">
|
||||
404
|
||||
</h1>
|
||||
<p className="mb-4 text-lg text-muted-foreground">
|
||||
Oops! Looks like you're lost.
|
||||
</p>
|
||||
<p className="mt-4 text-muted-foreground">
|
||||
Let's get you back{" "}
|
||||
Let's get you back{' '}
|
||||
<Link href="/" className="text-primary">
|
||||
home
|
||||
</Link>
|
||||
@@ -27,5 +29,5 @@ export function SlimLayout({ children }: { children: React.ReactNode }) {
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user