Skip to content

feat: add license settings UI #7210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
wip: extract components
  • Loading branch information
rodrimaia committed Apr 24, 2023
commit ee583e1d1930c830b491170e25233b562844cbe4
11 changes: 3 additions & 8 deletions site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,9 @@ const LicensesSettingsPage = lazy(
"./pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage"
),
)
const AddNewLicensePageView = lazy(
const AddNewLicensePage = lazy(
() =>
import(
"./pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView"
),
import("./pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage"),
)

export const AppRouter: FC = () => {
Expand Down Expand Up @@ -258,10 +256,7 @@ export const AppRouter: FC = () => {
>
<Route path="general" element={<GeneralSettingsPage />} />
<Route path="licenses" element={<LicensesSettingsPage />} />
<Route
path="licenses/add"
element={<AddNewLicensePageView />}
/>
<Route path="licenses/add" element={<AddNewLicensePage />} />
<Route path="security" element={<SecuritySettingsPage />} />
<Route path="appearance" element={<AppearanceSettingsPage />} />
<Route path="network" element={<NetworkSettingsPage />} />
Expand Down
2 changes: 1 addition & 1 deletion site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ type Claims = {
require_telemetry?: boolean
}

type GetLicensesResponse = Omit<TypesGen.License, "claims"> & {
export type GetLicensesResponse = Omit<TypesGen.License, "claims"> & {
claims: Claims
expires_at: string
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useMutation } from "@tanstack/react-query"
import { createLicense } from "api/api"
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"
import { FC } from "react"
import { useNavigate } from "react-router-dom"
import { AddNewLicensePageView } from "./AddNewLicensePageView"
import { pageTitle } from "utils/page"
import { Helmet } from "react-helmet-async"

const AddNewLicensePage: FC = () => {
const navigate = useNavigate()

const {
mutate: saveLicenseKeyApi,
isLoading: isCreating,
isError: creationFailed,
} = useMutation(createLicense, {
onSuccess: () => {
displaySuccess("You have successfully added a license")
navigate("/settings/deployment/licenses?success=true")
},
onError: () => displayError("Failed to save license key"),
})

function saveLicenseKey(licenseKey: string) {
saveLicenseKeyApi(
{ license: licenseKey },
{
onSuccess: () => {
displaySuccess("You have successfully added a license")
navigate("/settings/deployment/licenses?success=true")
},
onError: () => displayError("Failed to save license key"),
},
)
}

return (
<>
<Helmet>
<title>{pageTitle("License Settings")}</title>
</Helmet>

<AddNewLicensePageView
isSavingLicense={isCreating}
didSavingFailed={creationFailed}
onSaveLicenseKey={saveLicenseKey}
/>
</>
)
}

export default AddNewLicensePage
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import AddNewLicensePageView from "./AddNewLicensePageView"
import { AddNewLicensePageView } from "./AddNewLicensePageView"

export default {
title: "pages/AddNewLicensePageView",
component: AddNewLicensePageView,
}

export const Default = () => <AddNewLicensePageView />
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
import Button from "@material-ui/core/Button"
import TextField from "@material-ui/core/TextField"
import { makeStyles } from "@material-ui/core/styles"
import { useMutation } from "@tanstack/react-query"
import { createLicense } from "api/api"
import { Fieldset } from "components/DeploySettingsLayout/Fieldset"
import { Header } from "components/DeploySettingsLayout/Header"
import { DividerWithText } from "components/DividerWithText/DividerWithText"
import { FileUpload } from "components/FileUpload/FileUpload"
import { Form, FormFields, FormSection } from "components/Form/Form"
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"
import { displayError } from "components/GlobalSnackbar/utils"
import { Stack } from "components/Stack/Stack"
import { FC } from "react"
import { Link as RouterLink, useNavigate } from "react-router-dom"
import { Link as RouterLink } from "react-router-dom"

const AddNewLicense: FC = () => {
const styles = useStyles()
const navigate = useNavigate()
type AddNewLicenseProps = {
onSaveLicenseKey: (license: string) => void
isSavingLicense: boolean
didSavingFailed: boolean
}

const {
mutate: saveLicenseKeyApi,
isLoading: isCreating,
isError: creationFailed,
} = useMutation(createLicense)
export const AddNewLicensePageView: FC<AddNewLicenseProps> = ({
onSaveLicenseKey,
isSavingLicense,
didSavingFailed,
}) => {
const styles = useStyles()

function handleFileUploaded(files: File[]) {
const fileReader = new FileReader()
fileReader.onload = () => {
const licenseKey = fileReader.result as string

saveLicenseKey(licenseKey)
onSaveLicenseKey(licenseKey)

fileReader.onerror = () => {
displayError("Failed to read file")
Expand All @@ -38,19 +39,6 @@ const AddNewLicense: FC = () => {
fileReader.readAsText(files[0])
}

function saveLicenseKey(licenseKey: string) {
saveLicenseKeyApi(
{ license: licenseKey },
{
onSuccess: () => {
displaySuccess("You have successfully added a license")
navigate("/settings/deployment/licenses?success=true")
},
onError: () => displayError("Failed to save license key"),
},
)
}

const isUploading = false

function onUpload(file: File) {
Expand Down Expand Up @@ -99,7 +87,7 @@ const AddNewLicense: FC = () => {

<Fieldset
title="Paste your license key"
validation={creationFailed ? "License key is invalid" : undefined}
validation={didSavingFailed ? "License key is invalid" : undefined}
onSubmit={(e) => {
e.preventDefault()

Expand All @@ -108,10 +96,10 @@ const AddNewLicense: FC = () => {

const licenseKey = formData.get("licenseKey")

saveLicenseKey(licenseKey?.toString() || "")
onSaveLicenseKey(licenseKey?.toString() || "")
}}
button={
<Button type="submit" disabled={isCreating}>
<Button type="submit" disabled={isSavingLicense}>
Add license
</Button>
}
Expand All @@ -129,8 +117,6 @@ const AddNewLicense: FC = () => {
)
}

export default AddNewLicense

const useStyles = makeStyles((theme) => ({
main: {
paddingTop: theme.spacing(5),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
import Button from "@material-ui/core/Button"
import { makeStyles, useTheme } from "@material-ui/core/styles"
import Skeleton from "@material-ui/lab/Skeleton"
import { useMutation, useQuery } from "@tanstack/react-query"
import { useMachine } from "@xstate/react"
import { getLicenses, removeLicense } from "api/api"
import { Header } from "components/DeploySettingsLayout/Header"
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"
import { LicenseCard } from "components/LicenseCard/LicenseCard"
import { Stack } from "components/Stack/Stack"
import { FC, useEffect } from "react"
import Confetti from "react-confetti"
import { Helmet } from "react-helmet-async"
import { Link, useSearchParams } from "react-router-dom"
import { useSearchParams } from "react-router-dom"
import useToggle from "react-use/lib/useToggle"
import useWindowSize from "react-use/lib/useWindowSize"
import { pageTitle } from "utils/page"
import { entitlementsMachine } from "xServices/entitlements/entitlementsXService"
import LicensesSettingsPageView from "./LicensesSettingsPageView"

const LicensesSettingsPage: FC = () => {
const [entitlementsState] = useMachine(entitlementsMachine)
const { entitlements } = entitlementsState.context
const styles = useStyles()
const [searchParams, setSearchParams] = useSearchParams()
const success = searchParams.get("success")
const [confettiOn, toggleConfettiOn] = useToggle(false)
const { width, height } = useWindowSize()

const { mutate: removeLicenseApi, isLoading: isRemovingLicense } =
useMutation(removeLicense)

const theme = useTheme()

const {
data: licenses,
isLoading,
Expand All @@ -55,79 +44,28 @@ const LicensesSettingsPage: FC = () => {
<Helmet>
<title>{pageTitle("License Settings")}</title>
</Helmet>
<Confetti
width={width}
height={height}
numberOfPieces={confettiOn ? 200 : 0}
colors={[theme.palette.primary.main, theme.palette.secondary.main]}
<LicensesSettingsPageView
showConfetti={confettiOn}
isLoading={isLoading}
userLimitActual={entitlements?.features.user_limit.actual}
userLimitLimit={entitlements?.features.user_limit.limit}
licenses={licenses}
isRemovingLicense={isRemovingLicense}
removeLicense={(licenseId: number) =>
removeLicenseApi(licenseId, {
onSuccess: () => {
displaySuccess("Successfully removed license")
void refetchGetLicenses()
},
onError: () => {
displayError("Failed to remove license")
void refetchGetLicenses()
},
})
}
/>
<Stack
alignItems="baseline"
direction="row"
justifyContent="space-between"
>
<Header
title="Licenses"
description="Enterprise licenses unlock more features on your deployment."
/>

<Button
variant="outlined"
component={Link}
to="/settings/deployment/licenses/add"
>
Add new License
</Button>
</Stack>

{isLoading && <Skeleton variant="rect" height={200} />}

{!isLoading && licenses && licenses?.length > 0 && (
<Stack spacing={4}>
{licenses?.map((license) => (
<LicenseCard
key={license.id}
license={license}
userLimitActual={entitlements?.features.user_limit.actual}
userLimitLimit={entitlements?.features.user_limit.limit}
isRemoving={isRemovingLicense}
onRemove={(licenseId: number) =>
removeLicenseApi(licenseId, {
onSuccess: () => {
displaySuccess("Successfully removed license")
void refetchGetLicenses()
},
onError: () => {
displayError("Failed to remove license")
void refetchGetLicenses()
},
})
}
/>
))}
</Stack>
)}

{!isLoading && licenses && licenses.length === 0 && (
<Stack spacing={4} justifyContent="center" alignItems="center">
<Button className={styles.ctaButton} size="large">
Add license
</Button>
</Stack>
)}
</>
)
}

const useStyles = makeStyles((theme) => ({
ctaButton: {
backgroundImage: `linear-gradient(90deg, ${theme.palette.secondary.main} 0%, ${theme.palette.secondary.dark} 100%)`,
width: theme.spacing(30),
marginBottom: theme.spacing(4),
},
removeButtom: {
color: "red",
},
}))

export default LicensesSettingsPage
Loading