1
0
Fork 0

style: run prettier

This commit is contained in:
Vojtěch Mareš 2022-12-19 10:16:19 +01:00
parent 2193d68895
commit d80c0ec7de
Signed by: vojtech.mares
GPG key ID: C6827B976F17240D
27 changed files with 698 additions and 569 deletions

View file

@ -1,64 +1,60 @@
import Link from 'next/link' import Link from "next/link";
import clsx from 'clsx' import clsx from "clsx";
import { ReactNode } from 'react' import { ReactNode } from "react";
const baseStyles = { const baseStyles = {
solid: solid:
'group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2', "group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2",
outline: outline:
'group inline-flex ring-1 items-center justify-center rounded-full py-2 px-4 text-sm focus:outline-none', "group inline-flex ring-1 items-center justify-center rounded-full py-2 px-4 text-sm focus:outline-none",
} };
const variantStyles = { const variantStyles = {
solid: { solid: {
slate: slate:
'bg-slate-900 text-white hover:bg-slate-700 hover:text-slate-100 active:bg-slate-800 active:text-slate-300 focus-visible:outline-slate-900', "bg-slate-900 text-white hover:bg-slate-700 hover:text-slate-100 active:bg-slate-800 active:text-slate-300 focus-visible:outline-slate-900",
blue: 'bg-blue-600 text-white hover:text-slate-100 hover:bg-blue-500 active:bg-blue-800 active:text-blue-100 focus-visible:outline-blue-600', blue: "bg-blue-600 text-white hover:text-slate-100 hover:bg-blue-500 active:bg-blue-800 active:text-blue-100 focus-visible:outline-blue-600",
white: white:
'bg-white text-slate-900 hover:bg-blue-50 active:bg-blue-200 active:text-slate-600 focus-visible:outline-white', "bg-white text-slate-900 hover:bg-blue-50 active:bg-blue-200 active:text-slate-600 focus-visible:outline-white",
}, },
outline: { outline: {
slate: slate:
'ring-slate-200 text-slate-700 hover:text-slate-900 hover:ring-slate-300 active:bg-slate-100 active:text-slate-600 focus-visible:outline-blue-600 focus-visible:ring-slate-300', "ring-slate-200 text-slate-700 hover:text-slate-900 hover:ring-slate-300 active:bg-slate-100 active:text-slate-600 focus-visible:outline-blue-600 focus-visible:ring-slate-300",
white: white:
'ring-slate-700 text-white hover:ring-slate-500 active:ring-slate-700 active:text-slate-400 focus-visible:outline-white', "ring-slate-700 text-white hover:ring-slate-500 active:ring-slate-700 active:text-slate-400 focus-visible:outline-white",
blue: // Supress TypeScript error // Supress TypeScript error
'', // TODO: fix properly blue: "", // TODO: fix properly
}, },
} };
type Props = { type Props = {
variant?: 'solid'|'outline', variant?: "solid" | "outline";
color?: 'slate'|'white'|'blue', color?: "slate" | "white" | "blue";
className?: string, className?: string;
href?: string, href?: string;
children?: ReactNode, children?: ReactNode;
} };
export function Button({ export function Button({
variant = 'solid', variant = "solid",
color = 'slate', color = "slate",
className, className,
href, href,
children children,
}: Props) { }: Props) {
className = clsx( className = clsx(
baseStyles[variant], baseStyles[variant],
variantStyles[variant][color], variantStyles[variant][color],
className className
) );
if (href !== undefined) { if (href !== undefined) {
return ( return (
<Link href={href} className={className}> <Link href={href} className={className}>
{children} {children}
</Link> </Link>
) );
} else { } else {
return ( return <button className={className}>{children}</button>;
<button className={className}>
{children}
</button>
)
} }
} }

View file

@ -1,8 +1,8 @@
import Image from 'next/image' import Image from "next/image";
import { Button } from '@/components/Button' import { Button } from "@/components/Button";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
import backgroundImage from '@/images/background-call-to-action.jpg' import backgroundImage from "@/images/background-call-to-action.jpg";
export function CallToAction() { export function CallToAction() {
return ( return (
@ -24,14 +24,19 @@ export function CallToAction() {
Posuňte svoji aplikaci dnes Posuňte svoji aplikaci dnes
</h2> </h2>
<p className="mt-4 text-lg tracking-tight text-white"> <p className="mt-4 text-lg tracking-tight text-white">
Je na čase pozvednout vaši infrastrukturu na dnešní standardy. Je na čase pozvednout vaši infrastrukturu na dnešní standardy. Vaše
Vaše infrastruktura by měla nabídnout Vaši aplikaci světu, ne ji držet zpátky. infrastruktura by měla nabídnout Vaši aplikaci světu, ne ji držet
zpátky.
</p> </p>
<Button href="https://calendly.com/vojtechmares/30min" color="white" className="mt-10"> <Button
href="https://calendly.com/vojtechmares/30min"
color="white"
className="mt-10"
>
Domluvme si schůzku Domluvme si schůzku
</Button> </Button>
</div> </div>
</Container> </Container>
</section> </section>
) );
} }

View file

@ -1,17 +1,15 @@
import clsx from 'clsx' import clsx from "clsx";
import { ReactNode } from 'react' import { ReactNode } from "react";
type Props = { type Props = {
className?: string, className?: string;
children?: ReactNode children?: ReactNode;
} };
export function Container({ className, children }: Props ) { export function Container({ className, children }: Props) {
return ( return (
<div <div className={clsx("mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", className)}>
className={clsx('mx-auto max-w-7xl px-4 sm:px-6 lg:px-8', className)}
>
{children} {children}
</div> </div>
) );
} }

View file

@ -1,49 +1,48 @@
import Image from 'next/image' import Image from "next/image";
import { Container } from './Container' import { Container } from "./Container";
import logoKubernetes from '@/images/logos/tools/kubernetes.svg' import logoKubernetes from "@/images/logos/tools/kubernetes.svg";
import logok3s from '@/images/logos/tools/k3s.svg' import logok3s from "@/images/logos/tools/k3s.svg";
import logoRancher from '@/images/logos/tools/rancher.svg' import logoRancher from "@/images/logos/tools/rancher.svg";
import logoAWS from '@/images/logos/tools/amazonaws.svg' import logoAWS from "@/images/logos/tools/amazonaws.svg";
import logoDigitalOcean from '@/images/logos/tools/digitalocean.svg' import logoDigitalOcean from "@/images/logos/tools/digitalocean.svg";
import logovmware from '@/images/logos/tools/vmware.svg' import logovmware from "@/images/logos/tools/vmware.svg";
const features = [ const features = [
{ {
name: 'Kubernetes', name: "Kubernetes",
summary: 'Open Source systém pro automatizaci deploymentů, škálování a správu kontejnerizovaných aplikací.', summary:
"Open Source systém pro automatizaci deploymentů, škálování a správu kontejnerizovaných aplikací.",
description: description:
'Kubernetes je na platformě nezávislý systém, díky kterému může běžet vaše aplikace u vás v datacentru, v public cloudu nebo třeba v okrajových lokalitách, a nebo třeba na všech najednou, bez problému.', "Kubernetes je na platformě nezávislý systém, díky kterému může běžet vaše aplikace u vás v datacentru, v public cloudu nebo třeba v okrajových lokalitách, a nebo třeba na všech najednou, bez problému.",
icon: logoKubernetes, icon: logoKubernetes,
iconColor: '#326CE5', iconColor: "#326CE5",
}, },
{ {
name: 'k3s & rke2', name: "k3s & rke2",
summary: summary:
'k3s: lehká distrubuce Kubernetes; rke2: Kubernetes do vašeho datacentra.', "k3s: lehká distrubuce Kubernetes; rke2: Kubernetes do vašeho datacentra.",
description: description:
'k3s i rke2 jsou velice snadno instalovatelné distribuce Kubernetes, které můžete nainstalovat úplně všude a přitom mít stále k dispozici celý Kubernetes ekosystém, bez kompromisů.', "k3s i rke2 jsou velice snadno instalovatelné distribuce Kubernetes, které můžete nainstalovat úplně všude a přitom mít stále k dispozici celý Kubernetes ekosystém, bez kompromisů.",
icon: logok3s, icon: logok3s,
iconColor: '#FFC61C', iconColor: "#FFC61C",
}, },
{ {
name: 'Rancher', name: "Rancher",
summary: summary: "Kubernetes jako služba, ve vašem datacentru.",
'Kubernetes jako služba, ve vašem datacentru.',
description: description:
'Platforma, pro vaše Kubernetes clustery. Komplexní řešení celého životního cyklu clusteru. Neřešte každodení problémy, nechte je řešit Rancher řešit za vás.', "Platforma, pro vaše Kubernetes clustery. Komplexní řešení celého životního cyklu clusteru. Neřešte každodení problémy, nechte je řešit Rancher řešit za vás.",
icon: logoRancher, icon: logoRancher,
iconColor: '#0075A8', iconColor: "#0075A8",
}, },
{ {
name: 'AWS', name: "AWS",
summary: summary: "Největší veřejný cloud. Máte problém? AWS má na to službu.",
'Největší veřejný cloud. Máte problém? AWS má na to službu.',
description: description:
'Amazon Web Services (AWS) je cloudový poskytovatel služeb, včetně ukládání obrovských objemů dat, výpočetního výkonu a sítí. To vše aby pomohli firmám i jednotlivcům růst. AWS je nejrozšířenější poskytovatel, který je cenově dostupný, flexibilní cesta, jak stavět a provozovat aplikace a služby.', "Amazon Web Services (AWS) je cloudový poskytovatel služeb, včetně ukládání obrovských objemů dat, výpočetního výkonu a sítí. To vše aby pomohli firmám i jednotlivcům růst. AWS je nejrozšířenější poskytovatel, který je cenově dostupný, flexibilní cesta, jak stavět a provozovat aplikace a služby.",
icon: logoAWS, icon: logoAWS,
iconColor: '#232F3E', iconColor: "#232F3E",
}, },
// { // {
// name: 'Google Cloud Platform', // name: 'Google Cloud Platform',
@ -55,13 +54,12 @@ const features = [
// iconColor: '#4285F4', // iconColor: '#4285F4',
// }, // },
{ {
name: 'DigitalOcean', name: "DigitalOcean",
summary: summary: "Jednoduchý cloudový poskytovatel, ve kterém se neztratíte.",
'Jednoduchý cloudový poskytovatel, ve kterém se neztratíte.',
description: description:
'DigitalOcean patří k menším cloudovým poskytovatelům, avšak jejich portfolio vám pro vaši aplikaci bohatě stačí a nebudete se ztrácet v komplexitě velkých poskytovatelů se spoustou služeb.', "DigitalOcean patří k menším cloudovým poskytovatelům, avšak jejich portfolio vám pro vaši aplikaci bohatě stačí a nebudete se ztrácet v komplexitě velkých poskytovatelů se spoustou služeb.",
icon: logoDigitalOcean, icon: logoDigitalOcean,
iconColor: '#0080FF', iconColor: "#0080FF",
}, },
// { // {
// name: 'OpenStack', // name: 'OpenStack',
@ -73,50 +71,48 @@ const features = [
// iconColor: '#ED1944', // iconColor: '#ED1944',
// }, // },
{ {
name: 'VMware', name: "VMware",
summary: summary:
'Populární řešení pro správu vaše datacentra, od virtuálních serverů až po úložiště.', "Populární řešení pro správu vaše datacentra, od virtuálních serverů až po úložiště.",
description: description:
'VMware je virtualizační platforma, která umožňuje vytvářet a spravovat virtuální servery a jejich fyzickém hardwaru. Zároveň umožňuje vytvářet oddělená prostředí například pro vývoj a ostrý provoz, tak i pro zcela různé aplikace.', "VMware je virtualizační platforma, která umožňuje vytvářet a spravovat virtuální servery a jejich fyzickém hardwaru. Zároveň umožňuje vytvářet oddělená prostředí například pro vývoj a ostrý provoz, tak i pro zcela různé aplikace.",
icon: logovmware, icon: logovmware,
iconColor: '#607078', iconColor: "#607078",
}, },
] ];
type FeatureType = { type FeatureType = {
name: string, name: string;
summary: string, summary: string;
description: string, description: string;
icon: any, icon: any;
iconColor: string, iconColor: string;
} };
type FeatureProps = { type FeatureProps = {
feature: FeatureType feature: FeatureType;
className?: string, className?: string;
props?: any[], props?: any[];
} };
function Feature({ feature, className, ...props }: FeatureProps) { function Feature({ feature, className, ...props }: FeatureProps) {
return ( return (
<div <div className={className} {...props}>
className={className} <Image
{...props} src={feature.icon}
> className="rounded-lg p-2"
<Image src={feature.icon} className="rounded-lg p-2" width="128" height="128" color={feature.iconColor} alt="" /> width="128"
<h3 height="128"
className="mt-6 text-lg font-medium text-blue-600" color={feature.iconColor}
> alt=""
{feature.name} />
</h3> <h3 className="mt-6 text-lg font-medium text-blue-600">{feature.name}</h3>
<p className="mt-2 font-display text-xl text-slate-900"> <p className="font-display mt-2 text-xl text-slate-900">
{feature.summary} {feature.summary}
</p> </p>
<p className="mt-4 text-sm text-slate-600"> <p className="mt-4 text-sm text-slate-600">{feature.description}</p>
{feature.description}
</p>
</div> </div>
) );
} }
function FeaturesMobile() { function FeaturesMobile() {
@ -128,7 +124,7 @@ function FeaturesMobile() {
</div> </div>
))} ))}
</div> </div>
) );
} }
function FeaturesDesktop() { function FeaturesDesktop() {
@ -142,7 +138,7 @@ function FeaturesDesktop() {
))} ))}
</div> </div>
</div> </div>
) );
} }
export function Environment() { export function Environment() {
@ -158,12 +154,13 @@ export function Environment() {
Jakákoliv platforma, kdekoliv Jakákoliv platforma, kdekoliv
</h2> </h2>
<p className="mt-4 text-lg tracking-tight text-slate-700"> <p className="mt-4 text-lg tracking-tight text-slate-700">
Od veřejného cloudu přes on-premise po serverless, se vším vám poradím. Od veřejného cloudu přes on-premise po serverless, se vším vám
poradím.
</p> </p>
</div> </div>
<FeaturesMobile /> <FeaturesMobile />
<FeaturesDesktop /> <FeaturesDesktop />
</Container> </Container>
</section> </section>
) );
} }

