From ded9e0e049b548689eee49a5815fa7c072b94303 Mon Sep 17 00:00:00 2001 From: Storm <152523296+storm1729@users.noreply.github.com> Date: Thu, 12 Dec 2024 22:59:29 +0100 Subject: [PATCH] feat: Add Commercial License Trial (#489) * SimpleAnalytics * Signup * Add signup more info * add support for /v1/trial --- src/app/[lang]/dashboard/Dashboard.tsx | 21 ++-- .../[lang]/dashboard/StripeManageButton.tsx | 2 +- .../[lang]/dashboard/SubscriptionHeader.tsx | 2 +- src/app/[lang]/dashboard/Tabs.tsx | 41 ++++--- .../GetStartedCommercial.tsx | 5 +- .../dashboard/commercial_license/page.tsx | 2 +- .../dashboard/verify/GetStartedSaaS.tsx | 2 +- src/app/[lang]/pricing/Plans.tsx | 4 + src/app/[lang]/pricing/ProductCard.tsx | 8 +- src/app/[lang]/signup/Signup.tsx | 115 +++++++++++++++++- src/app/api/v1/bulk/webhook/route.ts | 2 +- .../api/v1/commercial_license_trial/route.ts | 68 +++++++++++ src/components/Nav/Nav.tsx | 4 +- src/dictionaries/en.json | 16 ++- src/dictionaries/fr.json | 16 ++- .../20241211221313_error_column.sql | 2 + .../20241211222608_sub_and_calls_trial.sql | 37 ++++++ 17 files changed, 301 insertions(+), 46 deletions(-) create mode 100644 src/app/api/v1/commercial_license_trial/route.ts create mode 100644 supabase/migrations/20241211221313_error_column.sql create mode 100644 supabase/migrations/20241211222608_sub_and_calls_trial.sql diff --git a/src/app/[lang]/dashboard/Dashboard.tsx b/src/app/[lang]/dashboard/Dashboard.tsx index 0cd93767..4f5bbcc2 100644 --- a/src/app/[lang]/dashboard/Dashboard.tsx +++ b/src/app/[lang]/dashboard/Dashboard.tsx @@ -5,7 +5,6 @@ import { Tabs, TabsProps } from "./Tabs"; import { SAAS_10K_PRODUCT_ID } from "@/util/subs"; import { SubscriptionHeader } from "./SubscriptionHeader"; import { Dictionary } from "@/dictionaries"; -import { ENABLE_BULK } from "@/util/helpers"; import { Tables } from "@/supabase/database.types"; interface DashboardProps { @@ -13,7 +12,7 @@ interface DashboardProps { d: Dictionary; showApiUsage?: boolean; subAndCalls: Tables<"sub_and_calls">; - tab: TabsProps["tab"] | false; + tab: TabsProps["tab"]; } export function Dashboard({ children, @@ -32,16 +31,14 @@ export function Dashboard({ )} - {tab !== false && ENABLE_BULK && ( - - )} + {children} ); diff --git a/src/app/[lang]/dashboard/StripeManageButton.tsx b/src/app/[lang]/dashboard/StripeManageButton.tsx index e857fc07..9ef7dd66 100644 --- a/src/app/[lang]/dashboard/StripeManageButton.tsx +++ b/src/app/[lang]/dashboard/StripeManageButton.tsx @@ -45,7 +45,7 @@ export function StripeMananageButton({ onClick={() => { redirectToCustomerPortal().catch(sentryException); }} - data-sa-link-event="dashboard:stripe-billing:click" + data-sa-link-event="dashboard_stripebilling_click" > {loading ? "Redirecting to Stripe..." : children} diff --git a/src/app/[lang]/dashboard/SubscriptionHeader.tsx b/src/app/[lang]/dashboard/SubscriptionHeader.tsx index 2ab365a6..cbd72dde 100644 --- a/src/app/[lang]/dashboard/SubscriptionHeader.tsx +++ b/src/app/[lang]/dashboard/SubscriptionHeader.tsx @@ -67,7 +67,7 @@ export function SubscriptionHeader({ {d.dashboard.header.upgrade} diff --git a/src/app/[lang]/dashboard/Tabs.tsx b/src/app/[lang]/dashboard/Tabs.tsx index 4a260110..9d59da8a 100644 --- a/src/app/[lang]/dashboard/Tabs.tsx +++ b/src/app/[lang]/dashboard/Tabs.tsx @@ -7,11 +7,13 @@ import Mail from "@geist-ui/react-icons/mail"; import Database from "@geist-ui/react-icons/database"; import Lock from "@geist-ui/react-icons/lock"; import { useRouter } from "next/navigation"; +import { ENABLE_BULK } from "@/util/helpers"; +import { Package } from "@geist-ui/react-icons"; export interface TabsProps { d: Dictionary; bulkDisabled: boolean; - tab: "verify" | "bulk" | "api"; + tab: "verify" | "commercial_license" | "bulk"; } export function Tabs({ bulkDisabled, tab, ...props }: TabsProps) { @@ -33,22 +35,33 @@ export function Tabs({ bulkDisabled, tab, ...props }: TabsProps) { } value="verify" /> + {ENABLE_BULK && ( + + + {d.bulk_locked} + + ) : ( + <> + + {d.bulk} + + ) + } + value="bulk" + /> + )} - - {d.bulk_locked} - - ) : ( - <> - - {d.bulk} - - ) + <> + + {d.commercial_license} + } - value="bulk" + value="commercial_license" /> ); diff --git a/src/app/[lang]/dashboard/commercial_license/GetStartedCommercial.tsx b/src/app/[lang]/dashboard/commercial_license/GetStartedCommercial.tsx index 392c6a42..9b052098 100644 --- a/src/app/[lang]/dashboard/commercial_license/GetStartedCommercial.tsx +++ b/src/app/[lang]/dashboard/commercial_license/GetStartedCommercial.tsx @@ -1,7 +1,7 @@ "use client"; import { Dictionary } from "@/dictionaries"; -import { Card, Text } from "@/components/Geist"; +import { Button, Card, Text } from "@/components/Geist"; import Markdown from "marked-react"; import React from "react"; @@ -13,6 +13,9 @@ export function GetStartedCommercial(props: { d: Dictionary }) { {d.title} {d.explanation} +
+ +
); } diff --git a/src/app/[lang]/dashboard/commercial_license/page.tsx b/src/app/[lang]/dashboard/commercial_license/page.tsx index 6823a6a7..0a46002c 100644 --- a/src/app/[lang]/dashboard/commercial_license/page.tsx +++ b/src/app/[lang]/dashboard/commercial_license/page.tsx @@ -23,7 +23,7 @@ export default async function CommercialLicensePage({ d={d} subAndCalls={subAndCalls} showApiUsage={false} - tab={false} + tab="commercial_license" > diff --git a/src/app/[lang]/dashboard/verify/GetStartedSaaS.tsx b/src/app/[lang]/dashboard/verify/GetStartedSaaS.tsx index c0ddae41..912dab3c 100644 --- a/src/app/[lang]/dashboard/verify/GetStartedSaaS.tsx +++ b/src/app/[lang]/dashboard/verify/GetStartedSaaS.tsx @@ -36,7 +36,7 @@ export function GetStartedSaaS({ const d = props.d.dashboard.get_started_saas; function handleVerify() { - window.sa_event && window.sa_event("dashboard:verify:click"); + window.sa_event && window.sa_event("dashboard_verify_click"); if (!email) { return; } diff --git a/src/app/[lang]/pricing/Plans.tsx b/src/app/[lang]/pricing/Plans.tsx index 134017c8..4518dc6a 100644 --- a/src/app/[lang]/pricing/Plans.tsx +++ b/src/app/[lang]/pricing/Plans.tsx @@ -16,6 +16,7 @@ import type { SubscriptionWithPrice, } from "@/supabase/supabaseServer"; +// Currently the 100K plan per month is disabled. const SHOW_100K = false; interface PlansProps { @@ -61,6 +62,7 @@ export function Plans({ d, products, subscription, isLoggedIn }: PlansProps) { (); @@ -86,10 +88,10 @@ export function ProductCard({ onClick={() => { window.sa_event && window.sa_event( - `pricing:${ + `pricing_${ product.id === COMMERCIAL_LICENSE_PRODUCT_ID ? "commercial" - : "saas" + : "saas10k" }` ); handleCheckout(price).catch(sentryException); @@ -104,7 +106,7 @@ export function ProductCard({ ? d.cards.current_plan : isLoggedIn ? d.cards.select_plan_cta - : d.cards.get_started} + : ctaLabel} ); diff --git a/src/app/[lang]/signup/Signup.tsx b/src/app/[lang]/signup/Signup.tsx index f7d59296..364ce585 100644 --- a/src/app/[lang]/signup/Signup.tsx +++ b/src/app/[lang]/signup/Signup.tsx @@ -1,6 +1,6 @@ "use client"; -import { Input, Select, Spacer, Text } from "@geist-ui/react"; +import { Input, Select, Spacer, Text, useToasts } from "@geist-ui/react"; import HCaptcha from "@hcaptcha/react-hcaptcha"; import React, { useState } from "react"; @@ -16,9 +16,20 @@ import { createClient } from "@/supabase/client"; import { DLink } from "@/components/DLink"; import { getWebappURL } from "@/util/helpers"; +type EMAIL_VOLUME = + | "SAAS_10K" + | "COMMERCIAL_1M" + | "COMMERCIAL_5M" + | "COMMERCIAL_10M" + | "COMMERCIAL_100M" + | "OTHER"; + export default function SignUp(props: { d: Dictionary }): React.ReactElement { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [companyWebsite, setCompanyWebsite] = useState(""); + const [b2bB2c, setB2bB2c] = useState<"B2B" | "B2C" | undefined>(); + const [emailVoume, setEmailVolume] = useState(); const [loading, setLoading] = useState(false); const [message, setMessage] = useState( undefined @@ -28,15 +39,21 @@ export default function SignUp(props: { d: Dictionary }): React.ReactElement { const supabase = createClient(); const d = props.d.signup; + const [, setToast] = useToasts(); const handleSignup = async () => { setLoading(true); setMessage(undefined); - if (email.endsWith("@gmail.com")) { + if ( + email.endsWith("@gmail.com") || + email.endsWith("@yahoo.com") || + email.endsWith("@hotmail.com") || + email.endsWith("@outlook.com") + ) { setMessage({ type: "error", content: - "Gmail addresses are currently disabled due to spam abuse. Please use a company email.", + "Personal addresses are currently disabled due to spam abuse. Please use a company email.", }); setLoading(false); return; @@ -46,13 +63,22 @@ export default function SignUp(props: { d: Dictionary }): React.ReactElement { email, password, options: { - data: feedback ? { heardFrom: feedback } : undefined, + data: { + b2bB2c, + companyWebsite, + heardFrom: feedback, + emailVoume, + }, emailRedirectTo: `${getWebappURL()}/auth/callback`, }, }); if (error) { setMessage({ type: "error", content: error?.message }); } else { + setToast({ + text: d.success_check_email, + type: "success", + }); setMessage({ type: "success", content: d.success_check_email, @@ -84,6 +110,21 @@ export default function SignUp(props: { d: Dictionary }): React.ReactElement { > {d.password} + + + {d.prevent_spam} + + setCompanyWebsite(e.currentTarget.value)} + required + type={message?.type} + width="100%" + > + {d.company_website} + + + {message && } @@ -120,6 +161,68 @@ export default function SignUp(props: { d: Dictionary }): React.ReactElement { ); } +function EmailVolume({ + onChange, + d, +}: { + onChange: (f: EMAIL_VOLUME) => void; + d: Dictionary["signup"]["email_volume"]; +}): React.ReactElement { + const options: { value: EMAIL_VOLUME; label: string }[] = [ + { value: "SAAS_10K", label: `10K ${d.per_month}` }, + { value: "COMMERCIAL_1M", label: `1M ${d.per_month}` }, + { value: "COMMERCIAL_5M", label: `5M ${d.per_month}` }, + { value: "COMMERCIAL_10M", label: `10M ${d.per_month}` }, + { value: "COMMERCIAL_100M", label: `100M ${d.per_month}` }, + { value: "OTHER", label: d.other }, + ]; + + return ( + <> + + {d.title} + + + + ); +} + +function B2BB2C({ + onChange, + d, +}: { + onChange: (f: "B2B" | "B2C") => void; + d: Dictionary["signup"]["b2b_b2c"]; +}): React.ReactElement { + return ( + <> + + {d.title} + + + + ); +} + function Feedback({ onChange, d, @@ -131,7 +234,9 @@ function Feedback({ return ( <> - {d.title} + + {d.title} +