diff --git a/app/settings/components/billing/billing-history.tsx b/app/settings/components/billing/billing-history.tsx new file mode 100644 index 0000000..f2da704 --- /dev/null +++ b/app/settings/components/billing/billing-history.tsx @@ -0,0 +1,99 @@ +const payments = [ + { + id: 1, + date: new Date(), + description: "", + amount: "340 USD", + href: "", + }, + { + id: 1, + date: new Date(), + description: "", + amount: "340 USD", + href: "", + }, + { + id: 1, + date: new Date(), + description: "", + amount: "340 USD", + href: "", + }, +]; + +export default function BillingHistory() { + return ( +
+
+
+

Billing history

+
+
+
+
+
+ + + + + + + {/* + `relative` is added here due to a weird bug in Safari that causes `sr-only` headings to introduce overflow on the body on mobile. + */} + + + + + {payments.map((payment) => ( + + + + + + + ))} + +
+ Date + + Description + + Amount + + View receipt +
+ + + {payment.description} + + {payment.amount} + + + View receipt + +
+
+
+
+
+
+
+ ); +} diff --git a/app/settings/components/paddle-link.tsx b/app/settings/components/billing/paddle-link.tsx similarity index 100% rename from app/settings/components/paddle-link.tsx rename to app/settings/components/billing/paddle-link.tsx diff --git a/app/settings/components/billing/plans.tsx b/app/settings/components/billing/plans.tsx new file mode 100644 index 0000000..4f80bea --- /dev/null +++ b/app/settings/components/billing/plans.tsx @@ -0,0 +1,126 @@ +import { HiCheck } from "react-icons/hi"; +import * as Panelbear from "@panelbear/panelbear-js"; +import clsx from "clsx"; + +import useSubscription from "../../hooks/use-subscription"; + +export default function Plans() { + const { subscription, subscribe } = useSubscription(); + + return ( +
+ {pricing.tiers.map((tier) => { + const isCurrentTier = subscription?.paddlePlanId === tier.planId; + const cta = isCurrentTier ? "Current plan" : !!subscription ? `Switch to ${tier.title}` : "Subscribe"; + + return ( +
+
+

{tier.title}

+ {tier.yearly ? ( +

+ Get 2 months free! +

+ ) : null} +

+ {tier.price}€ + {tier.frequency} +

+ {tier.yearly ? ( +

Billed yearly ({tier.price * 12}€)

+ ) : null} +

{tier.description}

+ +
    + {tier.features.map((feature) => ( +
  • +
  • + ))} + {tier.unavailableFeatures.map((feature) => ( +
  • + + {~feature.indexOf("(coming soon)") + ? feature.slice(0, feature.indexOf("(coming soon)")) + : feature} + +
  • + ))} +
+
+ + +
+ ); + })} +
+ ); +} + +const paidFeatures = [ + "SMS", + "MMS (coming soon)", + "Calls", + "SMS forwarding (coming soon)", + "Call forwarding (coming soon)", + "Voicemail (coming soon)", + "Call recording (coming soon)", +]; +const pricing = { + tiers: [ + { + title: "Free", + planId: "free", + price: 0, + frequency: "", + description: "The essentials to let you try Shellphone.", + features: ["SMS (send only)"], + unavailableFeatures: paidFeatures.slice(1), + cta: "Subscribe", + yearly: false, + }, + { + title: "Monthly", + planId: "727540", + price: 15, + frequency: "/month", + description: "Text and call anyone, anywhere in the world.", + features: paidFeatures, + unavailableFeatures: [], + cta: "Subscribe", + yearly: false, + }, + { + title: "Yearly", + planId: "727544", + price: 12.5, + frequency: "/month", + description: "Text and call anyone, anywhere in the world, all year long.", + features: paidFeatures, + unavailableFeatures: [], + cta: "Subscribe", + yearly: true, + }, + ], +}; diff --git a/app/settings/components/divider.tsx b/app/settings/components/divider.tsx new file mode 100644 index 0000000..8c78520 --- /dev/null +++ b/app/settings/components/divider.tsx @@ -0,0 +1,9 @@ +export default function Divider() { + return ( +
+
+
+
+
+ ); +} diff --git a/app/settings/hooks/use-subscription.ts b/app/settings/hooks/use-subscription.ts index 1719aa4..ed247e6 100644 --- a/app/settings/hooks/use-subscription.ts +++ b/app/settings/hooks/use-subscription.ts @@ -1,15 +1,20 @@ import { useEffect, useRef } from "react"; import { useQuery, useMutation, useRouter, useSession } from "blitz"; +import type { Subscription } from "db"; import getSubscription from "../queries/get-subscription"; import usePaddle from "./use-paddle"; import useCurrentUser from "../../core/hooks/use-current-user"; import updateSubscription from "../mutations/update-subscription"; -export default function useSubscription() { +type Params = { + initialData?: Subscription; +}; + +export default function useSubscription({ initialData }: Params = {}) { const session = useSession(); const { user } = useCurrentUser(); - const [subscription] = useQuery(getSubscription, null, { enabled: Boolean(session.orgId) }); + const [subscription] = useQuery(getSubscription, null, { enabled: Boolean(session.orgId), initialData }); const [updateSubscriptionMutation] = useMutation(updateSubscription); const router = useRouter(); diff --git a/app/settings/pages/settings/billing.tsx b/app/settings/pages/settings/billing.tsx index e94bd70..caede72 100644 --- a/app/settings/pages/settings/billing.tsx +++ b/app/settings/pages/settings/billing.tsx @@ -1,63 +1,24 @@ import type { BlitzPage } from "blitz"; -import { GetServerSideProps, Link, Routes } from "blitz"; -import * as Panelbear from "@panelbear/panelbear-js"; -import clsx from "clsx"; +import { GetServerSideProps, getSession, Routes } from "blitz"; +import db, { Subscription, SubscriptionStatus } from "db"; import useSubscription from "../../hooks/use-subscription"; import useRequireOnboarding from "../../../core/hooks/use-require-onboarding"; import SettingsLayout from "../../components/settings-layout"; -import appLogger from "../../../../integrations/logger"; -import PaddleLink from "../../components/paddle-link"; import SettingsSection from "../../components/settings-section"; -import { HiCheck } from "react-icons/hi"; +import Divider from "../../components/divider"; +import PaddleLink from "../../components/billing/paddle-link"; +import Plans from "../../components/billing/plans"; +import BillingHistory from "../../components/billing/billing-history"; +import appLogger from "../../../../integrations/logger"; const logger = appLogger.child({ page: "/account/settings/billing" }); -const paidFeatures = [ - "SMS", - "MMS (coming soon)", - "Calls", - "SMS forwarding (coming soon)", - "Call forwarding (coming soon)", - "Voicemail (coming soon)", - "Call recording (coming soon)", -]; -const pricing = { - tiers: [ - { - title: "Free", - price: 0, - frequency: "", - description: "The essentials to let you try Shellphone.", - features: ["SMS (send only)"], - unavailableFeatures: paidFeatures.slice(1), - cta: "Current tier", - yearly: false, - }, - { - title: "Monthly", - price: 15, - frequency: "/month", - description: "Text and call anyone, anywhere in the world.", - features: paidFeatures, - unavailableFeatures: [], - cta: "Subscribe", - yearly: false, - }, - { - title: "Yearly", - price: 12.5, - frequency: "/month", - description: "Text and call anyone, anywhere in the world, all year long.", - features: paidFeatures, - unavailableFeatures: [], - cta: "Subscribe", - yearly: true, - }, - ], +type Props = { + subscription?: Subscription; }; -const Billing: BlitzPage = () => { +const Billing: BlitzPage = (props) => { /* TODO: I want to be able to - subscribe @@ -70,81 +31,16 @@ const Billing: BlitzPage = () => { */ useRequireOnboarding(); - const { subscription, cancelSubscription, updatePaymentMethod } = useSubscription(); - console.log("subscription", subscription); + const { subscription, cancelSubscription, updatePaymentMethod } = useSubscription({ + initialData: props.subscription, + }); if (!subscription) { return ( - -
-

Subscribe

-

- Update your billing information. Please note that updating your location could affect your tax - rates. -

-
- -
- {pricing.tiers.map((tier) => ( -
-
-

{tier.title}

- {tier.yearly ? ( -

- Get 2 months free! -

- ) : null} -

- {tier.price}€ - {tier.frequency} -

- {tier.yearly ? ( -

Billed yearly ({tier.price * 12}€)

- ) : null} -

{tier.description}

- -
    - {tier.features.map((feature) => ( -
  • -
  • - ))} - {tier.unavailableFeatures.map((feature) => ( -
  • - - {~feature.indexOf("(coming soon)") - ? feature.slice(0, feature.indexOf("(coming soon)")) - : feature} - -
  • - ))} -
-
- - - Panelbear.track("redirect-to-join-waitlist")} - className={clsx( - tier.yearly - ? "bg-primary-500 text-white hover:bg-primary-600" - : "bg-primary-50 text-primary-700 hover:bg-primary-100", - "mt-8 block w-full py-3 px-6 border border-transparent rounded-md text-center font-medium", - )} - > - {tier.cta} - - -
- ))} -
-
+ <> + +

Prices include all applicable sales taxes.

+ ); } @@ -153,100 +49,22 @@ const Billing: BlitzPage = () => { updatePaymentMethod({ updateUrl: subscription.updateUrl })} - text="Update payment method on Paddle" + text="Update payment method" /> - - - {/**/} - - cancelSubscription({ cancelUrl: subscription.cancelUrl })} - text="Cancel subscription on Paddle" + text="Cancel subscription" /> -
-
-
-

- Billing history -

-
-
-
-
-
- - - - - - - {/* - `relative` is added here due to a weird bug in Safari that causes `sr-only` headings to introduce overflow on the body on mobile. - */} - - - - - {[ - { - id: 1, - date: new Date(), - description: "", - amount: "340 USD", - href: "", - }, - ].map((payment) => ( - - - - - - - ))} - -
- Date - - Description - - Amount - - View receipt -
- - - {payment.description} - - {payment.amount} - - - View receipt - -
-
-
-
-
-
-
+ + +
+ +
+ + +

Prices include all applicable sales taxes.

); }; @@ -255,8 +73,21 @@ Billing.getLayout = (page) => {page}; Billing.authenticate = { redirectTo: Routes.SignIn() }; -export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { - return { props: {} }; +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + const session = await getSession(req, res); + const subscription = await db.subscription.findFirst({ + where: { + organizationId: session.orgId, + status: SubscriptionStatus.active, + }, + }); + if (!subscription) { + return { props: {} }; + } + + return { + props: { subscription }, + }; }; export default Billing;