Skip to content

Commit

Permalink
yearly subscription
Browse files Browse the repository at this point in the history
  • Loading branch information
Rodriguespn committed Mar 2, 2024
1 parent 8791968 commit 51a3b3a
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 46 deletions.
1 change: 1 addition & 0 deletions .github/workflows/firebase-dev-hosting-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ env:

# Stripe
VITE_PRO_PRICE_ID: ${{ secrets.VITE_PRO_PRICE_ID_DEV }}
VITE_PRO_PRICE_YEARLY_ID: ${{ secrets.VITE_PRO_PRICE_YEARLY_ID_DEV }}

jobs:
build_and_deploy:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/firebase-hosting-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ env:

# Stripe
VITE_PRO_PRICE_ID: ${{ secrets.VITE_PRO_PRICE_ID }}
VITE_PRO_PRICE_YEARLY_ID: ${{ secrets.VITE_PRO_PRICE_YEARLY_ID }}

jobs:
build_and_deploy:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/firebase-hosting-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ env:

# Stripe
VITE_PRO_PRICE_ID: ${{ secrets.VITE_PRO_PRICE_ID_DEV }}
VITE_PRO_PRICE_YEARLY_ID: ${{ secrets.VITE_PRO_PRICE_YEARLY_ID_DEV }}

jobs:
build_and_preview:
Expand Down
93 changes: 55 additions & 38 deletions src/components/premium/PlansList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CheckIcon, SparklesIcon } from "@heroicons/react/20/solid";
import clsx from "clsx";
import { FC, useEffect, useMemo, useState } from "react";
import { FC, useEffect, useState } from "react";
import { logAnalyticsEvent } from "../../services/firestore/analytics/analytics";
import { Subscription, useCheckoutSessionUrl } from "../../services/stripe";
import { useCheckoutSessionUrl } from "../../services/stripe";
import { Colors } from "../../utils/colors";
import Badge from "../common/Badge";
import SubscriptionPeriodToggle, { SubscriptionPeriodMode, SubscriptionPeriodModes } from "./SubscriptionPeriodToggle";

