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 + +

+
+ +
+
+
+
+ ) +} + +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 + +

+
+ +
+
+
+
+ ) +} + +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'