From 24bececdc01c4318ca1025138746c5b24aa5c165 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 19 May 2024 19:45:49 -0500 Subject: [PATCH] =?UTF-8?q?landing=20page=20complete=20=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-v2/components.json | 17 ++ apps/web-v2/env.d.ts | 45 ++++- apps/web-v2/package.json | 8 +- apps/web-v2/src/app/(landing)/Cta.tsx | 44 ++++ apps/web-v2/src/app/(landing)/EmailInput.tsx | 80 +++++++- apps/web-v2/src/app/(landing)/Features.tsx | 111 +++++----- apps/web-v2/src/app/(landing)/Hero.tsx | 67 ++++++ apps/web-v2/src/app/(landing)/Navbar.tsx | 37 ++++ .../src/app/(landing)/RotatingIcons.tsx | 104 +++++----- .../src/app/(landing)/formSubmitAction.ts | 47 +++++ apps/web-v2/src/app/(landing)/page.tsx | 113 ++--------- apps/web-v2/src/app/globals.css | 85 +++++++- apps/web-v2/src/app/layout.tsx | 2 + apps/web-v2/src/components/ui/toast.tsx | 129 ++++++++++++ apps/web-v2/src/components/ui/toaster.tsx | 35 ++++ apps/web-v2/src/components/ui/use-toast.ts | 191 ++++++++++++++++++ apps/web-v2/src/lib/utils.ts | 6 + apps/web-v2/src/utils/icons.tsx | 16 ++ apps/web-v2/tailwind.config.ts | 75 ++++++- apps/web-v2/wrangler.toml | 7 + package.json | 2 +- 21 files changed, 1004 insertions(+), 217 deletions(-) create mode 100644 apps/web-v2/components.json create mode 100644 apps/web-v2/src/app/(landing)/Cta.tsx create mode 100644 apps/web-v2/src/app/(landing)/Hero.tsx create mode 100644 apps/web-v2/src/app/(landing)/Navbar.tsx create mode 100644 apps/web-v2/src/app/(landing)/formSubmitAction.ts create mode 100644 apps/web-v2/src/components/ui/toast.tsx create mode 100644 apps/web-v2/src/components/ui/toaster.tsx create mode 100644 apps/web-v2/src/components/ui/use-toast.ts create mode 100644 apps/web-v2/src/lib/utils.ts diff --git a/apps/web-v2/components.json b/apps/web-v2/components.json new file mode 100644 index 00000000..268ae38d --- /dev/null +++ b/apps/web-v2/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/apps/web-v2/env.d.ts b/apps/web-v2/env.d.ts index a0e1d952..ac17de2c 100644 --- a/apps/web-v2/env.d.ts +++ b/apps/web-v2/env.d.ts @@ -1,4 +1,47 @@ // Generated by Wrangler // by running `wrangler types --env-interface CloudflareEnv env.d.ts` -interface CloudflareEnv {} +interface CloudflareEnv { + RATELIMITER: RateLimitBinding; +} + +export interface RateLimitBinding { + limit: LimitFunc; +} + +export interface LimitFunc { + (options: LimitOptions): Promise; +} + +interface RateLimitResult { + success: boolean; +} + +export interface LimitOptions { + key: string; +} + +export interface RateLimitResponse { + key: string; + success: boolean; +} + +export interface RateLimitOptions { + continueOnRateLimit: boolean; +} + +export type RateLimitKeyFunc = { + (c: Context): string; +}; + +declare global { + namespace NodeJS { + interface ProcessEnv { + // [key: string]: string | undefined; + RATELIMITER: RateLimitBinding; + CLOUDFLARE_TURNSTILE_TOKEN: string; + } + } +} + +export {}; diff --git a/apps/web-v2/package.json b/apps/web-v2/package.json index 4910e3b3..69a39888 100644 --- a/apps/web-v2/package.json +++ b/apps/web-v2/package.json @@ -13,9 +13,15 @@ "cf-typegen": "wrangler types --env-interface CloudflareEnv env.d.ts" }, "dependencies": { + "@radix-ui/react-toast": "^1.1.5", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.378.0", + "next": "14.1.0", "react": "^18", "react-dom": "^18", - "next": "14.1.0" + "tailwind-merge": "^2.3.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@cloudflare/next-on-pages": "1", diff --git a/apps/web-v2/src/app/(landing)/Cta.tsx b/apps/web-v2/src/app/(landing)/Cta.tsx new file mode 100644 index 00000000..0024fbf0 --- /dev/null +++ b/apps/web-v2/src/app/(landing)/Cta.tsx @@ -0,0 +1,44 @@ +import Image from "next/image"; +import React from "react"; +import EmailInput from "./EmailInput"; + +function Cta() { + return ( +
+
+ {/* a blue gradient line that's slightly tilted with blur (a lotof blur)*/} +
+
+
+
+ Landing page background +

+ Your bookmarks are collecting dust. +

+
+ Launching July 1st, 2024 +
+

