Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/components/customerPortal/common/AgreementGate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Button } from '~/components/designSystem/Button'
import { Typography } from '~/components/designSystem/Typography'

interface AgreementGateProps {
signingUrl?: string | null
}

export const AGREEMENT_GATE_TEST_ID = 'customer-portal-agreement-gate'

// Blocking gate shown in the customer portal until the white-label / SDK MSA is
// signed. Signing happens on the hosted gate page (signingUrl), which renders
// the agreement, records the signature, and collects a payment method.
const AgreementGate = ({ signingUrl }: AgreementGateProps) => {
return (
<div
className="flex flex-col items-center gap-4 rounded-lg border border-grey-300 bg-grey-100 p-12 text-center"
data-test={AGREEMENT_GATE_TEST_ID}
>
<Typography variant="headline" color="grey700">
Action required: sign your agreement
</Typography>
<Typography className="max-w-md" variant="body" color="grey600">
Before you can access billing, invoices, and usage, please review and accept your
Master Services &amp; White-Label / SDK Agreement. It only takes a minute and no
login is required.
</Typography>
<Button
variant="primary"
size="large"
disabled={!signingUrl}
onClick={() => {
if (signingUrl) {
window.location.assign(signingUrl)
}
}}
>
Review &amp; Sign Agreement
</Button>
</div>
)
}

export default AgreementGate
13 changes: 13 additions & 0 deletions src/components/customerPortal/common/CustomerPortalSections.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import AgreementGate from '~/components/customerPortal/common/AgreementGate'
import { useCustomerPortalAgreement } from '~/components/customerPortal/common/hooks/useCustomerPortalAgreement'
import { useCustomerPortalData } from '~/components/customerPortal/common/hooks/useCustomerPortalData'
import useCustomerPortalNavigation from '~/components/customerPortal/common/hooks/useCustomerPortalNavigation'
import useCustomerPortalTranslate from '~/components/customerPortal/common/useCustomerPortalTranslate'
Expand All @@ -18,13 +20,24 @@ const CustomerPortalSections = () => {

const { data: portalData } = useCustomerPortalData()

const { mustSignAgreement, agreementSigningUrl } = useCustomerPortalAgreement()

const { viewWallet, viewSubscription, viewEditInformation, viewPlans } =
useCustomerPortalNavigation()

const showPoweredBy = !portalData?.customerPortalOrganization?.premiumIntegrations?.includes(
PremiumIntegrationTypeEnum.RemoveBrandingWatermark,
)

// White-label / SDK customers must sign the MSA before anything else is shown.
if (mustSignAgreement) {
return (
<div className="flex flex-col gap-12" data-test={CUSTOMER_PORTAL_SECTIONS_TEST_ID}>
<AgreementGate signingUrl={agreementSigningUrl} />
</div>
)
}

return (
<div className="flex flex-col gap-12" data-test={CUSTOMER_PORTAL_SECTIONS_TEST_ID}>
<WalletSection viewWallet={viewWallet} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { gql, useQuery } from '@apollo/client'
import { useParams } from 'react-router-dom'

import { useIsAuthenticated } from '~/hooks/auth/useIsAuthenticated'

// Raw Apollo query (no codegen dependency, matching the create-org precedent) so
// the new white-label fields are fetched at runtime without regenerating types.
const GET_CUSTOMER_PORTAL_AGREEMENT = gql`
query getCustomerPortalAgreement {
customerPortalUser {
id
mustSignAgreement
agreementSigningUrl
}
}
`

export const useCustomerPortalAgreement = () => {
const { token } = useParams()
const { isPortalAuthenticated } = useIsAuthenticated()

const { data, loading } = useQuery(GET_CUSTOMER_PORTAL_AGREEMENT, {
fetchPolicy: 'cache-first',
skip: !isPortalAuthenticated || !token,
})

const user = data?.customerPortalUser

return {
loading,
mustSignAgreement: Boolean(user?.mustSignAgreement),
agreementSigningUrl: (user?.agreementSigningUrl ?? null) as string | null,
}
}
Loading