From d1e0377b149083e46e8e7290d50e812006fda7d0 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Fri, 14 Apr 2023 20:54:06 +0000 Subject: [PATCH 01/21] wip: license page --- site/src/AppRouter.tsx | 2 ++ site/src/api/api.ts | 21 +++++++++++++++++++ .../DeploySettingsLayout/Sidebar.tsx | 7 +++++++ 3 files changed, 30 insertions(+) diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index 235061621f711..4aeb4312d8e9d 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -7,6 +7,7 @@ import GroupsPage from "pages/GroupsPage/GroupsPage" import LoginPage from "pages/LoginPage/LoginPage" import { SetupPage } from "pages/SetupPage/SetupPage" import { TemplateSettingsPage } from "pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage" +import LicensesSettingsPage from "pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage" import TemplatesPage from "pages/TemplatesPage/TemplatesPage" import UsersPage from "pages/UsersPage/UsersPage" import WorkspacesPage from "pages/WorkspacesPage/WorkspacesPage" @@ -244,6 +245,7 @@ export const AppRouter: FC = () => { element={} > } /> + } /> } /> } /> } /> diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 73cb6de377dd0..d26c1af272254 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -965,6 +965,27 @@ export const getWorkspaceBuildParameters = async ( ) return response.data } +type Claims = { + license_expires?: jwt.NumericDate; + account_type?: string; + account_id?: string; + trial: boolean; + all_features: boolean; + version: number; + features: Record; + require_telemetry?: boolean; +} + +type GetLicensesResponse = Omit & { + claims: Claims +} + +export const getLicenses = async (): Promise => { + const response = await axios.get( + `/api/v2/licenses`, + ) + return response.data +} export class MissingBuildParameters extends Error { parameters: TypesGen.TemplateVersionParameter[] = [] diff --git a/site/src/components/DeploySettingsLayout/Sidebar.tsx b/site/src/components/DeploySettingsLayout/Sidebar.tsx index b9d3735ae1c51..ea081f943eb9b 100644 --- a/site/src/components/DeploySettingsLayout/Sidebar.tsx +++ b/site/src/components/DeploySettingsLayout/Sidebar.tsx @@ -1,6 +1,7 @@ import { makeStyles } from "@material-ui/core/styles" import Brush from "@material-ui/icons/Brush" import LaunchOutlined from "@material-ui/icons/LaunchOutlined" +import ApprovalIcon from "@material-ui/icons/VerifiedUserOutlined" import LockRounded from "@material-ui/icons/LockOutlined" import Globe from "@material-ui/icons/PublicOutlined" import VpnKeyOutlined from "@material-ui/icons/VpnKeyOutlined" @@ -48,6 +49,12 @@ export const Sidebar: React.FC = () => { > General + } + > + Licenses + } From 16e0a5cd50f1131df134f6c14baca14c2987a90c Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Mon, 17 Apr 2023 19:30:12 +0000 Subject: [PATCH 02/21] WIP --- .../LicensesSettingsPage/AddNewLicense.tsx | 36 +++++++++++ .../LicensesSettingsPage.tsx | 61 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx new file mode 100644 index 0000000000000..bac5c50c68ad7 --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx @@ -0,0 +1,36 @@ + +import { useQuery } from "@tanstack/react-query" +import { getLicenses } from "api/api" +import { Stack } from "components/Stack/Stack" +import { FC } from "react" +import { Helmet } from "react-helmet-async" +import { pageTitle } from "utils/page" +import { License } from "api/typesGenerated" +import { Header } from "components/DeploySettingsLayout/Header" +import { Button, Card, CardContent, Typography } from "@material-ui/core" +import dayjs from "dayjs" +import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "components/PageHeader/PageHeader" +import { PlusOneOutlined } from "@material-ui/icons" + +const AddNewLicense: FC = () => { + + return ( + <> + + {pageTitle("Add new License")} + + +
+ + + + + + + ) +} + +export default AddNewLicense diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx new file mode 100644 index 0000000000000..0be8d7477c738 --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -0,0 +1,61 @@ +import { useQuery } from "@tanstack/react-query" +import { getLicenses } from "api/api" +import { Stack } from "components/Stack/Stack" +import { FC } from "react" +import { Helmet } from "react-helmet-async" +import { pageTitle } from "utils/page" +import { License } from "api/typesGenerated" +import { Header } from "components/DeploySettingsLayout/Header" +import { Button, Card, CardContent, Typography } from "@material-ui/core" +import dayjs from "dayjs" +import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "components/PageHeader/PageHeader" +import { PlusOneOutlined } from "@material-ui/icons" + +const LicensesSettingsPage: FC = () => { + const { data: licenses, isLoading } = useQuery({ + queryKey: ["licenses"], + queryFn: () => getLicenses() + }) + + console.log(licenses) + + + return ( + <> + + {pageTitle("General Settings")} + + +
+ + + + + + + {licenses?.map((license) => ( + + + + #{license.id} + + {/*

{dayjs.unix(license.claims.license_expires).toISOString()}

+

{license.claims.trial}

+

{license.claims.account_type}

*/} +
+
+ ))} +
+ + ) +} + +export default LicensesSettingsPage From 432456f2bc75ca4f5e55762aed488561c6fde05a Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Mon, 17 Apr 2023 19:41:42 +0000 Subject: [PATCH 03/21] WIP --- .../LicensesSettingsPage/AddNewLicense.stories.tsx | 8 ++++++++ .../LicensesSettingsPage/AddNewLicense.tsx | 12 ------------ 2 files changed, 8 insertions(+), 12 deletions(-) create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx new file mode 100644 index 0000000000000..908d9ccce3f66 --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx @@ -0,0 +1,8 @@ +import AddNewLicense from "./AddNewLicense"; + +export default { + title: "pages/AddNewLicense", + component: AddNewLicense, +} + +export const Default = () => ; diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx index bac5c50c68ad7..c441359a9bdfc 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx @@ -1,24 +1,12 @@ -import { useQuery } from "@tanstack/react-query" -import { getLicenses } from "api/api" import { Stack } from "components/Stack/Stack" import { FC } from "react" -import { Helmet } from "react-helmet-async" -import { pageTitle } from "utils/page" -import { License } from "api/typesGenerated" import { Header } from "components/DeploySettingsLayout/Header" -import { Button, Card, CardContent, Typography } from "@material-ui/core" -import dayjs from "dayjs" -import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "components/PageHeader/PageHeader" -import { PlusOneOutlined } from "@material-ui/icons" const AddNewLicense: FC = () => { return ( <> - - {pageTitle("Add new License")} -
Date: Mon, 17 Apr 2023 17:27:17 -0300 Subject: [PATCH 04/21] wip --- site/package.json | 1 + .../AddNewLicense.stories.tsx | 8 -- .../LicensesSettingsPage/AddNewLicense.tsx | 24 ---- .../AddNewLicensePageView.stories.tsx | 8 ++ .../AddNewLicensePageView.tsx | 116 ++++++++++++++++++ site/yarn.lock | 32 ++++- 6 files changed, 156 insertions(+), 33 deletions(-) delete mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx delete mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx diff --git a/site/package.json b/site/package.json index 20e6992413f8d..42dc091b147cb 100644 --- a/site/package.json +++ b/site/package.json @@ -64,6 +64,7 @@ "jest-location-mock": "1.0.9", "just-debounce-it": "3.1.1", "lodash": "4.17.21", + "material-ui-dropzone": "^3.5.0", "playwright": "1.29.2", "react": "18.2.0", "react-chartjs-2": "4.3.1", diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx deleted file mode 100644 index 908d9ccce3f66..0000000000000 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import AddNewLicense from "./AddNewLicense"; - -export default { - title: "pages/AddNewLicense", - component: AddNewLicense, -} - -export const Default = () => ; diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx deleted file mode 100644 index c441359a9bdfc..0000000000000 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicense.tsx +++ /dev/null @@ -1,24 +0,0 @@ - -import { Stack } from "components/Stack/Stack" -import { FC } from "react" -import { Header } from "components/DeploySettingsLayout/Header" - -const AddNewLicense: FC = () => { - - return ( - <> - -
- - - - - - - ) -} - -export default AddNewLicense diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx new file mode 100644 index 0000000000000..db1be6cdc28f2 --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx @@ -0,0 +1,8 @@ +import AddNewLicensePageView from "./AddNewLicensePageView"; + +export default { + title: "pages/AddNewLicensePageView", + component: AddNewLicensePageView, +} + +export const Default = () => diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx new file mode 100644 index 0000000000000..6cfe004cce875 --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -0,0 +1,116 @@ + +import { Stack } from "components/Stack/Stack" +import { FC, PropsWithChildren, useState } from "react" +import { Header } from "components/DeploySettingsLayout/Header" +import { FormSection, HorizontalForm } from "components/Form/Form" +import Button from "@material-ui/core/Button" +import { DropzoneDialog } from "material-ui-dropzone" +import { Divider, Input, TextareaAutosize, makeStyles } from "@material-ui/core" +import { Fieldset } from "components/DeploySettingsLayout/Fieldset" + +const AddNewLicense: FC = () => { + + return ( + <> + +
+ + + + { + console.log(data) + }} + > + + + + + + + + or + +
{ console.log(data) }} + > + +
+
+ + ) +} + +export default AddNewLicense + +const useStyles = makeStyles(theme => ({ + container: { + display: "flex", + alignItems: "center" + }, + border: { + borderBottom: `2px solid ${theme.palette.divider}`, + width: "100%" + }, + content: { + paddingTop: theme.spacing(0.5), + paddingBottom: theme.spacing(0.5), + paddingRight: theme.spacing(2), + paddingLeft: theme.spacing(2), + fontWeight: 500, + fontSize: theme.typography.h5.fontSize, + color: theme.palette.text.secondary + } +})); + +const DividerWithText: FC = ({ children }) => { + const classes = useStyles(); + return ( +
+
+ {children} +
+
+ ); +}; + +const DropzoneDialogExample = (props) => { + const [open, setOpen] = useState(false); + const [files, setFiles] = useState([]); + + function handleClose() { + setOpen(false); + } + + function handleSave(files) { + setFiles(files); + setOpen(false); + } + + function handleOpen() { + setOpen(true) + } + + return ( +
+ + +
+ ); +} diff --git a/site/yarn.lock b/site/yarn.lock index 6dbc022e88f9e..b21220207fe66 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -4201,6 +4201,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +attr-accept@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" + integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -4770,7 +4775,7 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -clsx@^1.0.4: +clsx@^1.0.2, clsx@^1.0.4: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -6154,6 +6159,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-selector@^0.1.12: + version "0.1.19" + resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.19.tgz#8ecc9d069a6f544f2e4a096b64a8052e70ec8abf" + integrity sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ== + dependencies: + tslib "^2.0.1" + file-system-cache@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-2.0.2.tgz#6b51d58c5786302146fa883529e0d7f88896e948" @@ -8433,6 +8445,15 @@ material-colors@^1.2.1: resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== +material-ui-dropzone@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/material-ui-dropzone/-/material-ui-dropzone-3.5.0.tgz#2d7f36032db96c24ce97b42e4d0053f94e91a9fc" + integrity sha512-3BC6mz/4OEM4ZpbqMfuMN065JQyqfEbifT6/VzIua7Zj4b0DaR5YPCgpN+fL/e8yBgTs9MGBZJQY06p5pfKwvw== + dependencies: + "@babel/runtime" "^7.4.4" + clsx "^1.0.2" + react-dropzone "^10.2.1" + mdast-util-definitions@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" @@ -9957,6 +9978,15 @@ react-dom@18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-dropzone@^10.2.1: + version "10.2.2" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.2.2.tgz#67b4db7459589a42c3b891a82eaf9ade7650b815" + integrity sha512-U5EKckXVt6IrEyhMMsgmHQiWTGLudhajPPG77KFSvgsMqNEHSyGpqWvOMc5+DhEah/vH4E1n+J5weBNLd5VtyA== + dependencies: + attr-accept "^2.0.0" + file-selector "^0.1.12" + prop-types "^15.7.2" + react-element-to-jsx-string@^15.0.0: version "15.0.0" resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz#1cafd5b6ad41946ffc8755e254da3fc752a01ac6" From 496f535add938735b4240eb1b3bf53c7017fc638 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 18 Apr 2023 14:57:15 -0300 Subject: [PATCH 05/21] wip --- site/package.json | 2 + .../AddNewLicensePageView.tsx | 126 ++++++----- site/yarn.lock | 201 +++++++++++++++++- 3 files changed, 277 insertions(+), 52 deletions(-) diff --git a/site/package.json b/site/package.json index 42dc091b147cb..21a424609243a 100644 --- a/site/package.json +++ b/site/package.json @@ -69,6 +69,7 @@ "react": "18.2.0", "react-chartjs-2": "4.3.1", "react-color": "2.19.3", + "react-confetti": "^6.1.0", "react-dom": "18.2.0", "react-headless-tabs": "6.0.3", "react-helmet-async": "1.3.0", @@ -76,6 +77,7 @@ "react-markdown": "8.0.3", "react-router-dom": "6.4.1", "react-syntax-highlighter": "15.5.0", + "react-use": "^17.4.0", "react-virtualized-auto-sizer": "1.0.7", "react-window": "1.8.8", "remark-gfm": "3.0.1", diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index 6cfe004cce875..b903fb32c5085 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -1,56 +1,114 @@ -import { Stack } from "components/Stack/Stack" -import { FC, PropsWithChildren, useState } from "react" -import { Header } from "components/DeploySettingsLayout/Header" -import { FormSection, HorizontalForm } from "components/Form/Form" +import { TextField, makeStyles, useTheme } from "@material-ui/core" import Button from "@material-ui/core/Button" -import { DropzoneDialog } from "material-ui-dropzone" -import { Divider, Input, TextareaAutosize, makeStyles } from "@material-ui/core" import { Fieldset } from "components/DeploySettingsLayout/Fieldset" +import { Header } from "components/DeploySettingsLayout/Header" +import { FormFields, FormSection } from "components/Form/Form" +import { Stack } from "components/Stack/Stack" +import { DropzoneDialog } from "material-ui-dropzone" +import { FC, PropsWithChildren, useState } from "react" +import Confetti from 'react-confetti' +import { useToggle } from 'react-use' +import useWindowSize from 'react-use/lib/useWindowSize' const AddNewLicense: FC = () => { + const styles = useStyles() + const { width, height } = useWindowSize() + const [confettiOn, toggleConfettiOn] = useToggle(false) + const [isDialogOpen, toggleDialogOpen] = useToggle(false) + const [files, setFiles] = useState([]); + const theme = useTheme() + + function handleSave(files: File[]) { + setFiles(files); + console.log(files) + toggleDialogOpen() + toggleConfettiOn() + setTimeout(() => { + toggleConfettiOn(false) + }, 2000 ) + } return ( <> +
+ - - { - console.log(data) - }} + - + +
+ + toggleDialogOpen(false)} + /> +
+
+ - or + or +
{ console.log(data) }} > + -
-
+ + ) } export default AddNewLicense + const useStyles = makeStyles(theme => ({ + formSectionRoot: { + alignItems: "center", + }, + description: { + color: theme.palette.text.secondary, + lineHeight: "160%", + }, + title: { + ...theme.typography.h5, + fontWeight: 600, + marging: theme.spacing(1) + }, container: { display: "flex", alignItems: "center" @@ -65,8 +123,8 @@ const useStyles = makeStyles(theme => ({ paddingRight: theme.spacing(2), paddingLeft: theme.spacing(2), fontWeight: 500, - fontSize: theme.typography.h5.fontSize, - color: theme.palette.text.secondary + fontSize: theme.typography.h5.fontSize, + color: theme.palette.text.secondary } })); @@ -80,37 +138,3 @@ const DividerWithText: FC = ({ children }) => {
); }; - -const DropzoneDialogExample = (props) => { - const [open, setOpen] = useState(false); - const [files, setFiles] = useState([]); - - function handleClose() { - setOpen(false); - } - - function handleSave(files) { - setFiles(files); - setOpen(false); - } - - function handleOpen() { - setOpen(true) - } - - return ( -
- - -
- ); -} diff --git a/site/yarn.lock b/site/yarn.lock index b21220207fe66..2c9cf648caed5 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -1099,7 +1099,7 @@ core-js-pure "^3.25.1" regenerator-runtime "^0.13.11" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== @@ -3144,6 +3144,11 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/js-cookie@^2.2.6": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" + integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== + "@types/js-levenshtein@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" @@ -3760,6 +3765,11 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440" integrity sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg== +"@xobotyi/scrollbar-width@^1.9.5": + version "1.9.5" + resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" + integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== + "@xstate/cli@0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@xstate/cli/-/cli-0.3.0.tgz#810faa6319fa11811310b1defdd021c4cda2ec26" @@ -4943,6 +4953,13 @@ cookie@^0.4.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +copy-to-clipboard@^3.3.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" + integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== + dependencies: + toggle-selection "^1.0.6" + core-js-compat@^3.25.1: version "3.30.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.0.tgz#99aa2789f6ed2debfa1df3232784126ee97f4d80" @@ -5034,6 +5051,21 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-in-js-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb" + integrity sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A== + dependencies: + hyphenate-style-name "^1.0.3" + +css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-vendor@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" @@ -5079,6 +5111,11 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== +csstype@^3.0.6: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -5484,6 +5521,13 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + es-abstract@^1.19.0, es-abstract@^1.20.4: version "1.21.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6" @@ -6107,11 +6151,26 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-loops@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75" + integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== + fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fast-shallow-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b" + integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw== + +fastest-stable-stringify@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76" + integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q== + fastq@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" @@ -6919,6 +6978,14 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +inline-style-prefixer@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz#4290ed453ab0e4441583284ad86e41ad88384f44" + integrity sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg== + dependencies: + css-in-js-utils "^3.1.0" + fast-loops "^1.1.3" + inquirer@^8.2.0: version "8.2.5" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" @@ -7884,6 +7951,11 @@ jest_workaround@0.1.14: resolved "https://registry.yarnpkg.com/jest_workaround/-/jest_workaround-0.1.14.tgz#0c82f35d75eeebd9f5ee183887588db44ae61bb6" integrity sha512-9FqnkYn0mihczDESOMazSIOxbKAZ2HQqE8e12F3CsVNvEJkLBebQj/CT1xqviMOTMESJDYh6buWtsw2/zYUepw== +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + js-levenshtein@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" @@ -8604,6 +8676,11 @@ mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0: dependencies: "@types/mdast" "^3.0.0" +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -9111,6 +9188,20 @@ nan@^2.17.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nano-css@^5.3.1: + version "5.3.5" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.5.tgz#3075ea29ffdeb0c7cb6d25edb21d8f7fa8e8fe8e" + integrity sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg== + dependencies: + css-tree "^1.1.2" + csstype "^3.0.6" + fastest-stable-stringify "^2.0.2" + inline-style-prefixer "^6.0.0" + rtl-css-js "^1.14.0" + sourcemap-codec "^1.4.8" + stacktrace-js "^2.0.2" + stylis "^4.0.6" + nanoclone@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" @@ -9949,6 +10040,13 @@ react-colorful@^5.1.2: resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw== +react-confetti@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.1.0.tgz#03dc4340d955acd10b174dbf301f374a06e29ce6" + integrity sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw== + dependencies: + tween-functions "^1.2.0" + react-docgen-typescript@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c" @@ -10124,6 +10222,31 @@ react-transition-group@^4.4.0: loose-envify "^1.4.0" prop-types "^15.6.2" +react-universal-interface@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" + integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== + +react-use@^17.4.0: + version "17.4.0" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.4.0.tgz#cefef258b0a6c534a5c8021c2528ac6e1a4cdc6d" + integrity sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q== + dependencies: + "@types/js-cookie" "^2.2.6" + "@xobotyi/scrollbar-width" "^1.9.5" + copy-to-clipboard "^3.3.1" + fast-deep-equal "^3.1.3" + fast-shallow-equal "^1.0.0" + js-cookie "^2.2.1" + nano-css "^5.3.1" + react-universal-interface "^0.6.2" + resize-observer-polyfill "^1.5.1" + screenfull "^5.1.0" + set-harmonic-interval "^1.0.1" + throttle-debounce "^3.0.1" + ts-easing "^0.2.0" + tslib "^2.1.0" + react-virtualized-auto-sizer@1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.7.tgz#bfb8414698ad1597912473de3e2e5f82180c1195" @@ -10378,6 +10501,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resize-observer@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/resize-observer/-/resize-observer-1.0.4.tgz#48beb64602ce408ebd1a433784d64ef76f38d321" @@ -10495,6 +10623,13 @@ rollup-plugin-visualizer@5.9.0: optionalDependencies: fsevents "~2.3.2" +rtl-css-js@^1.14.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80" + integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== + dependencies: + "@babel/runtime" "^7.1.2" + run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -10571,6 +10706,11 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +screenfull@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" + integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== + "semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -10657,6 +10797,11 @@ set-cookie-parser@^2.4.6: resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== +set-harmonic-interval@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" + integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -10864,6 +11009,13 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +stack-generator@^2.0.5: + version "2.0.10" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" + integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== + dependencies: + stackframe "^1.3.4" + stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -10871,6 +11023,28 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-gps@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" + integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== + dependencies: + source-map "0.5.6" + stackframe "^1.3.4" + +stacktrace-js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + state-local@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" @@ -11051,6 +11225,11 @@ style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" +stylis@^4.0.6: + version "4.1.3" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" + integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -11196,6 +11375,11 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throttle-debounce@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" + integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== + through2@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -11251,6 +11435,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== + toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -11308,6 +11497,11 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0: resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== +ts-easing@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" + integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== + ts-morph@^13.0.1: version "13.0.3" resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-13.0.3.tgz#c0c51d1273ae2edb46d76f65161eb9d763444c1d" @@ -11360,6 +11554,11 @@ tunnel@^0.0.6: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== +tween-functions@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff" + integrity sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" From bcb5d9a90c1a69ce52fd6b9d9d2650e8f95e8295 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 18 Apr 2023 18:29:52 +0000 Subject: [PATCH 06/21] wip --- site/src/AppRouter.tsx | 5 +++- .../AddNewLicensePageView.tsx | 28 +++++++++++++++---- .../LicensesSettingsPage.tsx | 8 ++++-- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index 4aeb4312d8e9d..97a9b1fd0ec7e 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -7,7 +7,6 @@ import GroupsPage from "pages/GroupsPage/GroupsPage" import LoginPage from "pages/LoginPage/LoginPage" import { SetupPage } from "pages/SetupPage/SetupPage" import { TemplateSettingsPage } from "pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage" -import LicensesSettingsPage from "pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage" import TemplatesPage from "pages/TemplatesPage/TemplatesPage" import UsersPage from "pages/UsersPage/UsersPage" import WorkspacesPage from "pages/WorkspacesPage/WorkspacesPage" @@ -157,6 +156,9 @@ const TemplateSchedulePage = lazy( ), ) +const LicensesSettingsPage = lazy(() => import("./pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage")) +const AddNewLicensePageView = lazy(() => import("./pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView")) + export const AppRouter: FC = () => { return ( }> @@ -246,6 +248,7 @@ export const AppRouter: FC = () => { > } /> } /> + } /> } /> } /> } /> diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index b903fb32c5085..c9b172ffe7036 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -1,5 +1,5 @@ -import { TextField, makeStyles, useTheme } from "@material-ui/core" +import { makeStyles, useTheme } from "@material-ui/core/styles" import Button from "@material-ui/core/Button" import { Fieldset } from "components/DeploySettingsLayout/Fieldset" import { Header } from "components/DeploySettingsLayout/Header" @@ -8,8 +8,12 @@ import { Stack } from "components/Stack/Stack" import { DropzoneDialog } from "material-ui-dropzone" import { FC, PropsWithChildren, useState } from "react" import Confetti from 'react-confetti' +import { NavLink, Link as RouterLink } from "react-router-dom" import { useToggle } from 'react-use' import useWindowSize from 'react-use/lib/useWindowSize' +import TextField from "@material-ui/core/TextField" +import PlusOneOutlined from "@material-ui/icons/PlusOneOutlined" +import { CloudUploadOutlined } from "@material-ui/icons" const AddNewLicense: FC = () => { const styles = useStyles() @@ -26,7 +30,7 @@ const AddNewLicense: FC = () => { toggleConfettiOn() setTimeout(() => { toggleConfettiOn(false) - }, 2000 ) + }, 2000) } return ( @@ -42,7 +46,13 @@ const AddNewLicense: FC = () => { title="Add your license" description="Add a license to your account to unlock more features." /> - + { height: "100%", }}>
- {
{ console.log(data) }} + onSubmit={(data: unknown) => { + console.log(data) + }} > diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index 0be8d7477c738..8b1f3c7a38fc4 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -10,6 +10,7 @@ import { Button, Card, CardContent, Typography } from "@material-ui/core" import dayjs from "dayjs" import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "components/PageHeader/PageHeader" import { PlusOneOutlined } from "@material-ui/icons" +import { Link, NavLink } from "react-router-dom" const LicensesSettingsPage: FC = () => { const { data: licenses, isLoading } = useQuery({ @@ -32,10 +33,11 @@ const LicensesSettingsPage: FC = () => { /> From 7adf2f171826fa5c520be4a445666e52f592ed1b Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 19 Apr 2023 18:13:09 +0000 Subject: [PATCH 07/21] wip --- enterprise/coderd/license/license.go | 8 + site/src/AppRouter.tsx | 19 +- site/src/api/api.ts | 21 +- .../AddNewLicensePageView.stories.tsx | 2 +- .../AddNewLicensePageView.tsx | 136 +++++------- .../LicensesSettingsPage.tsx | 200 +++++++++++++++--- 6 files changed, 265 insertions(+), 121 deletions(-) diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index d29dad402e613..6ae319e0c40e4 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -52,6 +52,13 @@ func Entitlements( return entitlements, xerrors.Errorf("query active user count: %w", err) } + // always shows active user count regardless of license + entitlements.Features[codersdk.FeatureUserLimit] = codersdk.Feature{ + Entitlement: codersdk.EntitlementNotEntitled, + Enabled: enablements[codersdk.FeatureUserLimit], + Actual: &activeUserCount, + } + allFeatures := false // Here we loop through licenses to detect enabled features. @@ -79,6 +86,7 @@ func Entitlements( switch featureName { // User limit has special treatment as our only non-boolean feature. case codersdk.FeatureUserLimit: + fmt.Println("user limit-------------", featureValue) limit := featureValue priorLimit := entitlements.Features[codersdk.FeatureUserLimit] if priorLimit.Limit != nil && *priorLimit.Limit > limit { diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index 97a9b1fd0ec7e..b529897ea8f9b 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -156,8 +156,18 @@ const TemplateSchedulePage = lazy( ), ) -const LicensesSettingsPage = lazy(() => import("./pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage")) -const AddNewLicensePageView = lazy(() => import("./pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView")) +const LicensesSettingsPage = lazy( + () => + import( + "./pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage" + ), +) +const AddNewLicensePageView = lazy( + () => + import( + "./pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView" + ), +) export const AppRouter: FC = () => { return ( @@ -248,7 +258,10 @@ export const AppRouter: FC = () => { > } /> } /> - } /> + } + /> } /> } /> } /> diff --git a/site/src/api/api.ts b/site/src/api/api.ts index d26c1af272254..ca7e14d653331 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -966,24 +966,23 @@ export const getWorkspaceBuildParameters = async ( return response.data } type Claims = { - license_expires?: jwt.NumericDate; - account_type?: string; - account_id?: string; - trial: boolean; - all_features: boolean; - version: number; - features: Record; - require_telemetry?: boolean; + license_expires?: jwt.NumericDate + account_type?: string + account_id?: string + trial: boolean + all_features: boolean + version: number + features: Record + require_telemetry?: boolean } type GetLicensesResponse = Omit & { claims: Claims + expires_at: string } export const getLicenses = async (): Promise => { - const response = await axios.get( - `/api/v2/licenses`, - ) + const response = await axios.get(`/api/v2/licenses`) return response.data } diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx index db1be6cdc28f2..acb25d8147834 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx @@ -1,4 +1,4 @@ -import AddNewLicensePageView from "./AddNewLicensePageView"; +import AddNewLicensePageView from "./AddNewLicensePageView" export default { title: "pages/AddNewLicensePageView", diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index c9b172ffe7036..03fa449ca4bbf 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -1,47 +1,34 @@ - -import { makeStyles, useTheme } from "@material-ui/core/styles" import Button from "@material-ui/core/Button" +import TextField from "@material-ui/core/TextField" +import { makeStyles } from "@material-ui/core/styles" +import CloudUploadOutlined from "@material-ui/icons/CloudUploadOutlined" import { Fieldset } from "components/DeploySettingsLayout/Fieldset" import { Header } from "components/DeploySettingsLayout/Header" -import { FormFields, FormSection } from "components/Form/Form" import { Stack } from "components/Stack/Stack" import { DropzoneDialog } from "material-ui-dropzone" import { FC, PropsWithChildren, useState } from "react" -import Confetti from 'react-confetti' -import { NavLink, Link as RouterLink } from "react-router-dom" -import { useToggle } from 'react-use' -import useWindowSize from 'react-use/lib/useWindowSize' -import TextField from "@material-ui/core/TextField" -import PlusOneOutlined from "@material-ui/icons/PlusOneOutlined" -import { CloudUploadOutlined } from "@material-ui/icons" +import { Link as RouterLink, useNavigate } from "react-router-dom" +import { useToggle } from "react-use" const AddNewLicense: FC = () => { const styles = useStyles() - const { width, height } = useWindowSize() - const [confettiOn, toggleConfettiOn] = useToggle(false) const [isDialogOpen, toggleDialogOpen] = useToggle(false) - const [files, setFiles] = useState([]); - const theme = useTheme() + const [files, setFiles] = useState([]) + const navigate = useNavigate() function handleSave(files: File[]) { - setFiles(files); - console.log(files) + setFiles(files) toggleDialogOpen() - toggleConfettiOn() - setTimeout(() => { - toggleConfettiOn(false) - }, 2000) + navigate("/settings/deployment/licenses#success=true") } return ( <> - - +
{ - - - - - -
- - toggleDialogOpen(false)} - /> -
-
- -
- + + + + + toggleDialogOpen(false)} + /> - or - + or
{ console.log(data) }} > - - +
@@ -111,8 +84,15 @@ const AddNewLicense: FC = () => { export default AddNewLicense - -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ + main: { + paddingTop: theme.spacing(5), + }, + ctaButton: { + backgroundImage: `linear-gradient(90deg, ${theme.palette.secondary.main} 0%, ${theme.palette.secondary.dark} 100%)`, + width: theme.spacing(30), + marginBottom: theme.spacing(4), + }, formSectionRoot: { alignItems: "center", }, @@ -123,15 +103,15 @@ const useStyles = makeStyles(theme => ({ title: { ...theme.typography.h5, fontWeight: 600, - marging: theme.spacing(1) + marging: theme.spacing(1), }, container: { display: "flex", - alignItems: "center" + alignItems: "center", }, border: { borderBottom: `2px solid ${theme.palette.divider}`, - width: "100%" + width: "100%", }, content: { paddingTop: theme.spacing(0.5), @@ -140,17 +120,17 @@ const useStyles = makeStyles(theme => ({ paddingLeft: theme.spacing(2), fontWeight: 500, fontSize: theme.typography.h5.fontSize, - color: theme.palette.text.secondary - } -})); + color: theme.palette.text.secondary, + }, +})) const DividerWithText: FC = ({ children }) => { - const classes = useStyles(); + const classes = useStyles() return (
{children}
- ); -}; + ) +} diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index 8b1f3c7a38fc4..e73a0efb5a4a7 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -1,32 +1,67 @@ +import { makeStyles, useTheme } from "@material-ui/core/styles" +import RemoveCircleOutlineSharp from "@material-ui/icons/RemoveCircleOutlineSharp" import { useQuery } from "@tanstack/react-query" +import { useMachine } from "@xstate/react" import { getLicenses } from "api/api" +import { Header } from "components/DeploySettingsLayout/Header" import { Stack } from "components/Stack/Stack" -import { FC } from "react" +import dayjs from "dayjs" +import { FC, useEffect } from "react" import { Helmet } from "react-helmet-async" +import { Link, useSearchParams } from "react-router-dom" import { pageTitle } from "utils/page" -import { License } from "api/typesGenerated" -import { Header } from "components/DeploySettingsLayout/Header" -import { Button, Card, CardContent, Typography } from "@material-ui/core" -import dayjs from "dayjs" -import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "components/PageHeader/PageHeader" -import { PlusOneOutlined } from "@material-ui/icons" -import { Link, NavLink } from "react-router-dom" +import { entitlementsMachine } from "xServices/entitlements/entitlementsXService" +import Confetti from "react-confetti" +import useWindowSize from "react-use/lib/useWindowSize" +import useToggle from "react-use/lib/useToggle" +import Button from "@material-ui/core/Button" +import Card from "@material-ui/core/Card" +import CardContent from "@material-ui/core/CardContent" +import Box from "@material-ui/core/Box" +import Skeleton from "@material-ui/lab/Skeleton" 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 theme = useTheme() + const { data: licenses, isLoading } = useQuery({ queryKey: ["licenses"], - queryFn: () => getLicenses() + queryFn: () => getLicenses(), }) - console.log(licenses) - + useEffect(() => { + if (success) { + toggleConfettiOn() + setTimeout(() => { + toggleConfettiOn(false) + setSearchParams() + }, 2000) + } + }, [setSearchParams, success, toggleConfettiOn]) return ( <> {pageTitle("General Settings")} - + +
{ component={Link} to="/settings/deployment/licenses/add" > - Add new license + Add new License - - - {licenses?.map((license) => ( - - - - #{license.id} - - {/*

{dayjs.unix(license.claims.license_expires).toISOString()}

-

{license.claims.trial}

-

{license.claims.account_type}

*/} -
-
- ))} -
+ {isLoading && } + + {!isLoading && licenses && licenses?.length > 0 && ( + + {licenses?.map((license) => ( + + + + + #{license.id} + + + {license.claims.account_type} License + + +
+ + {entitlements?.features.user_limit.actual} + + + /{" "} + {entitlements?.features.user_limit.limit || "Unlimited"}{" "} + users + +
+ + + + {dayjs(license.expires_at).format("MMMM D, YYYY")} + + + Valid until + + +
+ +
+
+
+
+
+ ))} +
+ )} + + {!isLoading && licenses && licenses.length === 0 && ( + + + + )} ) } +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", + }, + expirationDate: { + fontWeight: 600, + color: theme.palette.primary.main, + }, + expirationDateLabel: { + color: theme.palette.secondary.main, + fontSize: theme.typography.subtitle2.fontSize, + }, + userLimit: { + width: "33%", + }, + actions: { + width: "33%", + textAlign: "right", + }, + userLimitActual: { + // fontWeight: 600, + fontSize: theme.typography.h3.fontSize, + paddingRight: theme.spacing(1), + color: theme.palette.primary.main, + }, + userLimitLimit: { + color: theme.palette.secondary.main, + fontSize: theme.typography.h5.fontSize, + fontWeight: 600, + }, + licenseCard: {}, + cardContent: { + minHeight: 200, + }, + licenseId: { + color: theme.palette.secondary.main, + fontWeight: 600, + fontSize: theme.typography.h5.fontSize, + }, + accountType: { + fontWeight: 600, + fontSize: theme.typography.pxToRem(32), + justifyContent: "center", + alignItems: "center", + textTransform: "capitalize", + }, +})) + export default LicensesSettingsPage From 8ece19678ee7fdd329d16f8c213f5b5d6d631659 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 19 Apr 2023 20:13:19 +0000 Subject: [PATCH 08/21] wip --- site/src/api/api.ts | 11 ++++ .../AddNewLicensePageView.tsx | 65 +++++++++++++++---- .../LicensesSettingsPage.tsx | 57 +++++++++++++--- 3 files changed, 114 insertions(+), 19 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index ca7e14d653331..1bd928c44d733 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -986,6 +986,17 @@ export const getLicenses = async (): Promise => { return response.data } +export const createLicense = async ( + data: TypesGen.AddLicenseRequest, +): Promise => { + const response = await axios.post(`/api/v2/licenses`, data) + return response.data +} + +export const removeLicense = async (licenseId: number): Promise => { + await axios.delete(`/api/v2/licenses/${licenseId}`) +} + export class MissingBuildParameters extends Error { parameters: TypesGen.TemplateVersionParameter[] = [] diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index 03fa449ca4bbf..3c4d0c5cac3f7 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -2,24 +2,54 @@ import Button from "@material-ui/core/Button" import TextField from "@material-ui/core/TextField" import { makeStyles } from "@material-ui/core/styles" import CloudUploadOutlined from "@material-ui/icons/CloudUploadOutlined" +import { useMutation } from "@tanstack/react-query" +import { createLicense } from "api/api" import { Fieldset } from "components/DeploySettingsLayout/Fieldset" import { Header } from "components/DeploySettingsLayout/Header" +import { displayError, displaySuccess } from "components/GlobalSnackbar/utils" import { Stack } from "components/Stack/Stack" import { DropzoneDialog } from "material-ui-dropzone" -import { FC, PropsWithChildren, useState } from "react" +import { FC, PropsWithChildren } from "react" import { Link as RouterLink, useNavigate } from "react-router-dom" import { useToggle } from "react-use" const AddNewLicense: FC = () => { const styles = useStyles() const [isDialogOpen, toggleDialogOpen] = useToggle(false) - const [files, setFiles] = useState([]) const navigate = useNavigate() - function handleSave(files: File[]) { - setFiles(files) - toggleDialogOpen() - navigate("/settings/deployment/licenses#success=true") + const { + mutate: saveLicenseKeyApi, + isLoading: isCreating, + isError: creationFailed, + } = useMutation(createLicense) + + function handleFileUploaded(files: File[]) { + const fileReader = new FileReader() + fileReader.onload = () => { + const licenseKey = fileReader.result as string + + saveLicenseKey(licenseKey) + + fileReader.onerror = () => { + displayError("Failed to read file") + } + } + + fileReader.readAsText(files[0]) + } + + function saveLicenseKey(licenseKey: string) { + saveLicenseKeyApi( + { license: licenseKey }, + { + onSuccess: () => { + displaySuccess("License key saved") + navigate("/settings/deployment/licenses?success=true") + }, + onError: () => displayError("Failed to save license key"), + }, + ) } return ( @@ -50,13 +80,12 @@ const AddNewLicense: FC = () => { size="large" onClick={() => toggleDialogOpen()} > - Upload License File + Upload License Key toggleDialogOpen(false)} @@ -66,11 +95,25 @@ const AddNewLicense: FC = () => {
{ - console.log(data) + validation={creationFailed ? "License key is invalid" : undefined} + onSubmit={(e) => { + e.preventDefault() + + const form = e.target + const formData = new FormData(form as HTMLFormElement) + + const licenseKey = formData.get("licenseKey") + + saveLicenseKey(licenseKey?.toString() || "") }} + button={ + + } > { const [entitlementsState] = useMachine(entitlementsMachine) @@ -27,11 +29,21 @@ const LicensesSettingsPage: FC = () => { const [searchParams, setSearchParams] = useSearchParams() const success = searchParams.get("success") const [confettiOn, toggleConfettiOn] = useToggle(false) + const [licenseIDMarkedForRemoval, setLicenseIDMarkedForRemoval] = useState< + number | undefined + >(undefined) const { width, height } = useWindowSize() + const { mutate: removeLicenseApi, isLoading: isRemovingLicense } = + useMutation(removeLicense) + const theme = useTheme() - const { data: licenses, isLoading } = useQuery({ + const { + data: licenses, + isLoading, + refetch: refetchGetLicenses, + } = useQuery({ queryKey: ["licenses"], queryFn: () => getLicenses(), }) @@ -49,7 +61,7 @@ const LicensesSettingsPage: FC = () => { return ( <> - {pageTitle("General Settings")} + {pageTitle("License Settings")} { numberOfPieces={confettiOn ? 200 : 0} colors={[theme.palette.primary.main, theme.palette.secondary.main]} /> + { + if (!licenseIDMarkedForRemoval) { + return + } + removeLicenseApi(licenseIDMarkedForRemoval, { + onSuccess: () => { + displaySuccess("Successfully removed license") + void refetchGetLicenses() + }, + onError: () => { + displayError("Failed to remove license") + void refetchGetLicenses() + }, + }) + setLicenseIDMarkedForRemoval(undefined) + }} + onClose={() => setLicenseIDMarkedForRemoval(undefined)} + title="Confirm removal" + confirmLoading={isRemovingLicense} + confirmText="Remove" + description="Are you sure you want to remove this license?" + /> { component={Link} to="/settings/deployment/licenses/add" > - Add new License + Add new License key @@ -117,7 +155,9 @@ const LicensesSettingsPage: FC = () => { - {dayjs(license.expires_at).format("MMMM D, YYYY")} + {dayjs + .unix(license.claims.license_expires) + .format("MMMM D, YYYY")} Valid until @@ -128,6 +168,7 @@ const LicensesSettingsPage: FC = () => { startIcon={} variant="text" size="small" + onClick={() => setLicenseIDMarkedForRemoval(license.id)} > Remove @@ -143,7 +184,7 @@ const LicensesSettingsPage: FC = () => { {!isLoading && licenses && licenses.length === 0 && ( )} From 377494415d8f1d4c6fce690fcdf1a25504eb0330 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 19 Apr 2023 20:14:29 +0000 Subject: [PATCH 09/21] wip --- enterprise/coderd/license/license.go | 1 - 1 file changed, 1 deletion(-) diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index 6ae319e0c40e4..1d9b6f8e20945 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -86,7 +86,6 @@ func Entitlements( switch featureName { // User limit has special treatment as our only non-boolean feature. case codersdk.FeatureUserLimit: - fmt.Println("user limit-------------", featureValue) limit := featureValue priorLimit := entitlements.Features[codersdk.FeatureUserLimit] if priorLimit.Limit != nil && *priorLimit.Limit > limit { From 5b782ce00998fe26b4ca047848eebfcbfdf3b107 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Fri, 21 Apr 2023 10:28:47 -0300 Subject: [PATCH 10/21] Apply suggestions from code review Co-authored-by: Ben Potter --- .../LicensesSettingsPage/LicensesSettingsPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index ed9c9fbc0585d..dc347f4cd9b2e 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -90,7 +90,7 @@ const LicensesSettingsPage: FC = () => { setLicenseIDMarkedForRemoval(undefined) }} onClose={() => setLicenseIDMarkedForRemoval(undefined)} - title="Confirm removal" + title="Confirm license removal" confirmLoading={isRemovingLicense} confirmText="Remove" description="Are you sure you want to remove this license?" @@ -102,7 +102,7 @@ const LicensesSettingsPage: FC = () => { >
@@ -184,7 +184,7 @@ const LicensesSettingsPage: FC = () => { {!isLoading && licenses && licenses.length === 0 && ( )} From eaba405757cfca2b4e71caf99c317fe34940d9a7 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Fri, 21 Apr 2023 15:11:02 +0000 Subject: [PATCH 11/21] wip: ui improvements --- .../DividerWithText/DividerWithText.tsx | 32 +++ site/src/components/FileUpload/FileUpload.tsx | 178 ++++++++++++++++ .../components/LicenseCard/LicenseCard.tsx | 156 ++++++++++++++ .../CreateTemplatePage/TemplateUpload.tsx | 191 +++--------------- .../AddNewLicensePageView.tsx | 84 +++----- .../LicensesSettingsPage.tsx | 167 +++------------ 6 files changed, 451 insertions(+), 357 deletions(-) create mode 100644 site/src/components/DividerWithText/DividerWithText.tsx create mode 100644 site/src/components/FileUpload/FileUpload.tsx create mode 100644 site/src/components/LicenseCard/LicenseCard.tsx diff --git a/site/src/components/DividerWithText/DividerWithText.tsx b/site/src/components/DividerWithText/DividerWithText.tsx new file mode 100644 index 0000000000000..a246163f20591 --- /dev/null +++ b/site/src/components/DividerWithText/DividerWithText.tsx @@ -0,0 +1,32 @@ +import { makeStyles } from "@material-ui/core/styles" +import { FC, PropsWithChildren } from "react" + +export const DividerWithText: FC = ({ children }) => { + const classes = useStyles() + return ( +
+
+ {children} +
+
+ ) +} + +const useStyles = makeStyles((theme) => ({ + container: { + display: "flex", + alignItems: "center", + }, + border: { + borderBottom: `2px solid ${theme.palette.divider}`, + width: "100%", + }, + content: { + paddingTop: theme.spacing(0.5), + paddingBottom: theme.spacing(0.5), + paddingRight: theme.spacing(2), + paddingLeft: theme.spacing(2), + fontSize: theme.typography.h5.fontSize, + color: theme.palette.text.secondary, + }, +})) diff --git a/site/src/components/FileUpload/FileUpload.tsx b/site/src/components/FileUpload/FileUpload.tsx new file mode 100644 index 0000000000000..229aa2af02c9d --- /dev/null +++ b/site/src/components/FileUpload/FileUpload.tsx @@ -0,0 +1,178 @@ +import { makeStyles } from "@material-ui/core/styles" +import { Stack } from "components/Stack/Stack" +import { FC, DragEvent, useRef, ReactNode } from "react" +import UploadIcon from "@material-ui/icons/CloudUploadOutlined" +import { useClickable } from "hooks/useClickable" +import CircularProgress from "@material-ui/core/CircularProgress" +import { combineClasses } from "utils/combineClasses" +import IconButton from "@material-ui/core/IconButton" +import RemoveIcon from "@material-ui/icons/DeleteOutline" +import FileIcon from "@material-ui/icons/FolderOutlined" + +const useFileDrop = ( + callback: (file: File) => void, + fileTypeRequired?: string, +): { + onDragOver: (e: DragEvent) => void + onDrop: (e: DragEvent) => void +} => { + const onDragOver = (e: DragEvent) => { + e.preventDefault() + } + + const onDrop = (e: DragEvent) => { + e.preventDefault() + const file = e.dataTransfer.files[0] + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- file can be undefined + if (!file) { + return + } + if (fileTypeRequired && file.type !== fileTypeRequired) { + return + } + callback(file) + } + + return { + onDragOver, + onDrop, + } +} + +export interface FileUploadProps { + isUploading: boolean + onUpload: (file: File) => void + onRemove?: () => void + file?: File + removeLabel: string + title: string + description?: ReactNode + extension?: string + fileTypeRequired?: string +} + +export const FileUpload: FC = ({ + isUploading, + onUpload, + onRemove, + file, + removeLabel, + title, + description, + extension, + fileTypeRequired, +}) => { + const styles = useStyles() + const inputRef = useRef(null) + const tarDrop = useFileDrop(onUpload, fileTypeRequired) + const clickable = useClickable(() => { + if (inputRef.current) { + inputRef.current.click() + } + }) + + if (!isUploading && file) { + return ( + + + + {file.name} + + + + + + + ) + } + + return ( + <> +
+ + {isUploading ? ( + + ) : ( + + )} + + + {title} + {description} + + +
+ + { + const file = event.currentTarget.files?.[0] + if (file) { + onUpload(file) + } + }} + /> + + ) +} + +const useStyles = makeStyles((theme) => ({ + root: { + display: "flex", + alignItems: "center", + justifyContent: "center", + borderRadius: theme.shape.borderRadius, + border: `2px dashed ${theme.palette.divider}`, + padding: theme.spacing(6), + cursor: "pointer", + + "&:hover": { + backgroundColor: theme.palette.background.paper, + }, + }, + + disabled: { + pointerEvents: "none", + opacity: 0.75, + }, + + icon: { + fontSize: theme.spacing(8), + }, + + title: { + fontSize: theme.spacing(2), + }, + + description: { + color: theme.palette.text.secondary, + textAlign: "center", + maxWidth: theme.spacing(50), + }, + + input: { + display: "none", + }, + + file: { + borderRadius: theme.shape.borderRadius, + border: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(2), + background: theme.palette.background.paper, + }, +})) diff --git a/site/src/components/LicenseCard/LicenseCard.tsx b/site/src/components/LicenseCard/LicenseCard.tsx new file mode 100644 index 0000000000000..bb484121d532c --- /dev/null +++ b/site/src/components/LicenseCard/LicenseCard.tsx @@ -0,0 +1,156 @@ +import Box from "@material-ui/core/Box" +import Button from "@material-ui/core/Button" +import Paper from "@material-ui/core/Paper" +import { makeStyles } from "@material-ui/core/styles" +import { License } from "api/typesGenerated" +import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog" +import { Stack } from "components/Stack/Stack" +import dayjs from "dayjs" +import { useState } from "react" + +type LicenseCardProps = { + license: License + userLimitActual?: number + userLimitLimit?: number + onRemove: (licenseId: number) => void + isRemoving: boolean +} + +export const LicenseCard = ({ + license, + userLimitActual, + userLimitLimit, + onRemove, + isRemoving, +}: LicenseCardProps) => { + const styles = useStyles() + + const [licenseIDMarkedForRemoval, setLicenseIDMarkedForRemoval] = useState< + number | undefined + >(undefined) + + return ( + + { + if (!licenseIDMarkedForRemoval) { + return + } + onRemove(licenseIDMarkedForRemoval) + setLicenseIDMarkedForRemoval(undefined) + }} + onClose={() => setLicenseIDMarkedForRemoval(undefined)} + title="Confirm license removal" + confirmLoading={isRemoving} + confirmText="Remove" + description="Are you sure you want to remove this license?" + /> + + + #{license.id} + + + {license.claims.trial ? "Trial" : "Enterprise"} + + +
+ {userLimitActual} + + / {userLimitLimit || "Unlimited"} users + +
+ + + + {dayjs + .unix(license.claims.license_expires) + .format("MMMM D, YYYY")} + + Valid until + +
+ +
+
+
+
+ ) +} + +const useStyles = makeStyles((theme) => ({ + userLimit: { + width: "33%", + }, + actions: { + width: "33%", + textAlign: "right", + }, + userLimitActual: { + // fontWeight: 600, + paddingRight: "5px", + color: theme.palette.primary.main, + }, + userLimitLimit: { + color: theme.palette.secondary.main, + // fontSize: theme.typography.h5.fontSize, + fontWeight: 600, + }, + licenseCard: { + padding: theme.spacing(2), + }, + cardContent: { + minHeight: 100, + }, + licenseId: { + color: theme.palette.secondary.main, + fontWeight: 600, + // fontSize: theme.typography.h5.fontSize, + }, + accountType: { + fontWeight: 600, + fontSize: theme.typography.h4.fontSize, + justifyContent: "center", + alignItems: "center", + textTransform: "capitalize", + }, + expirationDate: { + // fontWeight: 600, + color: theme.palette.primary.main, + }, + expirationDateLabel: { + color: theme.palette.secondary.main, + }, + removeButton: { + height: "17px", + minHeight: "17px", + padding: 0, + border: "none", + color: theme.palette.error.main, + "&:hover": { + backgroundColor: "transparent", + }, + }, +})) diff --git a/site/src/pages/CreateTemplatePage/TemplateUpload.tsx b/site/src/pages/CreateTemplatePage/TemplateUpload.tsx index 4212bb057881f..7d56662e4eeae 100644 --- a/site/src/pages/CreateTemplatePage/TemplateUpload.tsx +++ b/site/src/pages/CreateTemplatePage/TemplateUpload.tsx @@ -1,43 +1,9 @@ -import { makeStyles } from "@material-ui/core/styles" -import { Stack } from "components/Stack/Stack" -import { FC, DragEvent, useRef } from "react" -import UploadIcon from "@material-ui/icons/CloudUploadOutlined" -import { useClickable } from "hooks/useClickable" -import CircularProgress from "@material-ui/core/CircularProgress" -import { combineClasses } from "utils/combineClasses" -import IconButton from "@material-ui/core/IconButton" -import RemoveIcon from "@material-ui/icons/DeleteOutline" -import FileIcon from "@material-ui/icons/FolderOutlined" -import { useTranslation } from "react-i18next" import Link from "@material-ui/core/Link" +import { FileUpload } from "components/FileUpload/FileUpload" +import { FC } from "react" +import { useTranslation } from "react-i18next" import { Link as RouterLink } from "react-router-dom" -const useTarDrop = ( - callback: (file: File) => void, -): { - onDragOver: (e: DragEvent) => void - onDrop: (e: DragEvent) => void -} => { - const onDragOver = (e: DragEvent) => { - e.preventDefault() - } - - const onDrop = (e: DragEvent) => { - e.preventDefault() - const file = e.dataTransfer.files[0] - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- file can be undefined - if (!file || file.type !== "application/x-tar") { - return - } - callback(file) - } - - return { - onDragOver, - onDrop, - } -} - export interface TemplateUploadProps { isUploading: boolean onUpload: (file: File) => void @@ -51,135 +17,36 @@ export const TemplateUpload: FC = ({ onRemove, file, }) => { - const styles = useStyles() - const inputRef = useRef(null) - const tarDrop = useTarDrop(onUpload) - const clickable = useClickable(() => { - if (inputRef.current) { - inputRef.current.click() - } - }) const { t } = useTranslation("createTemplatePage") - if (!isUploading && file) { - return ( - - - - {file.name} - - - - - - - ) - } - - return ( + const description = ( <> -
- - {isUploading ? ( - - ) : ( - - )} - - - {t("form.upload.title")} - - The template has to be a .tar file. You can also use our{" "} - { - e.stopPropagation() - }} - > - starter templates - {" "} - to getting started with Coder. - - - -
- - { - const file = event.currentTarget.files?.[0] - if (file) { - onUpload(file) - } + The template has to be a .tar file. You can also use our{" "} + { + e.stopPropagation() }} - /> + > + starter templates + {" "} + to getting started with Coder. ) -} - -const useStyles = makeStyles((theme) => ({ - root: { - display: "flex", - alignItems: "center", - justifyContent: "center", - borderRadius: theme.shape.borderRadius, - border: `2px dashed ${theme.palette.divider}`, - padding: theme.spacing(6), - cursor: "pointer", - - "&:hover": { - backgroundColor: theme.palette.background.paper, - }, - }, - - disabled: { - pointerEvents: "none", - opacity: 0.75, - }, - - icon: { - fontSize: theme.spacing(8), - }, - title: { - fontSize: theme.spacing(2), - }, - - description: { - color: theme.palette.text.secondary, - textAlign: "center", - maxWidth: theme.spacing(50), - }, - - input: { - display: "none", - }, - - file: { - borderRadius: theme.shape.borderRadius, - border: `1px solid ${theme.palette.divider}`, - padding: theme.spacing(2), - background: theme.palette.background.paper, - }, -})) + return ( + + ) +} diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index 3c4d0c5cac3f7..2e48cb5fc9789 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -1,21 +1,20 @@ import Button from "@material-ui/core/Button" import TextField from "@material-ui/core/TextField" import { makeStyles } from "@material-ui/core/styles" -import CloudUploadOutlined from "@material-ui/icons/CloudUploadOutlined" 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 { Stack } from "components/Stack/Stack" -import { DropzoneDialog } from "material-ui-dropzone" -import { FC, PropsWithChildren } from "react" +import { FC } from "react" import { Link as RouterLink, useNavigate } from "react-router-dom" -import { useToggle } from "react-use" const AddNewLicense: FC = () => { const styles = useStyles() - const [isDialogOpen, toggleDialogOpen] = useToggle(false) const navigate = useNavigate() const { @@ -44,7 +43,7 @@ const AddNewLicense: FC = () => { { license: licenseKey }, { onSuccess: () => { - displaySuccess("License key saved") + displaySuccess("You have successfully added a license") navigate("/settings/deployment/licenses?success=true") }, onError: () => displayError("Failed to save license key"), @@ -52,6 +51,12 @@ const AddNewLicense: FC = () => { ) } + const isUploading = false + + function onUpload(file: File) { + handleFileUploaded([file]) + } + return ( <> { >
- - - - - toggleDialogOpen(false)} - /> +
+ + + + + +
+ or
{ }} button={ } > @@ -148,32 +152,4 @@ const useStyles = makeStyles((theme) => ({ fontWeight: 600, marging: theme.spacing(1), }, - container: { - display: "flex", - alignItems: "center", - }, - border: { - borderBottom: `2px solid ${theme.palette.divider}`, - width: "100%", - }, - content: { - paddingTop: theme.spacing(0.5), - paddingBottom: theme.spacing(0.5), - paddingRight: theme.spacing(2), - paddingLeft: theme.spacing(2), - fontWeight: 500, - fontSize: theme.typography.h5.fontSize, - color: theme.palette.text.secondary, - }, })) - -const DividerWithText: FC = ({ children }) => { - const classes = useStyles() - return ( -
-
- {children} -
-
- ) -} diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index dc347f4cd9b2e..a1561a19d8a76 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -1,26 +1,21 @@ +import Button from "@material-ui/core/Button" import { makeStyles, useTheme } from "@material-ui/core/styles" -import RemoveCircleOutlineSharp from "@material-ui/icons/RemoveCircleOutlineSharp" +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 dayjs from "dayjs" -import { FC, useEffect, useState } from "react" +import { FC, useEffect } from "react" +import Confetti from "react-confetti" import { Helmet } from "react-helmet-async" import { Link, 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 Confetti from "react-confetti" -import useWindowSize from "react-use/lib/useWindowSize" -import useToggle from "react-use/lib/useToggle" -import Button from "@material-ui/core/Button" -import Card from "@material-ui/core/Card" -import CardContent from "@material-ui/core/CardContent" -import Box from "@material-ui/core/Box" -import Skeleton from "@material-ui/lab/Skeleton" -import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog" -import { displayError, displaySuccess } from "components/GlobalSnackbar/utils" const LicensesSettingsPage: FC = () => { const [entitlementsState] = useMachine(entitlementsMachine) @@ -29,9 +24,6 @@ const LicensesSettingsPage: FC = () => { const [searchParams, setSearchParams] = useSearchParams() const success = searchParams.get("success") const [confettiOn, toggleConfettiOn] = useToggle(false) - const [licenseIDMarkedForRemoval, setLicenseIDMarkedForRemoval] = useState< - number | undefined - >(undefined) const { width, height } = useWindowSize() const { mutate: removeLicenseApi, isLoading: isRemovingLicense } = @@ -69,32 +61,6 @@ const LicensesSettingsPage: FC = () => { numberOfPieces={confettiOn ? 200 : 0} colors={[theme.palette.primary.main, theme.palette.secondary.main]} /> - { - if (!licenseIDMarkedForRemoval) { - return - } - removeLicenseApi(licenseIDMarkedForRemoval, { - onSuccess: () => { - displaySuccess("Successfully removed license") - void refetchGetLicenses() - }, - onError: () => { - displayError("Failed to remove license") - void refetchGetLicenses() - }, - }) - setLicenseIDMarkedForRemoval(undefined) - }} - onClose={() => setLicenseIDMarkedForRemoval(undefined)} - title="Confirm license removal" - confirmLoading={isRemovingLicense} - confirmText="Remove" - description="Are you sure you want to remove this license?" - /> { {!isLoading && licenses && licenses?.length > 0 && ( {licenses?.map((license) => ( - - - - - #{license.id} - - - {license.claims.account_type} License - - -
- - {entitlements?.features.user_limit.actual} - - - /{" "} - {entitlements?.features.user_limit.limit || "Unlimited"}{" "} - users - -
- - - - {dayjs - .unix(license.claims.license_expires) - .format("MMMM D, YYYY")} - - - Valid until - - -
- -
-
-
-
-
+ 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() + }, + }) + } + /> ))}
)} @@ -201,48 +128,6 @@ const useStyles = makeStyles((theme) => ({ removeButtom: { color: "red", }, - expirationDate: { - fontWeight: 600, - color: theme.palette.primary.main, - }, - expirationDateLabel: { - color: theme.palette.secondary.main, - fontSize: theme.typography.subtitle2.fontSize, - }, - userLimit: { - width: "33%", - }, - actions: { - width: "33%", - textAlign: "right", - }, - userLimitActual: { - // fontWeight: 600, - fontSize: theme.typography.h3.fontSize, - paddingRight: theme.spacing(1), - color: theme.palette.primary.main, - }, - userLimitLimit: { - color: theme.palette.secondary.main, - fontSize: theme.typography.h5.fontSize, - fontWeight: 600, - }, - licenseCard: {}, - cardContent: { - minHeight: 200, - }, - licenseId: { - color: theme.palette.secondary.main, - fontWeight: 600, - fontSize: theme.typography.h5.fontSize, - }, - accountType: { - fontWeight: 600, - fontSize: theme.typography.pxToRem(32), - justifyContent: "center", - alignItems: "center", - textTransform: "capitalize", - }, })) export default LicensesSettingsPage From ee583e1d1930c830b491170e25233b562844cbe4 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Mon, 24 Apr 2023 14:11:48 +0000 Subject: [PATCH 12/21] wip: extract components --- site/src/AppRouter.tsx | 11 +- site/src/api/api.ts | 2 +- .../AddNewLicensePage.tsx | 53 +++++++++ .../AddNewLicensePageView.stories.tsx | 4 +- .../AddNewLicensePageView.tsx | 48 +++----- .../LicensesSettingsPage.tsx | 104 ++++-------------- .../LicensesSettingsPageView.tsx | 102 +++++++++++++++++ 7 files changed, 198 insertions(+), 126 deletions(-) create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index b529897ea8f9b..b86be4352f342 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -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 = () => { @@ -258,10 +256,7 @@ export const AppRouter: FC = () => { > } /> } /> - } - /> + } /> } /> } /> } /> diff --git a/site/src/api/api.ts b/site/src/api/api.ts index c299210c0bf01..ba39249cdcc3a 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -976,7 +976,7 @@ type Claims = { require_telemetry?: boolean } -type GetLicensesResponse = Omit & { +export type GetLicensesResponse = Omit & { claims: Claims expires_at: string } diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx new file mode 100644 index 0000000000000..9888e8fff8be1 --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx @@ -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 ( + <> + + {pageTitle("License Settings")} + + + + + ) +} + +export default AddNewLicensePage diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx index acb25d8147834..90186519ba499 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx @@ -1,8 +1,6 @@ -import AddNewLicensePageView from "./AddNewLicensePageView" +import { AddNewLicensePageView } from "./AddNewLicensePageView" export default { title: "pages/AddNewLicensePageView", component: AddNewLicensePageView, } - -export const Default = () => diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index 2e48cb5fc9789..361b6348c77b2 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -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 = ({ + 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") @@ -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) { @@ -99,7 +87,7 @@ const AddNewLicense: FC = () => {
{ e.preventDefault() @@ -108,10 +96,10 @@ const AddNewLicense: FC = () => { const licenseKey = formData.get("licenseKey") - saveLicenseKey(licenseKey?.toString() || "") + onSaveLicenseKey(licenseKey?.toString() || "") }} button={ - } @@ -129,8 +117,6 @@ const AddNewLicense: FC = () => { ) } -export default AddNewLicense - const useStyles = makeStyles((theme) => ({ main: { paddingTop: theme.spacing(5), diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index a1561a19d8a76..9a21170ac118a 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -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, @@ -55,79 +44,28 @@ const LicensesSettingsPage: FC = () => { {pageTitle("License Settings")} - + removeLicenseApi(licenseId, { + onSuccess: () => { + displaySuccess("Successfully removed license") + void refetchGetLicenses() + }, + onError: () => { + displayError("Failed to remove license") + void refetchGetLicenses() + }, + }) + } /> - -
- - - - - {isLoading && } - - {!isLoading && licenses && licenses?.length > 0 && ( - - {licenses?.map((license) => ( - - removeLicenseApi(licenseId, { - onSuccess: () => { - displaySuccess("Successfully removed license") - void refetchGetLicenses() - }, - onError: () => { - displayError("Failed to remove license") - void refetchGetLicenses() - }, - }) - } - /> - ))} - - )} - - {!isLoading && licenses && licenses.length === 0 && ( - - - - )} ) } -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 diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx new file mode 100644 index 0000000000000..831df35b828df --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -0,0 +1,102 @@ +import Button from "@material-ui/core/Button" +import { makeStyles, useTheme } from "@material-ui/core/styles" +import Skeleton from "@material-ui/lab/Skeleton" +import { GetLicensesResponse, removeLicense } from "api/api" +import { Header } from "components/DeploySettingsLayout/Header" +import { LicenseCard } from "components/LicenseCard/LicenseCard" +import { Stack } from "components/Stack/Stack" +import { FC } from "react" +import Confetti from "react-confetti" +import { Link } from "react-router-dom" +import useWindowSize from "react-use/lib/useWindowSize" + +type Props = { + showConfetti: boolean + isLoading: boolean + userLimitActual?: number + userLimitLimit?: number + licenses?: GetLicensesResponse[] + isRemovingLicense: boolean + removeLicense: (licenseId: number) => void +} + +const LicensesSettingsPageView: FC = ({ + showConfetti, + isLoading, + userLimitActual, + userLimitLimit, + licenses, + isRemovingLicense, +}) => { + const styles = useStyles() + const { width, height } = useWindowSize() + + const theme = useTheme() + + return ( + <> + + +
+ + + + + {isLoading && } + + {!isLoading && licenses && licenses?.length > 0 && ( + + {licenses?.map((license) => ( + + ))} + + )} + + {!isLoading && licenses && licenses.length === 0 && ( + + + + )} + + ) +} + +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 LicensesSettingsPageView From 21a610e45a9599f716c38bacd76fdfe7f5dc8dff Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Mon, 24 Apr 2023 11:30:06 -0300 Subject: [PATCH 13/21] wip: stories --- .../AddNewLicensePageView.stories.tsx | 7 +++++ .../LicensesSettingsPageView.stories.tsx | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx index 90186519ba499..1889668fdfcd0 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.stories.tsx @@ -4,3 +4,10 @@ export default { title: "pages/AddNewLicensePageView", component: AddNewLicensePageView, } + +export const Default = { + args: { + isSavingLicense: false, + didSavingFailed: false, + }, +} diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx new file mode 100644 index 0000000000000..c1b7d33cba283 --- /dev/null +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx @@ -0,0 +1,30 @@ +import { GetLicensesResponse } from "api/api" +import LicensesSettingsPageView from "./LicensesSettingsPageView" + +export default { + title: "pages/LicensesSettingsPage", + component: LicensesSettingsPageView, +} + +const licensesTest: GetLicensesResponse[] = [ + { + id: 1, + claims: { + trial: false, + all_features: true, + version: 1, + features: {}, + license_expires: 1682346425, + }, + }, +] + +export const Default = { + args: { + showConfetti: false, + isLoading: false, + userLimitActual: 1, + userLimitLimit: 10, + licenses: licensesTest, + }, +} From fba262c58a7ebc5bdb33db86831a5b51c93ef2e5 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Mon, 24 Apr 2023 11:39:52 -0300 Subject: [PATCH 14/21] wip: stories --- .../AddNewLicensePageView.tsx | 17 -------------- .../LicensesSettingsPageView.stories.tsx | 22 ++++++++++++++----- .../LicensesSettingsPageView.tsx | 4 +--- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index 361b6348c77b2..22075fafcbaba 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -121,21 +121,4 @@ const useStyles = makeStyles((theme) => ({ main: { paddingTop: theme.spacing(5), }, - ctaButton: { - backgroundImage: `linear-gradient(90deg, ${theme.palette.secondary.main} 0%, ${theme.palette.secondary.dark} 100%)`, - width: theme.spacing(30), - marginBottom: theme.spacing(4), - }, - formSectionRoot: { - alignItems: "center", - }, - description: { - color: theme.palette.text.secondary, - lineHeight: "160%", - }, - title: { - ...theme.typography.h5, - fontWeight: 600, - marging: theme.spacing(1), - }, })) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx index c1b7d33cba283..f5c5889b3070a 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx @@ -9,6 +9,9 @@ export default { const licensesTest: GetLicensesResponse[] = [ { id: 1, + uploaded_at: "1682346425", + expires_at: "1682346425", + uuid: "1", claims: { trial: false, all_features: true, @@ -19,12 +22,21 @@ const licensesTest: GetLicensesResponse[] = [ }, ] +const defaultArgs = { + showConfetti: false, + isLoading: false, + userLimitActual: 1, + userLimitLimit: 10, + licenses: licensesTest, +} + export const Default = { + args: defaultArgs, +} + +export const Empty = { args: { - showConfetti: false, - isLoading: false, - userLimitActual: 1, - userLimitLimit: 10, - licenses: licensesTest, + ...defaultArgs, + licenses: [], }, } diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index 831df35b828df..a7557b6780a7c 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -93,9 +93,7 @@ const useStyles = makeStyles((theme) => ({ 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", + marginTop: theme.spacing(4), }, })) From 0922b5bc5e9a450a793ab69a9f3016452ed79517 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 26 Apr 2023 16:00:40 +0000 Subject: [PATCH 15/21] fixes from PR reviews --- site/package.json | 1 - .../AddNewLicensePage.tsx | 4 +- .../AddNewLicensePageView.tsx | 37 ++++++++----------- .../LicensesSettingsPage}/DividerWithText.tsx | 0 .../LicensesSettingsPage.tsx | 35 ++++++++---------- .../LicensesSettingsPageView.tsx | 12 ++++-- site/yarn.lock | 32 +--------------- 7 files changed, 43 insertions(+), 78 deletions(-) rename site/src/{components/DividerWithText => pages/DeploySettingsPage/LicensesSettingsPage}/DividerWithText.tsx (100%) diff --git a/site/package.json b/site/package.json index 517b87b3cd2d1..718d8a69f552d 100644 --- a/site/package.json +++ b/site/package.json @@ -64,7 +64,6 @@ "jest-location-mock": "1.0.9", "just-debounce-it": "3.1.1", "lodash": "4.17.21", - "material-ui-dropzone": "^3.5.0", "playwright": "1.29.2", "react": "18.2.0", "react-chartjs-2": "4.3.1", diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx index 9888e8fff8be1..7c75dca3713c0 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePage.tsx @@ -13,7 +13,7 @@ const AddNewLicensePage: FC = () => { const { mutate: saveLicenseKeyApi, isLoading: isCreating, - isError: creationFailed, + error: savingLicenseError, } = useMutation(createLicense, { onSuccess: () => { displaySuccess("You have successfully added a license") @@ -43,7 +43,7 @@ const AddNewLicensePage: FC = () => { diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx index 22075fafcbaba..9d2266b816767 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/AddNewLicensePageView.tsx @@ -1,26 +1,27 @@ import Button from "@material-ui/core/Button" import TextField from "@material-ui/core/TextField" import { makeStyles } from "@material-ui/core/styles" +import { ApiErrorResponse } from "api/errors" +import { AlertBanner } from "components/AlertBanner/AlertBanner" 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 } from "components/GlobalSnackbar/utils" import { Stack } from "components/Stack/Stack" +import { DividerWithText } from "pages/DeploySettingsPage/LicensesSettingsPage/DividerWithText" import { FC } from "react" import { Link as RouterLink } from "react-router-dom" type AddNewLicenseProps = { onSaveLicenseKey: (license: string) => void isSavingLicense: boolean - didSavingFailed: boolean + savingLicenseError?: ApiErrorResponse } export const AddNewLicensePageView: FC = ({ onSaveLicenseKey, isSavingLicense, - didSavingFailed, + savingLicenseError, }) => { const styles = useStyles() @@ -65,29 +66,23 @@ export const AddNewLicensePageView: FC = ({ -
- - - - - -
+ {savingLicenseError && ( + + )} + + or
{ e.preventDefault() diff --git a/site/src/components/DividerWithText/DividerWithText.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/DividerWithText.tsx similarity index 100% rename from site/src/components/DividerWithText/DividerWithText.tsx rename to site/src/pages/DeploySettingsPage/LicensesSettingsPage/DividerWithText.tsx diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index 9a21170ac118a..2a8ae72b396e4 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from "@tanstack/react-query" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useMachine } from "@xstate/react" import { getLicenses, removeLicense } from "api/api" import { displayError, displaySuccess } from "components/GlobalSnackbar/utils" @@ -11,6 +11,7 @@ import { entitlementsMachine } from "xServices/entitlements/entitlementsXService import LicensesSettingsPageView from "./LicensesSettingsPageView" const LicensesSettingsPage: FC = () => { + const queryClient = useQueryClient() const [entitlementsState] = useMachine(entitlementsMachine) const { entitlements } = entitlementsState.context const [searchParams, setSearchParams] = useSearchParams() @@ -18,13 +19,17 @@ const LicensesSettingsPage: FC = () => { const [confettiOn, toggleConfettiOn] = useToggle(false) const { mutate: removeLicenseApi, isLoading: isRemovingLicense } = - useMutation(removeLicense) + useMutation(removeLicense, { + onSuccess: () => { + displaySuccess("Successfully removed license") + void queryClient.invalidateQueries(["licenses"]) + }, + onError: () => { + displayError("Failed to remove license") + }, + }) - const { - data: licenses, - isLoading, - refetch: refetchGetLicenses, - } = useQuery({ + const { data: licenses, isLoading } = useQuery({ queryKey: ["licenses"], queryFn: () => getLicenses(), }) @@ -32,10 +37,11 @@ const LicensesSettingsPage: FC = () => { useEffect(() => { if (success) { toggleConfettiOn() - setTimeout(() => { + const timeout = setTimeout(() => { toggleConfettiOn(false) setSearchParams() }, 2000) + return () => clearTimeout(timeout) } }, [setSearchParams, success, toggleConfettiOn]) @@ -51,18 +57,7 @@ const LicensesSettingsPage: FC = () => { 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() - }, - }) - } + removeLicense={(licenseId: number) => removeLicenseApi(licenseId)} /> ) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index a7557b6780a7c..a12da70ba270f 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -1,7 +1,7 @@ import Button from "@material-ui/core/Button" import { makeStyles, useTheme } from "@material-ui/core/styles" import Skeleton from "@material-ui/lab/Skeleton" -import { GetLicensesResponse, removeLicense } from "api/api" +import { GetLicensesResponse } from "api/api" import { Header } from "components/DeploySettingsLayout/Header" import { LicenseCard } from "components/LicenseCard/LicenseCard" import { Stack } from "components/Stack/Stack" @@ -27,6 +27,7 @@ const LicensesSettingsPageView: FC = ({ userLimitLimit, licenses, isRemovingLicense, + removeLicense, }) => { const styles = useStyles() const { width, height } = useWindowSize() @@ -77,9 +78,14 @@ const LicensesSettingsPageView: FC = ({ )} - {!isLoading && licenses && licenses.length === 0 && ( + {!isLoading && licenses === null && ( - diff --git a/site/yarn.lock b/site/yarn.lock index 62218a8ad763f..f580b5c1a9f68 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -4211,11 +4211,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -attr-accept@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" - integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== - available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -4785,7 +4780,7 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -clsx@^1.0.2, clsx@^1.0.4: +clsx@^1.0.4: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -6218,13 +6213,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-selector@^0.1.12: - version "0.1.19" - resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.19.tgz#8ecc9d069a6f544f2e4a096b64a8052e70ec8abf" - integrity sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ== - dependencies: - tslib "^2.0.1" - file-system-cache@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-2.0.2.tgz#6b51d58c5786302146fa883529e0d7f88896e948" @@ -8517,15 +8505,6 @@ material-colors@^1.2.1: resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== -material-ui-dropzone@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/material-ui-dropzone/-/material-ui-dropzone-3.5.0.tgz#2d7f36032db96c24ce97b42e4d0053f94e91a9fc" - integrity sha512-3BC6mz/4OEM4ZpbqMfuMN065JQyqfEbifT6/VzIua7Zj4b0DaR5YPCgpN+fL/e8yBgTs9MGBZJQY06p5pfKwvw== - dependencies: - "@babel/runtime" "^7.4.4" - clsx "^1.0.2" - react-dropzone "^10.2.1" - mdast-util-definitions@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" @@ -10076,15 +10055,6 @@ react-dom@18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" -react-dropzone@^10.2.1: - version "10.2.2" - resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.2.2.tgz#67b4db7459589a42c3b891a82eaf9ade7650b815" - integrity sha512-U5EKckXVt6IrEyhMMsgmHQiWTGLudhajPPG77KFSvgsMqNEHSyGpqWvOMc5+DhEah/vH4E1n+J5weBNLd5VtyA== - dependencies: - attr-accept "^2.0.0" - file-selector "^0.1.12" - prop-types "^15.7.2" - react-element-to-jsx-string@^15.0.0: version "15.0.0" resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz#1cafd5b6ad41946ffc8755e254da3fc752a01ac6" From 6ab9d3e846e1473cfb6a5ef333e200f8eb76e9bc Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 26 Apr 2023 16:02:53 +0000 Subject: [PATCH 16/21] fix stories --- .../LicensesSettingsPage/LicensesSettingsPageView.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx index f5c5889b3070a..1a43ab5b03613 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.stories.tsx @@ -37,6 +37,6 @@ export const Default = { export const Empty = { args: { ...defaultArgs, - licenses: [], + licenses: null, }, } From 8d55d9821a29e38e02067f3399d01e5675456ad9 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 26 Apr 2023 17:33:30 +0000 Subject: [PATCH 17/21] fix empty license page --- .../LicensesSettingsPageView.tsx | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index a12da70ba270f..365a815d5c5eb 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -79,27 +79,45 @@ const LicensesSettingsPageView: FC = ({ )} {!isLoading && licenses === null && ( - - - +
+ + + No Licenses yet + + Contact sales to learn + more. + + + +
)} ) } 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), - marginTop: theme.spacing(4), + title: { + fontSize: theme.spacing(2), + }, + + root: { + minHeight: theme.spacing(30), + display: "flex", + alignItems: "center", + justifyContent: "center", + borderRadius: theme.shape.borderRadius, + border: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(6), + + "&:hover": { + backgroundColor: theme.palette.background.paper, + }, + }, + + description: { + color: theme.palette.text.secondary, + textAlign: "center", + maxWidth: theme.spacing(50), }, })) From 832f599aea7ec3ca17a0dc6ea685a565b17b17a5 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 26 Apr 2023 18:41:59 +0000 Subject: [PATCH 18/21] fix copy --- .../LicensesSettingsPage/LicensesSettingsPageView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index 365a815d5c5eb..bc15b65e5f3a2 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -82,10 +82,11 @@ const LicensesSettingsPageView: FC = ({
- No Licenses yet + no Licenses yet - Contact sales to learn - more. + Contact sales or{" "} + request a trial license to + learn more. more. From c3a1141a4269ef640f1b2775b0dd56b2b47bfdbe Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 26 Apr 2023 18:43:57 +0000 Subject: [PATCH 19/21] fix --- .../LicensesSettingsPage/LicensesSettingsPageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index bc15b65e5f3a2..7219cca504b40 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -86,7 +86,7 @@ const LicensesSettingsPageView: FC = ({ Contact sales or{" "} request a trial license to - learn more. more. + learn more. From 16a8445a2f03023b5b36d811d6d4133aa1110ade Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 26 Apr 2023 18:45:43 +0000 Subject: [PATCH 20/21] wip --- .../LicensesSettingsPage/LicensesSettingsPageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index 7219cca504b40..70f625513443e 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -82,7 +82,7 @@ const LicensesSettingsPageView: FC = ({
- no Licenses yet + No licenses yet Contact sales or{" "} request a trial license to From 0322172496a11647cfc753f3ca0e14c66681ad1f Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 26 Apr 2023 19:53:17 +0000 Subject: [PATCH 21/21] add golang test --- enterprise/coderd/license/license_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index 9cd56c67875a3..d21d6f1e53558 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -37,6 +37,15 @@ func TestEntitlements(t *testing.T) { require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement) } }) + t.Run("Always return the current user count", func(t *testing.T) { + t.Parallel() + db := dbfake.New() + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all) + require.NoError(t, err) + require.False(t, entitlements.HasLicense) + require.False(t, entitlements.Trial) + require.Equal(t, *entitlements.Features[codersdk.FeatureUserLimit].Actual, int64(0)) + }) t.Run("SingleLicenseNothing", func(t *testing.T) { t.Parallel() db := dbfake.New()