Subscription.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. 'use client'
  2. import { useState, useEffect, SetStateAction, Dispatch } from 'react'
  3. import Link from 'next/link'
  4. import PlanCards from '@/components/Plan'
  5. import {
  6. UpdateBillingLink,
  7. CancelLink,
  8. ResumeButton,
  9. PauseLink,
  10. UnpauseButton
  11. } from '@/components/Manage'
  12. import { Plan, Subscription, SubscriptionState } from '@/types'
  13. export const SubscriptionComponent = ({ sub, plans }:
  14. { sub: Subscription | null, plans: Plan[] }
  15. ) => {
  16. // Make sure Lemon.js is loaded
  17. useEffect(() => {
  18. window.createLemonSqueezy()
  19. }, [])
  20. const [subscription, setSubscription] = useState<SubscriptionState>(() => {
  21. if (sub) {
  22. return {
  23. id: sub.lemonSqueezyId,
  24. planName: sub.plan?.variantName,
  25. planInterval: sub.plan?.interval,
  26. productId: sub.plan?.productId,
  27. variantId: sub.plan?.variantId,
  28. status: sub.status,
  29. renewalDate: sub.renewsAt,
  30. trialEndDate: sub.trialEndsAt,
  31. expiryDate: sub.endsAt,
  32. unpauseDate: sub.resumesAt,
  33. price: sub.price / 100,
  34. }
  35. } else {
  36. return undefined
  37. }
  38. })
  39. if (sub) {
  40. switch(subscription?.status) {
  41. case 'active':
  42. return <ActiveSubscription subscription={subscription} setSubscription={setSubscription} />
  43. case 'on_trial':
  44. return <TrialSubscription subscription={subscription} setSubscription={setSubscription} />
  45. case 'past_due':
  46. return <PastDueSubscription subscription={subscription} setSubscription={setSubscription} />
  47. case 'cancelled':
  48. return <CancelledSubscription subscription={subscription} setSubscription={setSubscription} />
  49. case 'paused':
  50. return <PausedSubscription subscription={subscription} setSubscription={setSubscription} />
  51. case 'unpaid':
  52. return <UnpaidSubscription subscription={subscription} setSubscription={setSubscription} />
  53. case 'expired':
  54. return <ExpiredSubscription subscription={subscription} plans={plans} setSubscription={setSubscription} />
  55. }
  56. }
  57. return (
  58. <>
  59. <p className="text-center">Please sign up to a paid plan.</p>
  60. <PlanCards plans={plans} setSubscription={setSubscription} />
  61. </>
  62. )
  63. }
  64. export const ActiveSubscription = ({ subscription, setSubscription }:
  65. { subscription: SubscriptionState, setSubscription: Dispatch<SetStateAction<SubscriptionState>> }
  66. ) => {
  67. return (
  68. <>
  69. <p className="mb-2">
  70. You are currently on the <b>{subscription?.planName} {subscription?.planInterval}ly</b> plan, paying ${subscription?.price}/{subscription?.planInterval}.
  71. </p>
  72. <p className="mb-2">Your next renewal will be on {formatDate(subscription?.renewalDate)}.</p>
  73. <hr className="my-8" />
  74. <p className="mb-4">
  75. <Link href="/billing/change-plan" className="px-6 py-2 font-bold">
  76. Change plan &rarr;
  77. </Link>
  78. </p>
  79. <p><UpdateBillingLink subscription={subscription} /></p>
  80. <p><PauseLink subscription={subscription} setSubscription={setSubscription} /></p>
  81. <p><CancelLink subscription={subscription} setSubscription={setSubscription} /></p>
  82. </>
  83. )
  84. }
  85. export const CancelledSubscription = ({ subscription, setSubscription }:
  86. { subscription: SubscriptionState, setSubscription: Dispatch<SetStateAction<SubscriptionState>> }
  87. ) => {
  88. return (
  89. <>
  90. <p className="mb-2">
  91. You are currently on the <b>{subscription?.planName} {subscription?.planInterval}ly</b> plan, paying ${subscription?.price}/{subscription?.planInterval}.
  92. </p>
  93. <p className="mb-8">Your subscription has been cancelled and <b>will end on {formatDate(subscription?.expiryDate)}</b>. After this date you will no longer have access to the app.</p>
  94. <p><ResumeButton subscription={subscription} setSubscription={setSubscription} /></p>
  95. </>
  96. )
  97. }
  98. export const PausedSubscription = ({ subscription, setSubscription }:
  99. { subscription: SubscriptionState, setSubscription: Dispatch<SetStateAction<SubscriptionState>> }
  100. ) => {
  101. return (
  102. <>
  103. <p className="mb-2">
  104. You are currently on the <b>{subscription?.planName} {subscription?.planInterval}ly</b> plan, paying ${subscription?.price}/{subscription?.planInterval}.
  105. </p>
  106. {subscription?.unpauseDate ? (
  107. <p className="mb-8">Your subscription payments are currently paused. Your subscription will automatically resume on {formatDate(subscription?.unpauseDate)}.</p>
  108. ) : (
  109. <p className="mb-8">Your subscription payments are currently paused.</p>
  110. )}
  111. <p><UnpauseButton subscription={subscription} setSubscription={setSubscription} /></p>
  112. </>
  113. )
  114. }
  115. export const TrialSubscription = ({ subscription, setSubscription }:
  116. { subscription: SubscriptionState, setSubscription: Dispatch<SetStateAction<SubscriptionState>> }
  117. ) => {
  118. return (
  119. <>
  120. <p className="mb-2">
  121. You are currently on a free trial of the <b>{subscription?.planName} {subscription?.planInterval}ly</b> plan, paying ${subscription?.price}/{subscription?.planInterval}.
  122. </p>
  123. <p className="mb-6">Your trial ends on {formatDate(subscription?.trialEndDate)}. You can cancel your subscription before this date and you won&apos;t be charged.</p>
  124. <hr className="my-8" />
  125. <p className="mb-4">
  126. <Link href="/billing/change-plan" className="px-6 py-2 font-bold">
  127. Change plan &rarr;
  128. </Link>
  129. </p>
  130. <p><UpdateBillingLink subscription={subscription} /></p>
  131. <p><CancelLink subscription={subscription} setSubscription={setSubscription} /></p>
  132. </>
  133. )
  134. }
  135. export const PastDueSubscription = ({ subscription, setSubscription }:
  136. { subscription: SubscriptionState, setSubscription: Dispatch<SetStateAction<SubscriptionState>> }
  137. ) => {
  138. return (
  139. <>
  140. <div className="my-8 p-4">
  141. Your latest payment failed. We will re-try this payment up to four times, after which your subscription will be cancelled.<br />
  142. If you need to update your billing details, you can do so below.
  143. </div>
  144. <p className="mb-2">
  145. You are currently on the <b>{subscription?.planName} {subscription?.planInterval}ly</b> plan, paying ${subscription?.price}/{subscription?.planInterval}.
  146. </p>
  147. <p className="mb-2">We will attempt a payment on {formatDate(subscription?.renewalDate)}.</p>
  148. <hr className="my-8" />
  149. <p><UpdateBillingLink subscription={subscription} /></p>
  150. <p><CancelLink subscription={subscription} setSubscription={setSubscription} /></p>
  151. </>
  152. )
  153. }
  154. export const UnpaidSubscription = ({ subscription, setSubscription }:
  155. { subscription: SubscriptionState, setSubscription: Dispatch<SetStateAction<SubscriptionState>> }
  156. ) => {
  157. /*
  158. Unpaid subscriptions have had four failed recovery payments.
  159. If you have dunning enabled in your store settings, customers will be sent emails trying to reactivate their subscription.
  160. If you don't have dunning enabled the subscription will remain "unpaid".
  161. */
  162. return (
  163. <>
  164. <p className="mb-2">We haven&apos;t been able to make a successful payment and your subscription is currently marked as unpaid.</p>
  165. <p className="mb-6">Please update your billing information to regain access.</p>
  166. <p><UpdateBillingLink subscription={subscription} elementType="button" /></p>
  167. <hr className="my-8" />
  168. <p><CancelLink subscription={subscription} setSubscription={setSubscription} /></p>
  169. </>
  170. )
  171. }
  172. export const ExpiredSubscription = ({ subscription, plans, setSubscription }:
  173. { subscription: SubscriptionState, plans: Plan[], setSubscription: Dispatch<SetStateAction<SubscriptionState>> }
  174. ) => {
  175. return (
  176. <>
  177. <p className="mb-2">Your subscription expired on {formatDate(subscription?.expiryDate)}.</p>
  178. <p className="mb-2">Please create a new subscription to regain access.</p>
  179. <hr className="my-8" />
  180. <PlanCards subscription={subscription} plans={plans} setSubscription={setSubscription} />
  181. </>
  182. )
  183. }
  184. function formatDate(date?: Date | null) {
  185. if (!date) return ''
  186. return new Date(date).toLocaleString('en-US', {
  187. month: 'short',
  188. day: "2-digit",
  189. year: 'numeric'
  190. })
  191. }