export interface PlanProps {
isPro: boolean;
Expand Down Expand Up @@ -49,30 +50,25 @@ const DiscoverPlan: FC<PlanProps> = ({ isPro }) => {

interface ProPlanProps extends PlanProps {
userID: string;
subscription?: Subscription;
subscriptionPeriodMode: SubscriptionPeriodMode;
successRedirectPath?: string;
cancelRedirectPath?: string;
}

const ProPlan: FC<ProPlanProps> = ({
isPro,
userID,
subscription,
subscriptionPeriodMode,
successRedirectPath,
cancelRedirectPath,
}) => {
const [buyPlanClicked, setBuyPlanClicked] = useState<boolean>(false);

const priceId = useMemo(
() => (subscription ? subscription.price.id : import.meta.env.VITE_PRO_PRICE_ID),
[subscription],
);

const {
url: checkoutSessionUrl,
loading: isLoadingCheckoutSession,
refetch: getCheckoutSession,
} = useCheckoutSessionUrl(priceId, userID, { enabled: false, successPath: successRedirectPath, cancelPath: cancelRedirectPath });
} = useCheckoutSessionUrl(subscriptionPeriodMode.priceId, userID, { enabled: false, successPath: successRedirectPath, cancelPath: cancelRedirectPath });

useEffect(() => {
if (buyPlanClicked) {
Expand All @@ -90,18 +86,34 @@ const ProPlan: FC<ProPlanProps> = ({

return (
<div className="relative flex w-96 flex-col overflow-hidden rounded-3xl bg-gray-800 p-8 shadow-md">
<h3 className="flex flex-row items-center gap-x-2 text-lg font-semibold leading-8 text-white">
Pro
{isPro && (
<Badge color={Colors.BLUE_DARK} text="Current" className="h-fit" />
)}
</h3>
<div className="flex flex-row items-center gap-x-2 text-white">
<h3 className="flex flex-row text-lg font-semibold leading-8">
Pro
{isPro && (
<Badge color={Colors.BLUE_DARK} text="Current" className="h-fit" />
)}
</h3>
{
subscriptionPeriodMode.name === SubscriptionPeriodModes[1].name && (
<span className="text-sm font-normal text-gray-300">
(Billed yearly)
</span>
)
}
</div>
<p className="mt-1 text-sm leading-6 text-gray-100">
Speed up your investigations and take them to the next level.
</p>
<p className="mt-4 text-4xl font-bold tracking-tight text-white">
€99.99
<span className="text-base font-normal text-gray-300">/mo</span>
<div className="gap-x-5 flex flex-row items-center">
<span>
{`${subscriptionPeriodMode.priceCurrency}${subscriptionPeriodMode.priceValueMonth}`}
<span className="text-base font-normal text-gray-300">/mo</span>
</span>
{subscriptionPeriodMode.name === SubscriptionPeriodModes[1].name && (
<Badge color={Colors.BLUE_DARK} text="Save 20%" className="h-fit" />
)}
</div>
<svg
viewBox="0 0 1208 1024"
className="pointer-events-none absolute top-0 h-[20rem] opacity-[7%] [mask-image:radial-gradient(closest-side,white,transparent)]"
Expand Down Expand Up @@ -212,39 +224,44 @@ const EnterprisePlan: FC = () => {
interface PlanListProps {
isPro: boolean;
userID: string;
subscription?: Subscription;
successRedirectPath?: string;
cancelRedirectPath?: string;
}

const PlansList: FC<PlanListProps> = ({
isPro,
userID,
subscription,
successRedirectPath,
cancelRedirectPath,
}) => {
const [subscriptionPeriodMode, setSubscriptionPeriodMode] = useState<SubscriptionPeriodMode>(SubscriptionPeriodModes[0]);

return (
<span className="flex flex-row justify-center gap-x-5">
<>
<DiscoverPlan isPro={isPro} />
<ProPlan
key={subscription?.id}
isPro={isPro}
userID={userID}
subscription={subscription}
successRedirectPath={successRedirectPath}
cancelRedirectPath={cancelRedirectPath}
<>
<div className="flex center justify-center gap-x-5">
<SubscriptionPeriodToggle
subscriptionPeriodMode={subscriptionPeriodMode}
setSubscriptionPeriodMode={setSubscriptionPeriodMode}
/>
<EnterprisePlan />
</>
</span>
</div>
<div className="flex flex-row justify-center gap-x-5">
<>
<DiscoverPlan isPro={isPro} />
<ProPlan
isPro={isPro}
userID={userID}
subscriptionPeriodMode={subscriptionPeriodMode}
successRedirectPath={successRedirectPath}
cancelRedirectPath={cancelRedirectPath}
/>
<EnterprisePlan />
</>
</div>
</>
)
}

export {
DiscoverPlan,
ProPlan,
EnterprisePlan,
PlansList,
};
DiscoverPlan, EnterprisePlan,
PlansList, ProPlan
};
89 changes: 89 additions & 0 deletions src/components/premium/SubscriptionPeriodToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import clsx from "clsx";
import { FC } from "react";

interface SubscriptionPeriodButtonProps {
isActive: boolean;
subscriptionPeriodMode: SubscriptionPeriodMode;
setSubscriptionPeriodMode: (mode: SubscriptionPeriodMode) => void;
}


export enum SubscriptionPeriodNames {
MONTHLY = "Monthly",
YEARLY = "Yearly",
}

export interface SubscriptionPeriodMode {
name: SubscriptionPeriodNames;
priceId: string;
priceValueMonth: number;
priceCurrency: string;
}

export const SubscriptionPeriodModes: SubscriptionPeriodMode[] = [
{
name: SubscriptionPeriodNames.MONTHLY,
priceId: import.meta.env.VITE_PRO_PRICE_ID as string,
priceValueMonth: 99.99,
priceCurrency: "€",
},
{
name: SubscriptionPeriodNames.YEARLY,
priceId: import.meta.env.VITE_PRO_PRICE_YEARLY_ID as string,
priceValueMonth: 83.33,
priceCurrency: "€",
},
];

const SubscriptionPeriodButton: FC<SubscriptionPeriodButtonProps> = ({
isActive,
subscriptionPeriodMode,
setSubscriptionPeriodMode,
}) => {
return (
<button
type="button"
className={clsx(
"flex flex-row items-center gap-x-1 rounded-md px-3 py-2 text-sm font-semibold",
{
"bg-blue-500 text-white shadow-md": isActive,
},
)}
onClick={() => setSubscriptionPeriodMode(subscriptionPeriodMode)}
>
<p
className="w-fit-content"
style={{
maxWidth: "5rem",
transition: "all 0.3s ease-in-out",
}}
>
{subscriptionPeriodMode.name}
</p>
</button>
);
};

interface SubscriptionPeriodToggleProps {
subscriptionPeriodMode: SubscriptionPeriodMode;
setSubscriptionPeriodMode: (mode: SubscriptionPeriodMode) => void;
}

const SubscriptionPeriodToggle: FC<SubscriptionPeriodToggleProps> = ({
subscriptionPeriodMode, setSubscriptionPeriodMode
}) => {
return (
<span className="flex flex-row gap-x-0.5 rounded-lg border border-gray-300 bg-white p-1 shadow-inner ring-1 ring-inset ring-white">
{SubscriptionPeriodModes.map((mode) => (
<SubscriptionPeriodButton
key={mode.name}
isActive={mode === subscriptionPeriodMode}
subscriptionPeriodMode={mode}
setSubscriptionPeriodMode={setSubscriptionPeriodMode}
/>
))}
</span>
);
};

export default SubscriptionPeriodToggle;
14 changes: 6 additions & 8 deletions src/templates/BillingTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import {
import { CreditCardIcon } from "@heroicons/react/24/outline";
import { FC, useEffect, useMemo, useState } from "react";
import BigButton from "../components/common/BigButton";
import SEO from "../components/common/SEO";
import { PlansList } from "../components/premium";
import useAuthState from "../hooks/useAuthState";
import { logAnalyticsEvent } from "../services/firestore/analytics/analytics";
import { usePremiumStatus } from "../services/firestore/user/premium/premium";
import {
useActiveSubscription,
useCustomerPortalUrl
} from "../services/stripe";
import { PlansList } from "../components/premium";
import SEO from "../components/common/SEO";

const BillingTemplate: FC = () => {
const [manageSubscriptionClicked, setManageSubscriptionClicked] =
Expand All @@ -31,18 +30,18 @@ const BillingTemplate: FC = () => {
refetch: getCustomerPortalUrl,
} = useCustomerPortalUrl(userID, { enabled: false });

const { subscription, loading: isLoadingActiveSubscription } =
useActiveSubscription();
/* const { subscription, loading: isLoadingActiveSubscription } =
useActiveSubscription(); */

const isLoading = useMemo(() => {
return (
isLoadingPremiumStatus ||
isLoadingActiveSubscription ||
//isLoadingActiveSubscription ||
isLoadingCustomerPortalUrl
);
}, [
isLoadingPremiumStatus,
isLoadingActiveSubscription,
//isLoadingActiveSubscription,
isLoadingCustomerPortalUrl,
]);

Expand Down Expand Up @@ -98,7 +97,6 @@ const BillingTemplate: FC = () => {
<PlansList
isPro={isPro}
userID={userID}
subscription={subscription}
successRedirectPath="graph/new"
cancelRedirectPath="billing"
/>
Expand Down

0 comments on commit 51a3b3a

Please sign in to comment.