From 04c66eca345c45fcc50aa69f8697080ff7232812 Mon Sep 17 00:00:00 2001 From: Sergey Garin Date: Thu, 25 Apr 2024 12:02:19 +0300 Subject: [PATCH] fixes --- app/ide-desktop/eslint.config.js | 1 - app/ide-desktop/lib/client/package.json | 3 +- app/ide-desktop/lib/client/src/bin/server.ts | 23 +++++- app/ide-desktop/lib/client/src/index.ts | 14 +++- app/ide-desktop/lib/client/start.ts | 1 + .../AriaComponents/Actions/Link/Link.tsx | 15 ++++ .../AriaComponents/Button/Button.tsx | 77 +++++++++++++++---- .../AriaComponents/Dialog/Dialog.tsx | 6 +- .../components/AriaComponents/Dialog/types.ts | 3 +- .../dashboard/src/hooks/toastAndLogHooks.ts | 8 +- .../lib/dashboard/src/layouts/Drive.tsx | 9 ++- .../src/modals/SetOrganizationNameModal.tsx | 23 ++++-- .../Subscribe/getComponentForPlan.tsx | 36 ++++----- app/ide-desktop/lib/types/modules.d.ts | 8 +- package-lock.json | 32 ++++++++ 15 files changed, 199 insertions(+), 60 deletions(-) create mode 100644 app/ide-desktop/lib/dashboard/src/components/AriaComponents/Actions/Link/Link.tsx diff --git a/app/ide-desktop/eslint.config.js b/app/ide-desktop/eslint.config.js index 79a3366bae..fc5421e14b 100644 --- a/app/ide-desktop/eslint.config.js +++ b/app/ide-desktop/eslint.config.js @@ -308,7 +308,6 @@ export default [ ], 'no-constant-condition': ['error', { checkLoops: false }], 'no-restricted-syntax': ['error', ...RESTRICTED_SYNTAXES], - 'prefer-arrow-callback': 'error', 'prefer-const': 'error', // Not relevant because TypeScript checks types. 'react/prop-types': 'off', diff --git a/app/ide-desktop/lib/client/package.json b/app/ide-desktop/lib/client/package.json index 1cf544e428..77a866ed71 100644 --- a/app/ide-desktop/lib/client/package.json +++ b/app/ide-desktop/lib/client/package.json @@ -28,7 +28,8 @@ "opener": "^1.5.2", "string-length": "^5.0.1", "tar": "^6.1.13", - "yargs": "17.6.2" + "yargs": "17.6.2", + "mkcert": "3.2.0" }, "comments": { "electron-builder": "Cannot be updated to a newer version because of a NSIS installer issue: https://github.com/enso-org/enso/issues/5169" diff --git a/app/ide-desktop/lib/client/src/bin/server.ts b/app/ide-desktop/lib/client/src/bin/server.ts index 80b16f1e45..7c46e4385c 100644 --- a/app/ide-desktop/lib/client/src/bin/server.ts +++ b/app/ide-desktop/lib/client/src/bin/server.ts @@ -6,6 +6,7 @@ import * as http from 'node:http' import * as os from 'node:os' import * as path from 'node:path' import * as stream from 'node:stream' +import * as mkcert from 'mkcert' import * as mime from 'mime-types' import * as portfinder from 'portfinder' @@ -107,10 +108,28 @@ export class Server { /** Start the server. */ run(): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { + const defaultValidity = 365 + const ca = await mkcert.createCA({ + organization: 'Hello CA', + countryCode: 'NP', + state: 'Bagmati', + locality: 'Kathmandu', + validity: defaultValidity, + }) + const cert = await mkcert.createCert({ + ca: { key: ca.key, cert: ca.cert }, + domains: ['127.0.0.1', 'localhost'], + validity: defaultValidity, + }) + createServer( { - http: this.config.port, + https: { + key: cert.key, + cert: cert.cert, + port: this.config.port, + }, handler: this.process.bind(this), }, (err, { http: httpServer }) => { diff --git a/app/ide-desktop/lib/client/src/index.ts b/app/ide-desktop/lib/client/src/index.ts index d55146c4b9..ebd390b31d 100644 --- a/app/ide-desktop/lib/client/src/index.ts +++ b/app/ide-desktop/lib/client/src/index.ts @@ -66,6 +66,18 @@ class App { this.setProjectToOpenOnStartup(id) }) + electron.app.commandLine.appendSwitch('allow-insecure-localhost', 'true') + electron.app.commandLine.appendSwitch('ignore-certificate-errors', 'true') + electron.app.on( + 'certificate-error', + (event, webContents, url, error, certificate, callback) => { + // Prevent having error + event.preventDefault() + // and continue + callback(true) + } + ) + const { windowSize, chromeOptions, fileToOpen, urlToOpen } = this.processArguments() this.handleItemOpening(fileToOpen, urlToOpen) if (this.args.options.version.value) { @@ -439,7 +451,7 @@ class App { searchParams[option.qualifiedName()] = option.value.toString() } } - const address = new URL('http://localhost') + const address = new URL('https://localhost') address.port = this.serverPort().toString() address.search = new URLSearchParams(searchParams).toString() logger.log(`Loading the window address '${address.toString()}'.`) diff --git a/app/ide-desktop/lib/client/start.ts b/app/ide-desktop/lib/client/start.ts index 1bca401403..2149e85154 100644 --- a/app/ide-desktop/lib/client/start.ts +++ b/app/ide-desktop/lib/client/start.ts @@ -3,6 +3,7 @@ import * as childProcess from 'node:child_process' import * as fs from 'node:fs/promises' import * as path from 'node:path' +import * as mkcert from 'mkcert' import * as esbuild from 'esbuild' diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Actions/Link/Link.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Actions/Link/Link.tsx new file mode 100644 index 0000000000..2bade35ef9 --- /dev/null +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Actions/Link/Link.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' + +import * as aria from '#/components/aria' + +/** + * + */ +export interface LinkProps extends aria.LinkProps {} + +/** + * + */ +export function Link(props: LinkProps) { + return +} diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx index f63edc9075..fd0ca7eb8f 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx @@ -15,7 +15,28 @@ import SvgMask from '#/components/SvgMask' // ============== /** Props for a {@link Button}. */ -export interface ButtonProps extends Readonly { +export type ButtonProps = + | (aria.ButtonProps & BaseButtonProps & PropsWithoutHref) + | (aria.LinkProps & BaseButtonProps & PropsWithHref) + +/** + * Props for a button with an href. + */ +interface PropsWithHref { + readonly href: string +} + +/** + * Props for a button without an href. + */ +interface PropsWithoutHref { + readonly href?: never +} + +/** + * Base props for a button. + */ +export interface BaseButtonProps { readonly loading?: boolean /** * The variant of the button @@ -58,10 +79,18 @@ export type IconPosition = 'end' | 'start' /** * The variant of the button */ -export type Variant = 'cancel' | 'custom' | 'delete' | 'icon' | 'outline' | 'primary' | 'submit' +export type Variant = + | 'cancel' + | 'custom' + | 'delete' + | 'icon' + | 'link' + | 'outline' + | 'primary' + | 'submit' const DEFAULT_CLASSES = - 'flex whitespace-nowrap cursor-pointer border border-transparent transition-[opacity,outline-offset] duration-200 ease-in-out select-none text-center items-center justify-center' + 'flex whitespace-nowrap cursor-pointer border border-transparent transition-[opacity,outline-offset] duration-150 ease-in-out select-none text-center items-center justify-center' const FOCUS_CLASSES = 'focus-visible:outline-offset-2 focus:outline-none focus-visible:outline focus-visible:outline-primary' const EXTRA_CLICK_ZONE_CLASSES = 'flex relative before:inset-[-12px] before:absolute before:z-10' @@ -80,6 +109,7 @@ const CLASSES_FOR_SIZE: Record = { const CLASSES_FOR_VARIANT: Record = { custom: '', + link: 'inline-flex px-0 py-0 rounded-sm text-primary hover:text-primary-90 hover:underline', primary: 'bg-primary text-white hover:bg-primary-90', cancel: 'bg-selected-frame opacity-80 hover:opacity-100', delete: 'bg-delete text-white', @@ -103,7 +133,10 @@ const ICON_POSITION: Record = { } /** A button allows a user to perform an action, with mouse, touch, and keyboard interactions. */ -export function Button(props: ButtonProps) { +export const Button = React.forwardRef(function Button( + props: ButtonProps, + ref: React.ForwardedRef +) { const { className, children, @@ -111,22 +144,27 @@ export function Button(props: ButtonProps) { icon, loading = false, isDisabled = loading, - type = 'button', iconPosition = 'start', size = 'xsmall', fullWidth = false, rounding = 'large', - ...ariaButtonProps + ...ariaProps } = props const focusChildProps = focusHooks.useFocusChild() + const isLink = ariaProps.href != null + + const Tag = isLink ? aria.Link : aria.Button + + const goodDefaults = isLink ? { rel: 'noopener noreferrer' } : { type: 'button' } + const classes = clsx( DEFAULT_CLASSES, DISABLED_CLASSES, FOCUS_CLASSES, - CLASSES_FOR_VARIANT[variant], CLASSES_FOR_SIZE[size], CLASSES_FOR_ROUNDING[rounding], + CLASSES_FOR_VARIANT[variant], { [LOADING_CLASSES]: loading, [FULL_WIDTH_CLASSES]: fullWidth } ) @@ -150,16 +188,21 @@ export function Button(props: ButtonProps) { } return ( - ()(ariaButtonProps, focusChildProps, { - type, + - tailwindMerge.twMerge( - classes, - typeof className === 'function' ? className(values) : className - ), })} + // @ts-expect-error eventhough typescript is complaining about the type of className, it is actually correct + className={states => + tailwindMerge.twMerge( + classes, + // this is safe, because the type of states has correct types outside + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + typeof className === 'function' ? className(states) : className + ) + } > {childrenFactory()} @@ -170,6 +213,6 @@ export function Button(props: ButtonProps) { )} - + ) -} +}) diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx index 2b29306333..3b5de7a4e5 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx @@ -46,9 +46,8 @@ export function Dialog(props: types.DialogProps) { isKeyboardDismissDisabled = false, hideCloseButton = false, className, - isOpen = false, - defaultOpen = false, onOpenChange = () => {}, + modalProps, ...ariaDialogProps } = props @@ -60,9 +59,8 @@ export function Dialog(props: types.DialogProps) { isDismissable={isDismissible} isKeyboardDismissDisabled={isKeyboardDismissDisabled} UNSTABLE_portalContainer={root.current} - isOpen={isOpen} onOpenChange={onOpenChange} - defaultOpen={defaultOpen} + {...modalProps} > void readonly isKeyboardDismissDisabled?: boolean - readonly isOpen?: boolean - readonly defaultOpen?: boolean + readonly modalProps?: Pick } /** The props for the DialogTrigger component. */ diff --git a/app/ide-desktop/lib/dashboard/src/hooks/toastAndLogHooks.ts b/app/ide-desktop/lib/dashboard/src/hooks/toastAndLogHooks.ts index 2eec4dc9c3..809b0aa84d 100644 --- a/app/ide-desktop/lib/dashboard/src/hooks/toastAndLogHooks.ts +++ b/app/ide-desktop/lib/dashboard/src/hooks/toastAndLogHooks.ts @@ -19,12 +19,16 @@ import * as errorModule from '#/utilities/error' export function useToastAndLog() { const { getText } = textProvider.useText() const logger = loggerProvider.useLogger() + return React.useCallback( ( textId: K | null, ...[error, ...replacements]: text.Replacements[K] extends readonly [] - ? [error?: errorModule.MustNotBeKnown] - : [error: errorModule.MustNotBeKnown | null, ...replacements: text.Replacements[K]] + ? [error?: Error | errorModule.MustNotBeKnown] + : [ + error: Error | errorModule.MustNotBeKnown | null, + ...replacements: text.Replacements[K], + ] ) => { const messagePrefix = textId == null diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Drive.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Drive.tsx index 04151df96a..64b8a11780 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Drive.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Drive.tsx @@ -1,6 +1,8 @@ /** @file The directory header bar and directory item listing. */ import * as React from 'react' +import * as router from 'react-router-dom' + import * as appUtils from '#/appUtils' import * as eventCallback from '#/hooks/eventCallbackHooks' @@ -328,9 +330,12 @@ export default function Drive(props: DriveProps) {
{getText('upgradeToUseCloud')} - + {getText('upgrade')} - + {!supportsLocalBackend && ( backend.getOrganization(), + queryFn: () => { + if (backend.type === backendModule.BackendType.remote) { + return backend.getOrganization().catch(() => null) + } else { + return null + } + }, + staleTime: Infinity, }) const submit = reactQuery.useMutation({ @@ -49,7 +59,9 @@ export function SetOrganizationNameModal() { }) const shouldShowModal = - userPlan === backendModule.Plan.team && data?.name?.toString() === 'Test123' + userPlan != null && + PLANS_TO_SPECIFY_ORG_NAME.includes(userPlan) && + data?.name?.toString() === '' return ( <> @@ -58,7 +70,7 @@ export function SetOrganizationNameModal() { isDismissible={false} isKeyboardDismissDisabled hideCloseButton - isOpen={shouldShowModal} + modalProps={{ isOpen: shouldShowModal }} > { @@ -110,6 +122,7 @@ export function SetOrganizationNameModal() { + ) diff --git a/app/ide-desktop/lib/dashboard/src/pages/subscribe/Subscribe/getComponentForPlan.tsx b/app/ide-desktop/lib/dashboard/src/pages/subscribe/Subscribe/getComponentForPlan.tsx index d8be43977c..da5b16b3cd 100644 --- a/app/ide-desktop/lib/dashboard/src/pages/subscribe/Subscribe/getComponentForPlan.tsx +++ b/app/ide-desktop/lib/dashboard/src/pages/subscribe/Subscribe/getComponentForPlan.tsx @@ -69,14 +69,9 @@ const COMPONENT_PER_PLAN: Record = { const { getText } = textProvider.useText() return ( - + {getText('learnMore')} - + ) }, pricing: 'soloPlanPricing', @@ -105,14 +100,9 @@ const COMPONENT_PER_PLAN: Record = { const { getText } = textProvider.useText() return ( - + {getText('learnMore')} - + ) }, pricing: 'teamPlanPricing', @@ -141,14 +131,9 @@ const COMPONENT_PER_PLAN: Record = { const { getText } = textProvider.useText() return ( - + {getText('learnMore')} - + ) }, pricing: 'enterprisePlanPricing', @@ -158,7 +143,14 @@ const COMPONENT_PER_PLAN: Record = { submitButton: () => { const { getText } = textProvider.useText() return ( - + {getText('contactSales')} ) diff --git a/app/ide-desktop/lib/types/modules.d.ts b/app/ide-desktop/lib/types/modules.d.ts index b32b3c558c..24bb923d7d 100644 --- a/app/ide-desktop/lib/types/modules.d.ts +++ b/app/ide-desktop/lib/types/modules.d.ts @@ -158,8 +158,14 @@ declare module 'create-servers' { /** Configuration options for `create-servers`. */ interface CreateServersOptions { - readonly http: number + readonly http?: number readonly handler: http.RequestListener + // eslint-disable-next-line no-restricted-syntax + readonly https?: { + readonly port: number + readonly key: string + readonly cert: string + } } /** An error passed to a callback when a HTTP request fails. */ diff --git a/package-lock.json b/package-lock.json index 7bb1732281..0bf28c7085 100644 --- a/package-lock.json +++ b/package-lock.json @@ -181,6 +181,7 @@ "create-servers": "3.2.0", "electron-is-dev": "^2.0.0", "mime-types": "^2.1.35", + "mkcert": "3.2.0", "opener": "^1.5.2", "string-length": "^5.0.1", "tar": "^6.1.13", @@ -14915,6 +14916,29 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/mkcert": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mkcert/-/mkcert-3.2.0.tgz", + "integrity": "sha512-026Eivq9RoOjOuLJGzbhGwXUAjBxRX11Z7Jbm4/7lqT/Av+XNy9SPrJte6+UpEt7i+W3e/HZYxQqlQcqXZWSzg==", + "dependencies": { + "commander": "^11.0.0", + "node-forge": "^1.3.1" + }, + "bin": { + "mkcert": "dist/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mkcert/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -15133,6 +15157,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-gyp-build": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz",