admin/account: better ux by selecting once and for all what type of account you are adding

Massimo Melina committed Mar 12, 2022 at 20:29 UTC dfa796260d2b32df8d26340d20a7e583297a2937
2 files changed +32 -13
admin/src/AccountsPage.ts
+30 -12
@@ -1,15 +1,17 @@
1 // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 -import { isValidElement, createElement as h, useState, useEffect, Fragment } from "react"
3 +import { isValidElement, createElement as h, useState, useEffect, Fragment, useRef } from "react"
4 import { apiCall, useApiComp } from './api'
5 import { Box, Button, Card, CardContent, Grid, List, ListItem, ListItemText, Typography } from '@mui/material'
6 -import { Delete, Group, MilitaryTech, Person, PersonAdd, Refresh } from '@mui/icons-material'
6 +import { Add, Delete, Group, MilitaryTech, Person, PersonAdd, Refresh } from '@mui/icons-material'
7 import { BoolField, Form, MultiSelectField, SelectField, StringField } from './Form'
8 import { alertDialog, confirmDialog } from './dialog'
9 import { iconTooltip, isEqualLax, onlyTruthy } from './misc'
10 import { TreeItem, TreeView } from '@mui/lab'
11 import { makeStyles } from '@mui/styles'
12 import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
13 +import MenuButton from './MenuButton'
14 +import addFiles, { addVirtual } from './addFiles'
15
16 const useStyles = makeStyles({
17 label: {
@@ -32,7 +34,7 @@ interface Account {
34 export default function AccountsPage() {
35 const [res, reload] = useApiComp('get_accounts')
36 const [sel, setSel] = useState<string[]>([])
35 - const [add, setAdd] = useState(false)
37 + const [addGroup, setAddGroup] = useState<boolean|null>(null)
38 const styles = useStyles()
39 useEffect(() => { // if accounts are reloaded, review the selection to remove elements that don't exist anymore
40 if (isValidElement(res) || !Array.isArray(res?.list)) return
@@ -41,7 +43,8 @@ export default function AccountsPage() {
43 if (isValidElement(res))
44 return res
45 const { list }: { list: Account[] } = res
44 - const account = add ? { username: '', hasPassword:true } : sel.length === 1 && list.find(x => x.username === sel[0])
46 + const account = addGroup !== null ? { username: '', hasPassword: !addGroup }
47 + : sel.length === 1 && list.find(x => x.username === sel[0])
48 return h(Grid, { container: true, maxWidth: '50em' },
49 h(Grid, { item: true, xs: 12 },
50 h(Box, {
@@ -56,11 +59,14 @@ export default function AccountsPage() {
59 width: 'fit-content',
60 },
61 },
59 - h(Button, {
62 + h(MenuButton, {
63 variant: 'contained',
64 startIcon: h(PersonAdd),
62 - onClick(){ setAdd(true) }
63 - }, "Add"),
65 + items: [
66 + { children: "user", onClick: () => switchTo(false) },
67 + { children: "group", onClick: () => switchTo(true) }
68 + ]
69 + }, 'Add'),
70 h(Button, {
71 disabled: !sel.length,
72 startIcon: h(Delete),
@@ -83,8 +89,7 @@ export default function AccountsPage() {
89 sx: { pr: 4, minWidth: '15em' },
90 selected: sel,
91 onNodeSelect(ev, ids) {
86 - setAdd(false)
87 - setSel(ids)
92 + switchTo(ids)
93 }
94 },
95 list.map((ac: Account) =>
@@ -102,14 +107,13 @@ export default function AccountsPage() {
107 )
108 )
109 ),
105 - (add || sel.length > 0) && h(Grid, { item: true, md: 6 },
110 + (addGroup !== null || sel.length > 0) && h(Grid, { item: true, md: 6 },
111 h(Card, {},
112 h(CardContent, {},
113 account ? h(AccountForm, {
114 account,
115 groups: list.filter(x => !x.hasPassword).map( x => x.username ),
116 done(username) {
112 - setAdd(false)
117 setSel([username])
118 reload()
119 }
@@ -122,6 +126,17 @@ export default function AccountsPage() {
126 )
127 )))
128 )
129 +
130 + function switchTo(what: boolean | string[]) {
131 + if (Array.isArray(what)) {
132 + setAddGroup(null)
133 + setSel(what)
134 + }
135 + else {
136 + setSel([])
137 + setAddGroup(what)
138 + }
139 + }
140 }
141
142 function hList(heading: string, list: any[]) {
@@ -140,10 +155,14 @@ function AccountForm({ account, done, groups }: { account: Account, groups: stri
155 useEffect(() => {
156 setValues(account)
157 setBelongOptions(groups.filter(x => x !== account.username ))
158 + //@ts-ignore
159 + ref.current?.querySelector('input')?.focus()
160 }, [JSON.stringify(account)]) //eslint-disable-line
161 const add = !account.username
162 const group = !values.hasPassword
163 + const ref = useRef()
164 return h(Form, {
165 + formRef: ref,
166 values,
167 set(v, { k }) {
168 setValues({ ...values, [k]: v })
@@ -151,7 +170,6 @@ function AccountForm({ account, done, groups }: { account: Account, groups: stri
170 barSx: { width: 'initial', justifyContent: 'space-between' },
171 addToBar: [ account2icon(values, { fontSize: 'large', sx: { p: 1 }}) ],
172 fields: [
154 - add && { k: 'hasPassword', comp: SelectField, label: 'Account type', options: [{ value: true, label: 'Simple' }, { value: false, label: 'Group' }] },
173 { k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off' },
174 !group && { k: 'password', comp: StringField, md: 6, type: 'password', autoComplete: 'new-password', label: add ? 'Password' : 'Change password' },
175 !group && { k: 'password2', comp: StringField, md: 6, type: 'password', autoComplete: 'off', label: 'Repeat password' },
admin/src/Form.ts
+2 -1
@@ -33,7 +33,7 @@ interface FormProps {
33 barSx?: Dict
34 [rest:string]: any
35 }
36 -export function Form({ fields, values, set, defaults, save, stickyBar, addToBar=[], barSx, ...rest }: FormProps) {
36 +export function Form({ fields, values, set, defaults, save, stickyBar, addToBar=[], barSx, formRef, ...rest }: FormProps) {
37 const [loading, setLoading] = useStateMounted(false)
38 const onClick = save?.onClick
39 if (onClick)
@@ -68,6 +68,7 @@ export function Form({ fields, values, set, defaults, save, stickyBar, addToBar=
68 )
69
70 return h('form', {
71 + ref: formRef,
72 onSubmit(ev) {
73 ev.preventDefault()
74 },