View file

@ -1,61 +1,61 @@
import Image from 'next/image' import Image from "next/image";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
import backgroundImage from '@/images/background-faqs.jpg' import backgroundImage from "@/images/background-faqs.jpg";
const faqs = [ const faqs = [
[ [
{ {
question: 'Does TaxPal handle VAT?', question: "Does TaxPal handle VAT?",
answer: answer:
'Well no, but if you move your company offshore you can probably ignore it.', "Well no, but if you move your company offshore you can probably ignore it.",
}, },
{ {
question: 'Can I pay for my subscription via purchase order?', question: "Can I pay for my subscription via purchase order?",
answer: 'Absolutely, we are happy to take your money in all forms.', answer: "Absolutely, we are happy to take your money in all forms.",
}, },
{ {
question: 'How do I apply for a job at TaxPal?', question: "How do I apply for a job at TaxPal?",
answer: answer:
'We only hire our customers, so subscribe for a minimum of 6 months and then lets talk.', "We only hire our customers, so subscribe for a minimum of 6 months and then lets talk.",
}, },
], ],
[ [
{ {
question: 'What was that testimonial about tax fraud all about?', question: "What was that testimonial about tax fraud all about?",
answer: answer:
'TaxPal is just a software application, ultimately your books are your responsibility.', "TaxPal is just a software application, ultimately your books are your responsibility.",
}, },
{ {
question: question:
'TaxPal sounds horrible but why do I still feel compelled to purchase?', "TaxPal sounds horrible but why do I still feel compelled to purchase?",
answer: answer:
'This is the power of excellent visual design. You just cant resist it, no matter how poorly it actually functions.', "This is the power of excellent visual design. You just cant resist it, no matter how poorly it actually functions.",
}, },
{ {
question: question:
'I found other companies called TaxPal, are you sure you can use this name?', "I found other companies called TaxPal, are you sure you can use this name?",
answer: answer:
'Honestly not sure at all. We havent actually incorporated or anything, we just thought it sounded cool and made this website.', "Honestly not sure at all. We havent actually incorporated or anything, we just thought it sounded cool and made this website.",
}, },
], ],
[ [
{ {
question: 'How do you generate reports?', question: "How do you generate reports?",
answer: answer:
'You just tell us what data you need a report for, and we get our kids to create beautiful charts for you using only the finest crayons.', "You just tell us what data you need a report for, and we get our kids to create beautiful charts for you using only the finest crayons.",
}, },
{ {
question: 'Can we expect more inventory features?', question: "Can we expect more inventory features?",
answer: 'In life its really better to never expect anything at all.', answer: "In life its really better to never expect anything at all.",
}, },
{ {
question: 'I lost my password, how do I get into my account?', question: "I lost my password, how do I get into my account?",
answer: answer:
'Send us an email and we will send you a copy of our latest password spreadsheet so you can find your information.', "Send us an email and we will send you a copy of our latest password spreadsheet so you can find your information.",
}, },
], ],
] ];
export function Faqs() { export function Faqs() {
return ( return (
@ -106,5 +106,5 @@ export function Faqs() {
</ul> </ul>
</Container> </Container>
</section> </section>
) );
} }

View file

@ -1,9 +1,9 @@
import Link from 'next/link' import Link from "next/link";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
// import { Logo } from '@/components/Logo' // import { Logo } from '@/components/Logo'
// import { NavLink } from '@/components/NavLink' // import { NavLink } from '@/components/NavLink'
import { Button } from '@/components/Button' import { Button } from "@/components/Button";
export function Footer() { export function Footer() {
return ( return (
@ -12,20 +12,29 @@ export function Footer() {
<div className="py-4"> <div className="py-4">
<div className="grid grid-cols-1 gap-y-6 lg:grid-cols-4 lg:gap-4"> <div className="grid grid-cols-1 gap-y-6 lg:grid-cols-4 lg:gap-4">
<div> <div>
<h4 className="text-lg font-medium" >Vojtěch Mareš</h4> <h4 className="text-lg font-medium">Vojtěch Mareš</h4>
<ul className="list-none mt-4"> <ul className="mt-4 list-none">
<li> <li>
<Link href="tel:+420732490651" className="underline">+420 732 490 651</Link> <Link href="tel:+420732490651" className="underline">
+420 732 490 651
</Link>
</li> </li>
<li> <li>
<Link href="mailto:iam@vojtechmares.com" className="underline">iam@vojtechmares.com</Link> <Link
href="mailto:iam@vojtechmares.com"
className="underline"
>
iam@vojtechmares.com
</Link>
</li> </li>
<li className="mt-4"> <li className="mt-4">
Company ID<br /> Company ID
<br />
<code id="company-id">06999280</code> <code id="company-id">06999280</code>
</li> </li>
<li className="mt-2"> <li className="mt-2">
VAT ID<br /> VAT ID
<br />
<code id="vat-id">CZ9709180063</code> <code id="vat-id">CZ9709180063</code>
</li> </li>
</ul> </ul>
@ -34,13 +43,17 @@ export function Footer() {
<h3 className="text-lg font-medium">Nejblíbenější školení</h3> <h3 className="text-lg font-medium">Nejblíbenější školení</h3>
<ul className="mt-4 list-disc pl-4"> <ul className="mt-4 list-disc pl-4">
<li> <li>
<Link href="/skoleni/kubernetes" className="underline">Kubernetes</Link> <Link href="/skoleni/kubernetes" className="underline">
Kubernetes
</Link>
</li> </li>
{/* <li> {/* <li>
<Link href="/skoleni/gitlab-ci" className="underline">GitLab CI</Link> <Link href="/skoleni/gitlab-ci" className="underline">GitLab CI</Link>
</li> */} </li> */}
<li> <li>
<Link href="/skoleni/terraform" className="underline">Terraform</Link> <Link href="/skoleni/terraform" className="underline">
Terraform
</Link>
</li> </li>
{/* <li> {/* <li>
<Link href="/skoleni/rancher" className="underline">Rancher</Link> <Link href="/skoleni/rancher" className="underline">Rancher</Link>
@ -85,7 +98,10 @@ export function Footer() {
Zaujal jsem vás avšak nevíte, jak přesně bych vám mohl pomoci? Zaujal jsem vás avšak nevíte, jak přesně bych vám mohl pomoci?
Nebojte se zeptat a společně vymyslíme, jak vám mohu pomoci. Nebojte se zeptat a společně vymyslíme, jak vám mohu pomoci.
</p> </p>
<Button href="https://calendly.com/vojtechmares/30min" className="mt-5"> <Button
href="https://calendly.com/vojtechmares/30min"
className="mt-5"
>
Domluvme si schůzku Domluvme si schůzku
</Button> </Button>
</div> </div>
@ -106,7 +122,7 @@ export function Footer() {
aria-hidden="true" aria-hidden="true"
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700" className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700"
> >
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/> <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg> </svg>
</Link> </Link>
<Link <Link
@ -137,10 +153,11 @@ export function Footer() {
</Link> </Link>
</div> </div>
<p className="mt-6 text-sm text-slate-500 sm:mt-0"> <p className="mt-6 text-sm text-slate-500 sm:mt-0">
Copyright &copy; {new Date().getFullYear()} Vojtěch Mareš. Všechna práva vyhrazena. Copyright &copy; {new Date().getFullYear()} Vojtěch Mareš. Všechna
práva vyhrazena.
</p> </p>
</div> </div>
</Container> </Container>
</footer> </footer>
) );
} }

View file

@ -1,8 +1,8 @@
import Link from "next/link" import Link from "next/link";
import { NavLink } from "@/components/NavLink"; import { NavLink } from "@/components/NavLink";
import { Container } from "@/components/Container" import { Container } from "@/components/Container";
import { Button } from "@/components/Button" import { Button } from "@/components/Button";
export function Header() { export function Header() {
return ( return (
@ -24,7 +24,8 @@ export function Header() {
<div className="flex items-center gap-x-5 md:gap-x-8"> <div className="flex items-center gap-x-5 md:gap-x-8">
<Button href="mailto:iam@vojtechmares.com" color="blue"> <Button href="mailto:iam@vojtechmares.com" color="blue">
<span className="text-lg"> <span className="text-lg">
Napište mi <span className="hidden lg:inline">ještě dnes</span> Napište mi{" "}
<span className="hidden lg:inline">ještě dnes</span>
</span> </span>
</Button> </Button>
</div> </div>

View file

@ -1,11 +1,11 @@
import { Button } from '@/components/Button' import { Button } from "@/components/Button";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
export function Hero() { export function Hero() {
return ( return (
<Container className="pt-20 pb-16 text-center lg:pt-32"> <Container className="pt-20 pb-16 text-center lg:pt-32">
<h1 className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-slate-900 sm:text-7xl"> <h1 className="font-display mx-auto max-w-4xl text-5xl font-medium tracking-tight text-slate-900 sm:text-7xl">
DevOps{' '} DevOps{" "}
<span className="relative whitespace-nowrap text-blue-600"> <span className="relative whitespace-nowrap text-blue-600">
<svg <svg
aria-hidden="true" aria-hidden="true"
@ -16,15 +16,17 @@ export function Hero() {
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" /> <path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
</svg> </svg>
<span className="relative">jednoduše</span> <span className="relative">jednoduše</span>
</span>{' '} </span>{" "}
pro všechny. pro všechny.
</h1> </h1>
<p className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-slate-700"> <p className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-slate-700">
Společně snížíme vaše náklady na infrasturkuturu, Společně snížíme vaše náklady na infrasturkuturu, zbavíme se technického
zbavíme se technického dluhu a připravíme vaší IT infrastrukturu na rapidní růst. dluhu a připravíme vaší IT infrastrukturu na rapidní růst.
</p> </p>
<div className="mt-10 flex justify-center gap-x-6"> <div className="mt-10 flex justify-center gap-x-6">
<Button href="https://calendly.com/vojtechmares/30min">Domluvme si schůzku</Button> <Button href="https://calendly.com/vojtechmares/30min">
Domluvme si schůzku
</Button>
{/* <Button {/* <Button
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
variant="outline" variant="outline"
@ -74,5 +76,5 @@ export function Hero() {
</ul> </ul>
</div> */} </div> */}
</Container> </Container>
) );
} }

View file

@ -1,12 +1,12 @@
import { ReactNode } from 'react'; import { ReactNode } from "react";
import Link from 'next/link'; import Link from "next/link";
type Props = { type Props = {
href: string, href: string;
children: ReactNode, children: ReactNode;
} };
export function NavLink({href, children}: Props) { export function NavLink({ href, children }: Props) {
return ( return (
<Link <Link
href={href} href={href}

View file

@ -1,9 +1,9 @@
import clsx from 'clsx' import clsx from "clsx";
import { Button } from '@/components/Button' import { Button } from "@/components/Button";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
type SwirlyDoodleProps = { className: string } type SwirlyDoodleProps = { className: string };
function SwirlyDoodle({ className }: SwirlyDoodleProps) { function SwirlyDoodle({ className }: SwirlyDoodleProps) {
return ( return (
@ -19,41 +19,48 @@ function SwirlyDoodle({ className }: SwirlyDoodleProps) {
d="M240.172 22.994c-8.007 1.246-15.477 2.23-31.26 4.114-18.506 2.21-26.323 2.977-34.487 3.386-2.971.149-3.727.324-6.566 1.523-15.124 6.388-43.775 9.404-69.425 7.31-26.207-2.14-50.986-7.103-78-15.624C10.912 20.7.988 16.143.734 14.657c-.066-.381.043-.344 1.324.456 10.423 6.506 49.649 16.322 77.8 19.468 23.708 2.65 38.249 2.95 55.821 1.156 9.407-.962 24.451-3.773 25.101-4.692.074-.104.053-.155-.058-.135-1.062.195-13.863-.271-18.848-.687-16.681-1.389-28.722-4.345-38.142-9.364-15.294-8.15-7.298-19.232 14.802-20.514 16.095-.934 32.793 1.517 47.423 6.96 13.524 5.033 17.942 12.326 11.463 18.922l-.859.874.697-.006c2.681-.026 15.304-1.302 29.208-2.953 25.845-3.07 35.659-4.519 54.027-7.978 9.863-1.858 11.021-2.048 13.055-2.145a61.901 61.901 0 0 0 4.506-.417c1.891-.259 2.151-.267 1.543-.047-.402.145-2.33.913-4.285 1.707-4.635 1.882-5.202 2.07-8.736 2.903-3.414.805-19.773 3.797-26.404 4.829Zm40.321-9.93c.1-.066.231-.085.29-.041.059.043-.024.096-.183.119-.177.024-.219-.007-.107-.079ZM172.299 26.22c9.364-6.058 5.161-12.039-12.304-17.51-11.656-3.653-23.145-5.47-35.243-5.576-22.552-.198-33.577 7.462-21.321 14.814 12.012 7.205 32.994 10.557 61.531 9.831 4.563-.116 5.372-.288 7.337-1.559Z" d="M240.172 22.994c-8.007 1.246-15.477 2.23-31.26 4.114-18.506 2.21-26.323 2.977-34.487 3.386-2.971.149-3.727.324-6.566 1.523-15.124 6.388-43.775 9.404-69.425 7.31-26.207-2.14-50.986-7.103-78-15.624C10.912 20.7.988 16.143.734 14.657c-.066-.381.043-.344 1.324.456 10.423 6.506 49.649 16.322 77.8 19.468 23.708 2.65 38.249 2.95 55.821 1.156 9.407-.962 24.451-3.773 25.101-4.692.074-.104.053-.155-.058-.135-1.062.195-13.863-.271-18.848-.687-16.681-1.389-28.722-4.345-38.142-9.364-15.294-8.15-7.298-19.232 14.802-20.514 16.095-.934 32.793 1.517 47.423 6.96 13.524 5.033 17.942 12.326 11.463 18.922l-.859.874.697-.006c2.681-.026 15.304-1.302 29.208-2.953 25.845-3.07 35.659-4.519 54.027-7.978 9.863-1.858 11.021-2.048 13.055-2.145a61.901 61.901 0 0 0 4.506-.417c1.891-.259 2.151-.267 1.543-.047-.402.145-2.33.913-4.285 1.707-4.635 1.882-5.202 2.07-8.736 2.903-3.414.805-19.773 3.797-26.404 4.829Zm40.321-9.93c.1-.066.231-.085.29-.041.059.043-.024.096-.183.119-.177.024-.219-.007-.107-.079ZM172.299 26.22c9.364-6.058 5.161-12.039-12.304-17.51-11.656-3.653-23.145-5.47-35.243-5.576-22.552-.198-33.577 7.462-21.321 14.814 12.012 7.205 32.994 10.557 61.531 9.831 4.563-.116 5.372-.288 7.337-1.559Z"
/> />
</svg> </svg>
) );
} }
type PlanProps = { type PlanProps = {
name: string, name: string;
price: string, price: string;
description: string, description: string;
href: string, href: string;
featured?: boolean, featured?: boolean;
buttonText?: string buttonText?: string;
} };
function Plan({ name, price, description, href, featured = false, buttonText = 'Napište mi' }: PlanProps) { function Plan({
name,
price,
description,
href,
featured = false,
buttonText = "Napište mi",
}: PlanProps) {
return ( return (
<section <section
className={clsx( className={clsx(
'flex flex-col rounded-3xl px-6 sm:px-8', "flex flex-col rounded-3xl px-6 sm:px-8",
featured ? 'order-first bg-blue-600 py-8 lg:order-none' : 'lg:py-8' featured ? "order-first bg-blue-600 py-8 lg:order-none" : "lg:py-8"
)} )}
> >
<h3 className="mt-5 font-display text-4xl text-white">{name}</h3> <h3 className="font-display mt-5 text-4xl text-white">{name}</h3>
<p className="mt-4 font-display text-lg font-light tracking-tight text-white"> <p className="font-display mt-4 text-lg font-light tracking-tight text-white">
{price} {price}
</p> </p>
<p <p
className={clsx( className={clsx(
'mt-2 text-base', "mt-2 text-base",
featured ? 'text-white' : 'text-slate-400' featured ? "text-white" : "text-slate-400"
)} )}
> >
{description} {description}
</p> </p>
<Button <Button
href={href} href={href}
variant={featured ? 'solid' : 'outline'} variant={featured ? "solid" : "outline"}
color="white" color="white"
className="mt-16" className="mt-16"
aria-label={`Get started with the ${name} plan for ${price}`} aria-label={`Get started with the ${name} plan for ${price}`}
@ -61,7 +68,7 @@ function Plan({ name, price, description, href, featured = false, buttonText = '
{buttonText} {buttonText}
</Button> </Button>
</section> </section>
) );
} }
export function Pricing() { export function Pricing() {
@ -77,11 +84,12 @@ export function Pricing() {
<span className="relative whitespace-nowrap"> <span className="relative whitespace-nowrap">
<SwirlyDoodle className="absolute top-1/2 left-0 h-[1em] w-full fill-blue-400" /> <SwirlyDoodle className="absolute top-1/2 left-0 h-[1em] w-full fill-blue-400" />
<span className="relative">Co pro Vás,</span> <span className="relative">Co pro Vás,</span>
</span>{' '} </span>{" "}
můžu udělat. můžu udělat.
</h3> </h3>
<p className="mt-4 text-lg text-slate-400"> <p className="mt-4 text-lg text-slate-400">
It doesnt matter what size your company is, we will find a way to help you. It doesnt matter what size your company is, we will find a way to
help you.
</p> </p>
</div> </div>
<div className="-mx-4 mt-16 grid max-w-2xl grid-cols-1 gap-y-10 sm:mx-auto lg:-mx-8 lg:max-w-none lg:grid-cols-3 xl:mx-0 xl:gap-x-8"> <div className="-mx-4 mt-16 grid max-w-2xl grid-cols-1 gap-y-10 sm:mx-auto lg:-mx-8 lg:max-w-none lg:grid-cols-3 xl:mx-0 xl:gap-x-8">
@ -109,5 +117,5 @@ export function Pricing() {
</div> </div>
</Container> </Container>
</section> </section>
) );
} }

View file

@ -1,27 +1,31 @@
import Image from 'next/image' import Image from "next/image";
import clsx from 'clsx' import clsx from "clsx";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
import backgroundImage from '@/images/background-features.jpg' import backgroundImage from "@/images/background-features.jpg";
const steps = [ const steps = [
{ {
name: 'Analýza současného stavu', name: "Analýza současného stavu",
description: 'Zjistíme kde jsou slabá místa vaší infrastruktury nebo aplikace, a nebo obojího.', description:
"Zjistíme kde jsou slabá místa vaší infrastruktury nebo aplikace, a nebo obojího.",
}, },
{ {
name: 'Návrh řešení', name: "Návrh řešení",
description: 'Navrhnu, jak tato slabá místa odstranit, na co si dát pozor a naplánujeme případné další kroky. ', description:
"Navrhnu, jak tato slabá místa odstranit, na co si dát pozor a naplánujeme případné další kroky. ",
}, },
{ {
name: 'Implementace', name: "Implementace",
description: 'Přesunu vaši aplikaci do Kubernetes, ať na vašem vlastním hardware nebo v public cloudu. Celá infrastruktura bude jasně deklarovaná jako kód pomocí Terraformu.', description:
"Přesunu vaši aplikaci do Kubernetes, ať na vašem vlastním hardware nebo v public cloudu. Celá infrastruktura bude jasně deklarovaná jako kód pomocí Terraformu.",
}, },
{ {
name: 'Proškolení vašeho týmu', name: "Proškolení vašeho týmu",
description: 'Naučím váš tým používat moderní technologie, tak abyste mohli rozvíjet vaší aplikaci a byznys a technologie byly nástrojem k rozvoji, ne břemenem, které s sebou táhnete.', description:
"Naučím váš tým používat moderní technologie, tak abyste mohli rozvíjet vaší aplikaci a byznys a technologie byly nástrojem k rozvoji, ne břemenem, které s sebou táhnete.",
}, },
] ];
export function PrimaryFeatures() { export function PrimaryFeatures() {
return ( return (
@ -48,11 +52,23 @@ export function PrimaryFeatures() {
</p> </p>
</div> </div>
<nav aria-label="Progress"> <nav aria-label="Progress">
<ol role="list" className="overflow-hidden pt-2 mt-20 max-w-3xl mx-auto"> <ol
role="list"
className="mx-auto mt-20 max-w-3xl overflow-hidden pt-2"
>
{steps.map((step, stepIdx) => ( {steps.map((step, stepIdx) => (
<li key={step.name} className={clsx(stepIdx !== steps.length - 1 ? 'pb-10' : '', 'relative')}> <li
key={step.name}
className={clsx(
stepIdx !== steps.length - 1 ? "pb-10" : "",
"relative"
)}
>
{stepIdx !== steps.length - 1 ? ( {stepIdx !== steps.length - 1 ? (
<div className="absolute top-4 left-6 -ml-px mt-0.5 h-full w-0.5 bg-white" aria-hidden="true" /> <div
className="absolute top-4 left-6 -ml-px mt-0.5 h-full w-0.5 bg-white"
aria-hidden="true"
/>
) : null} ) : null}
<div className="group relative flex items-start"> <div className="group relative flex items-start">
<span className="flex h-9 items-center" aria-hidden="true"> <span className="flex h-9 items-center" aria-hidden="true">
@ -61,8 +77,12 @@ export function PrimaryFeatures() {
</span> </span>
</span> </span>
<span className="ml-4 flex min-w-0 flex-col"> <span className="ml-4 flex min-w-0 flex-col">
<span className="text-2xl text-white font-normal">{step.name}</span> <span className="text-2xl font-normal text-white">
<span className="text-lg text-blue-200">{step.description}</span> {step.name}
</span>
<span className="text-lg text-blue-200">
{step.description}
</span>
</span> </span>
</div> </div>
</li> </li>
@ -71,5 +91,5 @@ export function PrimaryFeatures() {
</nav> </nav>
</Container> </Container>
</section> </section>
) );
} }

View file

@ -1,122 +1,125 @@
import { ReactNode } from 'react' import { ReactNode } from "react";
import Image, { StaticImageData } from 'next/image' import Image, { StaticImageData } from "next/image";
import { Tab } from '@headlessui/react' import { Tab } from "@headlessui/react";
import clsx from 'clsx' import clsx from "clsx";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
import screenshotArgoCD from "@/images/screenshots/argocd.png";
import screenshotGitLab from "@/images/screenshots/gitlab.png";
import screenshotTerraform from "@/images/screenshots/terraform.png";
import screenshotKubernetes from "@/images/screenshots/kubernetes.png";
import screenshotGrafana from "@/images/screenshots/grafana.png";
import screenshotPrometheus from "@/images/screenshots/prometheus.png";
import screenshotArgoCD from '@/images/screenshots/argocd.png' import logoTerraform from "@/images/logos/tools/terraform.svg";
import screenshotGitLab from '@/images/screenshots/gitlab.png' import logoGit from "@/images/logos/tools/git.svg";
import screenshotTerraform from '@/images/screenshots/terraform.png' import logoKubernetes from "@/images/logos/tools/kubernetes.svg";
import screenshotKubernetes from '@/images/screenshots/kubernetes.png' import logoPrometheus from "@/images/logos/tools/prometheus.svg";
import screenshotGrafana from '@/images/screenshots/grafana.png' import logoArgo from "@/images/logos/tools/argo.svg";
import screenshotPrometheus from '@/images/screenshots/prometheus.png' import logoGrafana from "@/images/logos/tools/grafana.svg";
import logoTerraform from '@/images/logos/tools/terraform.svg'
import logoGit from '@/images/logos/tools/git.svg'
import logoKubernetes from '@/images/logos/tools/kubernetes.svg'
import logoPrometheus from '@/images/logos/tools/prometheus.svg'
import logoArgo from '@/images/logos/tools/argo.svg'
import logoGrafana from '@/images/logos/tools/grafana.svg'
const features = [ const features = [
{ {
name: 'Verzování', name: "Verzování",
summary: summary: "Spravujte svůj kód pomocí verzovacího nástroje Git.",
'Spravujte svůj kód pomocí verzovacího nástroje Git.',
description: description:
'Mějte historii změn v aplikaci od začátku až do dnes, můžete se kdykoliv vrátit do bodu v čase. Řešte konflikty včas, předtím než je nasadíte do produkce a umožněte vaším programátorům spolupracovat na jednou, aniž by si překáželi.', "Mějte historii změn v aplikaci od začátku až do dnes, můžete se kdykoliv vrátit do bodu v čase. Řešte konflikty včas, předtím než je nasadíte do produkce a umožněte vaším programátorům spolupracovat na jednou, aniž by si překáželi.",
image: screenshotGitLab, image: screenshotGitLab,
icon: logoGit, icon: logoGit,
}, },
{ {
name: 'Infastruktura jako kód', name: "Infastruktura jako kód",
summary: 'Mějte vaši infrastrukturu deklarativně definovanou a verzovanou, díky Terraformu.', summary:
"Mějte vaši infrastrukturu deklarativně definovanou a verzovanou, díky Terraformu.",
description: description:
'Vaši infrastrukturu můžete snadno přesunout k jinému poskytovateli, a nebo jen vytvořit nové prostředí pro zákazníka, aby si váš produkt vyzkoušel a to během minut.', "Vaši infrastrukturu můžete snadno přesunout k jinému poskytovateli, a nebo jen vytvořit nové prostředí pro zákazníka, aby si váš produkt vyzkoušel a to během minut.",
image: screenshotTerraform, image: screenshotTerraform,
icon: logoTerraform, icon: logoTerraform,
}, },
{ {
name: 'Orchestrace', name: "Orchestrace",
summary: summary:
'Nechte vaši aplikaci běžet napříč mnoha servery a škálovat dle potřeb.', "Nechte vaši aplikaci běžet napříč mnoha servery a škálovat dle potřeb.",
description: description:
'Kubernetes se stalo nejen standardem, ale i hlavní platformou pro vývoj aplikací ať SaaS nebo dodávaných třetím stranám. Jde o skvělou platformu pro provoz vaší aplikace ať ve veřejném cloudu nebo na vlastním hardwaru popř. on edge blízko koncových uživatelů.', "Kubernetes se stalo nejen standardem, ale i hlavní platformou pro vývoj aplikací ať SaaS nebo dodávaných třetím stranám. Jde o skvělou platformu pro provoz vaší aplikace ať ve veřejném cloudu nebo na vlastním hardwaru popř. on edge blízko koncových uživatelů.",
image: screenshotKubernetes, image: screenshotKubernetes,
icon: logoKubernetes, icon: logoKubernetes,
}, },
{ {
name: 'Monitoring', name: "Monitoring",
summary: summary: "Sledujte Vaši aplikaci, jak se chová v čase.",
'Sledujte Vaši aplikaci, jak se chová v čase.',
description: description:
'Prometheus je standard pro monitoring aplikací, ať v Kubernetes ale i mimo. Sbírejte telemetrická data v čase o vaši aplikaci. Vyhodnoťte, kde má vaše aplikace slabá místa. Zároveň můžete tvořit pravidla, dle kterých vás AlertManager upozorní, když se něco pokazí.', "Prometheus je standard pro monitoring aplikací, ať v Kubernetes ale i mimo. Sbírejte telemetrická data v čase o vaši aplikaci. Vyhodnoťte, kde má vaše aplikace slabá místa. Zároveň můžete tvořit pravidla, dle kterých vás AlertManager upozorní, když se něco pokazí.",
image: screenshotPrometheus, image: screenshotPrometheus,
icon: logoPrometheus, icon: logoPrometheus,
}, },
{ {
name: 'Vizualizace', name: "Vizualizace",
summary: summary:
'Od grafů zatížení procesoru po počet otevřených TCP spojení, vše jasně a přehledně.', "Od grafů zatížení procesoru po počet otevřených TCP spojení, vše jasně a přehledně.",
description: description:
'Grafana je skvělý nástroj pro vizualizaci dat z monitoringu, vytvořte si dashboardy pro jednotlivé části vaší aplikace, mějte pohromadě infrastrukturu, provoz na síti, dostupnost a třeba počet neúspěšných pokusů o přihlášení, zda vůči vaší aplikaci neprobíhá hackerský útok.', "Grafana je skvělý nástroj pro vizualizaci dat z monitoringu, vytvořte si dashboardy pro jednotlivé části vaší aplikace, mějte pohromadě infrastrukturu, provoz na síti, dostupnost a třeba počet neúspěšných pokusů o přihlášení, zda vůči vaší aplikaci neprobíhá hackerský útok.",
image: screenshotGrafana, image: screenshotGrafana,
icon: logoGrafana, icon: logoGrafana,
}, },
{ {
name: 'GitOps', name: "GitOps",
summary: summary:
'Spravujte stav Vašich prostředí deklarativně, ať vždy víte, jaký je aktuální stav.', "Spravujte stav Vašich prostředí deklarativně, ať vždy víte, jaký je aktuální stav.",
description: description:
'ArgoCD je spolehlivý nástroj pro práci s Kubernetes a nasazováním změn a přitom si udržovat přehled o právě nasazených aplikacích, verzím a konfiguraci, snadno, soplehlivě, verzovaně.', "ArgoCD je spolehlivý nástroj pro práci s Kubernetes a nasazováním změn a přitom si udržovat přehled o právě nasazených aplikacích, verzím a konfiguraci, snadno, soplehlivě, verzovaně.",
image: screenshotArgoCD, image: screenshotArgoCD,
icon: logoArgo, icon: logoArgo,
}, },
] ];
type FeatureType = { type FeatureType = {
name: string|ReactNode, name: string | ReactNode;
summary: string, summary: string;
description: string, description: string;
image: StaticImageData, image: StaticImageData;
icon: any, icon: any;
} };
type FeatureProps = { type FeatureProps = {
feature: FeatureType, feature: FeatureType;
isActive: boolean, isActive: boolean;
className?: string, className?: string;
props?: any, props?: any;
} };
function Feature({ feature, isActive, className, ...props }: FeatureProps ) { function Feature({ feature, isActive, className, ...props }: FeatureProps) {
return ( return (
<div <div
className={clsx(className, !isActive && 'opacity-75 hover:opacity-100')} className={clsx(className, !isActive && "opacity-75 hover:opacity-100")}
{...props} {...props}
> >
<Image src={feature.icon} width="128" height="128" className={clsx( <Image
src={feature.icon}
width="128"
height="128"
className={clsx(
// 'w-32', // 'w-32',
'rounded-lg p-2 flex justify-center', "flex justify-center rounded-lg p-2"
// isActive ? 'fill-blue-600' : 'fill-slate-500' // isActive ? 'fill-blue-600' : 'fill-slate-500'
)} alt="" /> )}
alt=""
/>
<h3 <h3
className={clsx( className={clsx(
'mt-6 text-lg font-medium', "mt-6 text-lg font-medium",
isActive ? 'text-blue-600' : 'text-slate-600' isActive ? "text-blue-600" : "text-slate-600"
)} )}
> >
{feature.name} {feature.name}
</h3> </h3>
<p className="mt-2 font-display text-xl text-slate-900"> <p className="font-display mt-2 text-xl text-slate-900">
{feature.summary} {feature.summary}
</p> </p>
<p className="mt-4 text-sm text-slate-600">{feature.description}</p> <p className="mt-4 text-sm text-slate-600">{feature.description}</p>
</div> </div>
) );
} }
function FeaturesMobile() { function FeaturesMobile() {
@ -141,7 +144,7 @@ function FeaturesMobile() {
</div> </div>
))} ))}
</div> </div>
) );
} }
function FeaturesDesktop() { function FeaturesDesktop() {
@ -167,15 +170,15 @@ function FeaturesDesktop() {
/> />
))} ))}
</Tab.List> </Tab.List>
<Tab.Panels className="relative mt-20 overflow-hidden rounded-4xl bg-slate-200 px-14 py-16 xl:px-16"> <Tab.Panels className="rounded-4xl relative mt-20 overflow-hidden bg-slate-200 px-14 py-16 xl:px-16">
<div className="-mx-5 flex"> <div className="-mx-5 flex">
{features.map((feature, featureIndex) => ( {features.map((feature, featureIndex) => (
<Tab.Panel <Tab.Panel
static static
key={feature.name} key={feature.name}
className={clsx( className={clsx(
'px-5 transition duration-500 ease-in-out [&:not(:focus-visible)]:focus:outline-none', "px-5 transition duration-500 ease-in-out [&:not(:focus-visible)]:focus:outline-none",
featureIndex !== selectedIndex && 'opacity-60' featureIndex !== selectedIndex && "opacity-60"
)} )}
style={{ transform: `translateX(-${selectedIndex * 100}%)` }} style={{ transform: `translateX(-${selectedIndex * 100}%)` }}
aria-hidden={featureIndex !== selectedIndex} aria-hidden={featureIndex !== selectedIndex}
@ -193,12 +196,12 @@ function FeaturesDesktop() {
</Tab.Panel> </Tab.Panel>
))} ))}
</div> </div>
<div className="pointer-events-none absolute inset-0 rounded-4xl ring-1 ring-inset ring-slate-900/10" /> <div className="rounded-4xl pointer-events-none absolute inset-0 ring-1 ring-inset ring-slate-900/10" />
</Tab.Panels> </Tab.Panels>
</> </>
)} )}
</Tab.Group> </Tab.Group>
) );
} }
export function TechStack() { export function TechStack() {
@ -214,13 +217,13 @@ export function TechStack() {
Open Source DevOps stack Open Source DevOps stack
</h2> </h2>
<p className="mt-4 text-lg tracking-tight text-slate-700"> <p className="mt-4 text-lg tracking-tight text-slate-700">
Věřím v Open Source technologie, prakticky všichni je denně využíváme Věřím v Open Source technologie, prakticky všichni je denně
a jsou naší budoucností. využíváme a jsou naší budoucností.
</p> </p>
</div> </div>
<FeaturesMobile /> <FeaturesMobile />
<FeaturesDesktop /> <FeaturesDesktop />
</Container> </Container>
</section> </section>
) );
} }

