From 6820915d95ac53d829c4d06481a460ae8fb4a97d Mon Sep 17 00:00:00 2001 From: Diego Artiles Date: Sun, 27 Oct 2024 19:27:51 -0300 Subject: [PATCH 1/8] feat(apps/landing): first approach to landing page --- .../app/api/auth/[...nextauth]/route.ts | 18 -- .../{ => app}/components/home/card.tsx | 0 .../components/home/component-grid.tsx | 6 +- .../{ => app}/components/home/demo-modal.tsx | 2 +- .../{ => app}/components/home/web-vitals.tsx | 2 +- apps/landing/app/components/layout/nav.tsx | 5 + apps/landing/app/components/layout/navbar.tsx | 88 ++++++ .../components/layout/sign-in-modal.tsx | 4 +- .../components/layout/user-dropdown.tsx | 2 +- .../background-beams-with-collision.tsx.tsx | 260 ++++++++++++++++++ .../components/shared/counting-numbers.tsx | 0 .../components/shared/icons/buymeacoffee.tsx | 0 .../shared/icons/expanding-arrow.tsx | 0 .../components/shared/icons/github.tsx | 0 .../components/shared/icons/google.tsx | 0 .../components/shared/icons/index.tsx | 0 .../shared/icons/loading-circle.tsx | 0 .../shared/icons/loading-dots.module.css | 0 .../components/shared/icons/loading-dots.tsx | 0 .../shared/icons/loading-spinner.module.css | 0 .../shared/icons/loading-spinner.tsx | 0 .../components/shared/icons/twitter.tsx | 0 .../{ => app}/components/shared/modal.tsx | 2 +- .../{ => app}/components/shared/popover.tsx | 0 .../app/components/shared/text-morph.tsx | 61 ++++ .../app/components/shared/text-rotate.tsx | 35 +++ .../app/components/shared/text-scramble.tsx | 51 ++++ .../{ => app}/components/shared/tooltip.tsx | 0 apps/landing/app/favicon.ico | Bin 5152 -> 0 bytes apps/landing/app/layout.tsx | 36 ++- apps/landing/app/opengraph-image.tsx | 60 ---- apps/landing/app/page.tsx | 115 +++----- apps/landing/components/layout/footer.tsx | 28 -- apps/landing/components/layout/nav.tsx | 8 - apps/landing/components/layout/navbar.tsx | 130 --------- .../lib/hooks/use-intersection-observer.ts | 2 +- apps/landing/lib/prisma.ts | 11 - apps/landing/lib/utils.ts | 6 - apps/landing/next.config.js | 26 ++ apps/landing/package.json | 27 +- apps/landing/prisma/schema.prisma | 61 ---- apps/landing/public/icon1.svg | 34 +++ apps/landing/public/icon2.svg | 34 +++ apps/landing/public/logo-horizontal.svg | 92 +++++++ apps/landing/public/logo.svg | 45 +++ apps/landing/tsconfig.json | 5 +- 46 files changed, 824 insertions(+), 432 deletions(-) delete mode 100644 apps/landing/app/api/auth/[...nextauth]/route.ts rename apps/landing/{ => app}/components/home/card.tsx (100%) rename apps/landing/{ => app}/components/home/component-grid.tsx (93%) rename apps/landing/{ => app}/components/home/demo-modal.tsx (96%) rename apps/landing/{ => app}/components/home/web-vitals.tsx (93%) create mode 100644 apps/landing/app/components/layout/nav.tsx create mode 100644 apps/landing/app/components/layout/navbar.tsx rename apps/landing/{ => app}/components/layout/sign-in-modal.tsx (95%) rename apps/landing/{ => app}/components/layout/user-dropdown.tsx (97%) create mode 100644 apps/landing/app/components/shared/background-beams-with-collision.tsx.tsx rename apps/landing/{ => app}/components/shared/counting-numbers.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/buymeacoffee.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/expanding-arrow.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/github.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/google.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/index.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/loading-circle.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/loading-dots.module.css (100%) rename apps/landing/{ => app}/components/shared/icons/loading-dots.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/loading-spinner.module.css (100%) rename apps/landing/{ => app}/components/shared/icons/loading-spinner.tsx (100%) rename apps/landing/{ => app}/components/shared/icons/twitter.tsx (100%) rename apps/landing/{ => app}/components/shared/modal.tsx (97%) rename apps/landing/{ => app}/components/shared/popover.tsx (100%) create mode 100644 apps/landing/app/components/shared/text-morph.tsx create mode 100644 apps/landing/app/components/shared/text-rotate.tsx create mode 100644 apps/landing/app/components/shared/text-scramble.tsx rename apps/landing/{ => app}/components/shared/tooltip.tsx (100%) delete mode 100644 apps/landing/app/favicon.ico delete mode 100644 apps/landing/app/opengraph-image.tsx delete mode 100644 apps/landing/components/layout/footer.tsx delete mode 100644 apps/landing/components/layout/nav.tsx delete mode 100644 apps/landing/components/layout/navbar.tsx delete mode 100644 apps/landing/lib/prisma.ts delete mode 100644 apps/landing/lib/utils.ts delete mode 100644 apps/landing/prisma/schema.prisma create mode 100644 apps/landing/public/icon1.svg create mode 100644 apps/landing/public/icon2.svg create mode 100644 apps/landing/public/logo-horizontal.svg create mode 100644 apps/landing/public/logo.svg diff --git a/apps/landing/app/api/auth/[...nextauth]/route.ts b/apps/landing/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index b39b376..0000000 --- a/apps/landing/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,18 +0,0 @@ -import NextAuth, { NextAuthOptions } from "next-auth"; -import { PrismaAdapter } from "@next-auth/prisma-adapter"; -import prisma from "@/lib/prisma"; -import GoogleProvider from "next-auth/providers/google"; - -export const authOptions: NextAuthOptions = { - adapter: PrismaAdapter(prisma), - providers: [ - GoogleProvider({ - clientId: process.env.GOOGLE_CLIENT_ID as string, - clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, - }), - ], -}; - -const handler = NextAuth(authOptions); - -export { handler as GET, handler as POST }; diff --git a/apps/landing/components/home/card.tsx b/apps/landing/app/components/home/card.tsx similarity index 100% rename from apps/landing/components/home/card.tsx rename to apps/landing/app/components/home/card.tsx diff --git a/apps/landing/components/home/component-grid.tsx b/apps/landing/app/components/home/component-grid.tsx similarity index 93% rename from apps/landing/components/home/component-grid.tsx rename to apps/landing/app/components/home/component-grid.tsx index 150c29a..3866bcb 100644 --- a/apps/landing/components/home/component-grid.tsx +++ b/apps/landing/app/components/home/component-grid.tsx @@ -1,9 +1,9 @@ "use client"; import { useState } from "react"; -import { useDemoModal } from "@/components/home/demo-modal"; -import Popover from "@/components/shared/popover"; -import Tooltip from "@/components/shared/tooltip"; +import { useDemoModal } from "@/app/components/home/demo-modal"; +import Popover from "@/app/components/shared/popover"; +import Tooltip from "@/app/components/shared/tooltip"; import { ChevronDown } from "lucide-react"; export default function ComponentGrid() { diff --git a/apps/landing/components/home/demo-modal.tsx b/apps/landing/app/components/home/demo-modal.tsx similarity index 96% rename from apps/landing/components/home/demo-modal.tsx rename to apps/landing/app/components/home/demo-modal.tsx index 2bdcc5f..b2a6b9e 100644 --- a/apps/landing/components/home/demo-modal.tsx +++ b/apps/landing/app/components/home/demo-modal.tsx @@ -1,4 +1,4 @@ -import Modal from "@/components/shared/modal"; +import Modal from "@/app/components/shared/modal"; import { useState, Dispatch, diff --git a/apps/landing/components/home/web-vitals.tsx b/apps/landing/app/components/home/web-vitals.tsx similarity index 93% rename from apps/landing/components/home/web-vitals.tsx rename to apps/landing/app/components/home/web-vitals.tsx index 3d87512..e590286 100644 --- a/apps/landing/components/home/web-vitals.tsx +++ b/apps/landing/app/components/home/web-vitals.tsx @@ -1,7 +1,7 @@ "use client"; import { motion } from "framer-motion"; -import CountingNumbers from "@/components/shared/counting-numbers"; +import CountingNumbers from "@/app/components/shared/counting-numbers"; export default function WebVitals() { return ( diff --git a/apps/landing/app/components/layout/nav.tsx b/apps/landing/app/components/layout/nav.tsx new file mode 100644 index 0000000..3963691 --- /dev/null +++ b/apps/landing/app/components/layout/nav.tsx @@ -0,0 +1,5 @@ +import Navbar from "./navbar"; + +export default async function Nav() { + return ; +} diff --git a/apps/landing/app/components/layout/navbar.tsx b/apps/landing/app/components/layout/navbar.tsx new file mode 100644 index 0000000..a3f84d3 --- /dev/null +++ b/apps/landing/app/components/layout/navbar.tsx @@ -0,0 +1,88 @@ +"use client"; + +import useScroll from "@/lib/hooks/use-scroll"; +import { Button } from "@makify/ui"; +import Link from "next/link"; + +export default function NavBar() { + const scrolled = useScroll(50); + + return ( +
+
+
+ {/* + + + + Tools + + +
    + + Discover an interactive way to read your PDFs. + + + Upload any kind of file and share it. + + + Turn it into a QR code any kind of text. + +
+
+
+ + + + Pricing + + + +
+
*/} +
+
+ +
+
+
+ ); +} + +/* const ListItem = forwardRef< + React.ElementRef<"a">, + React.ComponentPropsWithoutRef<"a"> +>(({ className, title, children, ...props }, ref) => { + return ( +
  • + + +
    {title}
    +

    + {children} +

    +
    +
    +
  • + ); +}); +ListItem.displayName = "ListItem"; + */ diff --git a/apps/landing/components/layout/sign-in-modal.tsx b/apps/landing/app/components/layout/sign-in-modal.tsx similarity index 95% rename from apps/landing/components/layout/sign-in-modal.tsx rename to apps/landing/app/components/layout/sign-in-modal.tsx index 17b5021..c905049 100644 --- a/apps/landing/components/layout/sign-in-modal.tsx +++ b/apps/landing/app/components/layout/sign-in-modal.tsx @@ -1,4 +1,4 @@ -import Modal from "@/components/shared/modal"; +import Modal from "@/app/components/shared/modal"; import { signIn } from "next-auth/react"; import { useState, @@ -7,7 +7,7 @@ import { useCallback, useMemo, } from "react"; -import { LoadingDots, Google } from "@/components/shared/icons"; +import { LoadingDots, Google } from "@/app/components/shared/icons"; import Image from "next/image"; const SignInModal = ({ diff --git a/apps/landing/components/layout/user-dropdown.tsx b/apps/landing/app/components/layout/user-dropdown.tsx similarity index 97% rename from apps/landing/components/layout/user-dropdown.tsx rename to apps/landing/app/components/layout/user-dropdown.tsx index d64ec66..fd1d0d6 100644 --- a/apps/landing/components/layout/user-dropdown.tsx +++ b/apps/landing/app/components/layout/user-dropdown.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { signOut } from "next-auth/react"; import { LayoutDashboard, LogOut } from "lucide-react"; -import Popover from "@/components/shared/popover"; +import Popover from "@/app/components/shared/popover"; import Image from "next/image"; import { Session } from "next-auth"; diff --git a/apps/landing/app/components/shared/background-beams-with-collision.tsx.tsx b/apps/landing/app/components/shared/background-beams-with-collision.tsx.tsx new file mode 100644 index 0000000..b1348b8 --- /dev/null +++ b/apps/landing/app/components/shared/background-beams-with-collision.tsx.tsx @@ -0,0 +1,260 @@ +"use client"; + +import { cn } from "@makify/ui/lib/utils"; +import { motion, AnimatePresence } from "framer-motion"; +import React, { useRef, useState, useEffect } from "react"; + +export const BackgroundBeamsWithCollision = ({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) => { + const containerRef = useRef(null); + const parentRef = useRef(null); + + const beams = [ + { + initialX: 10, + translateX: 10, + duration: 7, + repeatDelay: 3, + delay: 2, + }, + { + initialX: 600, + translateX: 600, + duration: 3, + repeatDelay: 3, + delay: 4, + }, + { + initialX: 100, + translateX: 100, + duration: 7, + repeatDelay: 7, + className: "h-6", + }, + { + initialX: 400, + translateX: 400, + duration: 5, + repeatDelay: 14, + delay: 4, + }, + { + initialX: 800, + translateX: 800, + duration: 11, + repeatDelay: 2, + className: "h-20", + }, + { + initialX: 1000, + translateX: 1000, + duration: 4, + repeatDelay: 2, + className: "h-12", + }, + { + initialX: 1200, + translateX: 1200, + duration: 6, + repeatDelay: 4, + delay: 2, + className: "h-6", + }, + ]; + + return ( +
    + {beams.map((beam) => ( + + ))} + + {children} +
    +
    + ); +}; + +const CollisionMechanism = React.forwardRef< + HTMLDivElement, + { + containerRef: React.RefObject; + parentRef: React.RefObject; + beamOptions?: { + initialX?: number; + translateX?: number; + initialY?: number; + translateY?: number; + rotate?: number; + className?: string; + duration?: number; + delay?: number; + repeatDelay?: number; + }; + } +>(({ parentRef, containerRef, beamOptions = {} }, ref) => { + const beamRef = useRef(null); + const [collision, setCollision] = useState<{ + detected: boolean; + coordinates: { x: number; y: number } | null; + }>({ + detected: false, + coordinates: null, + }); + const [beamKey, setBeamKey] = useState(0); + const [cycleCollisionDetected, setCycleCollisionDetected] = useState(false); + + useEffect(() => { + const checkCollision = () => { + if ( + beamRef.current && + containerRef.current && + parentRef.current && + !cycleCollisionDetected + ) { + const beamRect = beamRef.current.getBoundingClientRect(); + const containerRect = containerRef.current.getBoundingClientRect(); + const parentRect = parentRef.current.getBoundingClientRect(); + + if (beamRect.bottom >= containerRect.top) { + const relativeX = + beamRect.left - parentRect.left + beamRect.width / 2; + const relativeY = beamRect.bottom - parentRect.top; + + setCollision({ + detected: true, + coordinates: { + x: relativeX, + y: relativeY, + }, + }); + setCycleCollisionDetected(true); + } + } + }; + + const animationInterval = setInterval(checkCollision, 50); + + return () => clearInterval(animationInterval); + }, [cycleCollisionDetected, containerRef]); + + useEffect(() => { + if (collision.detected && collision.coordinates) { + setTimeout(() => { + setCollision({ detected: false, coordinates: null }); + setCycleCollisionDetected(false); + }, 2000); + + setTimeout(() => { + setBeamKey((prevKey) => prevKey + 1); + }, 2000); + } + }, [collision]); + + return ( + <> + + + {collision.detected && collision.coordinates && ( + + )} + + + ); +}); + +CollisionMechanism.displayName = "CollisionMechanism"; + +const Explosion = ({ ...props }: React.HTMLProps) => { + const spans = Array.from({ length: 20 }, (_, index) => ({ + id: index, + initialX: 0, + initialY: 0, + directionX: Math.floor(Math.random() * 80 - 40), + directionY: Math.floor(Math.random() * -50 - 10), + })); + + return ( +
    + + {spans.map((span) => ( + + ))} +
    + ); +}; diff --git a/apps/landing/components/shared/counting-numbers.tsx b/apps/landing/app/components/shared/counting-numbers.tsx similarity index 100% rename from apps/landing/components/shared/counting-numbers.tsx rename to apps/landing/app/components/shared/counting-numbers.tsx diff --git a/apps/landing/components/shared/icons/buymeacoffee.tsx b/apps/landing/app/components/shared/icons/buymeacoffee.tsx similarity index 100% rename from apps/landing/components/shared/icons/buymeacoffee.tsx rename to apps/landing/app/components/shared/icons/buymeacoffee.tsx diff --git a/apps/landing/components/shared/icons/expanding-arrow.tsx b/apps/landing/app/components/shared/icons/expanding-arrow.tsx similarity index 100% rename from apps/landing/components/shared/icons/expanding-arrow.tsx rename to apps/landing/app/components/shared/icons/expanding-arrow.tsx diff --git a/apps/landing/components/shared/icons/github.tsx b/apps/landing/app/components/shared/icons/github.tsx similarity index 100% rename from apps/landing/components/shared/icons/github.tsx rename to apps/landing/app/components/shared/icons/github.tsx diff --git a/apps/landing/components/shared/icons/google.tsx b/apps/landing/app/components/shared/icons/google.tsx similarity index 100% rename from apps/landing/components/shared/icons/google.tsx rename to apps/landing/app/components/shared/icons/google.tsx diff --git a/apps/landing/components/shared/icons/index.tsx b/apps/landing/app/components/shared/icons/index.tsx similarity index 100% rename from apps/landing/components/shared/icons/index.tsx rename to apps/landing/app/components/shared/icons/index.tsx diff --git a/apps/landing/components/shared/icons/loading-circle.tsx b/apps/landing/app/components/shared/icons/loading-circle.tsx similarity index 100% rename from apps/landing/components/shared/icons/loading-circle.tsx rename to apps/landing/app/components/shared/icons/loading-circle.tsx diff --git a/apps/landing/components/shared/icons/loading-dots.module.css b/apps/landing/app/components/shared/icons/loading-dots.module.css similarity index 100% rename from apps/landing/components/shared/icons/loading-dots.module.css rename to apps/landing/app/components/shared/icons/loading-dots.module.css diff --git a/apps/landing/components/shared/icons/loading-dots.tsx b/apps/landing/app/components/shared/icons/loading-dots.tsx similarity index 100% rename from apps/landing/components/shared/icons/loading-dots.tsx rename to apps/landing/app/components/shared/icons/loading-dots.tsx diff --git a/apps/landing/components/shared/icons/loading-spinner.module.css b/apps/landing/app/components/shared/icons/loading-spinner.module.css similarity index 100% rename from apps/landing/components/shared/icons/loading-spinner.module.css rename to apps/landing/app/components/shared/icons/loading-spinner.module.css diff --git a/apps/landing/components/shared/icons/loading-spinner.tsx b/apps/landing/app/components/shared/icons/loading-spinner.tsx similarity index 100% rename from apps/landing/components/shared/icons/loading-spinner.tsx rename to apps/landing/app/components/shared/icons/loading-spinner.tsx diff --git a/apps/landing/components/shared/icons/twitter.tsx b/apps/landing/app/components/shared/icons/twitter.tsx similarity index 100% rename from apps/landing/components/shared/icons/twitter.tsx rename to apps/landing/app/components/shared/icons/twitter.tsx diff --git a/apps/landing/components/shared/modal.tsx b/apps/landing/app/components/shared/modal.tsx similarity index 97% rename from apps/landing/components/shared/modal.tsx rename to apps/landing/app/components/shared/modal.tsx index 6b0f5f8..79b0f4f 100644 --- a/apps/landing/components/shared/modal.tsx +++ b/apps/landing/app/components/shared/modal.tsx @@ -1,7 +1,7 @@ "use client"; import { Dispatch, SetStateAction } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@makify/ui/lib/utils"; import { Drawer } from "vaul"; import * as Dialog from "@radix-ui/react-dialog"; import useMediaQuery from "@/lib/hooks/use-media-query"; diff --git a/apps/landing/components/shared/popover.tsx b/apps/landing/app/components/shared/popover.tsx similarity index 100% rename from apps/landing/components/shared/popover.tsx rename to apps/landing/app/components/shared/popover.tsx diff --git a/apps/landing/app/components/shared/text-morph.tsx b/apps/landing/app/components/shared/text-morph.tsx new file mode 100644 index 0000000..19e6da2 --- /dev/null +++ b/apps/landing/app/components/shared/text-morph.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { cn } from "@makify/ui/lib/utils"; +import { AnimatePresence, motion } from "framer-motion"; +import { useMemo, useId } from "react"; + +type TextMorphProps = { + children: string; + as?: React.ElementType; + className?: string; + style?: React.CSSProperties; +}; + +export function TextMorph({ + children, + as: Component = "p", + className, + style, +}: TextMorphProps) { + const uniqueId = useId(); + + const characters = useMemo(() => { + const charCounts: Record = {}; + + return children.split("").map((char, index) => { + const lowerChar = char.toLowerCase(); + charCounts[lowerChar] = (charCounts[lowerChar] || 0) + 1; + + return { + id: `${uniqueId}-${lowerChar}${charCounts[lowerChar]}`, + label: index === 0 ? char.toUpperCase() : lowerChar, + }; + }); + }, [children, uniqueId]); + + return ( + + + {characters.map((character) => ( + + ))} + + + ); +} diff --git a/apps/landing/app/components/shared/text-rotate.tsx b/apps/landing/app/components/shared/text-rotate.tsx new file mode 100644 index 0000000..667b193 --- /dev/null +++ b/apps/landing/app/components/shared/text-rotate.tsx @@ -0,0 +1,35 @@ +import { cn } from "@makify/ui/lib/utils"; +import { AnimatePresence, motion } from "framer-motion"; + +type TextRotateProps = { + children: string; + as?: React.ElementType; + className?: string; + style?: React.CSSProperties; +}; + +export const TextRotate = ({ + children, + as: Component = "span", + className, + style, +}: TextRotateProps) => { + return ( + + {children} + + + {children} + + + + ); +}; diff --git a/apps/landing/app/components/shared/text-scramble.tsx b/apps/landing/app/components/shared/text-scramble.tsx new file mode 100644 index 0000000..3f8d290 --- /dev/null +++ b/apps/landing/app/components/shared/text-scramble.tsx @@ -0,0 +1,51 @@ +import { cn } from "@makify/ui/lib/utils"; +import React, { useState, useEffect } from "react"; + +type TextScrambleProps = { + children: string; + as?: React.ElementType; + className?: string; + style?: React.CSSProperties; +}; + +export function TextScramble({ + children, + as: Component = "p", + className, + style, +}: TextScrambleProps) { + const [text, setText] = useState(""); + const finalText = children; + const chars = "!<>-_\\/[]{}—=+*^?#________"; + + useEffect(() => { + let iteration = 0; + const interval = setInterval(() => { + setText((prevText) => + finalText + .split("") + .map((letter, index) => { + if (index < iteration) { + return finalText[index]; + } + return chars[Math.floor(Math.random() * chars.length)]; + }) + .join(""), + ); + + if (iteration >= finalText.length) { + clearInterval(interval); + } + + iteration += 1 / 3; + }, 30); + + return () => clearInterval(interval); + }, [children]); + + return ( + + {text} + + ); +} diff --git a/apps/landing/components/shared/tooltip.tsx b/apps/landing/app/components/shared/tooltip.tsx similarity index 100% rename from apps/landing/components/shared/tooltip.tsx rename to apps/landing/app/components/shared/tooltip.tsx diff --git a/apps/landing/app/favicon.ico b/apps/landing/app/favicon.ico deleted file mode 100644 index 7afc8d3a2d323a24413c035512e43f51f895f12f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5152 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 + -