Skip to content

Commit 3a9ad03

Browse files
committed
feat: add license card and license info modal
1 parent 05d1af4 commit 3a9ad03

File tree

14 files changed

+422
-1
lines changed

14 files changed

+422
-1
lines changed
Lines changed: 3 additions & 0 deletions
Loading

src/Assets/IconV2/ic-devtron.svg

Lines changed: 19 additions & 0 deletions
Loading

src/Assets/IconV2/ic-key.svg

Lines changed: 19 additions & 0 deletions
Loading

src/Assets/IconV2/ic-timer.svg

Lines changed: 19 additions & 0 deletions
Loading

src/Shared/Components/ConfirmationModal/ConfirmationModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const ConfirmationModalBody = ({
8282
return (
8383
<Backdrop onEscape={shouldCloseOnEscape ? handleCloseWrapper : noop}>
8484
<motion.div
85-
className={`${isLandscapeView ? 'w-500' : 'w-400'} confirmation-modal border__secondary flexbox-col br-8 bg__primary dc__m-auto mt-40 w-400`}
85+
className={`${isLandscapeView ? 'w-500' : 'w-400'} confirmation-modal border__secondary flexbox-col br-8 bg__primary dc__m-auto mt-40`}
8686
exit={{ y: 100, opacity: 0, scale: 0.75, transition: { duration: 0.35 } }}
8787
initial={{ y: 100, opacity: 0, scale: 0.75 }}
8888
animate={{ y: 0, opacity: 1, scale: 1 }}

src/Shared/Components/Icon/Icon.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ReactComponent as ICBrowser } from '@IconsV2/ic-browser.svg'
99
import { ReactComponent as ICBuildColor } from '@IconsV2/ic-build-color.svg'
1010
import { ReactComponent as ICCancelled } from '@IconsV2/ic-cancelled.svg'
1111
import { ReactComponent as ICCd } from '@IconsV2/ic-cd.svg'
12+
import { ReactComponent as ICChatCircleDots } from '@IconsV2/ic-chat-circle-dots.svg'
1213
import { ReactComponent as ICCiLinked } from '@IconsV2/ic-ci-linked.svg'
1314
import { ReactComponent as ICCiWebhook } from '@IconsV2/ic-ci-webhook.svg'
1415
import { ReactComponent as ICCircleLoader } from '@IconsV2/ic-circle-loader.svg'
@@ -18,6 +19,7 @@ import { ReactComponent as ICCode } from '@IconsV2/ic-code.svg'
1819
import { ReactComponent as ICContainer } from '@IconsV2/ic-container.svg'
1920
import { ReactComponent as ICCrown } from '@IconsV2/ic-crown.svg'
2021
import { ReactComponent as ICCube } from '@IconsV2/ic-cube.svg'
22+
import { ReactComponent as ICDevtron } from '@IconsV2/ic-devtron.svg'
2123
import { ReactComponent as ICDockerhub } from '@IconsV2/ic-dockerhub.svg'
2224
import { ReactComponent as ICEcr } from '@IconsV2/ic-ecr.svg'
2325
import { ReactComponent as ICEnv } from '@IconsV2/ic-env.svg'
@@ -42,6 +44,7 @@ import { ReactComponent as ICInfoFilled } from '@IconsV2/ic-info-filled.svg'
4244
import { ReactComponent as ICInfoOutline } from '@IconsV2/ic-info-outline.svg'
4345
import { ReactComponent as ICJobColor } from '@IconsV2/ic-job-color.svg'
4446
import { ReactComponent as ICK8sJob } from '@IconsV2/ic-k8s-job.svg'
47+
import { ReactComponent as ICKey } from '@IconsV2/ic-key.svg'
4548
import { ReactComponent as ICLdap } from '@IconsV2/ic-ldap.svg'
4649
import { ReactComponent as ICLoginDevtronLogo } from '@IconsV2/ic-login-devtron-logo.svg'
4750
import { ReactComponent as ICLogout } from '@IconsV2/ic-logout.svg'
@@ -62,6 +65,7 @@ import { ReactComponent as ICStack } from '@IconsV2/ic-stack.svg'
6265
import { ReactComponent as ICSuccess } from '@IconsV2/ic-success.svg'
6366
import { ReactComponent as ICSuspended } from '@IconsV2/ic-suspended.svg'
6467
import { ReactComponent as ICTimeoutTwoDash } from '@IconsV2/ic-timeout-two-dash.svg'
68+
import { ReactComponent as ICTimer } from '@IconsV2/ic-timer.svg'
6569
import { ReactComponent as ICUnknown } from '@IconsV2/ic-unknown.svg'
6670
import { ReactComponent as ICUserKey } from '@IconsV2/ic-user-key.svg'
6771
import { ReactComponent as ICWarning } from '@IconsV2/ic-warning.svg'
@@ -80,6 +84,7 @@ export const iconMap = {
8084
'ic-build-color': ICBuildColor,
8185
'ic-cancelled': ICCancelled,
8286
'ic-cd': ICCd,
87+
'ic-chat-circle-dots': ICChatCircleDots,
8388
'ic-ci-linked': ICCiLinked,
8489
'ic-ci-webhook': ICCiWebhook,
8590
'ic-circle-loader': ICCircleLoader,
@@ -89,6 +94,7 @@ export const iconMap = {
8994
'ic-container': ICContainer,
9095
'ic-crown': ICCrown,
9196
'ic-cube': ICCube,
97+
'ic-devtron': ICDevtron,
9298
'ic-dockerhub': ICDockerhub,
9399
'ic-ecr': ICEcr,
94100
'ic-env': ICEnv,
@@ -113,6 +119,7 @@ export const iconMap = {
113119
'ic-info-outline': ICInfoOutline,
114120
'ic-job-color': ICJobColor,
115121
'ic-k8s-job': ICK8sJob,
122+
'ic-key': ICKey,
116123
'ic-ldap': ICLdap,
117124
'ic-login-devtron-logo': ICLoginDevtronLogo,
118125
'ic-logout': ICLogout,
@@ -133,6 +140,7 @@ export const iconMap = {
133140
'ic-success': ICSuccess,
134141
'ic-suspended': ICSuspended,
135142
'ic-timeout-two-dash': ICTimeoutTwoDash,
143+
'ic-timer': ICTimer,
136144
'ic-unknown': ICUnknown,
137145
'ic-user-key': ICUserKey,
138146
'ic-warning': ICWarning,
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { ClipboardButton, DOCUMENTATION_HOME_PAGE } from '@Common/index'
2+
import { ReactComponent as ICChatSupport } from '@IconsV2/ic-chat-circle-dots.svg'
3+
import { Icon } from '../Icon'
4+
import { Button, ButtonStyleType, ButtonVariantType } from '../Button'
5+
import { InfoIconTippy } from '../InfoIconTippy'
6+
import './licenseInfoDialog.scss'
7+
8+
export enum LicenseStatus {
9+
ACTIVE = 'ACTIVE',
10+
EXPIRED = 'EXPIRED',
11+
REMINDER_THRESHOLD_REACHED = 'REMINDER_THRESHOLD_REACHED',
12+
}
13+
14+
export type DevtronLicenseCardProps = {
15+
enterpriseName: string
16+
expiryDate: string
17+
ttl: number
18+
licenseStatus: LicenseStatus
19+
isTrial: boolean
20+
} & (
21+
| {
22+
licenseKey: string
23+
licenseSuffix?: never
24+
}
25+
| {
26+
licenseKey?: never
27+
licenseSuffix: string
28+
}
29+
)
30+
31+
export const getColorAccordingToStatus = (licenseStatus: LicenseStatus) => {
32+
switch (licenseStatus) {
33+
case LicenseStatus.ACTIVE:
34+
return 'var(--G100)'
35+
case LicenseStatus.REMINDER_THRESHOLD_REACHED:
36+
return 'var(--Y100)'
37+
default:
38+
return 'var(--R100)'
39+
}
40+
}
41+
42+
export const DevtronLicenseCard = ({
43+
enterpriseName,
44+
licenseKey,
45+
licenseSuffix,
46+
expiryDate,
47+
licenseStatus,
48+
isTrial,
49+
ttl,
50+
}: DevtronLicenseCardProps) => {
51+
const colorValue = getColorAccordingToStatus(licenseStatus)
52+
53+
return (
54+
<div className="flexbox-col p-8 br-16" style={{ backgroundColor: colorValue }}>
55+
<div className="license-card flexbox-col dc__content-space br-12 p-20 h-200 bg__tertiary">
56+
<div className="flexbox dc__align-items-center dc__content-space">
57+
<span className="font-merriweather cn-9 fs-16 fw-7 lh-1-5">{enterpriseName}</span>
58+
<Icon name="ic-devtron" color="N900" size={24} />
59+
</div>
60+
<div className="flexbox-col dc__gap-4">
61+
<div className="flexbox dc__align-items-center dc__gap-6">
62+
<Icon name="ic-key" color={null} size={20} />
63+
<div className="flex dc__gap-4 cn-7 fs-16 fw-5 lh-1-5 font-ibm-plex-mono">
64+
<span>••••</span>
65+
<span>{licenseSuffix || licenseKey.slice(-8)}</span>
66+
</div>
67+
{licenseKey && <ClipboardButton content={licenseKey} />}
68+
</div>
69+
<div className="flexbox dc__align-items-center dc__gap-4">
70+
<span>{expiryDate}</span>
71+
<span></span>
72+
<span style={{ color: colorValue }}>2 months remaining</span>
73+
</div>
74+
</div>
75+
{isTrial && <div className="flexbox dc__align-items-center px-20 py-6">TRIAL LICENSE</div>}
76+
</div>
77+
{licenseStatus !== LicenseStatus.ACTIVE && (
78+
<div className="p-16 flexbox-col dc__gap-8">
79+
<div className="flexbox dc__gap-8">
80+
<span>
81+
{/* TODO: Confirm with product team */}
82+
To renew your license mail us at enterprise@devtron.ai or contact your Devtron
83+
representative.
84+
</span>
85+
<Icon name="ic-timer" color="Y500" size={16} />
86+
</div>
87+
<Button
88+
dataTestId="contact-support"
89+
startIcon={<ICChatSupport />}
90+
text="Contact support"
91+
variant={ButtonVariantType.text}
92+
/>
93+
</div>
94+
)}
95+
</div>
96+
)
97+
}
98+
99+
export const InstallationFingerprintInfo = ({
100+
installationFingerprint,
101+
showHelpTip = false,
102+
}: {
103+
installationFingerprint: string
104+
showHelpTip?: boolean
105+
}) => (
106+
<div className="flexbox-col dc__gap-6">
107+
<div className="flexbox dc__align-items-center dc__gap-4">
108+
<span className="fs-13 lh-20 cn-7 fw-4">Installation Fingerprint</span>
109+
{showHelpTip && (
110+
<InfoIconTippy
111+
heading="Installation Fingerprint"
112+
infoText="A unique fingerprint to identify your Devtron Installation. An enterprise license is generated against an installation fingerprint."
113+
documentationLinkText="Documentation"
114+
iconClassName="icon-dim-20 fcn-6"
115+
placement="right"
116+
documentationLink={DOCUMENTATION_HOME_PAGE}
117+
/>
118+
)}
119+
</div>
120+
<div className="flex dc__content-space">
121+
<span className="cn-9 fs-13 lh-1-5 fw-4">{installationFingerprint}</span>
122+
<ClipboardButton content={installationFingerprint} />
123+
</div>
124+
</div>
125+
)
126+
127+
export const LicenseInfo = ({ handleUpdateLicenseClick }: { handleUpdateLicenseClick: () => void }) => (
128+
<div className="flexbox-col dc__gap-20">
129+
<DevtronLicenseCard
130+
enterpriseName="BharatPe"
131+
licenseSuffix="4AF593V3"
132+
ttl={100}
133+
licenseStatus={LicenseStatus.ACTIVE}
134+
isTrial
135+
expiryDate="2025-05-17"
136+
/>
137+
<InstallationFingerprintInfo installationFingerprint="3ff0d8be-e7f2-4bf4-9c3f-70ec904b51f4" showHelpTip />
138+
<div className="border__primary--bottom h-1" />
139+
<div className="flex dc__content-space">
140+
<span>Have a new license?</span>
141+
<Button
142+
dataTestId="update-license"
143+
text="Update license"
144+
variant={ButtonVariantType.text}
145+
onClick={handleUpdateLicenseClick}
146+
/>
147+
</div>
148+
</div>
149+
)
150+
151+
export const AboutDevtron = () => (
152+
<div className="flexbox-col dc__align-items-center dc__gap-20">
153+
<div className="flex p-6 border__primary br-8">
154+
<Icon name="ic-devtron" color="B500" size={40} />
155+
</div>
156+
<div className="text-center">
157+
<p className="fs-16 cn-9 fw-6 lh-1-5 m-0">Devtron</p>
158+
{/* TODO: add version */}
159+
<p className="fs-13 cn-7 fw-4 lh-20 m-0">Enterprise Version (1.3.1)</p>
160+
</div>
161+
<p className="fs-13 cn-5 fw-4 lh-20 m-0">Copyright © 2025 Devtron Inc. All rights reserved.</p>
162+
{/* TODO: add links for all button below */}
163+
<div className="flexbox dc__align-items-center dc__gap-6">
164+
<Button
165+
dataTestId="terms-of-service"
166+
text="Terms of service"
167+
variant={ButtonVariantType.text}
168+
style={ButtonStyleType.neutral}
169+
/>
170+
<span></span>
171+
<Button
172+
dataTestId="privacy-policy"
173+
text="Privacy policy"
174+
variant={ButtonVariantType.text}
175+
style={ButtonStyleType.neutral}
176+
/>
177+
<span></span>
178+
<Button
179+
dataTestId="license-agreement"
180+
text="License agreement"
181+
variant={ButtonVariantType.text}
182+
style={ButtonStyleType.neutral}
183+
/>
184+
</div>
185+
</div>
186+
)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ChangeEvent, useState } from 'react'
2+
import { SegmentedControl } from '@Common/index'
3+
import { ComponentSizeType } from '@Shared/constants'
4+
import { AboutDevtron, LicenseInfo } from './LicenseInfoDialog.components'
5+
import { Button } from '../Button'
6+
import { LicenseInfoDialogType } from './types'
7+
import { ABOUT_DEVTRON_TABS } from './constants'
8+
import ActivateLicenseDialog from './UpdateLicenseDialog'
9+
import './licenseInfoDialog.scss'
10+
11+
const LicenseInfoDialog = () => {
12+
const [dialogType, setDialogType] = useState<LicenseInfoDialogType>(LicenseInfoDialogType.ABOUT)
13+
14+
const handleChangeDialogType = (e: ChangeEvent<HTMLInputElement>) => {
15+
setDialogType(e.target.value as LicenseInfoDialogType)
16+
}
17+
18+
const handleUpdateLicenseClick = () => {
19+
setDialogType(LicenseInfoDialogType.UPDATE)
20+
}
21+
22+
return dialogType === LicenseInfoDialogType.UPDATE ? (
23+
<ActivateLicenseDialog />
24+
) : (
25+
<div className="license-info-dialog w-400 br-12 bg__modal--primary border__primary">
26+
{/* Header */}
27+
<div className="px-24 pt-24 pb-8">
28+
<SegmentedControl
29+
name="about-devtron-segmented-control"
30+
tabs={ABOUT_DEVTRON_TABS}
31+
initialTab={dialogType}
32+
onChange={handleChangeDialogType}
33+
rootClassName="h-32 w-100"
34+
/>
35+
</div>
36+
{/* Body */}
37+
<div className="p-24 mxh-500 overflow-auto">
38+
{dialogType === LicenseInfoDialogType.ABOUT ? (
39+
<AboutDevtron />
40+
) : (
41+
<LicenseInfo handleUpdateLicenseClick={handleUpdateLicenseClick} />
42+
)}
43+
</div>
44+
{/* Footer */}
45+
<div className="flex px-24 py-20 dc__content-end">
46+
<Button dataTestId="license-info-okay" text="Okay" size={ComponentSizeType.medium} />
47+
</div>
48+
</div>
49+
)
50+
}
51+
52+
export default LicenseInfoDialog

0 commit comments

Comments
 (0)