Enterprise plans should not have the upgrade button (#7628)
* Enterprise plans should not have the upgrade button Fixes #7627 * Move the check to BillingDialog * Hide home box and change bool check * Add component tests * Clean up
This commit is contained in:
@ -125,8 +125,7 @@ test('Shows a loading spinner when uninitialized credit count', async () => {
|
|||||||
await expect(queryByTestId('spinner')).toBeVisible()
|
await expect(queryByTestId('spinner')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Shows the total credits for Unknown subscription', async () => {
|
const unKnownTierData = {
|
||||||
const data = {
|
|
||||||
balance: {
|
balance: {
|
||||||
monthlyApiCreditsRemaining: 10,
|
monthlyApiCreditsRemaining: 10,
|
||||||
stableApiCreditsRemaining: 25,
|
stableApiCreditsRemaining: 25,
|
||||||
@ -137,6 +136,46 @@ test('Shows the total credits for Unknown subscription', async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const freeTierData = {
|
||||||
|
balance: {
|
||||||
|
monthlyApiCreditsRemaining: 10,
|
||||||
|
stableApiCreditsRemaining: 0,
|
||||||
|
},
|
||||||
|
subscriptions: {
|
||||||
|
monthlyPayAsYouGoApiCreditsTotal: 20,
|
||||||
|
name: "free",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const proTierData = {
|
||||||
|
// These are all ignored
|
||||||
|
balance: {
|
||||||
|
monthlyApiCreditsRemaining: 10,
|
||||||
|
stableApiCreditsRemaining: 0,
|
||||||
|
},
|
||||||
|
subscriptions: {
|
||||||
|
// This should be ignored because it's Pro tier.
|
||||||
|
monthlyPayAsYouGoApiCreditsTotal: 20,
|
||||||
|
name: "pro",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const enterpriseTierData = {
|
||||||
|
// These are all ignored, user is part of an org.
|
||||||
|
balance: {
|
||||||
|
monthlyApiCreditsRemaining: 10,
|
||||||
|
stableApiCreditsRemaining: 0,
|
||||||
|
},
|
||||||
|
subscriptions: {
|
||||||
|
// This should be ignored because it's Pro tier.
|
||||||
|
monthlyPayAsYouGoApiCreditsTotal: 20,
|
||||||
|
// This should be ignored because the user is part of an Org.
|
||||||
|
name: "free",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Shows the total credits for Unknown subscription', async () => {
|
||||||
|
const data = unKnownTierData
|
||||||
server.use(
|
server.use(
|
||||||
http.get('*/user/payment/balance', (req, res, ctx) => {
|
http.get('*/user/payment/balance', (req, res, ctx) => {
|
||||||
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
||||||
@ -166,17 +205,7 @@ test('Shows the total credits for Unknown subscription', async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Progress bar reflects ratio left of Free subscription', async () => {
|
test('Progress bar reflects ratio left of Free subscription', async () => {
|
||||||
const data = {
|
const data = freeTierData
|
||||||
balance: {
|
|
||||||
monthlyApiCreditsRemaining: 10,
|
|
||||||
stableApiCreditsRemaining: 0,
|
|
||||||
},
|
|
||||||
subscriptions: {
|
|
||||||
monthlyPayAsYouGoApiCreditsTotal: 20,
|
|
||||||
name: "free",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get('*/user/payment/balance', (req, res, ctx) => {
|
http.get('*/user/payment/balance', (req, res, ctx) => {
|
||||||
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
||||||
@ -212,19 +241,7 @@ test('Progress bar reflects ratio left of Free subscription', async () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('Shows infinite credits for Pro subscription', async () => {
|
test('Shows infinite credits for Pro subscription', async () => {
|
||||||
const data = {
|
const data = proTierData
|
||||||
// These are all ignored
|
|
||||||
balance: {
|
|
||||||
monthlyApiCreditsRemaining: 10,
|
|
||||||
stableApiCreditsRemaining: 0,
|
|
||||||
},
|
|
||||||
subscriptions: {
|
|
||||||
// This should be ignored because it's Pro tier.
|
|
||||||
monthlyPayAsYouGoApiCreditsTotal: 20,
|
|
||||||
name: "pro",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get('*/user/payment/balance', (req, res, ctx) => {
|
http.get('*/user/payment/balance', (req, res, ctx) => {
|
||||||
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
||||||
@ -255,19 +272,7 @@ test('Shows infinite credits for Pro subscription', async () => {
|
|||||||
await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
|
await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
|
||||||
})
|
})
|
||||||
test('Shows infinite credits for Enterprise subscription', async () => {
|
test('Shows infinite credits for Enterprise subscription', async () => {
|
||||||
const data = {
|
const data = enterpriseTierData
|
||||||
// These are all ignored, user is part of an org.
|
|
||||||
balance: {
|
|
||||||
monthlyApiCreditsRemaining: 10,
|
|
||||||
stableApiCreditsRemaining: 0,
|
|
||||||
},
|
|
||||||
subscriptions: {
|
|
||||||
// This should be ignored because it's Pro tier.
|
|
||||||
monthlyPayAsYouGoApiCreditsTotal: 20,
|
|
||||||
// This should be ignored because the user is part of an Org.
|
|
||||||
name: "free",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get('*/user/payment/balance', (req, res, ctx) => {
|
http.get('*/user/payment/balance', (req, res, ctx) => {
|
||||||
@ -297,3 +302,58 @@ test('Shows infinite credits for Enterprise subscription', async () => {
|
|||||||
await expect(queryByTestId('infinity')).toBeVisible()
|
await expect(queryByTestId('infinity')).toBeVisible()
|
||||||
await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
|
await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Show upgrade button if credits are not infinite', async () => {
|
||||||
|
const data = freeTierData
|
||||||
|
server.use(
|
||||||
|
http.get('*/user/payment/balance', (req, res, ctx) => {
|
||||||
|
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
||||||
|
}),
|
||||||
|
http.get('*/user/payment/subscriptions', (req, res, ctx) => {
|
||||||
|
return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions))
|
||||||
|
}),
|
||||||
|
http.get('*/org', (req, res, ctx) => {
|
||||||
|
return new HttpResponse(403)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start()
|
||||||
|
|
||||||
|
const { queryByTestId } = render(<BillingDialog
|
||||||
|
billingActor={billingActor}
|
||||||
|
/>)
|
||||||
|
|
||||||
|
await act(() => {
|
||||||
|
billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" })
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(queryByTestId('billing-upgrade-button')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Hide upgrade button if credits are infinite', async () => {
|
||||||
|
const data = enterpriseTierData
|
||||||
|
server.use(
|
||||||
|
http.get('*/user/payment/balance', (req, res, ctx) => {
|
||||||
|
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
|
||||||
|
}),
|
||||||
|
http.get('*/user/payment/subscriptions', (req, res, ctx) => {
|
||||||
|
return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions))
|
||||||
|
}),
|
||||||
|
// Ok finally the first use of an org lol
|
||||||
|
http.get('*/org', (req, res, ctx) => {
|
||||||
|
return HttpResponse.json(createOrgResponse())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start()
|
||||||
|
|
||||||
|
const { queryByTestId } = render(<BillingDialog
|
||||||
|
billingActor={billingActor}
|
||||||
|
/>)
|
||||||
|
|
||||||
|
await act(() => {
|
||||||
|
billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" })
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(queryByTestId('billing-upgrade-button')).toBe(null)
|
||||||
|
})
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
BillingRemainingMode,
|
BillingRemainingMode,
|
||||||
} from '@src/components/BillingRemaining'
|
} from '@src/components/BillingRemaining'
|
||||||
|
|
||||||
import type { BillingActor } from '@src/machines/billingMachine'
|
import { type BillingActor } from '@src/machines/billingMachine'
|
||||||
|
|
||||||
export const BillingDialog = (props: { billingActor: BillingActor }) => {
|
export const BillingDialog = (props: { billingActor: BillingActor }) => {
|
||||||
const billingContext = useSelector(
|
const billingContext = useSelector(
|
||||||
@ -39,15 +39,18 @@ export const BillingDialog = (props: { billingActor: BillingActor }) => {
|
|||||||
mode={BillingRemainingMode.ProgressBarStretch}
|
mode={BillingRemainingMode.ProgressBarStretch}
|
||||||
billingActor={props.billingActor}
|
billingActor={props.billingActor}
|
||||||
/>
|
/>
|
||||||
|
{!hasUnlimited && (
|
||||||
<a
|
<a
|
||||||
className="bg-ml-black text-ml-white rounded-lg text-center p-1 cursor-pointer"
|
className="bg-ml-black text-ml-white rounded-lg text-center p-1 cursor-pointer"
|
||||||
href="https://zoo.dev/design-studio-pricing"
|
href="https://zoo.dev/design-studio-pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
data-testid="billing-upgrade-button"
|
||||||
onClick={openExternalBrowserIfDesktop()}
|
onClick={openExternalBrowserIfDesktop()}
|
||||||
>
|
>
|
||||||
Upgrade
|
Upgrade
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -66,6 +66,7 @@ import {
|
|||||||
defaultLocalStatusBarItems,
|
defaultLocalStatusBarItems,
|
||||||
defaultGlobalStatusBarItems,
|
defaultGlobalStatusBarItems,
|
||||||
} from '@src/components/StatusBar/defaultStatusBarItems'
|
} from '@src/components/StatusBar/defaultStatusBarItems'
|
||||||
|
import { useSelector } from '@xstate/react'
|
||||||
|
|
||||||
type ReadWriteProjectState = {
|
type ReadWriteProjectState = {
|
||||||
value: boolean
|
value: boolean
|
||||||
@ -81,6 +82,8 @@ const Home = () => {
|
|||||||
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
|
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
|
||||||
const apiToken = useToken()
|
const apiToken = useToken()
|
||||||
const networkMachineStatus = useNetworkMachineStatus()
|
const networkMachineStatus = useNetworkMachineStatus()
|
||||||
|
const billingContext = useSelector(billingActor, ({ context }) => context)
|
||||||
|
const hasUnlimitedCredits = billingContext.credits === Infinity
|
||||||
|
|
||||||
// Only create the native file menus on desktop
|
// Only create the native file menus on desktop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -354,11 +357,13 @@ const Home = () => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="flex flex-col">
|
<ul className="flex flex-col">
|
||||||
|
{!hasUnlimitedCredits && (
|
||||||
<li className="contents">
|
<li className="contents">
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<BillingDialog billingActor={billingActor} />
|
<BillingDialog billingActor={billingActor} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
)}
|
||||||
<li className="contents">
|
<li className="contents">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
|
Reference in New Issue
Block a user