diff --git a/package.json b/package.json
index 6c2351a7d0..fc06e93a5d 100644
--- a/package.json
+++ b/package.json
@@ -123,6 +123,7 @@
"@types/js-cookie": "3.0.6",
"@types/lodash": "4.17.6",
"@types/node": "^22.9.0",
+ "@types/pluralize": "^0.0.33",
"@types/prismjs": "^1.26.4",
"@types/prop-types": "15.7.12",
"@types/qs": "6.9.15",
diff --git a/src/pages/AccountSettings/AccountSettings.test.jsx b/src/pages/AccountSettings/AccountSettings.test.jsx
index c67e650a45..de90d7e030 100644
--- a/src/pages/AccountSettings/AccountSettings.test.jsx
+++ b/src/pages/AccountSettings/AccountSettings.test.jsx
@@ -38,6 +38,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
}
diff --git a/src/pages/AccountSettings/AccountSettingsSideMenu.test.jsx b/src/pages/AccountSettings/AccountSettingsSideMenu.test.jsx
index d47e234867..b1622b36f3 100644
--- a/src/pages/AccountSettings/AccountSettingsSideMenu.test.jsx
+++ b/src/pages/AccountSettings/AccountSettingsSideMenu.test.jsx
@@ -26,6 +26,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
}
diff --git a/src/pages/MembersPage/MembersActivation/Activation/Activation.jsx b/src/pages/MembersPage/MembersActivation/Activation/Activation.jsx
index 2fcd8cbd0f..846d92757d 100644
--- a/src/pages/MembersPage/MembersActivation/Activation/Activation.jsx
+++ b/src/pages/MembersPage/MembersActivation/Activation/Activation.jsx
@@ -1,3 +1,4 @@
+import pluralize from 'pluralize'
import { useParams } from 'react-router-dom'
import { useAccountDetails } from 'services/account/useAccountDetails'
@@ -74,6 +75,9 @@ function Activation() {
activated members of{' '}
{planQuantity} available
seats{' '}
+ {planData?.plan?.freeSeatCount
+ ? `(${planData?.plan?.freeSeatCount} free ${pluralize('seat', planData?.plan?.freeSeatCount)} included) `
+ : ''}
{accountDetails && (
{
expect(availableSeats).toBeInTheDocument()
})
+ it('displays number of plan free seats', async () => {
+ setup()
+
+ render(, { wrapper: wrapper() })
+
+ const freeSeats = await screen.findByText(/2 free seats included/)
+ expect(freeSeats).toBeInTheDocument()
+ })
+
it('displays change plan link', async () => {
setup()
diff --git a/src/pages/MembersPage/MembersActivation/MembersActivation.test.jsx b/src/pages/MembersPage/MembersActivation/MembersActivation.test.jsx
index 564cb42401..6364ef3f4a 100644
--- a/src/pages/MembersPage/MembersActivation/MembersActivation.test.jsx
+++ b/src/pages/MembersPage/MembersActivation/MembersActivation.test.jsx
@@ -39,6 +39,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 1,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/MembersPage/MembersList/MembersTable/MembersTable.test.tsx b/src/pages/MembersPage/MembersList/MembersTable/MembersTable.test.tsx
index d436cadc75..8d3d0c31d6 100644
--- a/src/pages/MembersPage/MembersList/MembersTable/MembersTable.test.tsx
+++ b/src/pages/MembersPage/MembersList/MembersTable/MembersTable.test.tsx
@@ -126,6 +126,7 @@ const mockPlanData = {
trialEndDate: '',
trialTotalDays: 0,
pretrialUsersCount: 0,
+ freeSeatCount: 0,
}
const server = setupServer()
diff --git a/src/pages/OwnerPage/HeaderBanners/ExceededUploadsAlert/ExceededUploadsAlert.test.jsx b/src/pages/OwnerPage/HeaderBanners/ExceededUploadsAlert/ExceededUploadsAlert.test.jsx
index 1020e7e610..6c55e4277a 100644
--- a/src/pages/OwnerPage/HeaderBanners/ExceededUploadsAlert/ExceededUploadsAlert.test.jsx
+++ b/src/pages/OwnerPage/HeaderBanners/ExceededUploadsAlert/ExceededUploadsAlert.test.jsx
@@ -36,6 +36,7 @@ const mockPlanDataResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/OwnerPage/HeaderBanners/HeaderBanners.test.jsx b/src/pages/OwnerPage/HeaderBanners/HeaderBanners.test.jsx
index d01b33bc09..5d060c73fa 100644
--- a/src/pages/OwnerPage/HeaderBanners/HeaderBanners.test.jsx
+++ b/src/pages/OwnerPage/HeaderBanners/HeaderBanners.test.jsx
@@ -39,6 +39,7 @@ const mockPlanDataResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/OwnerPage/HeaderBanners/ReachingUploadLimitAlert/ReachingUploadLimitAlert.test.jsx b/src/pages/OwnerPage/HeaderBanners/ReachingUploadLimitAlert/ReachingUploadLimitAlert.test.jsx
index 83a17a8e5d..98f934d8be 100644
--- a/src/pages/OwnerPage/HeaderBanners/ReachingUploadLimitAlert/ReachingUploadLimitAlert.test.jsx
+++ b/src/pages/OwnerPage/HeaderBanners/ReachingUploadLimitAlert/ReachingUploadLimitAlert.test.jsx
@@ -42,6 +42,7 @@ const mockPlanDataResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
}
diff --git a/src/pages/OwnerPage/Tabs/TrialReminder/TrialReminder.test.tsx b/src/pages/OwnerPage/Tabs/TrialReminder/TrialReminder.test.tsx
index 3aa5c66836..715e20d1ea 100644
--- a/src/pages/OwnerPage/Tabs/TrialReminder/TrialReminder.test.tsx
+++ b/src/pages/OwnerPage/Tabs/TrialReminder/TrialReminder.test.tsx
@@ -38,6 +38,7 @@ const mockResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isSentryPlan: false,
diff --git a/src/pages/PlanPage/subRoutes/CancelPlanPage/CancelPlanPage.test.tsx b/src/pages/PlanPage/subRoutes/CancelPlanPage/CancelPlanPage.test.tsx
index f19687315d..db68ba6342 100644
--- a/src/pages/PlanPage/subRoutes/CancelPlanPage/CancelPlanPage.test.tsx
+++ b/src/pages/PlanPage/subRoutes/CancelPlanPage/CancelPlanPage.test.tsx
@@ -19,7 +19,7 @@ vi.mock('./subRoutes/TeamPlanSpecialOffer', () => ({
const teamPlans = [
{
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -29,7 +29,7 @@ const teamPlans = [
},
{
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -85,6 +85,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/DowngradePlan/DowngradePlan.test.jsx b/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/DowngradePlan/DowngradePlan.test.jsx
index 24aeee22b2..0076e9b33c 100644
--- a/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/DowngradePlan/DowngradePlan.test.jsx
+++ b/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/DowngradePlan/DowngradePlan.test.jsx
@@ -47,6 +47,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 5,
+ freeSeatCount: 0,
hasSeatsLeft: false,
},
},
diff --git a/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/TeamPlanSpecialOffer/TeamPlanCard/TeamPlanCard.test.tsx b/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/TeamPlanSpecialOffer/TeamPlanCard/TeamPlanCard.test.tsx
index 2cbfcab47b..84bfa09673 100644
--- a/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/TeamPlanSpecialOffer/TeamPlanCard/TeamPlanCard.test.tsx
+++ b/src/pages/PlanPage/subRoutes/CancelPlanPage/subRoutes/TeamPlanSpecialOffer/TeamPlanCard/TeamPlanCard.test.tsx
@@ -87,7 +87,7 @@ const mockAvailablePlans = [
},
{
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -97,7 +97,7 @@ const mockAvailablePlans = [
},
{
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx
index 1403f1d69b..26322956d0 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx
@@ -12,6 +12,8 @@ interface URLParams {
owner: string
}
+export const MONTHS_PER_YEAR = 12
+
function BillingDetails() {
const { provider, owner } = useParams()
const { data: accountDetails } = useAccountDetails({
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/CardInformation.jsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/CardInformation.jsx
index 278d8c3be3..9d0fe78290 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/CardInformation.jsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/CardInformation.jsx
@@ -33,7 +33,7 @@ const cardBrand = {
},
}
-function CardInformation({ subscriptionDetail, card }) {
+function CardInformation({ subscriptionDetail, card, nextBillPrice }) {
const typeCard = cardBrand[card?.brand] ?? cardBrand?.fallback
let nextBilling = null
@@ -61,7 +61,11 @@ function CardInformation({ subscriptionDetail, card }) {
{nextBilling && (
Your next billing date is{' '}
- {nextBilling}.
+
+ {nextBilling}
+ {nextBillPrice ? ` for ${nextBillPrice}` : ''}
+
+ .
)}
@@ -76,6 +80,7 @@ CardInformation.propTypes = {
expMonth: PropTypes.number.isRequired,
expYear: PropTypes.number.isRequired,
}).isRequired,
+ nextBillPrice: PropTypes.string,
}
export default CardInformation
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx
index 56699229b8..ef82960af5 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx
@@ -1,8 +1,18 @@
import PropTypes from 'prop-types'
-import { useState } from 'react'
+import { useMemo, useState } from 'react'
import { accountDetailsPropType } from 'services/account/propTypes'
-import { formatTimestampToCalendarDate } from 'shared/utils/billing'
+import { usePlanData } from 'services/account/usePlanData'
+import {
+ BillingRate,
+ formatNumberToUSD,
+ formatTimestampToCalendarDate,
+} from 'shared/utils/billing'
+import {
+ calculatePriceProPlan,
+ calculatePriceSentryPlan,
+ calculatePriceTeamPlan,
+} from 'shared/utils/upgradeForm'
import A from 'ui/A'
import Button from 'ui/Button'
import Icon from 'ui/Icon'
@@ -10,18 +20,61 @@ import Icon from 'ui/Icon'
import BankInformation from './BankInformation'
import CardInformation from './CardInformation'
import PaymentMethodForm from './PaymentMethodForm'
+
+import { MONTHS_PER_YEAR } from '../BillingDetails'
function PaymentCard({ accountDetails, provider, owner }) {
const [isFormOpen, setIsFormOpen] = useState(false)
+ const { data: planData } = usePlanData({
+ provider,
+ owner,
+ })
const subscriptionDetail = accountDetails?.subscriptionDetail
const card = subscriptionDetail?.defaultPaymentMethod?.card
const usBankAccount = subscriptionDetail?.defaultPaymentMethod?.usBankAccount
-
let nextBillingDisplayDate = null
if (!subscriptionDetail?.cancelAtPeriodEnd) {
nextBillingDisplayDate = formatTimestampToCalendarDate(
subscriptionDetail?.currentPeriodEnd
)
}
+ const scheduledPhaseQuantity =
+ accountDetails?.scheduleDetail?.scheduledPhase?.quantity
+
+ const nextBillPrice = useMemo(() => {
+ const isPerYear = planData?.plan?.billingRate === BillingRate.ANNUALLY
+ let seats =
+ scheduledPhaseQuantity ??
+ (planData?.plan?.planUserCount ?? 0) -
+ (planData?.plan?.freeSeatCount ?? 0)
+ seats = Math.max(seats, 0)
+ const planBaseUnitPrice = planData?.plan?.baseUnitPrice ?? 0
+ const billPrice = planData?.plan?.isProPlan
+ ? calculatePriceProPlan({
+ seats,
+ baseUnitPrice: planBaseUnitPrice,
+ })
+ : planData?.plan?.isTeamPlan
+ ? calculatePriceTeamPlan({
+ seats,
+ baseUnitPrice: planBaseUnitPrice,
+ })
+ : calculatePriceSentryPlan({
+ seats,
+ baseUnitPrice: planBaseUnitPrice,
+ })
+
+ return formatNumberToUSD(
+ isPerYear ? billPrice * MONTHS_PER_YEAR : billPrice
+ )
+ }, [
+ planData?.plan?.billingRate,
+ planData?.plan?.baseUnitPrice,
+ planData?.plan?.planUserCount,
+ planData?.plan?.freeSeatCount,
+ planData?.plan?.isProPlan,
+ planData?.plan?.isTeamPlan,
+ scheduledPhaseQuantity,
+ ])
return (
@@ -45,7 +98,11 @@ function PaymentCard({ accountDetails, provider, owner }) {
accountDetails={accountDetails}
/>
) : card ? (
-
+
) : usBankAccount ? (
({
useUpdatePaymentMethod: vi.fn(),
useCreateStripeSetupIntent: vi.fn(),
@@ -35,11 +75,20 @@ vi.mock('services/account/useCreateStripeSetupIntent', async () => {
}
})
+beforeAll(() => {
+ server.listen()
+})
+
afterEach(() => {
+ server.resetHandlers()
queryClient.clear()
vi.clearAllMocks()
})
+afterAll(() => {
+ server.close()
+})
+
const subscriptionDetail = {
defaultPaymentMethod: {
card: {
@@ -102,16 +151,42 @@ vi.mock('@stripe/react-stripe-js', () => {
})
describe('PaymentCard', () => {
- function setup() {
- const user = userEvent.setup()
+ function setup({
+ trialStatus = TrialStatuses.NOT_STARTED,
+ planValue = mockedAccountDetails.plan.value,
+ isEnterprisePlan = false,
+ isTeamPlan = true,
+ }) {
+ const user = userEvent.setup({})
+
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ trialStatus,
+ value: planValue,
+ isEnterprisePlan,
+ isTeamPlan,
+ },
+ },
+ },
+ })
+ })
+ )
return { user }
}
describe(`when the user doesn't have any accountDetails`, () => {
it('renders the set payment method message', () => {
+ setup({})
render(
-
+ ,
+ { wrapper }
)
expect(
@@ -124,6 +199,7 @@ describe('PaymentCard', () => {
describe(`when the user doesn't have any payment method`, () => {
it('renders an error message', () => {
+ setup({})
render(
{
describe('when the user clicks on Set card', () => {
it(`doesn't render the card anymore`, async () => {
- const { user } = setup()
+ const { user } = setup({})
render(
{
})
it('renders the form', async () => {
- const { user } = setup()
+ const { user } = setup({})
render(
{
describe('when the user have a card', () => {
it('renders the card', () => {
+ setup({})
render(
{
})
it('renders the next billing', () => {
+ setup({})
render(
{
expect(screen.getByText(/December 1, 2020/)).toBeInTheDocument()
})
+
+ it('renders the next billing price', async () => {
+ setup({})
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$30.00/)).toBeInTheDocument()
+ })
+ })
+
+ describe('Pro Plan pricing', () => {
+ it('calculates Pro plan monthly billing correctly', async () => {
+ // Pro plan: baseUnitPrice 12, 4 paid seats = $48.00
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.MONTHLY,
+ baseUnitPrice: 12,
+ planUserCount: 5,
+ freeSeatCount: 1,
+ isProPlan: true,
+ isTeamPlan: false,
+ isSentryPlan: false,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$48.00/)).toBeInTheDocument()
+ })
+ })
+
+ it('calculates Pro plan annual billing correctly', async () => {
+ // Pro plan: baseUnitPrice 10, 3 paid seats × 12 = $360.00
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.ANNUALLY,
+ baseUnitPrice: 10,
+ planUserCount: 4,
+ freeSeatCount: 1,
+ isProPlan: true,
+ isTeamPlan: false,
+ isSentryPlan: false,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$360.00/)).toBeInTheDocument()
+ })
+ })
+ })
+
+ describe('Team Plan pricing', () => {
+ it('calculates Team plan monthly billing correctly', async () => {
+ // Team plan: baseUnitPrice 6, 8 paid seats = $48.00
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.MONTHLY,
+ baseUnitPrice: 6,
+ planUserCount: 10,
+ freeSeatCount: 2,
+ isProPlan: false,
+ isTeamPlan: true,
+ isSentryPlan: false,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$48.00/)).toBeInTheDocument()
+ })
+ })
+
+ it('calculates Team plan annual billing correctly', async () => {
+ // Team plan: baseUnitPrice 5, 7 paid seats × 12 = $420.00
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.ANNUALLY,
+ baseUnitPrice: 5,
+ planUserCount: 9,
+ freeSeatCount: 2,
+ isProPlan: false,
+ isTeamPlan: true,
+ isSentryPlan: false,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$420.00/)).toBeInTheDocument()
+ })
+ })
+ })
+
+ describe('Sentry Plan pricing', () => {
+ it('calculates Sentry plan monthly billing with 5 or fewer seats correctly', async () => {
+ // Sentry plan: 5 seats = $29.00 (base price)
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.MONTHLY,
+ baseUnitPrice: 12,
+ planUserCount: 5,
+ freeSeatCount: 0,
+ isProPlan: false,
+ isTeamPlan: false,
+ isSentryPlan: true,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$29.00/)).toBeInTheDocument()
+ })
+ })
+
+ it('calculates Sentry plan monthly billing with more than 5 seats correctly', async () => {
+ // Sentry plan: 5 seats included + 3 additional seats × $12 = $29 + $36 = $65.00
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.MONTHLY,
+ baseUnitPrice: 12,
+ planUserCount: 8,
+ freeSeatCount: 0,
+ isProPlan: false,
+ isTeamPlan: false,
+ isSentryPlan: true,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$65.00/)).toBeInTheDocument()
+ })
+ })
+
+ it('calculates Sentry plan annual billing with 5 or fewer seats correctly', async () => {
+ // Sentry plan: 5 seats × 12 months = $29 × 12 = $348.00
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.ANNUALLY,
+ baseUnitPrice: 10,
+ planUserCount: 5,
+ freeSeatCount: 0,
+ isProPlan: false,
+ isTeamPlan: false,
+ isSentryPlan: true,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$348.00/)).toBeInTheDocument()
+ })
+ })
+
+ it('calculates Sentry plan annual billing with more than 5 seats correctly', async () => {
+ // Sentry plan: (5 seats included + 2 additional seats × $10) × 12 = ($29 + $20) × 12 = $588.00
+ server.use(
+ graphql.query('GetPlanData', () => {
+ return HttpResponse.json({
+ data: {
+ owner: {
+ hasPrivateRepos: true,
+ plan: {
+ ...mockPlanData,
+ billingRate: BillingRate.ANNUALLY,
+ baseUnitPrice: 10,
+ planUserCount: 7,
+ freeSeatCount: 0,
+ isProPlan: false,
+ isTeamPlan: false,
+ isSentryPlan: true,
+ },
+ },
+ },
+ })
+ })
+ )
+
+ render(
+ ,
+ { wrapper }
+ )
+
+ await waitFor(() => {
+ expect(screen.getByText(/for \$588.00/)).toBeInTheDocument()
+ })
+ })
+ })
})
describe('when the user has a US bank account', () => {
it('renders the bank account details', () => {
+ setup({})
const testAccountDetails = {
...accountDetails,
subscriptionDetail: {
@@ -254,6 +659,7 @@ describe('PaymentCard', () => {
describe('when the subscription is set to expire', () => {
it(`doesn't render the next billing`, () => {
+ setup({})
render(
{
describe('when the user clicks on Edit card', () => {
it(`doesn't render the card anymore`, async () => {
- const { user } = setup()
+ const { user } = setup({})
const updatePaymentMethod = vi.fn()
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: updatePaymentMethod,
@@ -296,7 +702,7 @@ describe('PaymentCard', () => {
})
it('renders the form', async () => {
- const { user } = setup()
+ const { user } = setup({})
const updatePaymentMethod = vi.fn()
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: updatePaymentMethod,
@@ -317,7 +723,7 @@ describe('PaymentCard', () => {
describe('when submitting', () => {
it('calls the service to update the card', async () => {
- const { user } = setup()
+ const { user } = setup({})
const updatePaymentMethod = vi.fn()
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: updatePaymentMethod,
@@ -344,7 +750,7 @@ describe('PaymentCard', () => {
describe('when the user clicks on cancel', () => {
it(`doesn't render the form anymore`, async () => {
- const { user } = setup()
+ const { user } = setup({})
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: vi.fn(),
isLoading: false,
@@ -370,7 +776,7 @@ describe('PaymentCard', () => {
describe('when there is an error in the form', () => {
it('renders the error', async () => {
- const { user } = setup()
+ const { user } = setup({})
const randomError = 'not rich enough'
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: vi.fn(),
@@ -399,7 +805,7 @@ describe('PaymentCard', () => {
describe('when the form is loading', () => {
it('has the error and save button disabled', async () => {
- const { user } = setup()
+ const { user } = setup({})
mocks.useUpdatePaymentMethod.mockReturnValue({
mutate: vi.fn(),
isLoading: true,
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.test.tsx
index e6c9b0a66a..3885cbec88 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.test.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.test.tsx
@@ -217,7 +217,7 @@ describe('CurrentOrgPlan', () => {
const updatedAlert = await screen.findByText('Plan successfully updated')
expect(updatedAlert).toBeInTheDocument()
expect(
- screen.getByText(/with a monthly subscription for 34 seats/)
+ screen.getByText(/with a monthly subscription for 34 paid seats/)
).toBeInTheDocument()
})
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.tsx
index 5cede8bdd3..95a673f7d1 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.tsx
@@ -91,7 +91,7 @@ function CurrentOrgPlan() {
Plan successfully updated
The start date is {scheduleStart} with a monthly
- subscription for {scheduledPhase.quantity} seats.
+ subscription for {scheduledPhase.quantity} paid seats.
>
) : (
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/CurrentPlanCard.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/CurrentPlanCard.test.tsx
index 73ae27b27c..48a64f779c 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/CurrentPlanCard.test.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/CurrentPlanCard.test.tsx
@@ -156,6 +156,7 @@ describe('CurrentPlanCard', () => {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
monthlyUploadLimit: 100,
}
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/FreePlanCard.test.jsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/FreePlanCard.test.jsx
index bc63496d0b..d3dcb828b2 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/FreePlanCard.test.jsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/FreePlanCard.test.jsx
@@ -93,7 +93,7 @@ const allPlans = [
},
{
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -103,7 +103,7 @@ const allPlans = [
},
{
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -170,6 +170,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isProPlan: false,
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/PlanUpgradeTeam/PlanUpgradeTeam.test.jsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/PlanUpgradeTeam/PlanUpgradeTeam.test.jsx
index 9e69a0bd21..9045c4a113 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/PlanUpgradeTeam/PlanUpgradeTeam.test.jsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/PlanUpgradeTeam/PlanUpgradeTeam.test.jsx
@@ -31,6 +31,7 @@ const mockPlanBasic = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
}
@@ -53,6 +54,7 @@ const mockPlanPro = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 4,
+ freeSeatCount: 0,
hasSeatsLeft: true,
}
@@ -75,6 +77,7 @@ const mockPlanTrialing = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 4,
+ freeSeatCount: 0,
hasSeatsLeft: true,
}
@@ -155,7 +158,7 @@ const mockAvailablePlans = [
},
{
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -165,7 +168,7 @@ const mockAvailablePlans = [
},
{
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/ProPlanSubheading/ProPlanSubheading.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/ProPlanSubheading/ProPlanSubheading.test.tsx
index aa1d2da344..5d80b11728 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/ProPlanSubheading/ProPlanSubheading.test.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/FreePlanCard/ProPlanSubheading/ProPlanSubheading.test.tsx
@@ -22,6 +22,7 @@ const mockResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: true,
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.test.tsx
index 3fa5894bdd..4b3731513f 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.test.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.test.tsx
@@ -40,6 +40,7 @@ const mockProPlan = {
baseUnitPrice: 0,
benefits: ['Unlimited public repositories', 'Unlimited private repositories'],
planUserCount: 5,
+ freeSeatCount: 0,
monthlyUploadLimit: null,
trialStatus: TrialStatuses.CANNOT_TRIAL,
trialStartDate: '',
@@ -62,6 +63,7 @@ const mockTeamPlan = {
baseUnitPrice: 123,
benefits: ['Team benefits', 'Unlimited private repositories'],
planUserCount: 8,
+ freeSeatCount: 0,
monthlyUploadLimit: 2500,
trialStatus: TrialStatuses.CANNOT_TRIAL,
trialStartDate: '',
@@ -229,13 +231,15 @@ describe('PaidPlanCard', () => {
expect(benefitsList).toBeInTheDocument()
})
- it('renders seats number', async () => {
+ it('renders seats number without free/paid distinction', async () => {
render(, {
wrapper,
})
const seats = await screen.findByText(/plan has 8 seats/)
expect(seats).toBeInTheDocument()
+ const seatsWithPaid = screen.queryByText(/plan has 8 seats with \d+ paid/)
+ expect(seatsWithPaid).not.toBeInTheDocument()
})
it('renders the plan pricing', async () => {
@@ -255,6 +259,45 @@ describe('PaidPlanCard', () => {
const actionsBilling = await screen.findByText(/Actions Billing/)
expect(actionsBilling).toBeInTheDocument()
})
+
+ it('does not renders the free seats number if there are no free seats', () => {
+ render(, {
+ wrapper,
+ })
+
+ const planValue = screen.queryByText(
+ /Current plan \(\d+ free seats included\)/
+ )
+ expect(planValue).not.toBeInTheDocument()
+ const seats = screen.queryByText(/plan has 8 seats with \d+ free/)
+ expect(seats).not.toBeInTheDocument()
+ })
+ })
+
+ describe('When rendered with free seats', () => {
+ beforeEach(() => {
+ setup({ plan: { ...mockTeamPlan, freeSeatCount: 2 } })
+ })
+
+ it('renders the free seats number', async () => {
+ render(, {
+ wrapper,
+ })
+
+ const planValue = await screen.findByText(
+ /Current plan \(2 free seats included\)/
+ )
+ expect(planValue).toBeInTheDocument()
+ })
+
+ it('specifies the number of paid seats', async () => {
+ render(, {
+ wrapper,
+ })
+
+ const seats = await screen.findByText(/plan has 8 seats with 6 paid/)
+ expect(seats).toBeInTheDocument()
+ })
})
describe('When rendered with scheduled details', () => {
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.tsx
index c1938be2c5..6b54214049 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.tsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/PaidPlanCard/PaidPlanCard.tsx
@@ -1,5 +1,6 @@
import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5'
import isNumber from 'lodash/isNumber'
+import pluralize from 'pluralize'
import { useParams } from 'react-router-dom'
import { PlanPageDataQueryOpts } from 'pages/PlanPage/queries/PlanPageDataQueryOpts'
@@ -34,6 +35,7 @@ function PaidPlanCard() {
const benefits = plan?.benefits
const baseUnitPrice = plan?.baseUnitPrice
const seats = plan?.planUserCount
+ const freeSeats = plan?.freeSeatCount ?? 0
const numberOfUploads = ownerData?.numberOfUploads
return (
@@ -41,7 +43,12 @@ function PaidPlanCard() {
{marketingName} plan
- Current plan
+
+ Current plan
+ {freeSeats
+ ? ` (${freeSeats} free ${pluralize('seat', freeSeats)} included)`
+ : ''}
+
@@ -64,6 +71,9 @@ function PaidPlanCard() {
{seats ? (
plan has {seats} seats
+ {freeSeats && seats - freeSeats > 0
+ ? ` with ${seats - freeSeats} paid`
+ : ''}
) : null}
diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/shared/ActionsBilling/ActionsBilling.test.jsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/shared/ActionsBilling/ActionsBilling.test.jsx
index a8f84e89b5..9c62acd828 100644
--- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/shared/ActionsBilling/ActionsBilling.test.jsx
+++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentPlanCard/shared/ActionsBilling/ActionsBilling.test.jsx
@@ -174,6 +174,7 @@ const mockTrialData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: true,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/PlanDetailsControls/PlanDetailsControls.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/PlanDetailsControls/PlanDetailsControls.test.tsx
index bc1282ddab..0c1e18b676 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/PlanDetailsControls/PlanDetailsControls.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/PlanDetailsControls/PlanDetailsControls.test.tsx
@@ -76,7 +76,7 @@ const sentryPlanYear = {
const teamPlanMonth = {
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -87,7 +87,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/ProPlanDetails/ProPlanDetails.test.jsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/ProPlanDetails/ProPlanDetails.test.jsx
index 9ff3ae38f3..98278d9769 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/ProPlanDetails/ProPlanDetails.test.jsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/ProPlanDetails/ProPlanDetails.test.jsx
@@ -142,6 +142,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isProPlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/SentryPlanDetails/SentryPlanDetails.test.jsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/SentryPlanDetails/SentryPlanDetails.test.jsx
index cc767e0252..cd5b30a046 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/SentryPlanDetails/SentryPlanDetails.test.jsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/SentryPlanDetails/SentryPlanDetails.test.jsx
@@ -139,6 +139,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isProPlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/TeamPlanDetails/TeamPlanDetails.test.jsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/TeamPlanDetails/TeamPlanDetails.test.jsx
index bb2a9138a1..f5fa41cf87 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/TeamPlanDetails/TeamPlanDetails.test.jsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/TeamPlanDetails/TeamPlanDetails.test.jsx
@@ -17,7 +17,7 @@ vi.mock('shared/plan/ScheduledPlanDetails', () => ({
const teamPlanMonth = {
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -28,7 +28,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -100,6 +100,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/UpgradeDetails.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/UpgradeDetails.test.tsx
index 060ff04df7..c8792a5d17 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/UpgradeDetails.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeDetails/UpgradeDetails.test.tsx
@@ -82,7 +82,7 @@ const sentryPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -93,7 +93,7 @@ const teamPlanYear = {
const teamPlanMonth = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/Controller.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/Controller.test.tsx
index 4eaa554bcb..b4dda1c2d5 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/Controller.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/Controller.test.tsx
@@ -84,7 +84,7 @@ const sentryPlanYear = {
const teamPlanMonth = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -95,7 +95,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/BillingOptions/BillingOptions.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/BillingOptions/BillingOptions.test.tsx
index e9964e6fe5..8696efe3fb 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/BillingOptions/BillingOptions.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/BillingOptions/BillingOptions.test.tsx
@@ -73,6 +73,7 @@ const mockPlanDataResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.test.tsx
index c0cd5a6e34..95817776a2 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.test.tsx
@@ -126,9 +126,7 @@ describe('PriceCallout', () => {
const mockAccountDetails = {
subscriptionDetail: {
- latestInvoice: {
- periodEnd: periodEnd,
- },
+ currentPeriodEnd: periodEnd,
},
}
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.tsx
index d077122bb9..77644c1753 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/PriceCallout/PriceCallout.tsx
@@ -2,6 +2,7 @@ import { Fragment } from 'react'
import { UseFormSetValue } from 'react-hook-form'
import { useParams } from 'react-router-dom'
+import { MONTHS_PER_YEAR } from 'pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails'
import { useAccountDetails } from 'services/account/useAccountDetails'
import {
IndividualPlan,
@@ -60,12 +61,16 @@ const PriceCallout: React.FC = ({
{formatNumberToUSD(perYearPrice)}
- /month billed annually at {formatNumberToUSD(perYearPrice * 12)}
+ /month billed annually at{' '}
+ {formatNumberToUSD(perYearPrice * MONTHS_PER_YEAR)}
🎉 You{' '}
- save {formatNumberToUSD((perMonthPrice - perYearPrice) * 12)}
+ save{' '}
+ {formatNumberToUSD(
+ (perMonthPrice - perYearPrice) * MONTHS_PER_YEAR
+ )}
{' '}
with annual billing
{nextBillingDate && (
@@ -92,7 +97,10 @@ const PriceCallout: React.FC = ({
You could{' '}
- save {formatNumberToUSD((perMonthPrice - perYearPrice) * 12)}
+ save{' '}
+ {formatNumberToUSD(
+ (perMonthPrice - perYearPrice) * MONTHS_PER_YEAR
+ )}
{' '}
a year with annual billing
{nextBillingDate && (
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/ProPlanController.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/ProPlanController.test.tsx
index ff5c68874a..f9d8d0147c 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/ProPlanController.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/ProPlanController/ProPlanController.test.tsx
@@ -136,6 +136,7 @@ const mockPlanDataResponseMonthly = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isProPlan: true,
@@ -158,6 +159,7 @@ const mockPlanDataResponseYearly = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isProPlan: true,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/BillingOptions/BillingOptions.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/BillingOptions/BillingOptions.test.tsx
index 128b712580..54486fb35f 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/BillingOptions/BillingOptions.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/BillingOptions/BillingOptions.test.tsx
@@ -73,6 +73,7 @@ const mockPlanDataResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.test.tsx
index de2f5f9cec..0f20b2da65 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.test.tsx
@@ -98,9 +98,7 @@ describe('PriceCallout', () => {
const mockAccountDetails = {
subscriptionDetail: {
- latestInvoice: {
- periodEnd: periodEnd,
- },
+ currentPeriodEnd: periodEnd,
},
}
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.tsx
index deed6a31c0..e01c0fa234 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/PriceCallout/PriceCallout.tsx
@@ -2,6 +2,7 @@ import { Fragment } from 'react'
import { UseFormSetValue } from 'react-hook-form'
import { useParams } from 'react-router-dom'
+import { MONTHS_PER_YEAR } from 'pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails'
import { useAccountDetails } from 'services/account/useAccountDetails'
import {
IndividualPlan,
@@ -64,14 +65,15 @@ const PriceCallout: React.FC = ({
{formatNumberToUSD(perYearPrice)}
- /month billed annually at {formatNumberToUSD(perYearPrice * 12)}
+ /month billed annually at{' '}
+ {formatNumberToUSD(perYearPrice * MONTHS_PER_YEAR)}
🎉 You{' '}
save{' '}
{formatNumberToUSD(
- nonBundledCost + (perMonthPrice - perYearPrice) * 12
+ nonBundledCost + (perMonthPrice - perYearPrice) * MONTHS_PER_YEAR
)}
{' '}
with the Sentry bundle plan
@@ -110,7 +112,9 @@ const PriceCallout: React.FC = ({
, save an{' '}
additional{' '}
- {formatNumberToUSD((perMonthPrice - perYearPrice) * 12)}
+ {formatNumberToUSD(
+ (perMonthPrice - perYearPrice) * MONTHS_PER_YEAR
+ )}
{' '}
a year with annual billing
{nextBillingDate && (
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/SentryPlanController.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/SentryPlanController.test.tsx
index c92ec5e5d8..580928996b 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/SentryPlanController.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/SentryPlanController/SentryPlanController.test.tsx
@@ -120,6 +120,7 @@ const mockPlanDataResponseMonthly = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
@@ -142,6 +143,7 @@ const mockPlanDataResponseYearly = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/BillingOptions/BillingOptions.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/BillingOptions/BillingOptions.test.tsx
index f92d0e5857..0c7575d698 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/BillingOptions/BillingOptions.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/BillingOptions/BillingOptions.test.tsx
@@ -28,7 +28,7 @@ const freePlan = {
const teamPlanMonthly = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Team',
monthlyUploadLimit: 2500,
@@ -39,7 +39,7 @@ const teamPlanMonthly = {
const teamPlanYearly = {
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Team',
monthlyUploadLimit: 2500,
@@ -64,6 +64,7 @@ const mockPlanDataResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isFreePlan: false,
isProPlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/ErrorBanner/ErrorBanner.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/ErrorBanner/ErrorBanner.test.tsx
index a9aff67bf3..414d7cfb84 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/ErrorBanner/ErrorBanner.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/ErrorBanner/ErrorBanner.test.tsx
@@ -30,7 +30,7 @@ const basicPlan = {
const teamPlanMonth = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -42,7 +42,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -140,6 +140,7 @@ describe('ErrorBanner', () => {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
isEnterprisePlan: false,
isFreePlan: false,
isProPlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.test.tsx
index 7f7e90660f..4a59b677c0 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.test.tsx
@@ -109,9 +109,7 @@ describe('PriceCallout', () => {
const mockAccountDetails = {
subscriptionDetail: {
- latestInvoice: {
- periodEnd: periodEnd,
- },
+ currentPeriodEnd: periodEnd,
},
}
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.tsx
index 980d23c870..4bf0c56a7e 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/PriceCallout/PriceCallout.tsx
@@ -3,6 +3,7 @@ import { Fragment } from 'react'
import { UseFormSetValue } from 'react-hook-form'
import { useParams } from 'react-router-dom'
+import { MONTHS_PER_YEAR } from 'pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails'
import { useAccountDetails } from 'services/account/useAccountDetails'
import {
IndividualPlan,
@@ -61,12 +62,16 @@ const PriceCallout: React.FC = ({
{formatNumberToUSD(perYearPrice)}
- /month billed annually at {formatNumberToUSD(perYearPrice * 12)}
+ /month billed annually at{' '}
+ {formatNumberToUSD(perYearPrice * MONTHS_PER_YEAR)}
🎉 You{' '}
- save {formatNumberToUSD((perMonthPrice - perYearPrice) * 12)}
+ save{' '}
+ {formatNumberToUSD(
+ (perMonthPrice - perYearPrice) * MONTHS_PER_YEAR
+ )}
{' '}
with annual billing
{nextBillingDate && (
@@ -93,7 +98,10 @@ const PriceCallout: React.FC = ({
You could{' '}
- save {formatNumberToUSD((perMonthPrice - perYearPrice) * 12)}
+ save{' '}
+ {formatNumberToUSD(
+ (perMonthPrice - perYearPrice) * MONTHS_PER_YEAR
+ )}
{' '}
a year with annual billing
{nextBillingDate && (
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/TeamPlanController.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/TeamPlanController.test.tsx
index 6df7cf9543..7d5f793a7e 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/TeamPlanController.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/Controllers/TeamPlanController/TeamPlanController.test.tsx
@@ -43,7 +43,7 @@ const basicPlan = {
const teamPlanMonth = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -55,7 +55,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -120,6 +120,7 @@ const mockPlanDataResponseMonthly = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
@@ -142,6 +143,7 @@ const mockPlanDataResponseYearly = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PersonalOrgWarning.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PersonalOrgWarning.tsx
index ef33a19978..8509dc2446 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PersonalOrgWarning.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PersonalOrgWarning.tsx
@@ -12,9 +12,6 @@ export function PersonalOrgWarning() {
const { data } = useUser()
const username = data?.user.username
- console.log('owner', owner)
- console.log('username', username)
-
if (owner !== username) {
return null
}
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.test.tsx
index 1c50244cb5..e2c904d81e 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.test.tsx
@@ -93,7 +93,7 @@ const sentryPlanYear = {
const teamPlanMonth = {
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -104,7 +104,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -203,6 +203,7 @@ describe('PlanTypeOptions', () => {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
isEnterprisePlan: false,
isFreePlan: false,
isProPlan: false,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.tsx
index 320ce2ff91..b2b8af96cf 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/PlanTypeOptions/PlanTypeOptions.tsx
@@ -110,7 +110,7 @@ const PlanTypeOptions: React.FC = ({
{planOption === TierName.TEAM && (
- Up to {TEAM_PLAN_MAX_ACTIVE_USERS} users
+ Up to {TEAM_PLAN_MAX_ACTIVE_USERS} paid users
)}
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateBlurb/UpdateBlurb.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateBlurb/UpdateBlurb.test.tsx
index 3fc9ed029b..e0b2a9cb66 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateBlurb/UpdateBlurb.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateBlurb/UpdateBlurb.test.tsx
@@ -14,6 +14,7 @@ const planChunk = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 2,
+ freeSeatCount: 0,
isEnterprisePlan: false,
isFreePlan: false,
isSentryPlan: false,
@@ -41,7 +42,7 @@ const proPlanYear = {
const teamPlanYear = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -53,7 +54,7 @@ const teamPlanYear = {
const teamPlanMonth = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -65,7 +66,7 @@ const teamPlanMonth = {
const freePlan = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateButton/UpdateButton.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateButton/UpdateButton.test.tsx
index f944be88fa..a14ce477c4 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateButton/UpdateButton.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpdateButton/UpdateButton.test.tsx
@@ -90,12 +90,13 @@ afterAll(() => {
const mockPlanBasic = {
value: Plans.USERS_DEVELOPER,
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: 'annually',
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
hasSeatsLeft: true,
planUserCount: 1,
+ freeSeatCount: 0,
isFreePlan: true,
isTeamPlan: false,
}
@@ -103,12 +104,13 @@ const mockPlanBasic = {
const mockPlanProMonthly = {
value: Plans.USERS_PR_INAPPM,
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: 'annually',
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
hasSeatsLeft: true,
planUserCount: 4,
+ freeSeatCount: 0,
isFreePlan: false,
isTeamPlan: false,
}
@@ -116,12 +118,13 @@ const mockPlanProMonthly = {
const mockPlanTeamMonthly = {
value: Plans.USERS_TEAMM,
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: 'annually',
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
hasSeatsLeft: true,
planUserCount: 3,
+ freeSeatCount: 0,
isFreePlan: false,
isTeamPlan: true,
}
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpgradeForm.test.tsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpgradeForm.test.tsx
index e1ad1549de..2c07b9eb1d 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpgradeForm.test.tsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpgradeForm.test.tsx
@@ -122,7 +122,7 @@ const sentryPlanYear = {
const teamPlanMonth = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -133,7 +133,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 4,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -228,9 +228,11 @@ const mockPlanDataResponse = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 10,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isSentryPlan: false,
+ isFreePlan: false,
}
const mockUser = {
@@ -460,6 +462,7 @@ describe('UpgradeForm', () => {
value: planValue,
planUserCount,
},
+ pretrialPlan: null,
},
},
})
@@ -691,7 +694,7 @@ describe('UpgradeForm', () => {
})
describe('when updating to a team plan', () => {
- it('renders up to 10 seats text', async () => {
+ it('renders up to 10 paid users text', async () => {
const { user } = setup({
planValue: Plans.USERS_DEVELOPER,
hasTeamPlans: true,
@@ -701,7 +704,7 @@ describe('UpgradeForm', () => {
const teamOption = await screen.findByTestId('radio-team')
await user.click(teamOption)
- const auxiliaryText = await screen.findByText(/Up to 10 users/)
+ const auxiliaryText = await screen.findByText(/Up to 10 paid users/)
expect(auxiliaryText).toBeInTheDocument()
})
@@ -1072,7 +1075,7 @@ describe('UpgradeForm', () => {
})
describe('when updating to a team plan', () => {
- it('renders up to 10 seats text', async () => {
+ it('renders up to 10 paid users text', async () => {
const { user } = setup({
planValue: Plans.USERS_PR_INAPPM,
hasTeamPlans: true,
@@ -1082,7 +1085,7 @@ describe('UpgradeForm', () => {
const teamOption = await screen.findByTestId('radio-team')
await user.click(teamOption)
- const auxiliaryText = await screen.findByText(/Up to 10 users/)
+ const auxiliaryText = await screen.findByText(/Up to 10 paid users/)
expect(auxiliaryText).toBeInTheDocument()
})
@@ -1419,7 +1422,7 @@ describe('UpgradeForm', () => {
})
describe('when updating to a team plan', () => {
- it('renders up to 10 seats text', async () => {
+ it('renders up to 10 paid users text', async () => {
const { user } = setup({
planValue: Plans.USERS_PR_INAPPY,
hasTeamPlans: true,
@@ -1430,7 +1433,7 @@ describe('UpgradeForm', () => {
const teamOption = await screen.findByTestId('radio-team')
await user.click(teamOption)
- const auxiliaryText = await screen.findByText(/Up to 10 users/)
+ const auxiliaryText = await screen.findByText(/Up to 10 paid users/)
expect(auxiliaryText).toBeInTheDocument()
})
@@ -2035,7 +2038,7 @@ describe('UpgradeForm', () => {
expect(ownerTitle).toBeInTheDocument()
})
- it('renders up to 10 seats text', async () => {
+ it('renders up to 10 paid users text', async () => {
const { user } = setup({
planValue: Plans.USERS_TEAMY,
hasTeamPlans: true,
@@ -2046,7 +2049,7 @@ describe('UpgradeForm', () => {
const teamOption = await screen.findByTestId('radio-team')
await user.click(teamOption)
- const auxiliaryText = await screen.findByText(/Up to 10 users/)
+ const auxiliaryText = await screen.findByText(/Up to 10 paid users/)
expect(auxiliaryText).toBeInTheDocument()
})
@@ -2151,7 +2154,7 @@ describe('UpgradeForm', () => {
expect(update).toBeDisabled()
const error = screen.getByText(
- /Team plan is only available for 10 seats or fewer./
+ /Team plan is only available for 10 paid seats or fewer./
)
expect(error).toBeInTheDocument()
})
diff --git a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradePlanPage.test.jsx b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradePlanPage.test.jsx
index 4e11de5074..6fc492ed19 100644
--- a/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradePlanPage.test.jsx
+++ b/src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradePlanPage.test.jsx
@@ -130,7 +130,7 @@ const sentryPlanYear = {
const teamPlanMonth = {
baseUnitPrice: 6,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.MONTHLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -141,7 +141,7 @@ const teamPlanMonth = {
const teamPlanYear = {
baseUnitPrice: 5,
- benefits: ['Up to 10 users'],
+ benefits: ['Up to 10 paid users'],
billingRate: BillingRate.ANNUALLY,
marketingName: 'Users Team',
monthlyUploadLimit: 2500,
@@ -163,6 +163,7 @@ const mockPlanData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
}
@@ -387,7 +388,7 @@ describe('UpgradePlanPage', () => {
wrapper: wrapper('/plan/gh/codecov/upgrade?plan=team'),
})
- const userCount = await screen.findByText(/Up to 10 users/)
+ const userCount = await screen.findByText(/Up to 10 paid users/)
expect(userCount).toBeInTheDocument()
})
})
diff --git a/src/pages/RepoPage/ActivationAlert/ActivationAlert.test.tsx b/src/pages/RepoPage/ActivationAlert/ActivationAlert.test.tsx
index 314cc43eef..39628feaf0 100644
--- a/src/pages/RepoPage/ActivationAlert/ActivationAlert.test.tsx
+++ b/src/pages/RepoPage/ActivationAlert/ActivationAlert.test.tsx
@@ -62,6 +62,7 @@ const mockTrialData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isProPlan: false,
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.test.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.test.tsx
index 8d732521a9..c4b0bdf3ad 100644
--- a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.test.tsx
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.test.tsx
@@ -62,6 +62,7 @@ const mockTrialData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isProPlan: false,
diff --git a/src/services/account/usePlanData.test.tsx b/src/services/account/usePlanData.test.tsx
index e048cb4036..9f858b2ed2 100644
--- a/src/services/account/usePlanData.test.tsx
+++ b/src/services/account/usePlanData.test.tsx
@@ -22,6 +22,7 @@ const mockTrialData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
isEnterprisePlan: false,
isFreePlan: true,
@@ -102,6 +103,7 @@ describe('usePlanData', () => {
marketingName: 'Users Developer',
monthlyUploadLimit: 250,
planUserCount: 1,
+ freeSeatCount: 0,
pretrialUsersCount: 0,
trialEndDate: '2023-01-10T08:55:25',
trialStartDate: '2023-01-01T08:55:25',
diff --git a/src/services/account/usePlanData.ts b/src/services/account/usePlanData.ts
index 96cb7f72fc..9988624885 100644
--- a/src/services/account/usePlanData.ts
+++ b/src/services/account/usePlanData.ts
@@ -27,6 +27,7 @@ const PlanSchema = z.object({
trialStartDate: z.string().nullable(),
trialTotalDays: z.number().nullable(),
planUserCount: z.number().nullable(),
+ freeSeatCount: z.number().nullable(),
hasSeatsLeft: z.boolean(),
isEnterprisePlan: z.boolean(),
isFreePlan: z.boolean(),
@@ -86,6 +87,7 @@ export const query = `
trialStartDate
trialTotalDays
planUserCount
+ freeSeatCount
hasSeatsLeft
isEnterprisePlan
isFreePlan
diff --git a/src/shared/GlobalTopBanners/ProPlanFeedbackBanner/ProPlanFeedbackBanner.test.tsx b/src/shared/GlobalTopBanners/ProPlanFeedbackBanner/ProPlanFeedbackBanner.test.tsx
index f41198278b..551cd98ea2 100644
--- a/src/shared/GlobalTopBanners/ProPlanFeedbackBanner/ProPlanFeedbackBanner.test.tsx
+++ b/src/shared/GlobalTopBanners/ProPlanFeedbackBanner/ProPlanFeedbackBanner.test.tsx
@@ -32,6 +32,7 @@ const mockTrialData = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
hasSeatsLeft: true,
},
pretrialPlan: {
diff --git a/src/shared/GlobalTopBanners/TrialBanner/TrialBanner.test.tsx b/src/shared/GlobalTopBanners/TrialBanner/TrialBanner.test.tsx
index 7d70dc1dc4..e918ec2a47 100644
--- a/src/shared/GlobalTopBanners/TrialBanner/TrialBanner.test.tsx
+++ b/src/shared/GlobalTopBanners/TrialBanner/TrialBanner.test.tsx
@@ -30,6 +30,7 @@ const proPlanMonth = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
isEnterprisePlan: false,
isFreePlan: false,
isProPlan: true,
@@ -53,6 +54,7 @@ const trialPlan = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
isEnterprisePlan: false,
isFreePlan: false,
isProPlan: false,
@@ -76,6 +78,7 @@ const basicPlan = {
trialTotalDays: 0,
pretrialUsersCount: 0,
planUserCount: 1,
+ freeSeatCount: 0,
isFreePlan: true,
isProPlan: false,
isSentryPlan: false,
@@ -166,6 +169,7 @@ describe('TrialBanner', () => {
trialTotalDays: plan.trialTotalDays,
pretrialUsersCount: plan.pretrialUsersCount,
planUserCount: plan.planUserCount,
+ freeSeatCount: plan.freeSeatCount,
hasSeatsLeft: true,
isEnterprisePlan: plan.isEnterprisePlan,
isFreePlan: plan.isFreePlan,
diff --git a/src/shared/plan/ScheduledPlanDetails/ScheduledPlanDetails.tsx b/src/shared/plan/ScheduledPlanDetails/ScheduledPlanDetails.tsx
index 9054f300c5..2eb542c872 100644
--- a/src/shared/plan/ScheduledPlanDetails/ScheduledPlanDetails.tsx
+++ b/src/shared/plan/ScheduledPlanDetails/ScheduledPlanDetails.tsx
@@ -26,7 +26,7 @@ function ScheduledPlanDetails({
Start date {scheduleStart}
- {plan} with {quantity} seats
+ {plan} with {quantity} paid seats
)
diff --git a/src/shared/utils/billing.test.ts b/src/shared/utils/billing.test.ts
index 368ea1ea30..96490ae0ae 100644
--- a/src/shared/utils/billing.test.ts
+++ b/src/shared/utils/billing.test.ts
@@ -141,7 +141,7 @@ function getPlans() {
baseUnitPrice: 6,
monthlyUploadLimit: null,
benefits: [
- 'Up to 10 users',
+ 'Up to 10 paid users',
'Unlimited repositories',
'2500 repositories',
'Patch coverage analysis',
@@ -157,7 +157,7 @@ function getPlans() {
baseUnitPrice: 5,
monthlyUploadLimit: null,
benefits: [
- 'Up to 10 users',
+ 'Up to 10 paid users',
'Unlimited repositories',
'2500 repositories',
'Patch coverage analysis',
@@ -253,11 +253,9 @@ describe('getNextBillingDate', () => {
describe('there is a valid timestamp', () => {
it('returns formatted timestamp', () => {
const value = getNextBillingDate({
+ // @ts-expect-error - we're just testing this property we can ignore the other properties
subscriptionDetail: {
- // @ts-expect-error - we're just testing this property we can ignore the other properties
- latestInvoice: {
- periodEnd: 1660000000,
- },
+ currentPeriodEnd: 1660000000,
},
})
@@ -385,7 +383,7 @@ describe('findTeamPlans', () => {
baseUnitPrice: 6,
monthlyUploadLimit: null,
benefits: [
- 'Up to 10 users',
+ 'Up to 10 paid users',
'Unlimited repositories',
'2500 repositories',
'Patch coverage analysis',
@@ -409,7 +407,7 @@ describe('findTeamPlans', () => {
baseUnitPrice: 5,
monthlyUploadLimit: null,
benefits: [
- 'Up to 10 users',
+ 'Up to 10 paid users',
'Unlimited repositories',
'2500 repositories',
'Patch coverage analysis',
diff --git a/src/shared/utils/billing.ts b/src/shared/utils/billing.ts
index a49258660f..51170486b8 100644
--- a/src/shared/utils/billing.ts
+++ b/src/shared/utils/billing.ts
@@ -131,7 +131,7 @@ export const formatNumberToUSD = (value: number) =>
export function getNextBillingDate(
accountDetails?: z.infer | null
) {
- const timestamp = accountDetails?.subscriptionDetail?.latestInvoice?.periodEnd
+ const timestamp = accountDetails?.subscriptionDetail?.currentPeriodEnd
return timestamp ? format(fromUnixTime(timestamp), 'MMMM do, yyyy') : null
}
diff --git a/src/shared/utils/upgradeForm.test.ts b/src/shared/utils/upgradeForm.test.ts
index 0e6479efdb..e372c08650 100644
--- a/src/shared/utils/upgradeForm.test.ts
+++ b/src/shared/utils/upgradeForm.test.ts
@@ -166,6 +166,26 @@ describe('getDefaultValuesUpgradeForm', () => {
})
})
+ it('returns correct seats when free seats are present', () => {
+ const data = getDefaultValuesUpgradeForm({
+ accountDetails,
+ selectedPlan: proPlanYear,
+ plans: [teamPlanMonth],
+ plan: {
+ billingRate: BillingRate.MONTHLY,
+ value: Plans.USERS_TEAMM,
+ planUserCount: 5,
+ freeSeatCount: 2,
+ isTeamPlan: true,
+ } as Plan,
+ })
+
+ expect(data).toStrictEqual({
+ newPlan: { value: Plans.USERS_TEAMM },
+ seats: 3,
+ })
+ })
+
it('returns pro sentry plan if user is sentry upgrade', () => {
const data = getDefaultValuesUpgradeForm({
accountDetails,
@@ -214,6 +234,33 @@ describe('getDefaultValuesUpgradeForm', () => {
seats: 2,
})
})
+
+ describe('quantity calculation edge cases', () => {
+ it('handles case where freeSeatCount equals planUserCount', () => {
+ const data = getDefaultValuesUpgradeForm({
+ accountDetails,
+ selectedPlan: proPlanYear,
+ plans: [proPlanYear],
+ plan: {
+ billingRate: BillingRate.MONTHLY,
+ value: Plans.USERS_PR_INAPPM,
+ planUserCount: 3,
+ freeSeatCount: 3,
+ } as Plan,
+ })
+
+ expect(data).toStrictEqual({
+ newPlan: {
+ value: Plans.USERS_PR_INAPPM,
+ billingRate: BillingRate.MONTHLY,
+ planUserCount: 3,
+ freeSeatCount: 3,
+ },
+ // extractSeats() will be passed quantity: 0, but returns min plan seats
+ seats: 2,
+ })
+ })
+ })
})
describe('getSchema', () => {
@@ -328,7 +375,7 @@ describe('getSchema', () => {
const [issue] = response.error!.issues
expect(issue).toEqual(
expect.objectContaining({
- message: 'Team plan is only available for 10 seats or fewer.',
+ message: 'Team plan is only available for 10 paid seats or fewer.',
})
)
})
diff --git a/src/shared/utils/upgradeForm.ts b/src/shared/utils/upgradeForm.ts
index 6ee9e07a7a..a6fce721e4 100644
--- a/src/shared/utils/upgradeForm.ts
+++ b/src/shared/utils/upgradeForm.ts
@@ -16,8 +16,9 @@ export const MIN_NB_SEATS_PRO = 2
export const MIN_SENTRY_SEATS = 5
export const SENTRY_PRICE = 29
export const TEAM_PLAN_MAX_ACTIVE_USERS = 10
+export const MONTHS_PER_YEAR = 12
-export const UPGRADE_FORM_TOO_MANY_SEATS_MESSAGE = `Team plan is only available for ${TEAM_PLAN_MAX_ACTIVE_USERS} seats or fewer.`
+export const UPGRADE_FORM_TOO_MANY_SEATS_MESSAGE = `Team plan is only available for ${TEAM_PLAN_MAX_ACTIVE_USERS} paid seats or fewer.`
export function extractSeats({
quantity,
@@ -194,7 +195,9 @@ export const calculateSentryNonBundledCost = ({
baseUnitPrice = 0,
}: {
baseUnitPrice?: number
-}) => MIN_SENTRY_SEATS * baseUnitPrice * 12 - SENTRY_PRICE * 12
+}) =>
+ MIN_SENTRY_SEATS * baseUnitPrice * MONTHS_PER_YEAR -
+ SENTRY_PRICE * MONTHS_PER_YEAR
export const getDefaultValuesUpgradeForm = ({
accountDetails,
@@ -235,7 +238,10 @@ export const getDefaultValuesUpgradeForm = ({
}
const seats = extractSeats({
- quantity: plan?.planUserCount ?? 0,
+ // free seats are included in planUserCount but we want to use the paid number
+ quantity: plan?.planUserCount
+ ? plan?.planUserCount - (plan?.freeSeatCount ?? 0)
+ : 0,
activatedUserCount,
inactiveUserCount,
trialStatus,
diff --git a/yarn.lock b/yarn.lock
index e0d0f4fa2f..e88be4eb66 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5706,6 +5706,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/pluralize@npm:^0.0.33":
+ version: 0.0.33
+ resolution: "@types/pluralize@npm:0.0.33"
+ checksum: 10c0/24899caf85b79dd291a6b6e9b9f3b67b452b18d578d0ac0d531a705bf5ee0361d9386ea1f8532c64de9e22c6e9606c5497787bb5e31bd299c487980436c59785
+ languageName: node
+ linkType: hard
+
"@types/prismjs@npm:^1.26.4":
version: 1.26.4
resolution: "@types/prismjs@npm:1.26.4"
@@ -9128,6 +9135,7 @@ __metadata:
"@types/js-cookie": "npm:3.0.6"
"@types/lodash": "npm:4.17.6"
"@types/node": "npm:^22.9.0"
+ "@types/pluralize": "npm:^0.0.33"
"@types/prismjs": "npm:^1.26.4"
"@types/prop-types": "npm:15.7.12"
"@types/qs": "npm:6.9.15"