+ Time to change that.
Sign up for the waitlist and be the first to + try Supermemory +

+ +
+ ); +} + +export default Cta; diff --git a/apps/web-v2/src/app/(landing)/EmailInput.tsx b/apps/web-v2/src/app/(landing)/EmailInput.tsx index 5dd51678..2adfa922 100644 --- a/apps/web-v2/src/app/(landing)/EmailInput.tsx +++ b/apps/web-v2/src/app/(landing)/EmailInput.tsx @@ -1,15 +1,77 @@ -import React from "react"; +"use client"; + +import { FormEvent, useState } from "react"; +import formSubmitAction from "./formSubmitAction"; +import { useToast } from "@/components/ui/use-toast"; function EmailInput() { + const [email, setEmail] = useState(""); + const { toast } = useToast(); + return ( -
- -
+
) => { + e.preventDefault(); + + const value = await formSubmitAction(email, "token" as string); + + if (value.success) { + setEmail(""); + toast({ + title: "You are now on the waitlist! 🎉", + description: + "We will notify you when we launch. Check your inbox and spam folder for a surprise! 🎁", + }); + } else { + console.error("email submission failed: ", value.value); + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: `${value.value}`, + }); + } + }} + className="flex w-full items-center justify-center gap-2" + > +
+ setEmail(e.target.value)} + /> +
+
+ {email && ( + + )} +
); } diff --git a/apps/web-v2/src/app/(landing)/Features.tsx b/apps/web-v2/src/app/(landing)/Features.tsx index 93e6f725..24b1a228 100644 --- a/apps/web-v2/src/app/(landing)/Features.tsx +++ b/apps/web-v2/src/app/(landing)/Features.tsx @@ -4,6 +4,7 @@ import { useState, useRef, useEffect } from "react"; import { Transition } from "@headlessui/react"; import Image from "next/image"; import CarouselIllustration from "@/../public/images/carousel-illustration-01.png"; +import { X } from "@/utils/icons"; export default function Features() { const [tab, setTab] = useState(1); @@ -23,7 +24,10 @@ export default function Features() {
{/* Carousel */} -
+
{/* Content */}
@@ -62,8 +66,9 @@ export default function Features() { For Researchers
- Save time and keep things consistent with reusable images, - and 3D assets in shared libraries. + Add content to collections and use it as a knowledge base + for your research, link multiple sources together to get a + better understanding of the topic.
@@ -87,8 +92,8 @@ export default function Features() { For Content writers
- Save time and keep things consistent with reusable images, - and 3D assets in shared libraries. + Save time and use the writing assistant to generate + content based on your own saved collections and sources.
@@ -112,8 +117,8 @@ export default function Features() { For Developers
- Save time and keep things consistent with reusable images, - and 3D assets in shared libraries. + Talk to documentation websites, code snippets, etc. so you + never have to google the same thing a hundred times.
@@ -195,26 +200,22 @@ export default function Features() { {/* Features blocks */} -
+
{/* Block #1 */}
- - - +

- Discussions + Import all your Twitter bookmarks

- Keep workflows efficient with tools that give teams visibility - throughout the process. + Use all the knowledge you've saved on Twitter to train your own + supermemory.

{/* Block #2 */} @@ -229,35 +230,15 @@ export default function Features() {

- Team views + Chat with collections

- Keep workflows efficient with tools that give teams visibility - throughout the process. + Use collections to talk to specific knowledgebases like 'My + twitter bookmarks', or 'Learning web development'

{/* Block #3 */} -
-
- - - -

- Powerful search -

-
-

- Keep workflows efficient with tools that give teams visibility - throughout the process. -

-
- {/* Block #4 */}

- Enhancing + Powerful search

- Keep workflows efficient with tools that give teams visibility - throughout the process. + Look up anything you've saved in your supermemory, and get the + information you need in seconds. +

+
+ {/* Block #4 */} +
+
+ + + +

+ Knowledge canvas +

+
+

+ Arrange your saved information in a way that makes sense to you + in a 2d canvas.

{/* Block #5 */} @@ -289,32 +290,32 @@ export default function Features() {

- Powerful search + Just... bookmarks

- Keep workflows efficient with tools that give teams visibility - throughout the process. + AI is cool, but sometimes you just need a place to save your + stuff. Supermemory is that place.

{/* Block #6 */}
- +

- Team views + Writing assistant

- Keep workflows efficient with tools that give teams visibility - throughout the process. + Use our markdown editor to write content based on your saved + data, with the help of AI.

diff --git a/apps/web-v2/src/app/(landing)/Hero.tsx b/apps/web-v2/src/app/(landing)/Hero.tsx new file mode 100644 index 00000000..0a4284dd --- /dev/null +++ b/apps/web-v2/src/app/(landing)/Hero.tsx @@ -0,0 +1,67 @@ +"use client"; +import React from "react"; +import { motion } from "framer-motion"; +import { Twitter } from "@/utils/icons"; +import EmailInput from "./EmailInput"; + +const slap = { + initial: { + opacity: 0, + scale: 1.1, + }, + whileInView: { opacity: 1, scale: 1 }, + transition: { + duration: 0.5, + ease: "easeInOut", + }, + viewport: { once: true }, +}; + +function Hero() { + return ( + <> +
+ + Follow us on Twitter + + + Build your own second brain with Supermemory + + + Bring saved information from all over the internet into one place + where you can connect it, and ask AI about it + + +
+ + + ); +} + +export default Hero; diff --git a/apps/web-v2/src/app/(landing)/Navbar.tsx b/apps/web-v2/src/app/(landing)/Navbar.tsx new file mode 100644 index 00000000..4c3ea609 --- /dev/null +++ b/apps/web-v2/src/app/(landing)/Navbar.tsx @@ -0,0 +1,37 @@ +import Logo from "@/../public/logo.svg"; +import { Github } from "@/utils/icons"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +function Navbar() { + return ( + + ); +} + +export default Navbar; diff --git a/apps/web-v2/src/app/(landing)/RotatingIcons.tsx b/apps/web-v2/src/app/(landing)/RotatingIcons.tsx index fadf7d25..27d4eaed 100644 --- a/apps/web-v2/src/app/(landing)/RotatingIcons.tsx +++ b/apps/web-v2/src/app/(landing)/RotatingIcons.tsx @@ -24,52 +24,66 @@ const icons = [ const RotatingIcons: React.FC = () => { return ( -
-
- {icons.map((icon, index) => ( - - - {icon} - - - ))} - Supermemory logo +
+

+ Collect data from
{" "} + + any website{" "} + {" "} + on the internet +

+
+
+
+ {icons.map((icon, index) => ( + + + {icon} + + + ))} + Supermemory logo +
+
+

+ ... and bring it into your second brain +

); }; diff --git a/apps/web-v2/src/app/(landing)/formSubmitAction.ts b/apps/web-v2/src/app/(landing)/formSubmitAction.ts new file mode 100644 index 00000000..fa96b943 --- /dev/null +++ b/apps/web-v2/src/app/(landing)/formSubmitAction.ts @@ -0,0 +1,47 @@ +"use server"; +import { headers } from "next/headers"; + +const formSubmitAction = async (email: string, token: string) => { + console.log("email submitted:", email); + const formBody = `email=${encodeURIComponent(email)}`; + const h = await headers(); + const ip = h.get("cf-connecting-ip"); + + if (ip) { + if (process.env.RATELIMITER) { + const { success } = await process.env.RATELIMITER.limit({ + key: `waitlist-${ip}`, + }); + + if (!success) { + console.error("rate limit exceeded"); + return { value: "Rate limit exceeded", success: false }; + } + } else { + console.info("RATELIMITER not found in env"); + } + } else { + console.info("cf-connecting-ip not found in headers"); + } + + const resp = await fetch( + "https://app.loops.so/api/newsletter-form/clwcn8dde0059m6hobbdw2rwe", + { + method: "POST", + body: formBody, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }, + ); + + if (resp.ok) { + console.log("email submitted successfully"); + return { value: await resp.json(), success: true }; + } else { + console.error("email submission failed"); + return { value: await resp.text(), success: false }; + } +}; + +export default formSubmitAction; diff --git a/apps/web-v2/src/app/(landing)/page.tsx b/apps/web-v2/src/app/(landing)/page.tsx index d5dbd566..0f264a62 100644 --- a/apps/web-v2/src/app/(landing)/page.tsx +++ b/apps/web-v2/src/app/(landing)/page.tsx @@ -1,117 +1,46 @@ -import { Github, Twitter } from "@/utils/icons"; -import Image from "next/image"; -import EmailInput from "./EmailInput"; import Features from "./Features"; import RotatingIcons from "./RotatingIcons"; -import Logo from "@/../public/logo.svg"; +import Hero from "./Hero"; +import Navbar from "./Navbar"; +import Cta from "./Cta"; +import { Toaster } from "@/components/ui/toaster"; + +export const runtime = "edge"; export default function Home() { return ( -
-
- -
+
+ + {/* Background gradients */}
-
+
{/* a blue gradient line that's slightly tilted with blur (a lotof blur)*/} -
+
+ {/* Hero section */} -
- - Follow us on Twitter - -

- Build your own second brain with Supermemory -

-

- Bring saved information from all over the internet into one place - where you can connect it, and ask AI about it -

- -
- Landing page background + + + {/* Features section */} -
-

- Collect data from
{" "} - - any website{" "} - {" "} - on the internet -

- -

- ... and bring it into your second brain -

-
+ -
-
- {/* a blue gradient line that's slightly tilted with blur (a lotof blur)*/} -
-
-
-
- Landing page background -

- Your bookmarks are collecting dust. -

-

- Time to change that.
Sign up for the waitlist and be the first - to try Supermemory -

- -
+ + +