ux: better certificate dialog

Massimo Melina committed May 6, 2023 at 14:56 UTC 38dbbb92da920aa5490edad790ef6703f7303ead
3 files changed +38 -32
admin/src/OptionsPage.ts
+31 -28
@@ -5,7 +5,7 @@ import { createElement as h, Fragment, useEffect, useRef } from 'react';
5 import { apiCall, useApi, useApiEx } from './api'
6 import { state, useSnapState } from './state'
7 import { Info, Refresh, Warning } from '@mui/icons-material'
8 -import { Dict, modifiedSx, wikiLink, with_ } from './misc'
8 +import { Dict, Flex, modifiedSx, wikiLink, with_ } from './misc'
9 import {
10 Form,
11 BoolField,
@@ -17,7 +17,7 @@ import {
17 StringField
18 } from '@hfs/mui-grid-form';
19 import FileField from './FileField'
20 -import { alertDialog, closeDialog, confirmDialog, formDialog, newDialog, toast, waitDialog } from './dialog'
20 +import { alertDialog, closeDialog, confirmDialog, newDialog, toast } from './dialog'
21 import { proxyWarning } from './HomePage'
22 import _ from 'lodash';
23 import { proxy, subscribe, useSnapshot } from 'valtio'
@@ -307,33 +307,36 @@ function suggestMakingCert() {
307 export async function makeCertAndSave() {
308 if (!window.crypto.subtle)
309 return alertDialog("Retry this procedure on localhost", 'warning')
310 - const res = await formDialog<{ commonName: string }>({
311 - title: "We'll generate a basic certificate for you",
312 - form: {
313 - fields: [
314 - h(Box, { display: 'flex', gap: 1, alignItems: 'center' },
315 - h(Warning), "This certificate is just one click away, but will issue a warning on the browser"),
316 - { k: 'commonName', label: "Enter a domain, or leave empty" }
317 - ],
318 - save: { children: "Continue" },
319 - barSx: { gap: 1 },
320 - addToBar: [
321 - h(Link, { target: 'cert', href: 'https://letsencrypt.org/' }, h(Button, {}, "Get better certificate"))
322 - ],
323 - }
310 + const close = newDialog({
311 + title: "Get a certificate",
312 + Content: () => h(Flex, { flexDirection: 'column' },
313 + h('p', {}, "HTTPS needs a certificate to work."),
314 + "We suggest you to ",
315 + h(Link, {
316 + target: 'cert',
317 + href: 'https://letsencrypt.org/',
318 + onClick: close,
319 + }, h(Button, { size: 'small', color: 'success' }, "get a free but proper certificate")),
320 + "or, if you are in a hurry",
321 + h(Button, {
322 + size: 'small',
323 + color: 'warning',
324 + async onClick() {
325 + try {
326 + const saved = await apiCall('save_pem', await makeCert({}))
327 + await apiCall('set_config', { values: saved })
328 + if (loaded) // when undefined we are not in this page
329 + Object.assign(loaded, saved)
330 + setTimeout(exposedReloadStatus!, 1000) // give some time for backend to apply
331 + Object.assign(state.config, saved)
332 + await alertDialog("Certificate saved", 'success')
333 + }
334 + finally { close() }
335 + }
336 + }, "make a basic certificate"),
337 + wikiLink('HTTPS#certificate', h(Flex, {}, h(Warning, { color: 'warning' }), "but BEWARE it won't be perfect"))
338 + )
339 })
325 - if (!res) return
326 - const close = waitDialog()
327 - try {
328 - const saved = await apiCall('save_pem', await makeCert(res))
329 - await apiCall('set_config', { values: saved })
330 - if (loaded) // when undefined we are not in this page
331 - Object.assign(loaded, saved)
332 - setTimeout(exposedReloadStatus!, 1000) // give some time for backend to apply
333 - Object.assign(state.config, saved)
334 - await alertDialog("Certificate saved", 'success')
335 - }
336 - finally { close() }
340 }
341
342 async function makeCert(attributes: Record<string, string>) {
admin/src/dialog.ts
+4 -3
@@ -12,7 +12,7 @@ import {
12 import {
13 createElement as h, Dispatch, Fragment,
14 isValidElement,
15 - ReactElement, SetStateAction,
15 + ReactElement, ReactNode, SetStateAction,
16 useEffect,
17 useRef,
18 useState
@@ -117,12 +117,13 @@ export async function alertDialog(msg: ReactElement | string | Error, options?:
117 })
118 }
119
120 -interface ConfirmOptions { href?: string }
121 -export async function confirmDialog(msg: string | ReactElement, { href }: ConfirmOptions={}) : Promise<boolean> {
120 +interface ConfirmOptions extends Omit<DialogOptions, 'Content'> { href?: string }
121 +export async function confirmDialog(msg: ReactNode, { href, ...rest }: ConfirmOptions={}) : Promise<boolean> {
122 return new Promise(resolve => newDialog({
123 className: 'dialog-confirm',
124 icon: '?',
125 onClose: resolve,
126 + ...rest,
127 Content
128 }) )
129
admin/src/misc.ts
+3 -1
@@ -17,7 +17,7 @@ import { SxProps } from '@mui/system'
17 import { Refresh, SvgIconComponent } from '@mui/icons-material'
18 import { alertDialog, confirmDialog } from './dialog'
19 import { apiCall } from './api'
20 -import { formatPerc, useStateMounted } from '@hfs/shared'
20 +import { dontBotherWithKeys, formatPerc, useStateMounted } from '@hfs/shared'
21 import { Promisable } from '@hfs/mui-grid-form'
22 import { LoadingButton, LoadingButtonProps } from '@mui/lab'
23 export * from '@hfs/shared'
@@ -203,5 +203,7 @@ export const REPO_URL = 'https://github.com/rejetto/hfs/'
203 export const WIKI_URL = REPO_URL + '/wiki/'
204
205 export function wikiLink(uri: string, content: ReactNode) {
206 + if (Array.isArray(content))
207 + content = dontBotherWithKeys(content)
208 return h(Link, { href: WIKI_URL + uri, target: 'help' }, content)
209 }
\ No newline at end of file