From 985da98aee329e380904b81f2ed9c18996668b57 Mon Sep 17 00:00:00 2001 From: mehedi-hasan Date: Tue, 9 Apr 2024 02:01:54 +0600 Subject: [PATCH] feat: pricing page, navigation menu, footer --- package-lock.json | 37 ++++ package.json | 1 + src/app/(app)/layout.tsx | 22 +++ src/app/(app)/pricing/page.tsx | 72 +++++++ src/components/footer.tsx | 223 ++++++++++++++++++++++ src/components/navbar.tsx | 177 +++++++++++++++++ src/components/pricing/check-item.tsx | 10 + src/components/pricing/pricing-card.tsx | 100 ++++++++++ src/components/pricing/pricing-header.tsx | 9 + src/components/pricing/pricing-switch.tsx | 20 ++ src/components/ui/navigation-menu.tsx | 128 +++++++++++++ src/lib/markdown-to-html.ts | 1 + tailwind.config.js | 9 + 13 files changed, 809 insertions(+) create mode 100644 src/app/(app)/layout.tsx create mode 100644 src/app/(app)/pricing/page.tsx create mode 100644 src/components/footer.tsx create mode 100644 src/components/navbar.tsx create mode 100644 src/components/pricing/check-item.tsx create mode 100644 src/components/pricing/pricing-card.tsx create mode 100644 src/components/pricing/pricing-header.tsx create mode 100644 src/components/pricing/pricing-switch.tsx create mode 100644 src/components/ui/navigation-menu.tsx diff --git a/package-lock.json b/package-lock.json index f8ff0c2..58f5edb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-icons": "^1.3.0", "@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-progress": "^1.0.3", "@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": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz", diff --git a/package.json b/package.json index 42a7b3a..f180cf8 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-icons": "^1.3.0", "@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-progress": "^1.0.3", "@radix-ui/react-scroll-area": "^1.0.5", diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx new file mode 100644 index 0000000..65d92eb --- /dev/null +++ b/src/app/(app)/layout.tsx @@ -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 ( + +
+ +
{children}
+
+
+ ); +}; + +export default Layout; diff --git a/src/app/(app)/pricing/page.tsx b/src/app/(app)/pricing/page.tsx new file mode 100644 index 0000000..83f462e --- /dev/null +++ b/src/app/(app)/pricing/page.tsx @@ -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 ( +
+ + +
+ {plans.map((plan) => { + return ; + })} +
+
+ ); +} diff --git a/src/components/footer.tsx b/src/components/footer.tsx new file mode 100644 index 0000000..725dd75 --- /dev/null +++ b/src/components/footer.tsx @@ -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 ( +
+
+
+

+ Stay Connected +

+

+ Subscribe to our newsletter and follow us on our social media. +

+
+
+ + +
+
+ {/*
+ + + + + + + + + + + + +
*/} +
+
+ +
+
+
    +
  • + + About Us + +
  • +
  • + + Contact Us + +
  • +
  • + + FAQs + +
  • +
  • + + Terms of Service + +
  • +
+
    +
  • + + Privacy Policy + +
  • +
  • + + Blog + +
  • +
  • + + Careers + +
  • +
  • + + Resources + +
  • +
+
    +
  • + + Pricing + +
  • +
  • + + Feedback + +
  • +
  • + + Community Guidelines + +
  • +
  • + + Cookie Policy + +
  • +
+
+

Follow Us

+
+ + + + + + + + + + + + +
+
+
+ +
+

+ Skilled AI - Empowering Software Engineers to Thrive +

+

+ Copyright © 2024 All Rights Reserved. +

+
+
+
+ ); +} + +function FacebookIcon(props: any) { + return ( + + + + ); +} + +function InstagramIcon(props: any) { + return ( + + + + + + ); +} + +function LinkedinIcon(props: any) { + return ( + + + + + + ); +} + +function TwitterIcon(props: any) { + return ( + + + + ); +} diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx new file mode 100644 index 0000000..e05b37b --- /dev/null +++ b/src/components/navbar.tsx @@ -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 ( +
+
+ +

SkilledAI

+ + +
+ + + + Courses + + + + + + Career Paths + +
    + {careerPaths.map((path) => ( + + {path.description} + + ))} +
+
+
+ + + + Community + + + + + + + Pricing + + + +
+
+ + + Sign In + + +
+
+
+ ); +} + +const ListItem = React.forwardRef< + React.ElementRef<"a">, + React.ComponentPropsWithoutRef<"a"> +>(({ className, title, children, ...props }, ref) => { + return ( +
  • + + +
    {title}
    +

    + {children} +

    +
    +
    +
  • + ); +}); +ListItem.displayName = "ListItem"; diff --git a/src/components/pricing/check-item.tsx b/src/components/pricing/check-item.tsx new file mode 100644 index 0000000..48ad54c --- /dev/null +++ b/src/components/pricing/check-item.tsx @@ -0,0 +1,10 @@ +import { CheckCircle2 } from "lucide-react" + +const CheckItem = ({ text }: { text: string }) => ( +
    + +

    {text}

    +
    + ) + + export default CheckItem \ No newline at end of file diff --git a/src/components/pricing/pricing-card.tsx b/src/components/pricing/pricing-card.tsx new file mode 100644 index 0000000..6500600 --- /dev/null +++ b/src/components/pricing/pricing-card.tsx @@ -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) => ( + +
    + + {isYearly && yearlyPrice && monthlyPrice ? ( +
    + + {title} + +
    + Save ${monthlyPrice * 12 - yearlyPrice} +
    +
    + ) : ( + + {title} + + )} +
    +

    + {yearlyPrice && isYearly + ? "$" + yearlyPrice + : monthlyPrice + ? "$" + monthlyPrice + : "Custom"} +

    + + {yearlyPrice && isYearly ? "/year" : monthlyPrice ? "/month" : null} + +
    + {description} +
    + + {features.map((feature: string) => ( + + ))} + +
    + + + +
    +); + +export default PricingCard; diff --git a/src/components/pricing/pricing-header.tsx b/src/components/pricing/pricing-header.tsx new file mode 100644 index 0000000..3644fe2 --- /dev/null +++ b/src/components/pricing/pricing-header.tsx @@ -0,0 +1,9 @@ +const PricingHeader = ({ title, subtitle }: { title: string; subtitle: string }) => ( +
    +

    {title}

    +

    {subtitle}

    +
    +
    + ) + + export default PricingHeader \ No newline at end of file diff --git a/src/components/pricing/pricing-switch.tsx b/src/components/pricing/pricing-switch.tsx new file mode 100644 index 0000000..aee08ec --- /dev/null +++ b/src/components/pricing/pricing-switch.tsx @@ -0,0 +1,20 @@ +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +type PricingSwitchProps = { + onSwitch: (value: string) => void; +}; + +const PricingSwitch = ({ onSwitch }: PricingSwitchProps) => ( + + + + Monthly + + + Yearly + + + +); + +export default PricingSwitch; diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx new file mode 100644 index 0000000..5841fb3 --- /dev/null +++ b/src/components/ui/navigation-menu.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + +)) +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName + +const NavigationMenuList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +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, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children}{" "} + +)) +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName + +const NavigationMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName + +const NavigationMenuLink = NavigationMenuPrimitive.Link + +const NavigationMenuViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
    + +
    +)) +NavigationMenuViewport.displayName = + NavigationMenuPrimitive.Viewport.displayName + +const NavigationMenuIndicator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
    + +)) +NavigationMenuIndicator.displayName = + NavigationMenuPrimitive.Indicator.displayName + +export { + navigationMenuTriggerStyle, + NavigationMenu, + NavigationMenuList, + NavigationMenuItem, + NavigationMenuContent, + NavigationMenuTrigger, + NavigationMenuLink, + NavigationMenuIndicator, + NavigationMenuViewport, +} diff --git a/src/lib/markdown-to-html.ts b/src/lib/markdown-to-html.ts index 4946106..ef50e64 100644 --- a/src/lib/markdown-to-html.ts +++ b/src/lib/markdown-to-html.ts @@ -9,6 +9,7 @@ export async function markdownToHtml(tutorialCode: string) { .use(remarkParse) .use(remarkRehype) .use(rehypePrettyCode, {}) + // @ts-ignore .use(rehypeStringify) .process(tutorialCode); diff --git a/tailwind.config.js b/tailwind.config.js index 338e949..8aaf07d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -69,10 +69,19 @@ module.exports = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: 0 }, }, + "background-shine": { + from: { + backgroundPosition: "0 0", + }, + to: { + backgroundPosition: "-200% 0", + }, + }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", + "background-shine": "background-shine 2s linear infinite", }, }, },