Skip to content

Commit f30e5fe

Browse files
authored
Merge pull request #701 from devtron-labs/feat/migrate-activate-license
feat: add activate license dialog and related components
2 parents 3025f7b + 14fc102 commit f30e5fe

16 files changed

+319
-40
lines changed

package-lock.json

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "1.11.0",
3+
"version": "1.11.1",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
@@ -120,6 +120,7 @@
120120
"jsonpath-plus": "^10.3.0",
121121
"marked": "^13.0.3",
122122
"nanoid": "^3.3.8",
123+
"qrcode.react": "^4.2.0",
123124
"react-canvas-confetti": "^2.0.7",
124125
"react-dates": "^21.8.0",
125126
"react-diff-viewer-continued": "^3.4.0",

src/Common/Constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export const ROUTES = {
155155
RESOURCE_TEMPLATE: 'resource/template',
156156
ENVIRONMENT_DATA: 'global/environment-variables',
157157
DASHBOARD_EVENT: 'dashboard-event',
158+
LICENSE_DATA: 'license/data',
158159
} as const
159160

160161
export enum KEY_VALUE {

src/Shared/Components/DevtronLicenseCard/InstallationFingerprintInfo.tsx

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/Shared/Components/DevtronLicenseCard/index.tsx

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/Shared/Components/Header/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ReactComponent as File } from '@Icons/ic-file-text.svg'
1818
import { ReactComponent as Discord } from '@Icons/ic-discord-fill.svg'
1919
import { updatePostHogEvent } from './service'
2020
import { DISCORD_LINK, DOCUMENTATION_HOME_PAGE, LOGIN_COUNT } from '../../../Common'
21-
import { DevtronLicenseInfo, LicenseStatus } from '../DevtronLicenseCard'
21+
import { DevtronLicenseInfo, LicenseStatus } from '../License'
2222
import { EnterpriseHelpOptions, OSSHelpOptions, TrialHelpOptions } from './constants'
2323

