Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtron-labs/devtron-fe-common-lib",
"version": "1.19.0",
"version": "1.19.0-beta-5",
"description": "Supporting common component library",
"type": "module",
"main": "dist/index.js",
Expand Down
1 change: 1 addition & 0 deletions src/Shared/Components/GenericModal/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { GenericModalProps } from './types'

export const MODAL_WIDTH_TO_CLASS_NAME_MAP: Record<GenericModalProps['width'], string> = {
450: 'w-450',
500: 'w-500',
600: 'w-600',
800: 'w-800',
Expand Down
2 changes: 1 addition & 1 deletion src/Shared/Components/GenericModal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface GenericModalProps extends Partial<Pick<BackdropProps, 'onEscape
* Width of the modal (in pixels).
* @default 600
*/
width?: 500 | 600 | 800
width?: 450 | 500 | 600 | 800
/**
* Determines if the modal should close when the user clicks outside of it (on the backdrop).
* @default false
Expand Down
2 changes: 1 addition & 1 deletion src/Shared/Components/Header/HelpButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const HelpButton = ({ serverInfo, fetchingServerInfo, onClick, hideGettin
alignment="end"
width={220}
options={getHelpActionMenuOptions({
isTrial: licenseData?.isTrial ?? false,
isTrialOrFreemium: (licenseData?.isTrial || licenseData?.isFreemium) ?? false,
isEnterprise,
})}
onClick={handleActionMenuClick}
Expand Down
8 changes: 5 additions & 3 deletions src/Shared/Components/Header/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export const setActionWithExpiry = (key: string, days: number): void => {

export const getHelpActionMenuOptions = ({
isEnterprise,
isTrial,
isTrialOrFreemium,
}: {
isEnterprise: boolean
isTrial: boolean
isTrialOrFreemium: boolean
}): HelpButtonActionMenuProps['options'] => [
{
items: COMMON_HELP_ACTION_MENU_ITEMS,
Expand All @@ -56,7 +56,9 @@ export const getHelpActionMenuOptions = ({
? [
{
groupLabel: 'Enterprise Support',
items: isTrial ? ENTERPRISE_TRIAL_HELP_ACTION_MENU_ITEMS : ENTERPRISE_HELP_ACTION_MENU_ITEMS,
items: isTrialOrFreemium
? ENTERPRISE_TRIAL_HELP_ACTION_MENU_ITEMS
: ENTERPRISE_HELP_ACTION_MENU_ITEMS,
},
]
: [
Expand Down
132 changes: 100 additions & 32 deletions src/Shared/Components/License/DevtronLicenseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ClipboardButton, getTTLInHumanReadableFormat } from '@Common/index'
import { CONTACT_SUPPORT_LINK, ENTERPRISE_SUPPORT_LINK } from '@Shared/constants'
import { AppThemeType } from '@Shared/Providers'
import { getThemeOppositeThemeClass } from '@Shared/Providers/ThemeProvider/utils'
import { LicensingErrorCodes } from '@Shared/types'

import { Button, ButtonComponentType, ButtonVariantType } from '../Button'
import { Icon } from '../Icon'
Expand All @@ -33,21 +34,101 @@ import './licenseCard.scss'

const DAMPEN_FACTOR = 50

const ContactSupportButton = () => (
<Button
dataTestId="contact-support"
startIcon={<ICChatSupport />}
text="Contact support"
variant={ButtonVariantType.text}
component={ButtonComponentType.anchor}
anchorProps={{ href: CONTACT_SUPPORT_LINK }}
/>
)

const LicenseCardSubText = ({
isFreemium,
licenseStatus,
licenseStatusError,
}: Pick<DevtronLicenseCardProps, 'isFreemium' | 'licenseStatus' | 'licenseStatusError'>) => {
if (licenseStatus === LicenseStatus.ACTIVE && !isFreemium) {
return null
}

const freemiumLimitReached = licenseStatusError?.code === LicensingErrorCodes.ClusterLimitExceeded

if (licenseStatus === LicenseStatus.ACTIVE && isFreemium) {
return (
<div className="p-16 fs-13 lh-1-5 flexbox-col dc__gap-8">
<div className="flexbox dc__gap-8 dc__content-space fs-13 fw-4 lh-20 cn-9">
{freemiumLimitReached ? (
<div className="flexbox-col dc__gap-4">
<span className="fw-6">Multiple Clusters Detected</span>
<span>
Your account is connected to multiple clusters, which isn’t allowed on the freemium
plan. Upgrade to an Enterprise license or contact us.
</span>
</div>
) : (
<span className="fw-6">Unlimited single cluster usage</span>
)}
<Icon
name={freemiumLimitReached ? 'ic-error' : 'ic-success'}
color={freemiumLimitReached ? 'R500' : 'G500'}
size={20}
/>
</div>
<div className="mail-support">
<Button
dataTestId="mail-support"
startIcon={<Icon name="ic-email" color={null} />}
text={ENTERPRISE_SUPPORT_LINK}
variant={ButtonVariantType.text}
component={ButtonComponentType.anchor}
anchorProps={{ href: `mailto:${ENTERPRISE_SUPPORT_LINK}` }}
/>
</div>
<ContactSupportButton />
</div>
)
}

const isLicenseExpired = licenseStatus === LicenseStatus.EXPIRED

return (
<div className="p-16 fs-13 lh-1-5 flexbox-col dc__gap-8">
<div className="flexbox dc__gap-8 dc__content-space">
<span>
To renew your license mail us at&nbsp;
<a href={`mailto:${ENTERPRISE_SUPPORT_LINK}`}>{ENTERPRISE_SUPPORT_LINK}</a> or contact your Devtron
representative.
</span>
<Icon
name={isLicenseExpired ? 'ic-error' : 'ic-timer'}
color={isLicenseExpired ? 'R500' : 'Y500'}
size={16}
/>
</div>
<ContactSupportButton />
</div>
)
}

export const DevtronLicenseCard = ({
enterpriseName,
licenseKey,
licenseSuffix,
expiryDate,
licenseStatus,
isTrial,
isFreemium,
ttl,
appTheme,
handleCopySuccess,
licenseStatusError,
}: DevtronLicenseCardProps) => {
const { bgColor, textColor } = getLicenseColorsAccordingToStatus(licenseStatus)
const { bgColor, textColor } = getLicenseColorsAccordingToStatus({ isFreemium, licenseStatus, licenseStatusError })
const remainingTime = getTTLInHumanReadableFormat(ttl)
const remainingTimeString = ttl < 0 ? `Expired ${remainingTime} ago` : `${remainingTime} remaining`
const isLicenseValid = licenseStatus !== LicenseStatus.EXPIRED
const isThemeDark = appTheme === AppThemeType.dark

const cardRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -93,7 +174,7 @@ export const DevtronLicenseCard = ({
: useMotionTemplate`linear-gradient(55deg, transparent, rgba(255, 255, 255, ${sheenOpacity}) ${sheenPosition}%, transparent)`

return (
<div className="flexbox-col p-8 br-16" style={{ backgroundColor: bgColor }}>
<div className="license-card-wrapper flexbox-col p-8 br-16" style={{ backgroundColor: bgColor }}>
<div style={{ perspective: '1000px' }}>
<motion.div
className={`license-card shadow__overlay border__secondary flexbox-col br-12 h-200 dc__overflow-hidden bg__tertiary ${getThemeOppositeThemeClass(appTheme)}`}
Expand Down Expand Up @@ -130,43 +211,30 @@ export const DevtronLicenseCard = ({
)}
</div>
<div className="flexbox dc__align-items-center dc__gap-4 flex-wrap fs-12">
<span className="font-ibm-plex-mono cn-9">{expiryDate}</span>
<span className="cn-9">·</span>
<span style={{ color: textColor }}>{remainingTimeString}</span>
<span className="font-ibm-plex-mono cn-9">
{isFreemium ? 'VALID FOREVER' : expiryDate}
</span>
{!isFreemium && (
<>
<span className="cn-9">·</span>
<span style={{ color: textColor }}>{remainingTimeString}</span>
</>
)}
</div>
</div>
</div>
{isTrial && (
{(isTrial || isFreemium) && (
<span className="trial-license-badge flexbox dc__align-items-center px-20 py-6 cn-9 fs-11 fw-5 lh-1-5 dc__zi-2">
TRIAL LICENSE
{isFreemium ? 'FREEMIUM' : 'TRIAL'} LICENSE
</span>
)}
</motion.div>
</div>
{licenseStatus !== LicenseStatus.ACTIVE && (
<div className="p-16 fs-13 lh-1-5 flexbox-col dc__gap-8">
<div className="flexbox dc__gap-8">
<span>
To renew your license mail us at&nbsp;
<a href={`mailto:${ENTERPRISE_SUPPORT_LINK}`}>{ENTERPRISE_SUPPORT_LINK}</a> or contact your
Devtron representative.
</span>
<Icon
name={isLicenseValid ? 'ic-timer' : 'ic-error'}
color={isLicenseValid ? 'Y500' : 'R500'}
size={16}
/>
</div>
<Button
dataTestId="contact-support"
startIcon={<ICChatSupport />}
text="Contact support"
variant={ButtonVariantType.text}
component={ButtonComponentType.anchor}
anchorProps={{ href: CONTACT_SUPPORT_LINK }}
/>
</div>
)}
<LicenseCardSubText
isFreemium={isFreemium}
licenseStatusError={licenseStatusError}
licenseStatus={licenseStatus}
/>
</div>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/Shared/Components/License/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ALLOWED_CLUSTER_IN_FREEMIUM = 2
16 changes: 12 additions & 4 deletions src/Shared/Components/License/licenseCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@
* limitations under the License.
*/

.license-card {
.license-card-wrapper {
.license-card {
.trial-license-badge {
background-color: var(--divider-secondary-translucent);
letter-spacing: 0.55px;
}
}

.trial-license-badge {
background-color: var(--divider-secondary-translucent);
letter-spacing: 0.55px;
.mail-support {
.button {
text-transform: lowercase;
}
}
}

6 changes: 4 additions & 2 deletions src/Shared/Components/License/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { AppThemeType } from '@Shared/Providers'
import { DevtronLicenseBaseDTO, DevtronLicenseDTO } from '@Shared/types'
import { DevtronLicenseBaseDTO, DevtronLicenseDTO, LicenseErrorStruct } from '@Shared/types'

export enum LicenseStatus {
ACTIVE = 'ACTIVE',
Expand All @@ -29,7 +29,9 @@ export type DevtronLicenseCardProps = {
ttl: number
licenseStatus: LicenseStatus
isTrial: boolean
isFreemium: boolean
appTheme: AppThemeType
licenseStatusError: LicenseErrorStruct
} & (
| {
licenseKey: string
Expand All @@ -44,7 +46,7 @@ export type DevtronLicenseCardProps = {
)

export type DevtronLicenseInfo = Omit<DevtronLicenseCardProps, 'appTheme'> &
Pick<DevtronLicenseDTO, 'fingerprint' | 'showLicenseData' | 'licenseStatusError'>
Pick<DevtronLicenseDTO, 'fingerprint' | 'showLicenseData' | 'licenseStatusError' | 'moduleLimits'>

export interface ActivateLicenseDialogProps extends Pick<DevtronLicenseBaseDTO, 'fingerprint'> {
enterpriseName: string
Expand Down
39 changes: 33 additions & 6 deletions src/Shared/Components/License/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,25 @@ import moment from 'moment'

import { DATE_TIME_FORMATS } from '@Common/Constants'
import { getUrlWithSearchParams } from '@Common/index'
import { DevtronLicenseDTO } from '@Shared/types'
import { DevtronLicenseDTO, LicensingErrorCodes } from '@Shared/types'

import { ALLOWED_CLUSTER_IN_FREEMIUM } from './constants'
import { DevtronLicenseCardProps, DevtronLicenseInfo, LicenseStatus } from './types'

export const getLicenseColorsAccordingToStatus = (
licenseStatus: LicenseStatus,
): { bgColor: string; textColor: string } => {
export const getLicenseColorsAccordingToStatus = ({
isFreemium,
licenseStatus,
licenseStatusError,
}: Pick<DevtronLicenseCardProps, 'licenseStatus' | 'isFreemium' | 'licenseStatusError'>): {
bgColor: string
textColor: string
} => {
if (isFreemium) {
const freemiumLimitReached = licenseStatusError?.code === LicensingErrorCodes.ClusterLimitExceeded
return freemiumLimitReached
? { bgColor: 'var(--R100)', textColor: 'var(--R500)' }
: { bgColor: 'var(--G100)', textColor: 'var(--G500)' }
}
switch (licenseStatus) {
case LicenseStatus.ACTIVE:
return { bgColor: 'var(--G100)', textColor: 'var(--G500)' }
Expand Down Expand Up @@ -54,15 +66,26 @@ export const parseDevtronLicenseDTOIntoLicenseCardData = <isCentralDashboard ext
licenseDTO: DevtronLicenseDTO<isCentralDashboard>,
currentUserEmail?: isCentralDashboard extends true ? string : never,
): Omit<DevtronLicenseCardProps, 'appTheme'> => {
const { isTrial, expiry, ttl, reminderThreshold, organisationMetadata, license, claimedByUserDetails } =
licenseDTO || {}
const {
isTrial,
expiry,
ttl,
reminderThreshold,
organisationMetadata,
license,
claimedByUserDetails,
isFreemium,
licenseStatusError,
} = licenseDTO || {}

return {
enterpriseName: organisationMetadata?.name || 'Devtron Enterprise',
expiryDate: expiry ? moment(expiry).format(DATE_TIME_FORMATS['DD/MM/YYYY']) : '',
ttl,
licenseStatus: getDevtronLicenseStatus({ ttl, reminderThreshold }),
isTrial,
isFreemium,
licenseStatusError,
...(currentUserEmail && currentUserEmail === claimedByUserDetails?.email
? { licenseKey: license }
: { licenseSuffix: license }),
Expand All @@ -76,6 +99,10 @@ export const parseDevtronLicenseData = (result: DevtronLicenseDTO): DevtronLicen
fingerprint: result?.fingerprint || '',
showLicenseData: result?.showLicenseData,
licenseStatusError: result?.licenseStatusError,
moduleLimits: {
allAllowed: result?.moduleLimits?.allAllowed || false,
maxAllowedClusters: result?.moduleLimits?.maxAllowedClusters || ALLOWED_CLUSTER_IN_FREEMIUM,
},
}
}

Expand Down
Loading