From 43563fdb23f4978bfeca442a325f80a1c0148195 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaury1729@users.noreply.github.com> Date: Fri, 15 Dec 2023 19:00:11 +0100 Subject: [PATCH] feat: Remove Free Trial --- src/components/Dashboard.tsx | 51 ++++++++++++---- src/components/Footer.tsx | 2 +- .../GetStarted/GetStartedFreeTrial.tsx | 15 +++++ .../GetStartedLicense.tsx | 0 .../GetStartedSaas.tsx | 0 src/components/GetStarted/index.ts | 3 + src/components/Nav.tsx | 7 +-- src/components/ProductCard/ProductCard.tsx | 2 +- src/components/SubGetStarted/index.tsx | 21 ------- src/components/index.ts | 1 - src/pages/index.tsx | 17 +++++- src/pages/pricing.tsx | 59 ++++++++++++------- src/util/subs.ts | 2 +- src/util/useUser.tsx | 12 ++-- .../migrations/20231215163735_segments.sql | 15 +++++ 15 files changed, 138 insertions(+), 69 deletions(-) create mode 100644 src/components/GetStarted/GetStartedFreeTrial.tsx rename src/components/{SubGetStarted => GetStarted}/GetStartedLicense.tsx (100%) rename src/components/{SubGetStarted => GetStarted}/GetStartedSaas.tsx (100%) create mode 100644 src/components/GetStarted/index.ts delete mode 100644 src/components/SubGetStarted/index.tsx create mode 100644 supabase/migrations/20231215163735_segments.sql diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index efdc684c..e7ef1708 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -2,17 +2,27 @@ import { Link as GLink, Loading, Page, Spacer, Text } from "@geist-ui/react"; import React from "react"; import { StripeMananageButton } from "../components/StripeManageButton"; -import { SubGetStarted } from "../components/SubGetStarted/"; -import { COMMERCIAL_LICENSE_PRODUCT_ID, productName } from "@/util/subs"; +import { + GetStartedFreeTrial, + GetStartedLicense, + GetStartedSaas, +} from "../components/GetStarted"; +import { + COMMERCIAL_LICENSE_PRODUCT_ID, + SAAS_100K_PRODUCT_ID, + SAAS_10K_PRODUCT_ID, + productName, +} from "@/util/subs"; import { useUser } from "@/util/useUser"; import { ApiUsage } from "./ApiUsage"; import styles from "./Dashboard.module.css"; import { formatDate } from "@/util/helpers"; +import { SubscriptionWithPrice } from "@/supabase/domain.types"; export function Dashboard(): React.ReactElement { - const { userDetails, subscription, userFinishedLoading } = useUser(); + const { userDetails, subscription, subscriptionLoaded } = useUser(); - if (!userFinishedLoading) { + if (!subscriptionLoaded) { return ( @@ -26,8 +36,13 @@ export function Dashboard(): React.ReactElement {
Hello{userDetails?.full_name || ""}, - Thanks for using the Reacher{" "} - {productName(subscription?.prices?.products)}! + + {subscription + ? `Thanks for using the Reacher ${productName( + subscription?.prices?.products + )}.` + : "Thank for signing up for Reacher."} + {subscription && ( <> @@ -69,10 +84,26 @@ export function Dashboard(): React.ReactElement { - {subscription?.prices?.product_id !== - COMMERCIAL_LICENSE_PRODUCT_ID && } - - + {showContent(subscription)} ); } + +function showContent( + subscription: SubscriptionWithPrice | null +): React.ReactElement { + switch (subscription?.prices?.product_id) { + case COMMERCIAL_LICENSE_PRODUCT_ID: + return ; + case SAAS_10K_PRODUCT_ID: + case SAAS_100K_PRODUCT_ID: + return ( + <> + + + + ); + default: + return ; + } +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 2f24e5ce..106280d6 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -111,7 +111,7 @@ export function Footer(): React.ReactElement {
- © Reacher 2020-2022,{" "} + © Reacher 2020-2023,{" "} + Select a Plan +

+ Please head to the Pricing Page to + select a plan in order to use Reacher. +

+ + ); +} diff --git a/src/components/SubGetStarted/GetStartedLicense.tsx b/src/components/GetStarted/GetStartedLicense.tsx similarity index 100% rename from src/components/SubGetStarted/GetStartedLicense.tsx rename to src/components/GetStarted/GetStartedLicense.tsx diff --git a/src/components/SubGetStarted/GetStartedSaas.tsx b/src/components/GetStarted/GetStartedSaas.tsx similarity index 100% rename from src/components/SubGetStarted/GetStartedSaas.tsx rename to src/components/GetStarted/GetStartedSaas.tsx diff --git a/src/components/GetStarted/index.ts b/src/components/GetStarted/index.ts new file mode 100644 index 00000000..5d51867b --- /dev/null +++ b/src/components/GetStarted/index.ts @@ -0,0 +1,3 @@ +export * from "./GetStartedLicense"; +export * from "./GetStartedSaas"; +export * from "./GetStartedFreeTrial"; diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx index ab4e2fbc..20d56c96 100644 --- a/src/components/Nav.tsx +++ b/src/components/Nav.tsx @@ -18,10 +18,7 @@ export function Nav(): React.ReactElement { return (
- + Reacher logo diff --git a/src/components/ProductCard/ProductCard.tsx b/src/components/ProductCard/ProductCard.tsx index 149009eb..39d967dc 100644 --- a/src/components/ProductCard/ProductCard.tsx +++ b/src/components/ProductCard/ProductCard.tsx @@ -101,7 +101,7 @@ export function ProductCard({ : active ? "Current Plan" : user - ? "Upgrade Plan" + ? "Select Plan" : "Get Started"} } diff --git a/src/components/SubGetStarted/index.tsx b/src/components/SubGetStarted/index.tsx deleted file mode 100644 index 43995e19..00000000 --- a/src/components/SubGetStarted/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; - -import { COMMERCIAL_LICENSE_PRODUCT_ID } from "@/util/subs"; -import { GetStartedLicense } from "./GetStartedLicense"; -import { GetStartedSaas } from "./GetStartedSaas"; -import { SubscriptionWithPrice } from "@/supabase/domain.types"; - -interface SubGetStartedProps { - subscription: SubscriptionWithPrice | null; // null means Free Trial -} - -export function SubGetStarted({ - subscription, -}: SubGetStartedProps): React.ReactElement { - return subscription?.prices?.products?.id === - COMMERCIAL_LICENSE_PRODUCT_ID ? ( - - ) : ( - - ); -} diff --git a/src/components/index.ts b/src/components/index.ts index b04ceaac..25a0ef36 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,5 +4,4 @@ export * from "./Nav"; export * from "./Footer"; export * from "./Layout"; export * from "./ProductCard"; -export * from "./SubGetStarted"; export * from "./SigninLayout"; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index e72a1727..2c52df61 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -22,7 +22,7 @@ export const getStaticProps: GetStaticProps = async () => { export default function Index(): React.ReactElement { const router = useRouter(); - const { user, userFinishedLoading } = useUser(); + const { user, userFinishedLoading, subscription } = useUser(); const [isRedirecting, setIsRedirecting] = useState(false); const [emailSent, setEmailSent] = useState(false); // Set if we sent an email upon confirmation. Only do it once. @@ -71,9 +71,20 @@ export default function Index(): React.ReactElement { router.replace("/login").catch(sentryException); } else if (userFinishedLoading && user) { setIsRedirecting(true); - router.replace("/dashboard").catch(sentryException); + if (subscription) { + router.replace("/dashboard").catch(sentryException); + } else { + router.replace("/pricing").catch(sentryException); + } } - }, [isRedirecting, router, userFinishedLoading, user, emailSent]); + }, [ + isRedirecting, + router, + userFinishedLoading, + user, + emailSent, + subscription, + ]); return ( <> diff --git a/src/pages/pricing.tsx b/src/pages/pricing.tsx index a4bc6533..44970e88 100644 --- a/src/pages/pricing.tsx +++ b/src/pages/pricing.tsx @@ -12,7 +12,6 @@ import { import { getActiveProductWithPrices } from "@/util/supabaseClient"; import { useUser } from "@/util/useUser"; import { ProductWithPrice } from "@/supabase/domain.types"; -import Link from "next/link"; export const getStaticProps: GetStaticProps = async () => { const products = await getActiveProductWithPrices(); @@ -57,6 +56,9 @@ export default function Pricing({ Pricing + + All plans include a 30-day money back guarantee. +
@@ -122,30 +124,43 @@ export default function Pricing({ title="Can I verify 1 million (or more) emails?" initialVisible > - Reacher currently does not offer a - SaaS plan for verifying 1 million or more emails. If - you need to verify 1 million or more emails, please - check the Commercial License plan. You will need to - purchase your own servers separately and self-host - Reacher. + If you need to verify 200K, 500K, or 1+ million + emails, please check the{" "} + Commercial License Plan. You will + need to purchase your own servers separately and + self-host Reacher. - - Yes. Simple{" "} - create an account and - you can use the textbox to verify your email. + + Sure. If you use Reacher and find that the + verifications are slow or incorrect, I'll + refund you the full amount if you email me ✉️{" "} + + amaury@reacher.email + {" "} + within 30 days of your subscription. - - Yes. Follow these steps in the{" "} - - self-host guide - - . + +

+ If you are interested in the SaaS 10K or 100K + plans, then no, Reacher does not offer any free + trial. There is however a 30-day money back + guarantee. +

+ +

+ If you are interested in the Commercial License, + you can follow these steps in the{" "} + + self-host guide + {" "} + for a free trial . +

- + Send me an email to{" "} 📧 amaury@reacher.email diff --git a/src/util/subs.ts b/src/util/subs.ts index eede0d1a..44734bbd 100644 --- a/src/util/subs.ts +++ b/src/util/subs.ts @@ -16,7 +16,7 @@ if (!SAAS_10K_PRODUCT_ID || !COMMERCIAL_LICENSE_PRODUCT_ID) { // Get the user-friendly name of a product. export function productName(product?: Tables<"products">): string { - return product?.name || "Free Trial"; + return product?.name || "None"; } // Return the max monthly calls diff --git a/src/util/useUser.tsx b/src/util/useUser.tsx index d01c64e1..dbbc1c97 100644 --- a/src/util/useUser.tsx +++ b/src/util/useUser.tsx @@ -46,7 +46,7 @@ interface UserContext { subscription: SubscriptionWithPrice | null; user: User | null; userDetails: Tables<"users"> | null; - userLoaded: boolean; + subscriptionLoaded: boolean; userFinishedLoading: boolean; } @@ -59,7 +59,7 @@ interface UserContextProviderProps { export const UserContextProvider = ( props: UserContextProviderProps ): React.ReactElement => { - const [userLoaded, setUserLoaded] = useState(false); + const [subscriptionLoaded, setSubscriptionLoaded] = useState(false); const [userFinishedLoading, setUserFinishedLoading] = useState(false); const [session, setSession] = useState(null); const [user, setUser] = useState(null); @@ -97,6 +97,10 @@ export const UserContextProvider = ( .in("status", ["trialing", "active", "past_due"]) .order("current_period_start", { ascending: false }); useEffect(() => { + setUserDetails(null); + setSubscription(null); + setSubscriptionLoaded(false); + setUserFinishedLoading(false); if (user) { Promise.all([getUserDetails(), getSubscription()]) .then(([userDetails, sub]) => { @@ -108,7 +112,7 @@ export const UserContextProvider = ( } setUserDetails(userDetails.data); setSubscription(sub.data?.[0]); - setUserLoaded(true); + setSubscriptionLoaded(true); setUserFinishedLoading(true); }) .catch(sentryException); @@ -120,7 +124,7 @@ export const UserContextProvider = ( user, userDetails, userFinishedLoading, - userLoaded, + subscriptionLoaded, subscription, resetPassword: (email: string) => supabase.auth.api.resetPasswordForEmail(email, { diff --git a/supabase/migrations/20231215163735_segments.sql b/supabase/migrations/20231215163735_segments.sql new file mode 100644 index 00000000..a1f1994c --- /dev/null +++ b/supabase/migrations/20231215163735_segments.sql @@ -0,0 +1,15 @@ +-- Segment#0 defines all users that have made API calls, but did not subscribe +-- to any plan. Try to understand why. +CREATE OR REPLACE VIEW segment0 AS + SELECT + u.email, + COUNT(c.id) as number_of_calls, + u.created_at, + MAX(c.created_at) as last_api_call, + u.id + FROM calls c + LEFT JOIN subscriptions s ON c.user_id = s.user_id + JOIN auth.users u ON u.id = c.user_id + WHERE s.id IS NULL + AND u.created_at > NOW() - INTERVAL '3 day' + GROUP BY (u.id, s.id); \ No newline at end of file