2424
const millisecondsInDay = 86400000
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { ChangeEvent, SyntheticEvent, useState } from 'react'
2+
import { ReactComponent as ICCross } from '@Icons/ic-cross.svg'
3+
import {
4+
ActivateLicenseDialogProps,
5+
Button,
6+
ButtonStyleType,
7+
ButtonVariantType,
8+
getHandleOpenURL,
9+
handleSendAnalyticsEventToServer,
10+
InstallationFingerprintInfo,
11+
requiredField,
12+
ServerAnalyticsEventType,
13+
Textarea,
14+
ToastManager,
15+
ToastVariantType,
16+
} from '@Shared/index'
17+
import { API_STATUS_CODES } from '@Common/Constants'
18+
import { showError } from '@Common/index'
19+
import { getGateKeeperUrl } from './utils'
20+
import { activateLicense } from './services'
21+
import { GatekeeperQRDialog, ICDevtronWithBorder } from './License.components'
22+
23+
const ActivateLicenseDialog = ({
24+
fingerprint,
25+
enterpriseName,
26+
handleClose,
27+
handleLicenseActivateSuccess,
28+
}: ActivateLicenseDialogProps) => {
29+
const [showQRDialog, setShowQRDialog] = useState<boolean>(false)
30+
const [licenseKey, setLicenseKey] = useState<string>('')
31+
const [activatingLicense, setActivatingLicense] = useState<boolean>(false)
32+
const [error, setError] = useState<string>('')
33+
34+
const handleGetLicense = () => {
35+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
36+
handleSendAnalyticsEventToServer(ServerAnalyticsEventType.GET_LICENSE_CLICKED, true)
37+
const gateKeeperURL = getGateKeeperUrl(fingerprint)
38+
39+
getHandleOpenURL(gateKeeperURL)()
40+
setShowQRDialog(true)
41+
}
42+
43+
const handleCloseQRDialog = () => {
44+
setShowQRDialog(false)
45+
}
46+
47+
const validateForm = (updatedLicenseKey: string) => {
48+
const errorMessage = requiredField(updatedLicenseKey).message
49+
setError(errorMessage || '')
50+
return !errorMessage
51+
}
52+
53+
const handleLicenseKeyChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
54+
const { value } = e.target
55+
setLicenseKey(value)
56+
validateForm(value)
57+
}
58+
59+
const handleActivateLicense = async (e: SyntheticEvent) => {
60+
e.preventDefault()
61+
const isFormValid = validateForm(licenseKey)
62+
if (isFormValid) {
63+
setActivatingLicense(true)
64+
try {
65+
await activateLicense(licenseKey)
66+
ToastManager.showToast({
67+
description: 'License activated successfully',
68+
variant: ToastVariantType.success,
69+
})
70+
handleLicenseActivateSuccess()
71+
} catch (err) {
72+
if (err.code === API_STATUS_CODES.BAD_REQUEST) {
73+
setError('Invalid license key')
74+
}
75+
showError(err)
76+
} finally {
77+
setActivatingLicense(false)
78+
}
79+
}
80+
}
81+
82+
return (
83+
<div className="flexbox-col p-36 dc__gap-32 border__primary w-400 br-12 bg__primary mxh-600 dc__overflow-auto">
84+
<div className="flexbox-col dc__gap-20">
85+
<div className="flexbox dc__content-space">
86+
<ICDevtronWithBorder />
87+
{handleClose && (
88+
<Button
89+
dataTestId="close-dialog"
90+
variant={ButtonVariantType.borderLess}
91+
ariaLabel="close activate license dialog"
92+
onClick={handleClose}
93+
style={ButtonStyleType.negativeGrey}
94+
icon={<ICCross />}
95+
showAriaLabelInTippy={false}
96+
/>
97+
)}
98+
</div>
99+
<div className="flexbox-col dc__gap-4">
100+
<div className="fs-20 lh-1-5 fw-7 cn-9 font-merriweather dc__truncate">{enterpriseName}</div>
101+
<div className="fs-16 lh-1-5 cn-8 fw-4">Enter new enterprise license key</div>
102+
</div>
103+
</div>
104+
<div className="flexbox-col dc__gap-16">
105+
<InstallationFingerprintInfo fingerprint={fingerprint} />
106+
<Textarea
107+
placeholder="Enter license key"
108+
name="license-key"
109+
onChange={handleLicenseKeyChange}
110+
value={licenseKey}
111+
required
112+
label="License Key"
113+
error={error}
114+
/>
115+
</div>
116+
<div className="flexbox-col dc__gap-16">
117+
<Button
118+
dataTestId="activate-license"
119+
text="Activate"
120+
fullWidth
121+
isLoading={activatingLicense}
122+
onClick={handleActivateLicense}
123+
/>
124+
<div className="flexbox dc__align-items-center dc__content-space">
125+
<span className="fs-13 cn-9 lh-20 fw-4">Don’t have license key?</span>
126+
<Button
127+
dataTestId="get-license"
128+
text="Get license"
129+
variant={ButtonVariantType.text}
130+
onClick={handleGetLicense}
131+
/>
132+
</div>
133+
</div>
134+
{showQRDialog && <GatekeeperQRDialog fingerprint={fingerprint} handleClose={handleCloseQRDialog} />}
135+
</div>
136+
)
137+
}
138+
139+
export default ActivateLicenseDialog
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { ClipboardButton, copyToClipboard, showError } from '@Common/index'
2+
3+
import { DOCUMENTATION } from '@Common/Constants'
4+
import { useEffect, useRef, useState } from 'react'
5+
import { ReactComponent as ICClipboard } from '@Icons/ic-copy.svg'
6+
import { ReactComponent as ICCheck } from '@Icons/ic-check.svg'
7+
import { Backdrop, Button, ButtonStyleType, ButtonVariantType, Icon, InfoIconTippy, QRCode } from '..'
8+
import { CopyButtonProps, GatekeeperQRDialogProps, InstallFingerprintInfoProps } from './types'
9+
import { getGateKeeperUrl } from './utils'
10+
11+
const CopyButton = ({ copyContent }: CopyButtonProps) => {
12+
const [clicked, setClicked] = useState<boolean>(false)
13+
const timeoutId = useRef<ReturnType<typeof setTimeout> | null>(null)
14+
15+
const handleCopy = async () => {
16+
try {
17+
await copyToClipboard(copyContent)
18+
setClicked(true)
19+
timeoutId.current = setTimeout(() => {
20+
setClicked(false)
21+
}, 1000)
22+
} catch (err) {
23+
showError(err)
24+
}
25+
}
26+
27+
useEffect(
28+
() => () => {
29+
clearTimeout(timeoutId.current)
30+
},
31+
[],
32+
)
33+
34+
return (
35+
<Button
36+
dataTestId="copy-button"
37+
text="Copy link"
38+
startIcon={clicked ? <ICCheck /> : <ICClipboard />}
39+
style={ButtonStyleType.neutral}
40+
variant={ButtonVariantType.borderLess}
41+
onClick={handleCopy}
42+
/>
43+
)
44+
}
45+
46+
export const GatekeeperQRDialog = ({ handleClose, fingerprint }: GatekeeperQRDialogProps) => {
47+
const gateKeeperURL = getGateKeeperUrl(fingerprint)
48+
49+
return (
50+
<Backdrop onEscape={handleClose}>
51+
<div className="dc__m-auto bg__menu--primary br-12 w-400 border__primary mt-40">
52+
<div className="p-24 flexbox-col dc__gap-20">
53+
<div className="flexbox-col cn-9 lh-1-5 dc__gap-4">
54+
<div className="fs-16 fw-6">Get Devtron Enterprise License</div>
55+
<div className="fs-13 fw-4">
56+
Scan the below QR to visit the license dashboard and generate a license key for your Devtron
57+
installation.
58+
</div>
59+
</div>
60+
<div className="flex">
61+
<div className="p-20 br-12 border-secondary bg__modal--secondary">
62+
<QRCode value={gateKeeperURL} size={310} bgColor="N50" fgColor="N900" />
63+
</div>
64+
</div>
65+
</div>
66+
<div className="px-24 py-20 flexbox dc__align-items-center dc__content-space">
67+
<CopyButton copyContent={gateKeeperURL} />
68+
<Button dataTestId="qr-dialog-got-it" text="Got it" onClick={handleClose} />
69+
</div>
70+
</div>
71+
</Backdrop>
72+
)
73+
}
74+
75+
export const ICDevtronWithBorder = () => (
76+
<div className="flex p-6 border__primary br-8 dc__w-fit-content">
77+
<Icon name="ic-devtron" color="B500" size={40} />
78+
</div>
79+
)
80+
81+
const InstallationFingerprintInfo = ({ fingerprint, showHelpTooltip = false }: InstallFingerprintInfoProps) => (
82+
<div className="flexbox-col dc__gap-6">
83+
<div className="flexbox dc__align-items-center dc__gap-4">
84+
<span className="fs-13 lh-20 cn-7 fw-4">Installation Fingerprint</span>
85+
{showHelpTooltip && (
86+
<InfoIconTippy
87+
heading="Installation Fingerprint"
88+
infoText="A unique fingerprint to identify your Devtron Installation. An enterprise license is generated against an installation fingerprint."
89+
documentationLinkText="Documentation"
90+
iconClassName="icon-dim-20 fcn-6"
91+
placement="right"
92+
documentationLink={DOCUMENTATION.ENTERPRISE_LICENSE}
93+
/>
94+
)}
95+
</div>
96+
<div className="flex dc__gap-8">
97+
<span className="cn-9 fs-13 lh-1-5 fw-4 dc__truncate">{fingerprint}</span>
98+
<ClipboardButton content={fingerprint} />
99+
</div>
100+
</div>
101+
)
102+
103+
export default InstallationFingerprintInfo
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export { default as DevtronLicenseCard } from './DevtronLicenseCard'
2+
export { parseDevtronLicenseData } from './utils'
3+
export { default as InstallationFingerprintInfo, ICDevtronWithBorder } from './License.components'
4+
export { default as ActivateLicenseDialog } from './ActivateLicenseDialog'
5+
6+
export * from './types'

0 commit comments

Comments
 (0)