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}
+