feat: pricing page, navigation menu, footer
This commit is contained in:
parent
8a6d8dbfff
commit
985da98aee
|
@ -21,6 +21,7 @@
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.1.4",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-progress": "^1.0.3",
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
|
@ -2423,6 +2424,42 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-navigation-menu": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-Cc+seCS3PmWmjI51ufGG7zp1cAAIRqHVw7C9LOA2TZ+R4hG6rDvHcTqIsEEFLmZO3zNVH72jOOE7kKNy8W+RtA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.1",
|
||||||
|
"@radix-ui/react-collection": "1.0.3",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-context": "1.0.1",
|
||||||
|
"@radix-ui/react-direction": "1.0.1",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||||
|
"@radix-ui/react-id": "1.0.1",
|
||||||
|
"@radix-ui/react-presence": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||||
|
"@radix-ui/react-use-previous": "1.0.1",
|
||||||
|
"@radix-ui/react-visually-hidden": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popover": {
|
"node_modules/@radix-ui/react-popover": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.1.4",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-progress": "^1.0.3",
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Footer from "@/components/footer";
|
||||||
|
import Navbar from "@/components/navbar";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Skilled Ai",
|
||||||
|
description: "Skilled Ai",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className="flex flex-col min-h-screen">
|
||||||
|
<Navbar />
|
||||||
|
<main className="grow max-w-7xl mx-auto px-4">{children}</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
|
@ -0,0 +1,72 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import PricingHeader from "@/components/pricing/pricing-header";
|
||||||
|
import PricingSwitch from "@/components/pricing/pricing-switch";
|
||||||
|
import PricingCard from "@/components/pricing/pricing-card";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [isYearly, setIsYearly] = useState(false);
|
||||||
|
const togglePricingPeriod = (value: string) =>
|
||||||
|
setIsYearly(parseInt(value) === 1);
|
||||||
|
|
||||||
|
const plans = [
|
||||||
|
{
|
||||||
|
title: "Basic",
|
||||||
|
monthlyPrice: 10,
|
||||||
|
yearlyPrice: 100,
|
||||||
|
description: "Essential features you need to get started",
|
||||||
|
features: [
|
||||||
|
"Full access to our extensive library of courses and tutorials",
|
||||||
|
"Regularly updated content to keep you ahead of the curve",
|
||||||
|
"Skill assessments and progress tracking",
|
||||||
|
"Community forums for networking and support",
|
||||||
|
"Certificate of completion for each course",
|
||||||
|
],
|
||||||
|
actionLabel: "Get Started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pro",
|
||||||
|
monthlyPrice: 25,
|
||||||
|
yearlyPrice: 250,
|
||||||
|
description: "Perfect for owners of small & medium businessess",
|
||||||
|
features: [
|
||||||
|
"All features included in the Basic tier",
|
||||||
|
"Exclusive access to advanced courses and workshops",
|
||||||
|
"Personalized career coaching sessions",
|
||||||
|
"Priority customer support",
|
||||||
|
"Premium resources such as e-books and industry reports",
|
||||||
|
],
|
||||||
|
actionLabel: "Get Started",
|
||||||
|
popular: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Enterprise",
|
||||||
|
price: "Custom",
|
||||||
|
description: "Dedicated support and infrastructure to fit your needs",
|
||||||
|
features: [
|
||||||
|
"Tailored solutions for businesses and teams",
|
||||||
|
"Dedicated account management",
|
||||||
|
"Customizable learning paths and content curation",
|
||||||
|
"Team analytics and progress tracking",
|
||||||
|
"On-site workshops and training sessions (optional)",
|
||||||
|
],
|
||||||
|
actionLabel: "Contact Sales",
|
||||||
|
exclusive: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="py-8 md:py-24">
|
||||||
|
<PricingHeader
|
||||||
|
title="Welcome to Skilled AI Pricing"
|
||||||
|
subtitle="At Skilled AI, we empower software engineers with top-notch upskilling resources tailored to their career goals. Explore our pricing plans below and take the next step towards mastering your craft."
|
||||||
|
/>
|
||||||
|
<PricingSwitch onSwitch={togglePricingPeriod} />
|
||||||
|
<section className="flex flex-col sm:flex-row sm:flex-wrap justify-center gap-8 mt-8">
|
||||||
|
{plans.map((plan) => {
|
||||||
|
return <PricingCard key={plan.title} {...plan} isYearly={isYearly} />;
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<section className="w-full py-12 md:20 border-t">
|
||||||
|
<div className="container px-4 md:px-6 flex flex-col items-center text-center">
|
||||||
|
<h2 className="text-2xl font-bold tracking-tighter sm:text-3xl md:text-4xl lg:text-5xl/none ">
|
||||||
|
Stay Connected
|
||||||
|
</h2>
|
||||||
|
<p className="mx-auto max-w-[700px] md:text-lg text-muted-foreground mt-2">
|
||||||
|
Subscribe to our newsletter and follow us on our social media.
|
||||||
|
</p>
|
||||||
|
<div className="w-full max-w-md space-y-2 my-4">
|
||||||
|
<form className="flex space-x-2">
|
||||||
|
<Input
|
||||||
|
className="max-w-lg flex-1 "
|
||||||
|
placeholder="Enter your email"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
|
<Button className="" type="submit" variant="outline">
|
||||||
|
Subscribe
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/* <div className="flex justify-center space-x-4">
|
||||||
|
<Link aria-label="Facebook page" className="" href="#">
|
||||||
|
<FacebookIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
<Link aria-label="Twitter profile" className="" href="#">
|
||||||
|
<TwitterIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
<Link aria-label="Instagram profile" className="" href="#">
|
||||||
|
<InstagramIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
<Link aria-label="LinkedIn profile" className="" href="#">
|
||||||
|
<LinkedinIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer className="border-t py-12 md:20">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 grid grid-cols-4 gap-4">
|
||||||
|
<ul className="space-y-1">
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
About Us
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Contact Us
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
FAQs
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Terms of Service
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Privacy Policy
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Blog
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Careers
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Resources
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Pricing
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Feedback
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Community Guidelines
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link className="text-muted-foreground hover:underline" href="#">
|
||||||
|
Cookie Policy
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-semibold">Follow Us</h4>
|
||||||
|
<div className="flex space-x-4 text-muted-foreground">
|
||||||
|
<Link aria-label="Facebook page" className="" href="#">
|
||||||
|
<FacebookIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
<Link aria-label="Twitter profile" className="" href="#">
|
||||||
|
<TwitterIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
<Link aria-label="Instagram profile" className="" href="#">
|
||||||
|
<InstagramIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
<Link aria-label="LinkedIn profile" className="" href="#">
|
||||||
|
<LinkedinIcon className="h-6 w-6" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 flex justify-between mt-8 md:mt-16">
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Skilled AI - Empowering Software Engineers to Thrive
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Copyright © 2024 All Rights Reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FacebookIcon(props: any) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function InstagramIcon(props: any) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<rect width="20" height="20" x="2" y="2" rx="5" ry="5" />
|
||||||
|
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" />
|
||||||
|
<line x1="17.5" x2="17.51" y1="6.5" y2="6.5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinkedinIcon(props: any) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
|
||||||
|
<rect width="4" height="12" x="2" y="9" />
|
||||||
|
<circle cx="4" cy="4" r="2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TwitterIcon(props: any) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Icons } from "@/components/icons";
|
||||||
|
import {
|
||||||
|
NavigationMenu,
|
||||||
|
NavigationMenuContent,
|
||||||
|
NavigationMenuItem,
|
||||||
|
NavigationMenuLink,
|
||||||
|
NavigationMenuList,
|
||||||
|
NavigationMenuTrigger,
|
||||||
|
navigationMenuTriggerStyle,
|
||||||
|
} from "@/components/ui/navigation-menu";
|
||||||
|
import ThemeToggle from "./layout/ThemeToggle/theme-toggle";
|
||||||
|
import { buttonVariants } from "./ui/button";
|
||||||
|
|
||||||
|
const careerPaths: { title: string; href: string; description: string }[] = [
|
||||||
|
{
|
||||||
|
title: "Frontend Developer",
|
||||||
|
href: "#",
|
||||||
|
description:
|
||||||
|
"Master frontend technologies like HTML, CSS, and JavaScript to build dynamic and user-friendly web interfaces.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Backend Developer",
|
||||||
|
href: "#",
|
||||||
|
description:
|
||||||
|
"Learn server-side programming languages and frameworks to create scalable and efficient web applications.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Full Stack Developer",
|
||||||
|
href: "#",
|
||||||
|
description:
|
||||||
|
"Combine frontend and backend development skills to build end-to-end web applications from scratch.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Mobile App Developer",
|
||||||
|
href: "#",
|
||||||
|
description:
|
||||||
|
"Specialize in mobile app development for iOS or Android platforms using Swift, Kotlin, or cross-platform frameworks.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Game Developer",
|
||||||
|
href: "#",
|
||||||
|
description:
|
||||||
|
"Dive into game development with Unity or Unreal Engine, creating immersive gaming experiences for various platforms.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Machine Learning Engineer",
|
||||||
|
href: "#",
|
||||||
|
description:
|
||||||
|
"Develop machine learning models and algorithms to solve complex problems and make predictions based on data.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Navbar() {
|
||||||
|
return (
|
||||||
|
<header className="border-b py-3">
|
||||||
|
<div className="max-w-7xl mx-auto flex justify-between items-center px-4">
|
||||||
|
<Link href="/" className="inline-block w-fit">
|
||||||
|
<h2 className="text-xl font-bold">SkilledAI</h2>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="flex gap-8 items-center">
|
||||||
|
<NavigationMenu>
|
||||||
|
<NavigationMenuList>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuTrigger>Courses</NavigationMenuTrigger>
|
||||||
|
<NavigationMenuContent>
|
||||||
|
<ul className="grid gap-3 p-4 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
|
||||||
|
<li className="row-span-3">
|
||||||
|
<NavigationMenuLink asChild>
|
||||||
|
<a
|
||||||
|
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<Icons.logo className="h-6 w-6" />
|
||||||
|
<div className="mb-2 mt-4 text-lg font-medium">
|
||||||
|
Skilled AI
|
||||||
|
</div>
|
||||||
|
<p className="text-sm leading-tight text-muted-foreground">
|
||||||
|
Browse our extensive catalog of courses covering a
|
||||||
|
wide range of topics in software engineering, from
|
||||||
|
programming languages to advanced technologies.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
|
<ListItem href="#" title="Python Programming">
|
||||||
|
Learn the fundamentals of Python programming language,
|
||||||
|
from basic syntax to advanced concepts like
|
||||||
|
object-oriented programming.
|
||||||
|
</ListItem>
|
||||||
|
<ListItem href="#" title="Web Development with React.js:">
|
||||||
|
Master frontend web development using React.js library,
|
||||||
|
including state management, component-based architecture,
|
||||||
|
and modern UI design techniques.
|
||||||
|
</ListItem>
|
||||||
|
<ListItem href="#" title="Machine Learning Fundamentals">
|
||||||
|
Dive into the world of machine learning, covering topics
|
||||||
|
such as supervised and unsupervised learning, neural
|
||||||
|
networks, and model evaluation.
|
||||||
|
</ListItem>
|
||||||
|
</ul>
|
||||||
|
</NavigationMenuContent>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuTrigger>Career Paths</NavigationMenuTrigger>
|
||||||
|
<NavigationMenuContent>
|
||||||
|
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
|
||||||
|
{careerPaths.map((path) => (
|
||||||
|
<ListItem
|
||||||
|
key={path.title}
|
||||||
|
title={path.title}
|
||||||
|
href={path.href}
|
||||||
|
>
|
||||||
|
{path.description}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</NavigationMenuContent>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<Link href="#" legacyBehavior passHref>
|
||||||
|
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||||
|
Community
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</Link>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<Link href="/pricing" legacyBehavior passHref>
|
||||||
|
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||||
|
Pricing
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</Link>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
</NavigationMenuList>
|
||||||
|
</NavigationMenu>
|
||||||
|
|
||||||
|
<Link className={buttonVariants({ variant: "secondary" })} href="/">
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListItem = React.forwardRef<
|
||||||
|
React.ElementRef<"a">,
|
||||||
|
React.ComponentPropsWithoutRef<"a">
|
||||||
|
>(({ className, title, children, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<NavigationMenuLink asChild>
|
||||||
|
<a
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="text-sm font-medium leading-none">{title}</div>
|
||||||
|
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ListItem.displayName = "ListItem";
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { CheckCircle2 } from "lucide-react"
|
||||||
|
|
||||||
|
const CheckItem = ({ text }: { text: string }) => (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<CheckCircle2 size={18} className="my-auto text-green-400" />
|
||||||
|
<p className="pt-0.5 text-zinc-700 dark:text-zinc-300 text-sm">{text}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default CheckItem
|
|
@ -0,0 +1,100 @@
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import CheckItem from "./check-item";
|
||||||
|
|
||||||
|
type PricingCardProps = {
|
||||||
|
isYearly?: boolean;
|
||||||
|
title: string;
|
||||||
|
monthlyPrice?: number;
|
||||||
|
yearlyPrice?: number;
|
||||||
|
description: string;
|
||||||
|
features: string[];
|
||||||
|
actionLabel: string;
|
||||||
|
popular?: boolean;
|
||||||
|
exclusive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PricingCard = ({
|
||||||
|
isYearly,
|
||||||
|
title,
|
||||||
|
monthlyPrice,
|
||||||
|
yearlyPrice,
|
||||||
|
description,
|
||||||
|
features,
|
||||||
|
actionLabel,
|
||||||
|
popular,
|
||||||
|
exclusive,
|
||||||
|
}: PricingCardProps) => (
|
||||||
|
<Card
|
||||||
|
className={cn(
|
||||||
|
`w-72 flex flex-col justify-between py-1 ${
|
||||||
|
popular ? "border border-primary" : "border"
|
||||||
|
} mx-auto sm:mx-0`,
|
||||||
|
{
|
||||||
|
"animate-background-shine bg-white dark:bg-[linear-gradient(110deg,#000103,45%,#1e2631,55%,#000103)] bg-[length:200%_100%] transition-colors":
|
||||||
|
exclusive,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<CardHeader className="pb-8 pt-4">
|
||||||
|
{isYearly && yearlyPrice && monthlyPrice ? (
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<CardTitle className="text-zinc-700 dark:text-zinc-300 text-lg">
|
||||||
|
{title}
|
||||||
|
</CardTitle>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"px-2.5 rounded-xl h-fit text-sm py-1 bg-zinc-200 text-black dark:bg-zinc-800 dark:text-white",
|
||||||
|
{
|
||||||
|
"bg-gradient-to-r from-orange-400 to-rose-400 dark:text-black ":
|
||||||
|
popular,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Save ${monthlyPrice * 12 - yearlyPrice}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CardTitle className="text-zinc-700 dark:text-zinc-300 text-lg">
|
||||||
|
{title}
|
||||||
|
</CardTitle>
|
||||||
|
)}
|
||||||
|
<div className="flex gap-0.5">
|
||||||
|
<h3 className="text-3xl font-bold">
|
||||||
|
{yearlyPrice && isYearly
|
||||||
|
? "$" + yearlyPrice
|
||||||
|
: monthlyPrice
|
||||||
|
? "$" + monthlyPrice
|
||||||
|
: "Custom"}
|
||||||
|
</h3>
|
||||||
|
<span className="flex flex-col justify-end text-sm mb-1">
|
||||||
|
{yearlyPrice && isYearly ? "/year" : monthlyPrice ? "/month" : null}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<CardDescription className="pt-1.5 h-12">{description}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-2">
|
||||||
|
{features.map((feature: string) => (
|
||||||
|
<CheckItem key={feature} text={feature} />
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</div>
|
||||||
|
<CardFooter className="mt-2">
|
||||||
|
<Button className="relative inline-flex w-full items-center justify-center rounded-md bg-black text-white dark:bg-white px-6 font-medium dark:text-black transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50">
|
||||||
|
<div className="absolute -inset-0.5 -z-10 rounded-lg bg-gradient-to-b from-[#c7d2fe] to-[#8678f9] opacity-75 blur" />
|
||||||
|
{actionLabel}
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PricingCard;
|
|
@ -0,0 +1,9 @@
|
||||||
|
const PricingHeader = ({ title, subtitle }: { title: string; subtitle: string }) => (
|
||||||
|
<section className="text-center">
|
||||||
|
<h2 className="text-3xl font-bold">{title}</h2>
|
||||||
|
<p className="pt-1 text-muted-foreground max-w-2xl mx-auto text-balance">{subtitle}</p>
|
||||||
|
<br />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default PricingHeader
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
|
type PricingSwitchProps = {
|
||||||
|
onSwitch: (value: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PricingSwitch = ({ onSwitch }: PricingSwitchProps) => (
|
||||||
|
<Tabs defaultValue="0" className="w-40 mx-auto" onValueChange={onSwitch}>
|
||||||
|
<TabsList className="py-6 px-2">
|
||||||
|
<TabsTrigger value="0" className="text-base">
|
||||||
|
Monthly
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="1" className="text-base">
|
||||||
|
Yearly
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PricingSwitch;
|
|
@ -0,0 +1,128 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||||
|
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const NavigationMenu = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<NavigationMenuViewport />
|
||||||
|
</NavigationMenuPrimitive.Root>
|
||||||
|
))
|
||||||
|
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const NavigationMenuList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||||
|
|
||||||
|
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||||
|
|
||||||
|
const navigationMenuTriggerStyle = cva(
|
||||||
|
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||||
|
)
|
||||||
|
|
||||||
|
const NavigationMenuTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}{" "}
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</NavigationMenuPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const NavigationMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
||||||
|
|
||||||
|
const NavigationMenuViewport = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||||
|
<NavigationMenuPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
NavigationMenuViewport.displayName =
|
||||||
|
NavigationMenuPrimitive.Viewport.displayName
|
||||||
|
|
||||||
|
const NavigationMenuIndicator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Indicator
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||||
|
</NavigationMenuPrimitive.Indicator>
|
||||||
|
))
|
||||||
|
NavigationMenuIndicator.displayName =
|
||||||
|
NavigationMenuPrimitive.Indicator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
navigationMenuTriggerStyle,
|
||||||
|
NavigationMenu,
|
||||||
|
NavigationMenuList,
|
||||||
|
NavigationMenuItem,
|
||||||
|
NavigationMenuContent,
|
||||||
|
NavigationMenuTrigger,
|
||||||
|
NavigationMenuLink,
|
||||||
|
NavigationMenuIndicator,
|
||||||
|
NavigationMenuViewport,
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ export async function markdownToHtml(tutorialCode: string) {
|
||||||
.use(remarkParse)
|
.use(remarkParse)
|
||||||
.use(remarkRehype)
|
.use(remarkRehype)
|
||||||
.use(rehypePrettyCode, {})
|
.use(rehypePrettyCode, {})
|
||||||
|
// @ts-ignore
|
||||||
.use(rehypeStringify)
|
.use(rehypeStringify)
|
||||||
.process(tutorialCode);
|
.process(tutorialCode);
|
||||||
|
|
||||||
|
|
|
@ -69,10 +69,19 @@ module.exports = {
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
from: { height: "var(--radix-accordion-content-height)" },
|
||||||
to: { height: 0 },
|
to: { height: 0 },
|
||||||
},
|
},
|
||||||
|
"background-shine": {
|
||||||
|
from: {
|
||||||
|
backgroundPosition: "0 0",
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
backgroundPosition: "-200% 0",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
|
"background-shine": "background-shine 2s linear infinite",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue