Skip to content
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
3 changes: 3 additions & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ NEXT_PUBLIC_BUSINESS_ANNUALLY_PAYMENT_LINK=#
NEXT_PUBLIC_BUSINESS_MONTHLY_VARIANT_ID=123
NEXT_PUBLIC_BUSINESS_ANNUALLY_VARIANT_ID=123

NEXT_PUBLIC_COPILOT_MONTHLY_PAYMENT_LINK=#
NEXT_PUBLIC_COPILOT_MONTHLY_VARIANT_ID=123

NEXT_PUBLIC_LIFETIME_PAYMENT_LINK=#
NEXT_PUBLIC_LIFETIME_VARIANT_ID=123
NEXT_PUBLIC_LIFETIME_EXTRA_SEATS_PAYMENT_LINK=#
Expand Down
4 changes: 4 additions & 0 deletions apps/web/app/(app)/admin/AdminUpgradeUserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export const AdminUpgradeUserForm = () => {
label: PremiumTier.BASIC_MONTHLY,
value: PremiumTier.BASIC_MONTHLY,
},
{
label: PremiumTier.COPILOT_MONTHLY,
value: PremiumTier.COPILOT_MONTHLY,
},
{
label: PremiumTier.LIFETIME,
value: PremiumTier.LIFETIME,
Expand Down
1 change: 1 addition & 0 deletions apps/web/app/(app)/admin/validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const changePremiumStatusSchema = z.object({
PremiumTier.PRO_ANNUALLY,
PremiumTier.BUSINESS_MONTHLY,
PremiumTier.BUSINESS_ANNUALLY,
PremiumTier.COPILOT_MONTHLY,
PremiumTier.LIFETIME,
]),
upgrade: z.boolean(),
Expand Down
1 change: 0 additions & 1 deletion apps/web/app/(app)/license/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { type SubmitHandler, useForm } from "react-hook-form";
import useSWR from "swr";
import { Button } from "@/components/Button";
import { Input } from "@/components/Input";
import { toastSuccess, toastError } from "@/components/Toast";
import { TopSection } from "@/components/TopSection";
import { activateLicenseKeyAction } from "@/utils/actions/premium";
import type { UserResponse } from "@/app/api/user/me/route";
Expand Down
24 changes: 14 additions & 10 deletions apps/web/app/(app)/premium/Pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,15 @@ export function Pricing() {
</RadioGroup>

<div className="ml-1">
<Badge>Save up to 40%!</Badge>
{/* <Badge>Save up to 40%!</Badge> */}
<Badge>Save 32%!</Badge>
</div>
</div>

<div className="isolate mx-auto mt-10 grid max-w-md grid-cols-1 gap-y-8 lg:mx-0 lg:max-w-none lg:grid-cols-3">
{/* 3 col layout */}
{/* <div className="isolate mx-auto mt-10 grid max-w-md grid-cols-1 gap-y-8 lg:mx-0 lg:max-w-none lg:grid-cols-3"> */}
{/* 2 col layout */}
<div className="isolate mx-auto mt-10 grid max-w-md grid-cols-1 gap-x-4 gap-y-8 lg:max-w-4xl lg:grid-cols-2">
{tiers.map((tier, tierIdx) => {
const isCurrentPlan = tier.tiers?.[frequency.value] === premiumTier;

Expand All @@ -160,9 +164,11 @@ export function Pricing() {
<div
key={tier.name}
className={clsx(
tierIdx === 1 ? "lg:z-10 lg:rounded-b-none" : "lg:mt-8", // middle tier
tierIdx === 0 ? "lg:rounded-r-none" : "",
tierIdx === tiers.length - 1 ? "lg:rounded-l-none" : "",
// 3 col layout
// tierIdx === 1 ? "lg:z-10 lg:rounded-b-none" : "lg:mt-8", // middle tier
// tierIdx === 0 ? "lg:rounded-r-none" : "",
// tierIdx === tiers.length - 1 ? "lg:rounded-l-none" : "",
// 2 col layout
"flex flex-col justify-between rounded-3xl bg-white p-8 ring-1 ring-gray-200 xl:p-10",
)}
>
Expand All @@ -186,11 +192,9 @@ export function Pricing() {
<span className="text-4xl font-bold tracking-tight text-gray-900">
${tier.price[frequency.value]}
</span>
{!tier.hideFrequency && (
<span className="text-sm font-semibold leading-6 text-gray-600">
{frequency.priceSuffix}
</span>
)}
<span className="text-sm font-semibold leading-6 text-gray-600">
{frequency.priceSuffix}
</span>

{!!tier.discount?.[frequency.value] && (
<Badge>
Expand Down
247 changes: 147 additions & 100 deletions apps/web/app/(app)/premium/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const pricing: Record<PremiumTier, number> = {
[PremiumTier.PRO_ANNUALLY]: 9,
[PremiumTier.BUSINESS_MONTHLY]: 22,
[PremiumTier.BUSINESS_ANNUALLY]: 15,
[PremiumTier.COPILOT_MONTHLY]: 99,
[PremiumTier.LIFETIME]: 299,
};

Expand All @@ -23,113 +24,159 @@ export const pricingAdditonalEmail: Record<PremiumTier, number> = {
[PremiumTier.PRO_ANNUALLY]: 2.5,
[PremiumTier.BUSINESS_MONTHLY]: 3.5,
[PremiumTier.BUSINESS_ANNUALLY]: 3,
[PremiumTier.COPILOT_MONTHLY]: 0,
[PremiumTier.LIFETIME]: 59,
};

function discount(monthly: number, annually: number) {
return ((monthly - annually) / monthly) * 100;
}

export const tiers = [
{
name: "Basic",
tiers: {
monthly: PremiumTier.BASIC_MONTHLY,
annually: PremiumTier.BASIC_ANNUALLY,
},
href: {
monthly: env.NEXT_PUBLIC_BASIC_MONTHLY_PAYMENT_LINK,
annually: env.NEXT_PUBLIC_BASIC_ANNUALLY_PAYMENT_LINK,
},
price: { monthly: pricing.BASIC_MONTHLY, annually: pricing.BASIC_ANNUALLY },
priceAdditional: {
monthly: pricingAdditonalEmail.BASIC_MONTHLY,
annually: pricingAdditonalEmail.BASIC_ANNUALLY,
},
discount: {
monthly: 0,
annually: discount(pricing.BASIC_MONTHLY, pricing.BASIC_ANNUALLY),
},
description: "Unlimited unsubscribe credits.",
features: [
"Bulk email unsubscriber",
"Unlimited unsubscribes",
"Unlimited archives",
"Email analytics",
],
cta: "Upgrade",
},
{
name: "Pro",
tiers: {
monthly: PremiumTier.PRO_MONTHLY,
annually: PremiumTier.PRO_ANNUALLY,
},
href: {
monthly: env.NEXT_PUBLIC_PRO_MONTHLY_PAYMENT_LINK,
annually: env.NEXT_PUBLIC_PRO_ANNUALLY_PAYMENT_LINK,
},
price: { monthly: pricing.PRO_MONTHLY, annually: pricing.PRO_ANNUALLY },
priceAdditional: {
monthly: pricingAdditonalEmail.PRO_MONTHLY,
annually: pricingAdditonalEmail.PRO_ANNUALLY,
},
discount: {
monthly: 0,
annually: discount(pricing.PRO_MONTHLY, pricing.PRO_ANNUALLY),
},
description: "Unlock AI features when using your own OpenAI key",
features: [
"Everything in free",
"AI automation when using your own OpenAI API key",
"Cold email blocker when using your own OpenAI API key",
],
cta: "Upgrade",
mostPopular: false,
},
{
name: "Business",
tiers: {
monthly: PremiumTier.BUSINESS_MONTHLY,
annually: PremiumTier.BUSINESS_ANNUALLY,
},
href: {
monthly: env.NEXT_PUBLIC_BUSINESS_MONTHLY_PAYMENT_LINK,
annually: env.NEXT_PUBLIC_BUSINESS_ANNUALLY_PAYMENT_LINK,
},
price: {
monthly: pricing.BUSINESS_MONTHLY,
annually: pricing.BUSINESS_ANNUALLY,
},
priceAdditional: {
monthly: pricingAdditonalEmail.BUSINESS_MONTHLY,
annually: pricingAdditonalEmail.BUSINESS_ANNUALLY,
},
discount: {
monthly: 0,
annually: discount(pricing.BUSINESS_MONTHLY, pricing.BUSINESS_ANNUALLY),
},
description: "Unlock full AI-powered email management",
features: [
"Everything in pro",
"Unlimited AI credits",
"No need to provide your own OpenAI API key",
"Priority support",
],
cta: "Upgrade",
mostPopular: true,
hideFrequency: false,
},
// {
// name: "Enterprise",
// id: "tier-enterprise",
// href: env.NEXT_PUBLIC_CALL_LINK,
// price: { monthly: "Book a call", annually: "Book a call" },
// description: "For help self-hosting, and dedicated support.",
// features: ["Self-hosted", "Everything in pro", "Dedicated support"],
// hideFrequency: true,
// cta: "Book a call",
// },
const basicTier = {
name: "Basic",
tiers: {
monthly: PremiumTier.BASIC_MONTHLY,
annually: PremiumTier.BASIC_ANNUALLY,
},
href: {
monthly: env.NEXT_PUBLIC_BASIC_MONTHLY_PAYMENT_LINK,
annually: env.NEXT_PUBLIC_BASIC_ANNUALLY_PAYMENT_LINK,
},
price: { monthly: pricing.BASIC_MONTHLY, annually: pricing.BASIC_ANNUALLY },
priceAdditional: {
monthly: pricingAdditonalEmail.BASIC_MONTHLY,
annually: pricingAdditonalEmail.BASIC_ANNUALLY,
},
discount: {
monthly: 0,
annually: discount(pricing.BASIC_MONTHLY, pricing.BASIC_ANNUALLY),
},
description: "Unlimited unsubscribe credits.",
features: [
"Bulk email unsubscriber",
"Unlimited unsubscribes",
"Unlimited archives",
"Email analytics",
],
cta: "Upgrade",
};

const proTier = {
name: "Pro",
tiers: {
monthly: PremiumTier.PRO_MONTHLY,
annually: PremiumTier.PRO_ANNUALLY,
},
href: {
monthly: env.NEXT_PUBLIC_PRO_MONTHLY_PAYMENT_LINK,
annually: env.NEXT_PUBLIC_PRO_ANNUALLY_PAYMENT_LINK,
},
price: { monthly: pricing.PRO_MONTHLY, annually: pricing.PRO_ANNUALLY },
priceAdditional: {
monthly: pricingAdditonalEmail.PRO_MONTHLY,
annually: pricingAdditonalEmail.PRO_ANNUALLY,
},
discount: {
monthly: 0,
annually: discount(pricing.PRO_MONTHLY, pricing.PRO_ANNUALLY),
},
description: "Unlock AI features when using your own OpenAI key",
features: [
"Everything in free",
"AI automation when using your own OpenAI API key",
"Cold email blocker when using your own OpenAI API key",
],
cta: "Upgrade",
mostPopular: false,
};

const businessTier = {
name: "Business",
tiers: {
monthly: PremiumTier.BUSINESS_MONTHLY,
annually: PremiumTier.BUSINESS_ANNUALLY,
},
href: {
monthly: env.NEXT_PUBLIC_BUSINESS_MONTHLY_PAYMENT_LINK,
annually: env.NEXT_PUBLIC_BUSINESS_ANNUALLY_PAYMENT_LINK,
},
price: {
monthly: pricing.BUSINESS_MONTHLY,
annually: pricing.BUSINESS_ANNUALLY,
},
priceAdditional: {
monthly: pricingAdditonalEmail.BUSINESS_MONTHLY,
annually: pricingAdditonalEmail.BUSINESS_ANNUALLY,
},
discount: {
monthly: 0,
annually: discount(pricing.BUSINESS_MONTHLY, pricing.BUSINESS_ANNUALLY),
},
description: "Unlock full AI-powered email management",
// features: [
// "Everything in pro",
// "Unlimited AI credits",
// "No need to provide your own OpenAI API key",
// "Priority support",
// ],
features: [
"AI automation",
"Bulk email unsubscriber",
"Cold email blocker",
"Email analytics",
"Unlimited AI credits",
"Priority support",
],
cta: "Upgrade",
mostPopular: true,
};

const copilotTier = {
name: "Co-Pilot",
tiers: {
monthly: PremiumTier.COPILOT_MONTHLY,
annually: PremiumTier.COPILOT_MONTHLY,
},
href: {
monthly: env.NEXT_PUBLIC_COPILOT_MONTHLY_PAYMENT_LINK,
annually: env.NEXT_PUBLIC_COPILOT_MONTHLY_PAYMENT_LINK,
},
price: {
monthly: pricing.COPILOT_MONTHLY,
annually: pricing.COPILOT_MONTHLY,
},
priceAdditional: {
monthly: pricingAdditonalEmail.COPILOT_MONTHLY,
annually: pricingAdditonalEmail.COPILOT_MONTHLY,
},
discount: { monthly: 0, annually: 0 },
description:
"Get a 30-minute monthly call to help you get your email organized",
features: [
"Everything in Business",
"30-minute 1:1 monthly call to help you get your email organized",
"Full refund if not satisfied",
],
cta: "Upgrade",
mostPopular: false,
};

export const tiers: {
name: string;
tiers: { monthly: PremiumTier; annually: PremiumTier };
href: { monthly: string; annually: string };
price: { monthly: number; annually: number };
priceAdditional: { monthly: number; annually: number };
discount: { monthly: number; annually: number };
description: string;
features: string[];
cta: string;
mostPopular?: boolean;
}[] = [
// basicTier,
// proTier,
businessTier,
copilotTier,
];

export const lifetimeFeatures = [
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(app)/settings/LabelsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ function LabelsSectionFormInner(props: {
<div className="mt-8">
<SectionHeader>Suggested Labels</SectionHeader>
<SectionDescription>
Labels we suggest adding to organise your emails. Click a label
Labels we suggest adding to organize your emails. Click a label
to add it.
</SectionDescription>
<div className="mt-2">
Expand Down
3 changes: 3 additions & 0 deletions apps/web/app/api/lemon-squeezy/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ function getSubscriptionTier({
return PremiumTier.BUSINESS_MONTHLY;
case env.NEXT_PUBLIC_BUSINESS_ANNUALLY_VARIANT_ID:
return PremiumTier.BUSINESS_ANNUALLY;

case env.NEXT_PUBLIC_COPILOT_MONTHLY_VARIANT_ID:
return PremiumTier.COPILOT_MONTHLY;
}

throw new Error(`Unknown variant id: ${variantId}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ Organize a list of people that will support your launch. My list was 100 people.

You can also add communities to the list. Many Slack or Discord communities have a channel to promote yourself. These are a good places to get some extra eyeballs. And of course LinkedIn, Twitter, Facebook groups, and other social media platforms you’re on.

You could skip organizing the list ahead of time. But it adds a bit less pressure to the day of the launch if you have it organised.
You could skip organizing the list ahead of time. But it adds a bit less pressure to the day of the launch if you have it organized.

I used a mini Notion CRM myself and had a status column for each person.

Expand Down
Loading