admin/account: animated save button
Massimo Melina committed
Apr 27, 2026 at 14:12 UTC
7cefd4ffd4cda3ba95b6fa9017a659016da99a06
3 files changed
+14
-7
admin/src/AccountForm.ts
+10
-4
@@ -13,8 +13,13 @@ import { state, useSnapState } from './state'
13
import VfsPathField from './VfsPathField'
14
import { DateTimeField } from './DateTimeField'
15
16
-interface FormProps { account: Account, groups: string[], done: (username: string)=>void, reload: ()=>void, addToBar: ReactNode }
17
-export default function AccountForm({ account, done, groups, addToBar, reload }: FormProps) {
16
+export default function AccountForm({ account, done, groups, addToBar, reload }: {
17
+ account: Account,
18
+ groups: string[],
19
+ done: (username: string, saveBtn?: HTMLButtonElement) => void,
20
+ reload: () => void,
21
+ addToBar: ReactNode
22
+}) {
23
const { username } = useSnapState()
24
const [values, setValues] = useState<Account & { password?: string, password2?: string }>(account)
25
const [belongsOptions, setBelongOptions] = useState<string[]>([])
@@ -117,6 +122,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
122
...propsForModifiedValues(isModifiedConfig(values, account)),
123
async onClick() {
124
const { password='', password2, adminActualAccess, hasPassword, invalidated, canLogin, members, ...withoutPassword } = values
125
+ const saveBtn = ref.current?.querySelector<HTMLButtonElement>('button.saveBtn') || undefined
126
if (add) {
127
const got = await apiCall('add_account', withoutPassword)
128
if (password)
@@ -125,7 +131,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
131
void apiCall('del_account', { username: values.username }) // best effort, don't wait
132
throw e
133
}
128
- done(got?.username)
134
+ done(got?.username, saveBtn)
135
return
136
}
137
const got = await apiCall('set_account', {
@@ -136,7 +142,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
142
await apiNewPassword(values.username, password)
143
if (account.username === username)
144
state.username = values.username
139
- done(got?.username) // username may have been changed, so we pass it back
145
+ done(got?.username, saveBtn) // username may have been changed, so we pass it back
146
}
147
}
148
})
admin/src/AccountsPage.ts
+3
-3
@@ -7,7 +7,7 @@ import {
7
AccountTree, ChevronRight, Close, Delete, DoNotDisturb, ExpandMore, Group, MilitaryTech, Person, PersonAdd, Schedule
8
} from '@mui/icons-material'
9
import { newDialog, with_, md, Jsonify } from './misc'
10
-import { Btn, Flex, IconBtn, iconTooltip, reloadBtn, useBreakpoint, useToggleButton } from './mui'
10
+import { Btn, execDoneMessage, Flex, IconBtn, iconTooltip, reloadBtn, useBreakpoint, useToggleButton } from './mui'
11
import { TreeItem, SimpleTreeView } from '@mui/x-tree-view'
12
import MenuButton from './MenuButton'
13
import AccountForm from './AccountForm'
@@ -58,10 +58,10 @@ export default function AccountsPage() {
58
h(IconBtn, { icon: Close, title: "Close", onClick: selectNone }),
59
],
60
reload,
61
- done(username) {
61
+ done(username, saveBtn) {
62
setSel(isSideBreakpoint ? [username] : [])
63
reload()
64
- toast("Account saved", 'success')
64
+ execDoneMessage('', saveBtn)
65
}
66
}))
67
useEffect(() => {
mui-grid-form/index.ts
+1
@@ -227,6 +227,7 @@ export function Form<Values extends Dict>({
227
children: "Save",
228
loading: useDebounce(phase !== Phase.Idle), // debounce fixes click being ignored at state change and flickering
229
...saveBtn,
230
+ className: `saveBtn ${saveBtn?.className||''}`,
231
onClick() {
232
pleaseSubmitAndValidate()
233
},