diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.spec.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.spec.tsx
index 0c24fc7114..9eb8164290 100644
--- a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.spec.tsx
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.spec.tsx
@@ -7,6 +7,8 @@ import { MemoryRouter, Route } from 'react-router-dom'
import ActivationBanner from './ActivationBanner'
jest.mock('./TrialEligibleBanner', () => () => 'TrialEligibleBanner')
+jest.mock('./FreePlanSeatsLimitBanner', () => () => 'FreePlanSeatsLimitBanner')
+jest.mock('./PaidPlanSeatsLimitBanner', () => () => 'PaidPlanSeatsLimitBanner')
const queryClient = new QueryClient()
@@ -51,7 +53,8 @@ describe('ActivationBanner', () => {
function setup(
privateRepos = true,
trialStatus = 'NOT_STARTED',
- value = 'users-basic'
+ value = 'users-basic',
+ hasSeatsLeft = true
) {
server.use(
graphql.query('GetPlanData', (req, res, ctx) => {
@@ -64,6 +67,7 @@ describe('ActivationBanner', () => {
...mockTrialData,
trialStatus,
value,
+ hasSeatsLeft,
},
pretrialPlan: {
baseUnitPrice: 10,
@@ -89,7 +93,7 @@ describe('ActivationBanner', () => {
})
it('does not render trial eligible banner if user is not eligible to trial', async () => {
- setup(false)
+ setup(false, 'ONGOING', 'users-basic', true)
const { container } = render( , { wrapper })
await waitFor(() => queryClient.isFetching)
@@ -97,4 +101,24 @@ describe('ActivationBanner', () => {
expect(container).toBeEmptyDOMElement()
})
+
+ it('renders seats limit reached banner if user has no seats left and on free plan', async () => {
+ setup(true, 'ONGOING', 'users-basic', false)
+ render( , { wrapper })
+
+ const FreePlanSeatsLimitBanner = await screen.findByText(
+ /FreePlanSeatsLimitBanner/
+ )
+ expect(FreePlanSeatsLimitBanner).toBeInTheDocument()
+ })
+
+ it('renders seats limit reached banner if user has no seats left and on paid plan', async () => {
+ setup(true, 'ONGOING', 'users-inappy', false)
+ render( , { wrapper })
+
+ const PaidPlanSeatsLimitBanner = await screen.findByText(
+ /PaidPlanSeatsLimitBanner/
+ )
+ expect(PaidPlanSeatsLimitBanner).toBeInTheDocument()
+ })
})
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.tsx
index 800cc40ef9..b310475453 100644
--- a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.tsx
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationBanner.tsx
@@ -1,8 +1,10 @@
import { useParams } from 'react-router-dom'
import { TrialStatuses, usePlanData } from 'services/account'
-import { isBasicPlan } from 'shared/utils/billing'
+import { isBasicPlan, isFreePlan } from 'shared/utils/billing'
+import FreePlanSeatsLimitBanner from './FreePlanSeatsLimitBanner'
+import PaidPlanSeatsLimitBanner from './PaidPlanSeatsLimitBanner'
import TrialEligibleBanner from './TrialEligibleBanner'
interface URLParams {
@@ -21,16 +23,22 @@ function ActivationBanner() {
isBasicPlan(planData?.plan?.value) &&
planData?.hasPrivateRepos &&
isNewTrial
+ const seatsLimitReached = !planData?.plan?.hasSeatsLeft
+ const isFreePlanValue = isFreePlan(planData?.plan?.value)
- if (!isTrialEligible) {
- return null
+ if (isTrialEligible) {
+ return
}
- return (
-
-
-
- )
+ if (seatsLimitReached && isFreePlanValue) {
+ return
+ }
+
+ if (seatsLimitReached && !isFreePlanValue) {
+ return
+ }
+
+ return null
}
export default ActivationBanner
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/FreePlanSeatsLimitBanner.spec.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/FreePlanSeatsLimitBanner.spec.tsx
new file mode 100644
index 0000000000..6c50ed052c
--- /dev/null
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/FreePlanSeatsLimitBanner.spec.tsx
@@ -0,0 +1,40 @@
+import { render, screen } from '@testing-library/react'
+import { MemoryRouter, Route } from 'react-router-dom'
+
+import FreePlanSeatsLimitBanner from './FreePlanSeatsLimitBanner'
+
+const wrapper: React.FC = ({ children }) => (
+
+ {children}
+
+)
+
+describe('FreePlanSeatsLimitBanner', () => {
+ it('renders the banner with correct content', () => {
+ render( , { wrapper })
+
+ const bannerHeading = screen.getByRole('heading', {
+ name: /All Seats Taken/,
+ })
+ expect(bannerHeading).toBeInTheDocument()
+
+ const description = screen.getByText(
+ /Your organization is on the Developer free plan/i
+ )
+ expect(description).toBeInTheDocument()
+ })
+
+ it('renders correct links', () => {
+ render( , { wrapper })
+
+ const upgradeLink = screen.getByRole('link', { name: /Upgrade/ })
+ expect(upgradeLink).toBeInTheDocument()
+ expect(upgradeLink).toHaveAttribute('href', '/plan/gh/codecov/upgrade')
+
+ const manageMembersLink = screen.getByRole('link', {
+ name: /manage members/,
+ })
+ expect(manageMembersLink).toBeInTheDocument()
+ expect(manageMembersLink).toHaveAttribute('href', '/members/gh/codecov')
+ })
+})
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/FreePlanSeatsLimitBanner.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/FreePlanSeatsLimitBanner.tsx
new file mode 100644
index 0000000000..faef74bf22
--- /dev/null
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/FreePlanSeatsLimitBanner.tsx
@@ -0,0 +1,45 @@
+import A from 'ui/A'
+import Banner from 'ui/Banner'
+import BannerContent from 'ui/Banner/BannerContent'
+import BannerHeading from 'ui/Banner/BannerHeading'
+import Button from 'ui/Button'
+
+function FreePlanSeatsLimitBanner() {
+ return (
+
+
+
+ ℹ All Seats Taken
+
+
+
+ Your organization is on the Developer free plan, limited to one
+ seat, which is currently occupied. You can add any amount of seats
+ by upgrading for more flexibility.{' '}
+
+ manage members
+
+
+
+
+ Upgrade
+
+
+
+
+
+ )
+}
+
+export default FreePlanSeatsLimitBanner
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/index.ts b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/index.ts
new file mode 100644
index 0000000000..ada35104ba
--- /dev/null
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/FreePlanSeatsLimitBanner/index.ts
@@ -0,0 +1 @@
+export { default } from './FreePlanSeatsLimitBanner'
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/PaidPlanSeatsLimitBanner.spec.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/PaidPlanSeatsLimitBanner.spec.tsx
new file mode 100644
index 0000000000..4daa748afc
--- /dev/null
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/PaidPlanSeatsLimitBanner.spec.tsx
@@ -0,0 +1,40 @@
+import { render, screen } from '@testing-library/react'
+import { MemoryRouter, Route } from 'react-router-dom'
+
+import PaidPlanSeatsLimitBanner from './PaidPlanSeatsLimitBanner'
+
+const wrapper: React.FC = ({ children }) => (
+
+ {children}
+
+)
+
+describe('PaidPlanSeatsLimitBanner', () => {
+ it('renders the banner with correct content', () => {
+ render( , { wrapper })
+
+ const bannerHeading = screen.getByRole('heading', {
+ name: /Seats Limit Reached/,
+ })
+ expect(bannerHeading).toBeInTheDocument()
+
+ const description = screen.getByText(
+ /Your organization has utilized all available seats on this plan./i
+ )
+ expect(description).toBeInTheDocument()
+ })
+
+ it('renders correct links', () => {
+ render( , { wrapper })
+
+ const upgradeLink = screen.getByRole('link', { name: /Upgrade/ })
+ expect(upgradeLink).toBeInTheDocument()
+ expect(upgradeLink).toHaveAttribute('href', '/plan/gh/codecov/upgrade')
+
+ const manageMembersLink = screen.getByRole('link', {
+ name: /manage members/,
+ })
+ expect(manageMembersLink).toBeInTheDocument()
+ expect(manageMembersLink).toHaveAttribute('href', '/members/gh/codecov')
+ })
+})
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/PaidPlanSeatsLimitBanner.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/PaidPlanSeatsLimitBanner.tsx
new file mode 100644
index 0000000000..a0801294e5
--- /dev/null
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/PaidPlanSeatsLimitBanner.tsx
@@ -0,0 +1,44 @@
+import A from 'ui/A'
+import Banner from 'ui/Banner'
+import BannerContent from 'ui/Banner/BannerContent'
+import BannerHeading from 'ui/Banner/BannerHeading'
+import Button from 'ui/Button'
+
+function PaidPlanSeatsLimitBanner() {
+ return (
+
+
+
+ ℹ Seats Limit Reached
+
+
+
+ Your organization has utilized all available seats on this plan. To
+ add more members, please increase your seat count.{' '}
+
+ manage members
+
+
+
+
+ Upgrade
+
+
+
+
+
+ )
+}
+
+export default PaidPlanSeatsLimitBanner
diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/index.ts b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/index.ts
new file mode 100644
index 0000000000..e317517ee6
--- /dev/null
+++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/PaidPlanSeatsLimitBanner/index.ts
@@ -0,0 +1 @@
+export { default } from './PaidPlanSeatsLimitBanner'