diff options
| author | Shreyans Jain <[email protected]> | 2025-09-04 16:50:55 -0700 |
|---|---|---|
| committer | Shreyans Jain <[email protected]> | 2025-09-04 16:50:55 -0700 |
| commit | 593bb2556abe16132e2db70474d859de24d525eb (patch) | |
| tree | 881eddbb4ce88fbd1e197ca7be6e955f6dd5c7db | |
| parent | feat: referrals (diff) | |
| download | supermemory-shreyans/09-03-feat_referrals.tar.xz supermemory-shreyans/09-03-feat_referrals.zip | |
more stuffshreyans/09-03-feat_referrals
| -rw-r--r-- | apps/web/app/ref/[code]/page.tsx | 117 | ||||
| -rw-r--r-- | apps/web/app/ref/page.tsx | 113 | ||||
| -rw-r--r-- | apps/web/middleware.ts | 2 | ||||
| -rw-r--r-- | apps/web/next.config.ts | 68 | ||||
| -rw-r--r-- | bun.lock | 3 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | packages/ui/text/heading.tsx | 63 | ||||
| -rw-r--r-- | packages/ui/text/label.tsx | 68 | ||||
| -rw-r--r-- | packages/ui/text/title.tsx | 65 |
9 files changed, 407 insertions, 93 deletions
diff --git a/apps/web/app/ref/[code]/page.tsx b/apps/web/app/ref/[code]/page.tsx index 8d7bc6fe..9d3b9b87 100644 --- a/apps/web/app/ref/[code]/page.tsx +++ b/apps/web/app/ref/[code]/page.tsx @@ -11,13 +11,20 @@ import { CardTitle, } from "@ui/components/card" import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar" -import { ShareIcon, LoaderIcon } from "lucide-react" +import { ShareIcon, LoaderIcon, ArrowRightIcon } from "lucide-react" import Link from "next/link" -import { useParams } from "next/navigation" +import { useParams, useRouter } from "next/navigation" +import { labelVariants } from "@ui/text/label" +import { cn } from "@lib/utils" +import { headingVariants } from "@ui/text/heading" +import { titleVariants } from "@ui/text/title" +import { useSession } from "@lib/auth" export default function ReferralInvitePage() { const params = useParams() const code = params.code as string + const router = useRouter() + const session = useSession() const { data: referrerData, @@ -48,6 +55,10 @@ export default function ReferralInvitePage() { retry: 1, }) + if (session.data) { + router.push("/") + } + const referrer = referrerData?.referrer if (isLoading) { @@ -55,7 +66,13 @@ export default function ReferralInvitePage() { <div className="min-h-screen flex items-center justify-center p-4 bg-[#0f1419]"> <div className="flex flex-col items-center gap-4"> <LoaderIcon className="w-8 h-8 text-orange-500 animate-spin" /> - <p className="text-white/60">Loading invitation...</p> + <p + className={cn( + labelVariants({ level: 1, weight: "regular", color: "muted" }), + )} + > + Loading invitation... + </p> </div> </div> ) @@ -64,15 +81,22 @@ export default function ReferralInvitePage() { if (error || !code) { return ( <div className="min-h-screen flex items-center justify-center p-4 bg-[#0f1419]"> - <Card className="max-w-md w-full bg-[#1a1f2a] border-white/10"> + <Card className="max-w-md w-full bg-sm-shark"> <CardHeader className="text-center"> <div className="mx-auto mb-4 w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center"> <ShareIcon className="w-8 h-8 text-red-500" /> </div> - <CardTitle className="text-2xl font-bold text-white"> + <CardTitle + className={cn(titleVariants({ level: 2, weight: "bold" }))} + > Invalid Referral Link </CardTitle> - <CardDescription className="text-white/60 mt-2"> + <CardDescription + className={cn( + labelVariants({ level: 1, weight: "regular", color: "muted" }), + "mt-2", + )} + > {error instanceof Error ? error.message : "Invalid referral code"} </CardDescription> </CardHeader> @@ -80,10 +104,17 @@ export default function ReferralInvitePage() { <div className="space-y-4"> <div className="text-center"> <Link - className="text-orange-500 hover:text-orange-400 text-sm underline" + className={cn( + labelVariants({ + level: 1, + weight: "regular", + color: "default", + }), + "underline underline-offset-2 hover:opacity-50 transition-all", + )} href="https://supermemory.ai" > - Learn more about supermemory + Learn more </Link> </div> </div> @@ -95,7 +126,7 @@ export default function ReferralInvitePage() { return ( <div className="min-h-screen flex items-center justify-center p-4 bg-[#0f1419]"> - <Card className="max-w-md w-full bg-[#1a1f2a] border-white/10"> + <Card className="max-w-md w-full bg-sm-shark"> <CardHeader className="text-center"> <div className="mx-auto mb-4"> <Avatar className="w-20 h-20"> @@ -105,10 +136,17 @@ export default function ReferralInvitePage() { </AvatarFallback> </Avatar> </div> - <CardTitle className="text-2xl font-bold text-white"> + <CardTitle + className={cn(titleVariants({ level: 2, weight: "bold" }))} + > You've been invited! </CardTitle> - <CardDescription className="text-white/60 mt-2"> + <CardDescription + className={cn( + labelVariants({ level: 1, weight: "regular", color: "muted" }), + "mt-2", + )} + > <span className="text-orange-400 font-semibold"> {referrer?.name} </span>{" "} @@ -117,29 +155,66 @@ export default function ReferralInvitePage() { </CardHeader> <CardContent> <div className="space-y-4"> - <div className="bg-[#0f1419] rounded-lg p-4 border border-white/10"> - <h3 className="text-white font-semibold mb-2"> + <div + className={ + (cn( + headingVariants({ + level: "h2", + weight: "medium", + }), + ), + "text-white text-center") + } + > + You and {referrer?.name} both get a free month of{" "} + <b>supermemory pro</b> + </div> + <div className="bg-sm-shark rounded-lg p-4 border border-white/10"> + <h3 + className={cn(headingVariants({ level: "h3", weight: "bold" }))} + > What is supermemory? </h3> - <p className="text-white/70 text-sm leading-relaxed"> + <p + className={cn( + labelVariants({ + level: 1, + weight: "regular", + color: "muted", + }), + )} + > supermemory is an AI-powered personal knowledge base that helps you store, organize, and interact with all your digital memories. </p> </div> - <Link href={`/login?ref=${code}`} className="block"> - <Button className="w-full bg-orange-500 hover:bg-orange-600 text-white"> - Get Started - </Button> - </Link> + <Button + className={cn( + labelVariants({ level: 1, weight: "medium" }), + "w-full", + )} + asChild + > + <Link href={`/login?ref=${code}`}> + Get Started <ArrowRightIcon className="w-4 h-4" /> + </Link> + </Button> <div className="text-center"> <Link - className="text-orange-500 hover:text-orange-400 text-sm underline" + className={cn( + labelVariants({ + level: 1, + weight: "regular", + color: "default", + }), + "underline underline-offset-2 hover:opacity-50 transition-all", + )} href="https://supermemory.ai" > - Learn more about supermemory + Learn more </Link> </div> </div> diff --git a/apps/web/app/ref/page.tsx b/apps/web/app/ref/page.tsx index 78373693..ed10d0aa 100644 --- a/apps/web/app/ref/page.tsx +++ b/apps/web/app/ref/page.tsx @@ -1,56 +1,95 @@ -"use client"; +"use client" -import { Button } from "@ui/components/button"; +import { Button } from "@ui/components/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "@ui/components/card"; -import { ShareIcon } from "lucide-react"; -import Link from "next/link"; +} from "@ui/components/card" +import { ArrowRightIcon, HeartIcon, ShareIcon } from "lucide-react" +import Link from "next/link" +import { labelVariants } from "@ui/text/label" +import { cn } from "@lib/utils" +import { headingVariants } from "@ui/text/heading" +import { titleVariants } from "@ui/text/title" export default function ReferralHomePage() { return ( <div className="min-h-screen flex items-center justify-center p-4 bg-[#0f1419]"> - <Card className="max-w-md w-full bg-[#1a1f2a] border-white/10"> - <CardHeader className="text-center"> - <div className="mx-auto mb-4 w-16 h-16 rounded-full bg-orange-500/10 flex items-center justify-center"> - <ShareIcon className="w-8 h-8 text-orange-500" /> + <Card className="max-w-md w-full bg-sm-shark"> + <CardHeader className="flex flex-col items-center gap-4"> + <div className="mx-auto size-16 rounded-full bg-black/40 flex items-center justify-center"> + <HeartIcon className="h-full" /> + </div> + <div className="flex flex-col items-center gap-1"> + <CardTitle + className={cn(titleVariants({ level: 3, weight: "bold" }))} + > + Missing Referral Code? + </CardTitle> + <CardDescription + className={cn( + labelVariants({ + level: 1, + weight: "regular", + color: "muted", + }), + "text-center", + )} + > + It looks like you're missing a referral code. Get one from a + friend or join directly! + </CardDescription> </div> - <CardTitle className="text-2xl font-bold text-white"> - Missing Referral Code - </CardTitle> - <CardDescription className="text-white/60 mt-2"> - It looks like you're missing a referral code. Get one from a friend - or join directly! - </CardDescription> </CardHeader> - <CardContent> - <div className="space-y-4"> - <div className="bg-[#0f1419] rounded-lg p-4 border border-white/10"> - <h3 className="text-white font-semibold mb-2"> - What is supermemory? - </h3> - <p className="text-white/70 text-sm leading-relaxed"> - supermemory is an AI-powered personal knowledge base that helps - you store, organize, and interact with all your digital - memories. - </p> - </div> + <CardContent className="flex flex-col gap-4"> + <div className="bg-sm-shark rounded-lg p-4 border border-white/10"> + <h3 + className={cn(headingVariants({ level: "h3", weight: "bold" }))} + > + What is supermemory? + </h3> + <p + className={cn( + labelVariants({ level: 1, weight: "regular", color: "muted" }), + )} + > + supermemory is an AI-powered personal knowledge base that helps + you store, organize, and interact with all your digital memories. + </p> + </div> + + <Button + className={cn( + labelVariants({ level: 1, weight: "medium" }), + "w-full", + )} + asChild + > + <Link href={"/login"}> + Get Started <ArrowRightIcon className="w-4 h-4" /> + </Link> + </Button> - <div className="text-center"> - <Link - className="text-orange-500 hover:text-orange-400 text-sm underline" - href="https://supermemory.ai" - > - Learn more about supermemory - </Link> - </div> + <div className="text-center"> + <Link + className={cn( + labelVariants({ + level: 1, + weight: "regular", + color: "default", + }), + "underline underline-offset-2 hover:opacity-50 transition-all", + )} + href="https://supermemory.ai" + > + Learn more + </Link> </div> </CardContent> </Card> </div> - ); + ) } diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 9862462d..59a3e856 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -47,6 +47,6 @@ export default async function middleware(request: Request) { export const config = { matcher: [ - "/((?!_next/static|_next/image|images|icon.png|monitoring|opengraph-image.png|ingest|api|login|api/emails).*)", + "/((?!_next/static|_next/image|images|icon.png|monitoring|opengraph-image.png|ingest|api|login|api/emails|ref).*)", ], } diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index a6ee01f3..5b9db494 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,63 +1,63 @@ -import { withSentryConfig } from "@sentry/nextjs"; -import type { NextConfig } from "next"; +import { withSentryConfig } from "@sentry/nextjs" +import type { NextConfig } from "next" const nextConfig: NextConfig = { - experimental: { - reactCompiler: true, - viewTransition: true, - }, - eslint: { - ignoreDuringBuilds: true, - }, - poweredByHeader: false, - async rewrites() { - return [ - { - source: "/ingest/static/:path*", - destination: "https://us-assets.i.posthog.com/static/:path*", - }, - { - source: "/ingest/:path*", - destination: "https://us.i.posthog.com/:path*", - }, - ]; - }, - skipTrailingSlashRedirect: true, -}; + experimental: { + reactCompiler: true, + viewTransition: true, + }, + eslint: { + ignoreDuringBuilds: true, + }, + poweredByHeader: false, + async rewrites() { + return [ + { + source: "/ingest/static/:path*", + destination: "https://us-assets.i.posthog.com/static/:path*", + }, + { + source: "/ingest/:path*", + destination: "https://us.i.posthog.com/:path*", + }, + ] + }, + skipTrailingSlashRedirect: true, +} export default withSentryConfig(nextConfig, { - // For all available options, see: + // For all available options, see: // https://www.npmjs.com/package/@sentry/webpack-plugin#options org: "supermemory", - project: "consumer-app", + project: "consumer-app", - // Only print logs for uploading source maps in CI + // Only print logs for uploading source maps in CI silent: !process.env.CI, - // For all available options, see: + // For all available options, see: // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ // Upload a larger set of source maps for prettier stack traces (increases build time) widenClientFileUpload: true, - // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. + // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. // This can increase your server load as well as your hosting bill. // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- // side errors will fail. tunnelRoute: "/monitoring", - // Automatically tree-shake Sentry logger statements to reduce bundle size + // Automatically tree-shake Sentry logger statements to reduce bundle size disableLogger: true, - // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.) + // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.) // See the following for more information: // https://docs.sentry.io/product/crons/ // https://vercel.com/docs/cron-jobs automaticVercelMonitors: true, -}); +}) -import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; +import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare" -initOpenNextCloudflareForDev();
\ No newline at end of file +initOpenNextCloudflareForDev() @@ -20,6 +20,7 @@ "boxen": "^8.0.1", "cloudflare": "^4.5.0", "compromise": "^14.14.4", + "cva": "^1.0.0-beta.4", "dedent": "^1.6.0", "destr": "^2.0.5", "drizzle-orm": "^0.44.3", @@ -2002,6 +2003,8 @@ "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "cva": ["[email protected]", "", { "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { "typescript": ">= 4.5.5" }, "optionalPeers": ["typescript"] }, "sha512-F/JS9hScapq4DBVQXcK85l9U91M6ePeXoBMSp7vypzShoefUBxjQTo3g3935PUHgQd+IW77DjbPRIxugy4/GCQ=="], + "cytoscape": ["[email protected]", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], "cytoscape-cose-bilkent": ["[email protected]", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], diff --git a/package.json b/package.json index a7cc83e7..e8e5a70b 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "boxen": "^8.0.1", "cloudflare": "^4.5.0", "compromise": "^14.14.4", + "cva": "^1.0.0-beta.4", "dedent": "^1.6.0", "destr": "^2.0.5", "drizzle-orm": "^0.44.3", diff --git a/packages/ui/text/heading.tsx b/packages/ui/text/heading.tsx new file mode 100644 index 00000000..04324421 --- /dev/null +++ b/packages/ui/text/heading.tsx @@ -0,0 +1,63 @@ +import { Root } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "cva" +import { cn } from "../../lib/utils" + +export const headingVariants = cva({ + base: "tracking-[-0.4px]", + variants: { + level: { + h1: "text-sm sm:text-base md:text-lg lg:text-xl leading-[32px]", + h2: "text-xs sm:text-sm md:text-base lg:text-lg leading-[30px]", + h3: "text-[0.625rem] sm:text-xs md:text-sm lg:text-base leading-[28px]", + h4: "text-[0.5rem] sm:text-[0.625rem] md:text-xs lg:text-sm leading-[24px]", + }, + weight: { + bold: "font-bold", + medium: "font-medium", + }, + }, + defaultVariants: { + level: "h1", + weight: "medium", + }, +}) + +export interface HeadingProps + extends React.HTMLAttributes<HTMLHeadingElement>, + VariantProps<typeof headingVariants> { + asChild?: boolean +} + +export function Heading({ + className, + level = "h1", + weight = "medium", + asChild, + ...props +}: HeadingProps) { + const Comp = asChild ? Root : level + return ( + <Comp + className={cn(headingVariants({ level, weight }), className)} + {...props} + /> + ) +} + +// Export individual variant classes for compatibility +export const headingH1Bold = () => + headingVariants({ level: "h1", weight: "bold" }) +export const headingH1Medium = () => + headingVariants({ level: "h1", weight: "medium" }) +export const headingH2Bold = () => + headingVariants({ level: "h2", weight: "bold" }) +export const headingH2Medium = () => + headingVariants({ level: "h2", weight: "medium" }) +export const headingH3Bold = () => + headingVariants({ level: "h3", weight: "bold" }) +export const headingH3Medium = () => + headingVariants({ level: "h3", weight: "medium" }) +export const headingH4Bold = () => + headingVariants({ level: "h4", weight: "bold" }) +export const headingH4Medium = () => + headingVariants({ level: "h4", weight: "medium" }) diff --git a/packages/ui/text/label.tsx b/packages/ui/text/label.tsx new file mode 100644 index 00000000..5c20b12d --- /dev/null +++ b/packages/ui/text/label.tsx @@ -0,0 +1,68 @@ +import { Root } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "cva" +import { cn } from "../../lib/utils" + +export const labelVariants = cva({ + base: "tracking-[-0.4px]", + variants: { + level: { + 1: "text-[0.875rem] md:text-[1rem] leading-[1.5rem]", + 2: "text-[0.25rem] sm:text-[0.375rem] md:text-[0.5rem] lg:text-[0.625rem] leading-[18px]", + 3: "text-[0.125rem] sm:text-[0.25rem] md:text-[0.375rem] lg:text-[0.5rem] leading-[16px] tracking-[-0.2px]", + }, + weight: { + medium: "font-medium", + regular: "font-normal", + }, + color: { + default: "", + muted: "text-sm-silver-chalice", + }, + }, + compoundVariants: [ + { + level: [2, 3], + color: "default", + class: "text-sm-silver-chalice", + }, + ], + defaultVariants: { + level: 1, + weight: "regular", + color: "default", + }, +}) + +export interface LabelProps + extends Omit<React.HTMLAttributes<HTMLParagraphElement>, "color">, + VariantProps<typeof labelVariants> { + asChild?: boolean +} + +export function Label({ + className, + level = 1, + weight = "regular", + color = "default", + asChild, + ...props +}: LabelProps) { + const Comp = asChild ? Root : "p" + return ( + <Comp + className={cn(labelVariants({ level, weight, color }), className)} + {...props} + /> + ) +} + +// Export individual variant classes for compatibility +export const label1Medium = () => labelVariants({ level: 1, weight: "medium" }) +export const label1Regular = () => + labelVariants({ level: 1, weight: "regular" }) +export const label2Medium = () => labelVariants({ level: 2, weight: "medium" }) +export const label2Regular = () => + labelVariants({ level: 2, weight: "regular" }) +export const label3Medium = () => labelVariants({ level: 3, weight: "medium" }) +export const label3Regular = () => + labelVariants({ level: 3, weight: "regular" }) diff --git a/packages/ui/text/title.tsx b/packages/ui/text/title.tsx new file mode 100644 index 00000000..5a6c5f5a --- /dev/null +++ b/packages/ui/text/title.tsx @@ -0,0 +1,65 @@ +import { Root } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "cva" +import { cn } from "../../lib/utils" + +export const titleVariants = cva({ + base: "tracking-[-0.4px]", + variants: { + level: { + 1: "text-xl sm:text-2xl md:text-3xl lg:text-4xl leading-[70px] tracking-[-0.8px]", + 2: "text-lg sm:text-xl md:text-2xl lg:text-3xl leading-[48px]", + 3: "text-base sm:text-lg md:text-xl lg:text-2xl leading-[40px]", + }, + weight: { + bold: "font-bold", + medium: "font-medium", + }, + }, + compoundVariants: [ + { + level: 2, + weight: "medium", + class: "leading-[32px] md:leading-[48px]", + }, + ], + defaultVariants: { + level: 1, + weight: "medium", + }, +}) + +export interface TitleProps + extends React.HTMLAttributes<HTMLHeadingElement>, + VariantProps<typeof titleVariants> { + asChild?: boolean +} + +export function Title({ + className, + level = 1, + weight = "medium", + asChild, + ...props +}: TitleProps) { + const levelMap = { + 1: "h1", + 2: "h2", + 3: "h3", + } as const + + const Comp = asChild ? Root : levelMap[level] + return ( + <Comp + className={cn(titleVariants({ level, weight }), className)} + {...props} + /> + ) +} + +// Export individual variant classes for compatibility +export const title1Bold = () => titleVariants({ level: 1, weight: "bold" }) +export const title1Medium = () => titleVariants({ level: 1, weight: "medium" }) +export const title2Bold = () => titleVariants({ level: 2, weight: "bold" }) +export const title2Medium = () => titleVariants({ level: 2, weight: "medium" }) +export const title3Bold = () => titleVariants({ level: 3, weight: "bold" }) +export const title3Medium = () => titleVariants({ level: 3, weight: "medium" }) |