Skip to content

Commit

Permalink
refactor(saas): Promotion section
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniDuese committed May 7, 2024
1 parent d7eeb50 commit b1be06c
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 24 deletions.
26 changes: 24 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions starterkits/saas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@
"@tanstack/react-table": "^8.16.0",
"@uploadthing/react": "^6.4.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"clsx": "^2.1.1",
"cmdk": "^0.2.1",
"date-fns": "^3.6.0",
"drizzle-orm": "^0.29.4",
"drizzle-zod": "^0.5.1",
"framer-motion": "^11.1.9",
"geist": "^1.3.0",
"github-slugger": "^2.0.0",
"lucide-react": "^0.368.0",
Expand All @@ -64,7 +65,7 @@
"server-only": "^0.0.1",
"sonner": "^1.4.41",
"superjson": "^2.2.1",
"tailwind-merge": "^2.2.2",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"unist-util-visit": "^5.0.0",
"uploadthing": "^6.9.0",
Expand Down
232 changes: 212 additions & 20 deletions starterkits/saas/src/app/(web)/_components/promotion.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,218 @@
import Balancer from "react-wrap-balancer";
"use client";

import React, { useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";

import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import Link from "next/link";
import { siteUrls } from "@/config/urls";
import { buttonVariants } from "@/components/ui/button";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

export function Promotion() {
return (
<section className="flex min-h-96 w-full flex-col items-center justify-center gap-5 rounded-[26px] bg-foreground p-8 py-10 text-background">
<Balancer
as="h2"
className="text-center font-heading text-3xl font-bold md:text-5xl"
>
Launch your SaaS in just a few days 🚀
</Balancer>
<Balancer
as="p"
className="text-center text-base leading-relaxed text-background/70 sm:text-xl"
>
Because Rapidlaunch comes with a SaaS starter kit, Blocks and
guides, and more, you can launch your SaaS in just a few days.
Get started with our starter kits, components, building guides,
and more. Customizable.{" "}
<span className="rounded-[5px] bg-background p-1 font-semibold text-foreground">
Open Source.
</span>
</Balancer>
<section className="flex py-4 w-full items-center justify-center antialiased">
<GlowingStarsBackgroundCard>
<GlowingStarsTitle>Launch your SaaS in just a few days 🚀</GlowingStarsTitle>
<div className="sm:flex md:justify-between items-end space-y-4">
<GlowingStarsDescription>
Rapidlaunch comes with a SaaS starter kit, Blocks and
guides, and more, you can launch your SaaS in just a few days.
Get started with our starter kits, components, building guides,
and more. Customizable.{" "} <span className="font-semibold text-foreground">
Open Source.
</span>
</GlowingStarsDescription>
<div className="rounded-full items-center justify-center">
<Icon />
</div>
</div>
</GlowingStarsBackgroundCard>
</section>
);
}

const Icon = () => {
return (
<Link
href={siteUrls.github}
target="_blank"
className={buttonVariants({ variant: "outline" })}
rel="noopener noreferrer"
>
<p className="text-foreground text-sm w-14 font-semibold">GitHub</p>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="h-4 w-4 text-foreground stroke-2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M17.25 8.25L21 12m0 0l-3.75 3.75M21 12H3"
/>
</svg>
</Link>
);
};


export const GlowingStarsBackgroundCard = ({
className,
children,
}: {
className?: string;
children?: React.ReactNode;
}) => {
const [mouseEnter, setMouseEnter] = useState(false);

return (
<div
onMouseEnter={() => {
setMouseEnter(true);
}}
onMouseLeave={() => {
setMouseEnter(false);
}}
className={cn(
"bg-muted/50 p-4 h-full w-full rounded-[26px] border transition-colors duration-300 ",
className
)}
>
<div className="flex justify-center items-center">
<Illustration mouseEnter={mouseEnter} />
</div>
<div className="px-2 pb-2">{children}</div>
</div>
);
};

export const GlowingStarsDescription = ({
className,
children,
}: {
className?: string;
children?: React.ReactNode;
}) => {
return (
<p className={cn("text-base text-muted-foreground max-w-[50rem]", className)}>
{children}
</p>
);
};

export const GlowingStarsTitle = ({
className,
children,
}: {
className?: string;
children?: React.ReactNode;
}) => {
return (
<h2 className={cn("font-bold text-2xl text-foreground pb-2", className)}>
{children}
</h2>
);
};

export const Illustration = ({ mouseEnter }: { mouseEnter: boolean }) => {
const stars = 216;
const columns = 36;

const [glowingStars, setGlowingStars] = useState<number[]>([]);

const highlightedStars = useRef<number[]>([]);

useEffect(() => {
const interval = setInterval(() => {
highlightedStars.current = Array.from({ length: 20 }, () =>
Math.floor(Math.random() * stars)
);
setGlowingStars([...highlightedStars.current]);
}, 3000);

return () => clearInterval(interval);
}, []);

return (
<div
className="h-48 p-1 w-full"
style={{
display: "grid",
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: `1px`,
}}
>
{[...Array(stars)].map((_, starIdx) => {
const isGlowing = glowingStars.includes(starIdx);
const delay = (starIdx % 10) * 0.1;
const staticDelay = starIdx * 0.01;
return (
<div
key={`matrix-col-${starIdx}}`}
className="relative flex items-center justify-center"
>
<Star
isGlowing={mouseEnter ? true : isGlowing}
delay={mouseEnter ? staticDelay : delay}
/>
{mouseEnter && <Glow delay={staticDelay} />}
<AnimatePresence mode="wait">
{isGlowing && <Glow delay={delay} />}
</AnimatePresence>
</div>
);
})}
</div>
);
};

const Star = ({ isGlowing, delay }: { isGlowing: boolean; delay: number }) => {
return (
<motion.div
key={delay}
initial={{
scale: 1,
}}
animate={{
scale: isGlowing ? [1, 1.2, 2.5, 2.2, 1.5] : 1,
background: isGlowing ? "#fff" : "#666",
}}
transition={{
duration: 2,
ease: "easeInOut",
delay: delay,
}}
className={cn("bg-[#666] h-[1px] w-[1px] rounded-full relative z-20")}
></motion.div>
);
};

const Glow = ({ delay }: { delay: number }) => {
return (
<motion.div
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
transition={{
duration: 2,
ease: "easeInOut",
delay: delay,
}}
exit={{
opacity: 0,
}}
className="absolute left-1/2 -translate-x-1/2 z-10 h-[4px] w-[4px] rounded-full bg-blue-500 blur-[1px] shadow-2xl shadow-blue-400"
/>
);
};

0 comments on commit b1be06c

Please sign in to comment.