View file

@ -1,29 +1,29 @@
import Image from 'next/image' import Image from "next/image";
import { Container } from '@/components/Container' import { Container } from "@/components/Container";
import avatarImage1 from '@/images/avatars/avatar-1.png' import avatarImage1 from "@/images/avatars/avatar-1.png";
import avatarImage2 from '@/images/avatars/avatar-2.png' import avatarImage2 from "@/images/avatars/avatar-2.png";
import avatarImage3 from '@/images/avatars/avatar-3.png' import avatarImage3 from "@/images/avatars/avatar-3.png";
import avatarImage4 from '@/images/avatars/avatar-4.png' import avatarImage4 from "@/images/avatars/avatar-4.png";
import avatarImage5 from '@/images/avatars/avatar-5.png' import avatarImage5 from "@/images/avatars/avatar-5.png";
const testimonials = [ const testimonials = [
[ [
{ {
content: content:
'TaxPal is so easy to use I cant help but wonder if its really doing the things the government expects me to do.', "TaxPal is so easy to use I cant help but wonder if its really doing the things the government expects me to do.",
author: { author: {
name: 'Sheryl Berge', name: "Sheryl Berge",
role: 'CEO at Lynch LLC', role: "CEO at Lynch LLC",
image: avatarImage1, image: avatarImage1,
}, },
}, },
{ {
content: content:
'Im trying to get a hold of someone in support, Im in a lot of trouble right now and they are saying it has something to do with my books. Please get back to me right away.', "Im trying to get a hold of someone in support, Im in a lot of trouble right now and they are saying it has something to do with my books. Please get back to me right away.",
author: { author: {
name: 'Amy Hahn', name: "Amy Hahn",
role: 'Director at Velocity Industries', role: "Director at Velocity Industries",
image: avatarImage4, image: avatarImage4,
}, },
}, },
@ -31,19 +31,19 @@ const testimonials = [
[ [
{ {
content: content:
'The best part about TaxPal is every time I pay my employees, my bank balance doesnt go down like it used to. Looking forward to spending this extra cash when I figure out why my card is being declined.', "The best part about TaxPal is every time I pay my employees, my bank balance doesnt go down like it used to. Looking forward to spending this extra cash when I figure out why my card is being declined.",
author: { author: {
name: 'Leland Kiehn', name: "Leland Kiehn",
role: 'Founder of Kiehn and Sons', role: "Founder of Kiehn and Sons",
image: avatarImage5, image: avatarImage5,
}, },
}, },
{ {
content: content:
'There are so many things I had to do with my old software that I just dont do at all with TaxPal. Suspicious but I cant say I dont love it.', "There are so many things I had to do with my old software that I just dont do at all with TaxPal. Suspicious but I cant say I dont love it.",
author: { author: {
name: 'Erin Powlowski', name: "Erin Powlowski",
role: 'COO at Armstrong Inc', role: "COO at Armstrong Inc",
image: avatarImage2, image: avatarImage2,
}, },
}, },
@ -51,31 +51,31 @@ const testimonials = [
[ [
{ {
content: content:
'I used to have to remit tax to the EU and with TaxPal I somehow dont have to do that anymore. Nervous to travel there now though.', "I used to have to remit tax to the EU and with TaxPal I somehow dont have to do that anymore. Nervous to travel there now though.",
author: { author: {
name: 'Peter Renolds', name: "Peter Renolds",
role: 'Founder of West Inc', role: "Founder of West Inc",
image: avatarImage3, image: avatarImage3,
}, },
}, },
{ {
content: content:
'This is the fourth email Ive sent to your support team. I am literally being held in jail for tax fraud. Please answer your damn emails, this is important.', "This is the fourth email Ive sent to your support team. I am literally being held in jail for tax fraud. Please answer your damn emails, this is important.",
author: { author: {
name: 'Amy Hahn', name: "Amy Hahn",
role: 'Director at Velocity Industries', role: "Director at Velocity Industries",
image: avatarImage4, image: avatarImage4,
}, },
}, },
], ],
] ];
function QuoteIcon(props: any) { function QuoteIcon(props: any) {
return ( return (
<svg aria-hidden="true" width={105} height={78} {...props}> <svg aria-hidden="true" width={105} height={78} {...props}>
<path d="M25.086 77.292c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622C1.054 58.534 0 53.411 0 47.686c0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C28.325 3.917 33.599 1.507 39.324 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Zm54.24 0c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622-2.11-4.52-3.164-9.643-3.164-15.368 0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C82.565 3.917 87.839 1.507 93.564 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Z" /> <path d="M25.086 77.292c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622C1.054 58.534 0 53.411 0 47.686c0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C28.325 3.917 33.599 1.507 39.324 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Zm54.24 0c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622-2.11-4.52-3.164-9.643-3.164-15.368 0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C82.565 3.917 87.839 1.507 93.564 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Z" />
</svg> </svg>
) );
} }
export function Testimonials() { export function Testimonials() {
@ -140,5 +140,5 @@ export function Testimonials() {
</ul> </ul>
</Container> </Container>
</section> </section>
) );
} }

View file

@ -1,45 +1,66 @@
import Image from "next/image" import Image from "next/image";
import TrainingType from "@/types/training" import TrainingType from "@/types/training";
import { Container } from "./Container" import { Container } from "./Container";
import { Button } from "./Button" import { Button } from "./Button";
type Props = { type Props = {
training: TrainingType training: TrainingType;
} };
export const Training = ({ training }: Props) => { export const Training = ({ training }: Props) => {
const formatter = new Intl.NumberFormat('cs', { style: 'currency', currency: 'CZK', maximumFractionDigits: 0}) const formatter = new Intl.NumberFormat("cs", {
style: "currency",
currency: "CZK",
maximumFractionDigits: 0,
});
return ( return (
<> <>
<div className="pb-14 sm:pb-20 lg:pb-32"> <div className="pb-14 sm:pb-20 lg:pb-32">
<div className="bg-slate-900 pt-16 pb-16"> <div className="bg-slate-900 pt-16 pb-16">
<Container className="flex justify-around"> <Container className="flex justify-around">
<Image src={training.logo} className="h-40 w-40 rounded-full" width="1500" height="1500" alt="" /> <Image
<h2 className="font-display text-4xl tracking-tight text-white sm:text-6xl self-center"> src={training.logo}
className="h-40 w-40 rounded-full"
width="1500"
height="1500"
alt=""
/>
<h2 className="font-display self-center text-4xl tracking-tight text-white sm:text-6xl">
Školení {training.name} Školení {training.name}
</h2> </h2>
</Container> </Container>
</div> </div>
<Container> <Container>
<div className="md:grid md:grid-cols-5 md:gap-x-4 md:gap-y-4 mt-12"> <div className="mt-12 md:grid md:grid-cols-5 md:gap-x-4 md:gap-y-4">
<div className="md:col-span-3"> <div className="md:col-span-3">
<div className="prose prose-slate prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg prose-h1:font-medium prose-h2:font-medium prose-h3:font-medium prose-li:my-0" dangerouslySetInnerHTML={{ __html: training.content }} /> <div
className="prose prose-slate prose-h1:text-2xl prose-h1:font-medium prose-h2:text-xl prose-h2:font-medium prose-h3:text-lg prose-h3:font-medium prose-li:my-0"
dangerouslySetInnerHTML={{ __html: training.content }}
/>
</div> </div>
<div className="md:col-span-2 mt-8 md:mt-0"> <div className="mt-8 md:col-span-2 md:mt-0">
{ training.days === 2 ? ( {training.days === 2 ? (
<div className=" bg-blue-50 p-4 rounded-lg shadow mb-8"> <div className=" mb-8 rounded-lg bg-blue-50 p-4 shadow">
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
{/* <ExclamationTriangleIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" /> */} {/* <ExclamationTriangleIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" /> */}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-5 h-5 text-blue-400"> <svg
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" /> xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-5 w-5 text-blue-400"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
clip-rule="evenodd"
/>
</svg> </svg>
</div> </div>
<div className="ml-3"> <div className="ml-3">
<p className="text-blue-700"> <p className="text-blue-700">
Toto školení je{' '} Toto školení je{" "}
<span className="font-medium text-blue-700"> <span className="font-medium text-blue-700">
dvoudenní. dvoudenní.
</span> </span>
@ -47,20 +68,32 @@ export const Training = ({ training }: Props) => {
</div> </div>
</div> </div>
</div> </div>
) : <></>} ) : (
<div className="overflow-hidden bg-slate-50 shadow rounded-lg mt-8 md:mt-0"> <></>
)}
<div className="mt-8 overflow-hidden rounded-lg bg-slate-50 shadow md:mt-0">
<div className="px-4 py-5 sm:px-6"> <div className="px-4 py-5 sm:px-6">
<h3 className="text-lg font-medium leading-6 text-slate-900" >Cena za školení</h3> <h3 className="text-lg font-medium leading-6 text-slate-900">
Cena za školení
</h3>
</div> </div>
<div className="border-t border-gray-200 px-4 py-5"> <div className="border-t border-gray-200 px-4 py-5">
<dl className="sm:divide-y sm:divide-gray-200"> <dl className="sm:divide-y sm:divide-gray-200">
<div className="py-4 sm:grid sm:grid-cols-2 sm:gap-4 sm:py-5 sm:px-6"> <div className="py-4 sm:grid sm:grid-cols-2 sm:gap-4 sm:py-5 sm:px-6">
<dt className="font-medium text-gray-500">Veřejný termín</dt> <dt className="font-medium text-gray-500">
<dd className="mt-1 text-gray-900 sm:mt-0">{formatter.format(training.priceOpen)} bez DPH</dd> Veřejný termín
</dt>
<dd className="mt-1 text-gray-900 sm:mt-0">
{formatter.format(training.priceOpen)} bez DPH
</dd>
</div> </div>
<div className="py-4 sm:grid sm:grid-cols-2 sm:gap-4 sm:py-5 sm:px-6"> <div className="py-4 sm:grid sm:grid-cols-2 sm:gap-4 sm:py-5 sm:px-6">
<dt className="font-medium text-gray-500">Firemní školení</dt> <dt className="font-medium text-gray-500">
<dd className="mt-1 text-gray-900 sm:mt-0">{formatter.format(training.priceCompany)} bez DPH</dd> Firemní školení
</dt>
<dd className="mt-1 text-gray-900 sm:mt-0">
{formatter.format(training.priceCompany)} bez DPH
</dd>
</div> </div>
</dl> </dl>
</div> </div>
@ -80,5 +113,5 @@ export const Training = ({ training }: Props) => {
</Container> </Container>
</div> </div>
</> </>
) );
} };

View file

@ -1,95 +1,113 @@
import Image from "next/image" import Image from "next/image";
import clsx from "clsx" import clsx from "clsx";
import TrainingType from "@/types/training" import TrainingType from "@/types/training";
import { Container } from "./Container" import { Container } from "./Container";
import { Button } from "./Button" import { Button } from "./Button";
type TrainingDetailProps = { type TrainingDetailProps = {
training: TrainingType, training: TrainingType;
className?: string, className?: string;
props?: any[], props?: any[];
} };
const TrainingDetail = ({ training, className, ...props }: TrainingDetailProps) => { const TrainingDetail = ({
training,
className,
...props
}: TrainingDetailProps) => {
return ( return (
<div <div
className={ className={clsx(
clsx(className, className,
"rounded-3xl shadow py-8 px-6 sm:px-8", "rounded-3xl py-8 px-6 shadow sm:px-8",
training.featured ? 'bg-blue-600 py-8' : '' training.featured ? "bg-blue-600 py-8" : ""
) )}
}
{...props} {...props}
> >
<Image src={training.logo} className="h-32 w-32 rounded-full" width="1500" height="1500" alt="" /> <Image
<div className="flex justify-between mt-6"> src={training.logo}
className="h-32 w-32 rounded-full"
width="1500"
height="1500"
alt=""
/>
<div className="mt-6 flex justify-between">
<div className="flex"> <div className="flex">
<h3 <h3 className="font-display text-xl font-medium text-white">
className="text-xl font-display font-medium text-white"
>
{training.name} {training.name}
</h3> </h3>
{ training.new ? ( {training.new ? (
<> <>
<span className="ml-2 rounded-full bg-orange-100 px-2 py-1 text-xs font-medium text-orange-800 border border-1 border-orange-800"> <span className="border-1 ml-2 rounded-full border border-orange-800 bg-orange-100 px-2 py-1 text-xs font-medium text-orange-800">
new! new!
</span> </span>
</> </>
) : <></> } ) : (
<></>
)}
</div> </div>
<p className="max-w-2xl text-md text-white">{training.days}{' '}{training.days === 1 ? 'den' : 'dny'}</p> <p className="text-md max-w-2xl text-white">
{training.days} {training.days === 1 ? "den" : "dny"}
</p>
</div> </div>
<p className={ <p
clsx( className={clsx(
"mt-4 text-md", "text-md mt-4",
training.featured ? 'text-white' : 'text-slate-200' training.featured ? "text-white" : "text-slate-200"
)} )}
> >
{training.description.split(" ").splice(0,40).join(" ") + "..."} {training.description.split(" ").splice(0, 40).join(" ") + "..."}
</p> </p>
<Button <Button
href={"/skoleni/" + training.slug} href={"/skoleni/" + training.slug}
variant={training.featured ? 'solid' : 'outline'} variant={training.featured ? "solid" : "outline"}
color="white" color="white"
className="mt-8 w-full" className="mt-8 w-full"
> >
Zjistit více Zjistit více
</Button> </Button>
</div> </div>
) );
} };
type TrainingListProps = { type TrainingListProps = {
trainingList: TrainingType[] trainingList: TrainingType[];
} };
const TrainingListMobile = ({ trainingList }: TrainingListProps) => { const TrainingListMobile = ({ trainingList }: TrainingListProps) => {
return ( return (
<div className="-mx-4 mt-20 flex flex-col gap-y-10 overflow-hidden sm:-mx-6 sm:px-6 lg:hidden"> <div className="-mx-4 mt-20 flex flex-col gap-y-10 overflow-hidden sm:-mx-6 sm:px-6 lg:hidden">
{trainingList.map((training) => ( {trainingList.map((training) => (
<div key={training.slug} className={clsx(training.featured ? 'order-first lg:order-none' : '')}> <div
key={training.slug}
className={clsx(training.featured ? "order-first lg:order-none" : "")}
>
<TrainingDetail training={training} className="mx-auto max-w-2xl" /> <TrainingDetail training={training} className="mx-auto max-w-2xl" />
</div> </div>
))} ))}
</div> </div>
) );
} };
const TrainingListDesktop = ({ trainingList }: TrainingListProps) => { const TrainingListDesktop = ({ trainingList }: TrainingListProps) => {
return ( return (
<div className="hidden lg:mt-20 lg:block"> <div className="hidden lg:mt-20 lg:block">
<div className="grid grid-cols-3 gap-x-8 gap-y-8" <div className="grid grid-cols-3 gap-x-8 gap-y-8">
>
{trainingList.map((training) => ( {trainingList.map((training) => (
<div key={training.slug} className={clsx(training.featured ? 'order-first lg:order-none' : '')}> <div
key={training.slug}
className={clsx(
training.featured ? "order-first lg:order-none" : ""
)}
>
<TrainingDetail training={training} className="relative" /> <TrainingDetail training={training} className="relative" />
</div> </div>
))} ))}
</div> </div>
</div> </div>
) );
} };
export const TrainingListGrid = ({ trainingList }: TrainingListProps) => { export const TrainingListGrid = ({ trainingList }: TrainingListProps) => {
return ( return (
@ -97,7 +115,7 @@ export const TrainingListGrid = ({ trainingList }: TrainingListProps) => {
<section <section
id="training-list" id="training-list"
aria-label="Seznam školení" aria-label="Seznam školení"
className="pt-16 pb-14 sm:pb-20 sm:pt-24 lg:pb-32 bg-slate-900" className="bg-slate-900 pt-16 pb-14 sm:pb-20 sm:pt-24 lg:pb-32"
> >
<Container> <Container>
<div className="mx-auto max-w-2xl md:text-center"> <div className="mx-auto max-w-2xl md:text-center">
@ -105,7 +123,8 @@ export const TrainingListGrid = ({ trainingList }: TrainingListProps) => {
Moje školení Moje školení
</h2> </h2>
<p className="mt-4 text-lg tracking-tight text-slate-400"> <p className="mt-4 text-lg tracking-tight text-slate-400">
Od veřejného cloudu přes on-premise po serverless, se vším vám poradím. Od veřejného cloudu přes on-premise po serverless, se vším vám
poradím.
</p> </p>
</div> </div>
<TrainingListMobile trainingList={trainingList} /> <TrainingListMobile trainingList={trainingList} />
@ -113,5 +132,5 @@ export const TrainingListGrid = ({ trainingList }: TrainingListProps) => {
</Container> </Container>
</section> </section>
</> </>
) );
} };

View file

@ -1,52 +1,51 @@
import fs from 'fs' import fs from "fs";
import { join } from 'path' import { join } from "path";
import matter from 'gray-matter' import matter from "gray-matter";
const trainingDir = join(process.cwd(), 'content/training') const trainingDir = join(process.cwd(), "content/training");
export const getTrainingSlugs = () => { export const getTrainingSlugs = () => {
return fs.readdirSync(trainingDir) return fs.readdirSync(trainingDir);
} };
export const getTrainingBySlug = (slug: string, fields: string[] = []) => { export const getTrainingBySlug = (slug: string, fields: string[] = []) => {
const realSlug = slug.replace(/\.md$/, '') const realSlug = slug.replace(/\.md$/, "");
const fullPath = join(trainingDir, `${realSlug}.md`) const fullPath = join(trainingDir, `${realSlug}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8') const fileContents = fs.readFileSync(fullPath, "utf8");
const { data, content } = matter(fileContents) const { data, content } = matter(fileContents);
type Items = { type Items = {
[key: string]: string [key: string]: string;
} };
const items: Items = {} const items: Items = {};
// Ensure only the minimal needed data is exposed // Ensure only the minimal needed data is exposed
fields.forEach((field) => { fields.forEach((field) => {
if (field === 'slug') { if (field === "slug") {
items[field] = realSlug items[field] = realSlug;
} }
if (field === 'content') { if (field === "content") {
items[field] = content items[field] = content;
} }
if (typeof data[field] !== 'undefined') { if (typeof data[field] !== "undefined") {
items[field] = data[field] items[field] = data[field];
} }
}) });
return items return items;
};
}
export const getAllTraining = (fields: string[] = []) => { export const getAllTraining = (fields: string[] = []) => {
const slugs = getTrainingSlugs() const slugs = getTrainingSlugs();
const trainingList = slugs const trainingList = slugs
.map((slug) => getTrainingBySlug(slug, fields)) .map((slug) => getTrainingBySlug(slug, fields))
.sort((tr1, tr2) => { .sort((tr1, tr2) => {
const tr1w = parseInt(tr1.weight) const tr1w = parseInt(tr1.weight);
const tr2w = parseInt(tr2.weight) const tr2w = parseInt(tr2.weight);
return tr1w > tr2w ? -1 : 1 return tr1w > tr2w ? -1 : 1;
}) });
return trainingList return trainingList;
} };

View file

@ -1,9 +1,9 @@
import { remark } from 'remark' import { remark } from "remark";
import html from 'remark-html' import html from "remark-html";
const markdownToHTML = async (markdown: string) => { const markdownToHTML = async (markdown: string) => {
const result = await remark().use(html).process(markdown) const result = await remark().use(html).process(markdown);
return result.toString() return result.toString();
} };
export default markdownToHTML export default markdownToHTML;

View file

@ -1,9 +1,9 @@
import { AppProps } from 'next/app'; import { AppProps } from "next/app";
import '@/css/tailwind.css'; import "@/css/tailwind.css";
function App({ Component, pageProps }: AppProps) { function App({ Component, pageProps }: AppProps) {
return (<Component {...pageProps} />); return <Component {...pageProps} />;
} }
export default App; export default App;

View file

@ -1,7 +1,7 @@
import { Head, Html, Main, NextScript } from 'next/document' import { Head, Html, Main, NextScript } from "next/document";
export default function Document(props: any) { export default function Document(props: any) {
let pageProps = props.__NEXT_DATA__?.props?.pageProps let pageProps = props.__NEXT_DATA__?.props?.pageProps;
return ( return (
<Html <Html
@ -19,19 +19,37 @@ export default function Document(props: any) {
rel="stylesheet" rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Lexend:wght@400;500&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Lexend:wght@400;500&display=swap"
/> */} /> */}
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> rel="apple-touch-icon"
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" /> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" /> <meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />
<script defer data-domain="vojtechmares.com" src="https://plausible.io/js/script.js"></script> <script
defer
data-domain="vojtechmares.com"
src="https://plausible.io/js/script.js"
></script>
</Head> </Head>
<body className="flex h-full flex-col"> <body className="flex h-full flex-col">
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>
</Html> </Html>
) );
} }

View file

@ -1,15 +1,15 @@
import Head from "next/head"; import Head from "next/head";
import { Header } from "@/components/Header" import { Header } from "@/components/Header";
import { Hero } from "@/components/Hero" import { Hero } from "@/components/Hero";
import { CallToAction } from '@/components/CallToAction' import { CallToAction } from "@/components/CallToAction";
import { Faqs } from '@/components/Faqs' import { Faqs } from "@/components/Faqs";
import { Footer } from '@/components/Footer' import { Footer } from "@/components/Footer";
import { Pricing } from '@/components/Pricing' import { Pricing } from "@/components/Pricing";
import { PrimaryFeatures } from '@/components/PrimaryFeatures' import { PrimaryFeatures } from "@/components/PrimaryFeatures";
import { TechStack } from '@/components/TechStack' import { TechStack } from "@/components/TechStack";
import { Testimonials } from '@/components/Testimonials' import { Testimonials } from "@/components/Testimonials";
import { Environment } from '@/components/Environment' import { Environment } from "@/components/Environment";
export default function Home() { export default function Home() {
return ( return (

View file

@ -1,24 +1,24 @@
import Head from "next/head" import Head from "next/head";
import { useRouter } from "next/router" import { useRouter } from "next/router";
import ErrorPage from "next/error" import ErrorPage from "next/error";
import TrainingType from "@/types/training" import TrainingType from "@/types/training";
import { getAllTraining, getTrainingBySlug } from "@/lib/cms/training" import { getAllTraining, getTrainingBySlug } from "@/lib/cms/training";
import markdownToHTML from "@/lib/markdownToHTML" import markdownToHTML from "@/lib/markdownToHTML";
import { Header } from "@/components/Header" import { Header } from "@/components/Header";
import { Footer } from "@/components/Footer" import { Footer } from "@/components/Footer";
import { Training } from "@/components/Training" import { Training } from "@/components/Training";
type Props = { type Props = {
training: TrainingType training: TrainingType;
featuredTrainingList: TrainingType[] featuredTrainingList: TrainingType[];
} };
const TrainingPage = ({ training, featuredTrainingList}: Props) => { const TrainingPage = ({ training, featuredTrainingList }: Props) => {
const router = useRouter() const router = useRouter();
console.log(!router.isFallback) console.log(!router.isFallback);
console.log(!training?.slug) console.log(!training?.slug);
console.log(typeof training.draft === 'undefined') console.log(typeof training.draft === "undefined");
// if ((!router.isFallback && !training?.slug) || typeof training.draft === 'undefined') { // if ((!router.isFallback && !training?.slug) || typeof training.draft === 'undefined') {
// return <ErrorPage statusCode={404} /> // return <ErrorPage statusCode={404} />
// } // }
@ -26,7 +26,10 @@ const TrainingPage = ({ training, featuredTrainingList}: Props) => {
return ( return (
<> <>
<Head> <Head>
<title>Školení {training.name} | Vojtěch Mareš - DevOps konzultant, lektor, engineer</title> <title>
Školení {training.name} | Vojtěch Mareš - DevOps konzultant, lektor,
engineer
</title>
<meta <meta
name="description" name="description"
@ -39,54 +42,54 @@ const TrainingPage = ({ training, featuredTrainingList}: Props) => {
</main> </main>
<Footer /> <Footer />
</> </>
) );
} };
export default TrainingPage export default TrainingPage;
type Params = { type Params = {
params: { params: {
slug: string slug: string;
} };
} };
export const getStaticProps = async ({ params }: Params) => { export const getStaticProps = async ({ params }: Params) => {
const training = getTrainingBySlug(params.slug, [ const training = getTrainingBySlug(params.slug, [
'name', "name",
'content', "content",
'priceOpen', "priceOpen",
'priceCompany', "priceCompany",
'days', "days",
'logo', "logo",
'content', "content",
'draft', "draft",
'new', "new",
'featured', "featured",
]) ]);
const content = await markdownToHTML(training.content || '') const content = await markdownToHTML(training.content || "");
return { return {
props: { props: {
training: { training: {
...training, ...training,
content, content,
} },
} },
} };
} };
export const getStaticPaths = async () => { export const getStaticPaths = async () => {
const trainingList = getAllTraining(['slug']) const trainingList = getAllTraining(["slug"]);
return { return {
paths: trainingList.map((training) => { paths: trainingList.map((training) => {
return { return {
params: { params: {
slug: training.slug, slug: training.slug,
} },
} };
}), }),
fallback: false, fallback: false,
} };
} };

View file

@ -1,41 +1,43 @@
import Head from "next/head"; import Head from "next/head";
import { Header } from "@/components/Header" import { Header } from "@/components/Header";
import { Footer } from '@/components/Footer' import { Footer } from "@/components/Footer";
import { getAllTraining } from "@/lib/cms/training"; import { getAllTraining } from "@/lib/cms/training";
import TrainingType from "@/types/training"; import TrainingType from "@/types/training";
import { TrainingListGrid } from "@/components/TrainingListGrid"; import { TrainingListGrid } from "@/components/TrainingListGrid";
export const getStaticProps = async () => { export const getStaticProps = async () => {
const allTraining = getAllTraining([ const allTraining = getAllTraining([
'name', "name",
'slug', "slug",
'logo', "logo",
'description', "description",
'days', "days",
'weight', "weight",
'featured', "featured",
'new', "new",
'draft', "draft",
]) ]);
return { return {
props: { allTraining }, props: { allTraining },
} };
} };
type Props = { type Props = {
allTraining: TrainingType[] allTraining: TrainingType[];
} };
const TrainingList = ({ allTraining }: Props) => { const TrainingList = ({ allTraining }: Props) => {
// remove drafts // remove drafts
const trainingList = allTraining.filter((val) => !val.draft) const trainingList = allTraining.filter((val) => !val.draft);
return ( return (
<> <>
<Head> <Head>
<title>Moje školení | Vojtěch Mareš - DevOps konzultant, lektor, engineer</title> <title>
Moje školení | Vojtěch Mareš - DevOps konzultant, lektor, engineer
</title>
<meta <meta
name="description" name="description"
@ -49,6 +51,6 @@ const TrainingList = ({ allTraining }: Props) => {
<Footer /> <Footer />
</> </>
); );
} };
export default TrainingList export default TrainingList;

4
prettier.config.cjs Normal file
View file

@ -0,0 +1,4 @@
/** @type {import("prettier").Config} */
module.exports = {
plugins: [require.resolve("prettier-plugin-tailwindcss")],
};

View file

@ -1,19 +1,19 @@
import { expect, test } from 'vitest' import { expect, test } from "vitest";
import { render, screen, cleanup } from '@testing-library/react' import { render, screen, cleanup } from "@testing-library/react";
import { Button } from '@/components/Button' import { Button } from "@/components/Button";
test('button', () => { test("button", () => {
render(<Button>Hello</Button>) render(<Button>Hello</Button>);
expect(screen.getByText('Hello')).toBeDefined() expect(screen.getByText("Hello")).toBeDefined();
cleanup() cleanup();
}) });
test('button-with-link', () => { test("button-with-link", () => {
render(<Button href="/country-road">Hello</Button>) render(<Button href="/country-road">Hello</Button>);
expect(screen.getByText('Hello').getAttribute('href')).toBe('/country-road') expect(screen.getByText("Hello").getAttribute("href")).toBe("/country-road");
cleanup() cleanup();
}) });

View file

@ -1,19 +1,25 @@
import { expect, test } from 'vitest' import { expect, test } from "vitest";
import { render, screen, within } from '@testing-library/react' import { render, screen, within } from "@testing-library/react";
import Home from '../pages' import Home from "../pages";
test('home', () => { test("home", () => {
render(<Home />) render(<Home />);
const main = within(screen.getByRole('main')) const main = within(screen.getByRole("main"));
expect(main.getByRole('heading', { level: 1, name: /DevOps jednoduše pro všechny./i })).toBeDefined() expect(
main.getByRole("heading", {
level: 1,
name: /DevOps jednoduše pro všechny./i,
})
).toBeDefined();
const meets = screen.getAllByText(/Domluvme si schůzku/i) const meets = screen.getAllByText(/Domluvme si schůzku/i);
meets.map( meets.map((m) =>
(m) => expect(m.getAttribute('href')) expect(m.getAttribute("href")).toBe(
.toBe('https://calendly.com/vojtechmares/30min') "https://calendly.com/vojtechmares/30min"
) )
);
// const footer = within(screen.getByRole('contentinfo')) // const footer = within(screen.getByRole('contentinfo'))
// const link = within(footer.getByRole('link')) // const link = within(footer.getByRole('link'))
// expect(link.getByRole('img', { name: /vercel logo/i })).toBeDefined() // expect(link.getByRole('img', { name: /vercel logo/i })).toBeDefined()
}) });

View file

@ -1,18 +1,18 @@
type TrainingType = { type TrainingType = {
name: string name: string;
slug: string slug: string;
priceOpen: number priceOpen: number;
priceCompany: number priceCompany: number;
logo: string logo: string;
days: number days: number;
description: string description: string;
content: string content: string;
trainingHubId?: string trainingHubId?: string;
draft?: true draft?: true;
new?: true new?: true;
featured?: true featured?: true;
weight: number // custom sorting weight: number; // custom sorting
} };
export default TrainingType export default TrainingType;

View file

@ -1,20 +1,18 @@
/// <reference types="vitest" /> /// <reference types="vitest" />
import { defineConfig } from 'vitest/config' import { defineConfig } from "vitest/config";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
import { resolve } from 'path' import { resolve } from "path";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [react()],
react(),
],
test: { test: {
environment: 'jsdom', environment: "jsdom",
}, },
resolve: { resolve: {
alias: { alias: {
'@': resolve(__dirname, '.') "@": resolve(__dirname, "."),
}
}, },
}) },
});