Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Remove Free Trial #477

Merged
merged 2 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Page>
<Loading />
Expand All @@ -26,8 +36,13 @@ export function Dashboard(): React.ReactElement {
<div>
<Text h2>Hello{userDetails?.full_name || ""},</Text>
<Text p>
Thanks for using the Reacher{" "}
{productName(subscription?.prices?.products)}!
<span>
{subscription
? `Thanks for using the Reacher ${productName(
subscription?.prices?.products
)}.`
: "Thank for signing up for Reacher."}
</span>
</Text>
{subscription && (
<>
Expand Down Expand Up @@ -69,10 +84,26 @@ export function Dashboard(): React.ReactElement {

<Spacer y={3} />

{subscription?.prices?.product_id !==
COMMERCIAL_LICENSE_PRODUCT_ID && <ApiUsage />}

<SubGetStarted subscription={subscription} />
{showContent(subscription)}
</Page>
);
}

function showContent(
subscription: SubscriptionWithPrice | null
): React.ReactElement {
switch (subscription?.prices?.product_id) {
case COMMERCIAL_LICENSE_PRODUCT_ID:
return <GetStartedLicense />;
case SAAS_10K_PRODUCT_ID:
case SAAS_100K_PRODUCT_ID:
return (
<>
<ApiUsage />
<GetStartedSaas />
</>
);
default:
return <GetStartedFreeTrial />;
}
}
2 changes: 1 addition & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function Footer(): React.ReactElement {

<div className={styles.bottom}>
<Text small>
© Reacher 2020-2022,{" "}
© Reacher 2020-2023,{" "}
<Link
href="https://help.reacher.email/mentions-lgales"
target="_blank"
Expand Down
15 changes: 15 additions & 0 deletions src/components/GetStarted/GetStartedFreeTrial.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Card, Text } from "@geist-ui/react";
import Link from "next/link";
import React from "react";

export function GetStartedFreeTrial(): React.ReactElement {
return (
<Card>
<Text h4>Select a Plan</Text>
<p>
Please head to the <Link href="/pricing">Pricing Page</Link> to
select a plan in order to use Reacher.
</p>
</Card>
);
}
3 changes: 3 additions & 0 deletions src/components/GetStarted/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./GetStartedLicense";
export * from "./GetStartedSaas";
export * from "./GetStartedFreeTrial";
5 changes: 1 addition & 4 deletions src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ export function Nav(): React.ReactElement {
return (
<header className={styles.container}>
<div>
<a
className="flex"
href={user ? "/dashboard" : "https://reacher.email"}
>
<a className="flex" href={user ? "/" : "https://reacher.email"}>
<Image
alt="Reacher logo"
height={24}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProductCard/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function ProductCard({
: active
? "Current Plan"
: user
? "Upgrade Plan"
? "Select Plan"
: "Get Started"}
</Button>
}
Expand Down
21 changes: 0 additions & 21 deletions src/components/SubGetStarted/index.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ export * from "./Nav";
export * from "./Footer";
export * from "./Layout";
export * from "./ProductCard";
export * from "./SubGetStarted";
export * from "./SigninLayout";
17 changes: 14 additions & 3 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 (
<>
Expand Down
59 changes: 37 additions & 22 deletions src/pages/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -57,6 +56,9 @@ export default function Pricing({
<Text className="text-center" h2>
Pricing
</Text>
<Text p em className="text-center">
All plans include a 30-day money back guarantee.
</Text>

<Spacer y={2} />
<section>
Expand Down Expand Up @@ -122,30 +124,43 @@ export default function Pricing({
title="Can I verify 1 million (or more) emails?"
initialVisible
>
Reacher currently <strong>does not</strong> 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{" "}
<strong>Commercial License Plan</strong>. You will
need to purchase your own servers separately and
self-host Reacher.
</Collapse>
<Collapse title="I need to verify one single email, can I use Reacher?">
Yes. Simple{" "}
<Link href="/signup">create an account</Link> and
you can use the textbox to verify your email.
<Collapse title="Can I get a refund?">
Sure. If you use Reacher and find that the
verifications are slow or incorrect, I&apos;ll
refund you the full amount if you email me ✉️{" "}
<a href="mailto:amaury@reacher.email">
amaury@reacher.email
</a>{" "}
within 30 days of your subscription.
</Collapse>
<Collapse title="Can I get a free trial of the Commercial Plan?">
Yes. Follow these steps in the{" "}
<a
href="https://help.reacher.email/self-host-guide#2a0e764e7cb94933b81c967be334dffd"
target="_blank"
rel="noopener noreferrer"
>
self-host guide
</a>
.
<Collapse title="Can I get a free trial?">
<p>
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.
</p>

<p>
If you are interested in the Commercial License,
you can follow these steps in the{" "}
<a
href="https://help.reacher.email/self-host-guide#2a0e764e7cb94933b81c967be334dffd"
target="_blank"
rel="noopener noreferrer"
>
self-host guide
</a>{" "}
for a free trial .
</p>
</Collapse>
<Collapse title="I have another question.">
<Collapse title="Another question?">
Send me an email to{" "}
<a href="mailto:amaury@reacher.email">
📧 amaury@reacher.email
Expand Down
2 changes: 1 addition & 1 deletion src/util/subs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions src/util/useUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ interface UserContext {
subscription: SubscriptionWithPrice | null;
user: User | null;
userDetails: Tables<"users"> | null;
userLoaded: boolean;
subscriptionLoaded: boolean;
userFinishedLoading: boolean;
}

Expand All @@ -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<Session | null>(null);
const [user, setUser] = useState<User | null>(null);
Expand Down Expand Up @@ -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]) => {
Expand All @@ -108,7 +112,7 @@ export const UserContextProvider = (
}
setUserDetails(userDetails.data);
setSubscription(sub.data?.[0]);
setUserLoaded(true);
setSubscriptionLoaded(true);
setUserFinishedLoading(true);
})
.catch(sentryException);
Expand All @@ -120,7 +124,7 @@ export const UserContextProvider = (
user,
userDetails,
userFinishedLoading,
userLoaded,
subscriptionLoaded,
subscription,
resetPassword: (email: string) =>
supabase.auth.api.resetPasswordForEmail(email, {
Expand Down
15 changes: 15 additions & 0 deletions supabase/migrations/20231215163735_segments.sql
Original file line number Diff line number Diff line change
@@ -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);
Loading