Skip to content

Commit

Permalink
feat(ui): display alerts for invalid licenses
Browse files Browse the repository at this point in the history
  • Loading branch information
liangfung committed Nov 1, 2024
1 parent 3e79e23 commit 6536c19
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
import { capitalize } from 'lodash-es'
import moment from 'moment'

import { LicenseInfo, LicenseType } from '@/lib/gql/generates/graphql'
import {
LicenseInfo,
LicenseStatus,
LicenseType
} from '@/lib/gql/generates/graphql'
import { useLicense } from '@/lib/hooks/use-license'
import { Badge } from '@/components/ui/badge'
import { IconAlertTriangle } from '@/components/ui/icons'
import { Skeleton } from '@/components/ui/skeleton'
import LoadingWrapper from '@/components/loading-wrapper'
import { SubHeader } from '@/components/sub-header'
Expand Down Expand Up @@ -60,11 +66,27 @@ function License({ license }: { license: LicenseInfo }) {
<div className="grid font-bold lg:grid-cols-3">
<div>
<div className="mb-1 text-muted-foreground">Expires at</div>
<div className="text-3xl">{expiresAt}</div>
<div className="text-3xl flex items-center gap-2">
{expiresAt}
{license.status === LicenseStatus.Expired && (
<Badge variant="destructive" className="flex items-center gap-1">
<IconAlertTriangle className="h-3 w-3" />
Expired
</Badge>
)}
</div>
</div>
<div>
<div className="mb-1 text-muted-foreground">Assigned / Total Seats</div>
<div className="text-3xl">{seatsText}</div>
<div className="text-3xl flex items-center gap-2">
{seatsText}
{license.status === LicenseStatus.Expired && (
<Badge variant="destructive" className="flex items-center gap-1">
<IconAlertTriangle className="h-3 w-3" />
Seats exceeded
</Badge>
)}
</div>
</div>
<div>
<div className="mb-1 text-muted-foreground">Current plan</div>
Expand Down
31 changes: 29 additions & 2 deletions ee/tabby-ui/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import * as React from 'react'
import { compare } from 'compare-versions'

import { LicenseStatus } from '@/lib/gql/generates/graphql'
import { useHealth } from '@/lib/hooks/use-health'
import { ReleaseInfo, useLatestRelease } from '@/lib/hooks/use-latest-release'
import { useLicenseInfo } from '@/lib/hooks/use-license'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
import { IconNotice } from '@/components/ui/icons'
import { IconInfoCircled, IconNotice } from '@/components/ui/icons'

import { ClientOnly } from './client-only'
import { ThemeToggle } from './theme-toggle'
Expand All @@ -22,7 +24,7 @@ export function Header() {

return (
<header className="sticky top-0 z-50 flex h-16 w-full shrink-0 items-center justify-between border-b px-4 backdrop-blur-xl lg:px-10">
<div className="flex items-center">
<div className="flex items-center gap-4">
{newVersionAvailable && (
<a
target="_blank"
Expand All @@ -36,6 +38,7 @@ export function Header() {
</span>
</a>
)}
<LicenseAlert />
</div>
<div className="flex items-center justify-center gap-6">
<ClientOnly>
Expand All @@ -60,3 +63,27 @@ function isNewVersionAvailable(version?: string, latestRelease?: ReleaseInfo) {
return true
}
}

function LicenseAlert() {
const license = useLicenseInfo()

if (!license) return null

if (license.status === LicenseStatus.Expired) {
return (
<div className="flex items-center gap-1 text-destructive text-sm font-semibold">
<IconInfoCircled />
Your license has expired.
</div>
)
}

if (license.status === LicenseStatus.SeatsExceeded) {
return (
<div className="flex items-center gap-1 text-destructive text-sm font-semibold">
<IconInfoCircled />
Your seat count has exceeded the limit.
</div>
)
}
}
100 changes: 87 additions & 13 deletions ee/tabby-ui/components/license-guard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { capitalize } from 'lodash-es'

import {
GetLicenseInfoQuery,
LicenseInfo,
LicenseStatus,
LicenseType
} from '@/lib/gql/generates/graphql'
Expand All @@ -18,19 +19,25 @@ import {

interface LicenseGuardProps {
licenses: LicenseType[]
isSeatCountRelated?: boolean
children: (params: {
hasValidLicense: boolean
license: GetLicenseInfoQuery['license'] | undefined | null
}) => React.ReactNode
}

const LicenseGuard: React.FC<LicenseGuardProps> = ({ licenses, children }) => {
const LicenseGuard: React.FC<LicenseGuardProps> = ({
licenses,
isSeatCountRelated,
children
}) => {
const [open, setOpen] = React.useState(false)
const license = useLicenseInfo()

const hasValidLicense =
!!license &&
license.status === LicenseStatus.Ok &&
licenses.includes(license.type)
licenses.includes(license.type) &&
license.status === LicenseStatus.Ok

const onOpenChange = (v: boolean) => {
if (hasValidLicense) return
Expand All @@ -46,16 +53,11 @@ const LicenseGuard: React.FC<LicenseGuardProps> = ({ licenses, children }) => {
return (
<HoverCard open={open} onOpenChange={onOpenChange} openDelay={100}>
<HoverCardContent side="top" collisionPadding={16} className="w-[400px]">
<div>
This feature is only available on Tabby&apos;s{' '}
<span className="font-semibold">{licenseText}</span> plan. Upgrade to
use this feature.
</div>
<div className="mt-4 text-center">
<Link className={buttonVariants()} href="/settings/subscription">
Upgrade to {licenseString}
</Link>
</div>
<LicenseTips
licenses={licenses}
license={license}
isSeatCountRelated={!!isSeatCountRelated}
/>
</HoverCardContent>
<HoverCardTrigger
asChild
Expand All @@ -76,3 +78,75 @@ const LicenseGuard: React.FC<LicenseGuardProps> = ({ licenses, children }) => {
LicenseGuard.displayName = 'LicenseGuard'

export { LicenseGuard }

function LicenseTips({
licenses,
license,
isSeatCountRelated
}: {
licenses: LicenseType[]
license: LicenseInfo | undefined
isSeatCountRelated: boolean
}) {
const hasSufficientLicense = !!license && licenses.includes(license.type)
const isLicenseExpired =
hasSufficientLicense && license?.status === LicenseStatus.Expired
const isSearCountRelated =
hasSufficientLicense && license?.status === LicenseStatus.SeatsExceeded

const licenseString = capitalize(licenses[0])
let insufficientLicenseText = licenseString
if (licenses.length == 2) {
insufficientLicenseText = `${capitalize(licenses[0])} or ${capitalize(
licenses[1]
)}`
}

if (isSearCountRelated && isSeatCountRelated) {
return (
<>
<div>
Your seat count has exceeded the limit. Please upgrade your license to
continue using this feature.
</div>
<div className="mt-4 text-center">
<Link className={buttonVariants()} href="/settings/subscription">
Upgrade license
</Link>
</div>
``
</>
)
}

if (isLicenseExpired) {
return (
<>
<div>
Your license has expired. Please update your license to use this
feature.
</div>
<div className="mt-4 text-center">
<Link className={buttonVariants()} href="/settings/subscription">
Update license
</Link>
</div>
</>
)
}

return (
<>
<div>
This feature is only available on Tabby&apos;s{' '}
<span className="font-semibold">{insufficientLicenseText}</span> plan.
Upgrade to use this feature.
</div>
<div className="mt-4 text-center">
<Link className={buttonVariants()} href="/settings/subscription">
Upgrade to {licenseString}
</Link>
</div>
</>
)
}

0 comments on commit 6536c19

Please sign in to comment.