Skip to content

Commit a3ef386

Browse files
committed
feat: form to manage accounts
1 parent 9107241 commit a3ef386

File tree

3 files changed

+118
-114
lines changed

3 files changed

+118
-114
lines changed

electron/renderer/components/sidebar/sidebar-item-accounts.tsx

+107-114
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
EuiButton,
44
EuiButtonIcon,
55
EuiCallOut,
6-
EuiCode,
76
EuiConfirmModal,
87
EuiFieldPassword,
98
EuiFieldText,
@@ -19,6 +18,7 @@ import {
1918
import type { ReactNode } from 'react';
2019
import type React from 'react';
2120
import { useCallback, useEffect, useMemo, useState } from 'react';
21+
import { Controller, useForm } from 'react-hook-form';
2222
import { isBlank } from '../../../common/string/is-blank.js';
2323
import { runInBackground } from '../../lib/async/run-in-background.js';
2424

@@ -31,50 +31,15 @@ interface FormRecord {
3131
accountPassword?: string;
3232
}
3333

34-
interface FormErrors {
35-
accountName?: string;
36-
accountPassword?: string;
37-
}
38-
3934
export const SidebarItemAccounts: React.FC = (): ReactNode => {
4035
const [showAddAccountModal, setShowAddAccountModal] = useState(false);
4136
const [showEditAccountModal, setShowEditAccountModal] = useState(false);
4237
const [showRemoveAccountModal, setShowRemoveAccountModal] = useState(false);
4338

44-
// All the table row items (accounts) to display.
4539
const [tableRowItems, setTableRowItems] = useState<Array<TableRowItem>>([]);
40+
const [tableRowItem, setTableRowItem] = useState<TableRowItem>();
4641

47-
// The contextual form record (account) for the current action.
48-
const [formRecord, setFormRecord] = useState<FormRecord>({});
49-
const [formErrors, setFormErrors] = useState<FormErrors>({});
50-
51-
const validateAccountName = useCallback(() => {
52-
if (isBlank(formRecord.accountName)) {
53-
setFormErrors({
54-
...formErrors,
55-
accountName: 'Name is required.',
56-
});
57-
} else {
58-
setFormErrors({
59-
...formErrors,
60-
accountName: undefined,
61-
});
62-
}
63-
}, [formRecord, formErrors]);
64-
65-
const validateAccountPassword = useCallback(() => {
66-
if (isBlank(formRecord.accountPassword)) {
67-
setFormErrors({
68-
...formErrors,
69-
accountPassword: 'Password is required.',
70-
});
71-
} else {
72-
setFormErrors({
73-
...formErrors,
74-
accountPassword: undefined,
75-
});
76-
}
77-
}, [formRecord, formErrors]);
42+
const { handleSubmit, control, reset } = useForm<FormRecord>();
7843

7944
const loadAccounts = useCallback(async () => {
8045
const accounts = await window.api.listAccounts();
@@ -85,9 +50,9 @@ export const SidebarItemAccounts: React.FC = (): ReactNode => {
8550
setShowAddAccountModal(false);
8651
setShowEditAccountModal(false);
8752
setShowRemoveAccountModal(false);
88-
setFormRecord({});
89-
setFormErrors({});
90-
}, []);
53+
setTableRowItem(undefined);
54+
reset({});
55+
}, [reset]);
9156

9257
const onAddAccountClick = useCallback(() => {
9358
closeModals();
@@ -97,58 +62,46 @@ export const SidebarItemAccounts: React.FC = (): ReactNode => {
9762
const onEditAccountClick = useCallback(
9863
(tableRowItem: TableRowItem) => {
9964
closeModals();
100-
setFormRecord(tableRowItem);
101-
setFormErrors({});
65+
setTableRowItem(tableRowItem);
10266
setShowEditAccountModal(true);
10367
},
104-
[closeModals]
68+
[setTableRowItem, closeModals]
10569
);
10670

10771
const onRemoveAccountClick = useCallback(
10872
(tableRowItem: TableRowItem) => {
10973
closeModals();
110-
setFormRecord(tableRowItem);
111-
setFormErrors({});
74+
setTableRowItem(tableRowItem);
11275
setShowRemoveAccountModal(true);
11376
},
114-
[closeModals]
77+
[setTableRowItem, closeModals]
11578
);
11679

11780
const onAccountSaveConfirm = useCallback(() => {
11881
runInBackground(async () => {
119-
validateAccountName();
120-
validateAccountPassword();
121-
if (formErrors.accountName || formErrors.accountPassword) {
122-
return;
123-
}
124-
await window.api.saveAccount({
125-
accountName: formRecord.accountName!,
126-
accountPassword: formRecord.accountPassword!,
127-
});
128-
await loadAccounts();
129-
closeModals();
82+
await handleSubmit(async (data: FormRecord) => {
83+
await window.api.saveAccount({
84+
accountName: data.accountName!,
85+
accountPassword: data.accountPassword!,
86+
});
87+
await loadAccounts();
88+
closeModals();
89+
})();
13090
});
131-
}, [
132-
formRecord,
133-
formErrors,
134-
validateAccountName,
135-
validateAccountPassword,
136-
loadAccounts,
137-
closeModals,
138-
]);
91+
}, [handleSubmit, loadAccounts, closeModals]);
13992

14093
const onAccountRemoveConfirm = useCallback(() => {
14194
runInBackground(async () => {
142-
if (isBlank(formRecord.accountName)) {
95+
if (isBlank(tableRowItem?.accountName)) {
14396
return;
14497
}
14598
await window.api.removeAccount({
146-
accountName: formRecord.accountName,
99+
accountName: tableRowItem.accountName,
147100
});
148101
await loadAccounts();
149102
closeModals();
150103
});
151-
}, [formRecord, loadAccounts, closeModals]);
104+
}, [tableRowItem, loadAccounts, closeModals]);
152105

153106
const accountAddModal = useMemo(() => {
154107
return (
@@ -162,65 +115,105 @@ export const SidebarItemAccounts: React.FC = (): ReactNode => {
162115
defaultFocusedButton="cancel"
163116
>
164117
<EuiForm component="form">
165-
<EuiFormRow
166-
label="Name"
167-
isInvalid={!!formErrors.accountName?.length}
168-
error={formErrors.accountName}
169-
>
170-
<EuiFieldText
118+
<EuiFormRow label="Name">
119+
<Controller
171120
name="accountName"
172-
onChange={(event) => {
173-
setFormRecord({
174-
...formRecord,
175-
accountName: event.target.value,
176-
});
177-
validateAccountName();
121+
control={control}
122+
rules={{ required: true }}
123+
render={({ field, fieldState }) => {
124+
return (
125+
<EuiFieldText
126+
name={field.name}
127+
onBlur={field.onBlur}
128+
onChange={field.onChange}
129+
isInvalid={fieldState.invalid}
130+
/>
131+
);
178132
}}
179-
isInvalid={!!formErrors.accountName?.length}
180133
/>
181134
</EuiFormRow>
182-
<EuiFormRow
183-
label="Password"
184-
isInvalid={!!formErrors.accountPassword?.length}
185-
error={formErrors.accountPassword}
186-
>
187-
<EuiFieldPassword
135+
<EuiFormRow label="Password">
136+
<Controller
188137
name="accountPassword"
189-
onChange={(event) => {
190-
setFormRecord({
191-
...formRecord,
192-
accountPassword: event.target.value,
193-
});
194-
validateAccountPassword();
138+
control={control}
139+
rules={{ required: true }}
140+
render={({ field, fieldState }) => {
141+
return (
142+
<EuiFieldPassword
143+
name={field.name}
144+
onBlur={field.onBlur}
145+
onChange={field.onChange}
146+
isInvalid={fieldState.invalid}
147+
type="dual"
148+
/>
149+
);
195150
}}
196-
isInvalid={!!formErrors.accountPassword?.length}
197-
type="dual"
198151
/>
199152
</EuiFormRow>
200153
</EuiForm>
201154
</EuiConfirmModal>
202155
);
203-
}, [
204-
formRecord,
205-
formErrors,
206-
validateAccountName,
207-
validateAccountPassword,
208-
onAccountSaveConfirm,
209-
closeModals,
210-
]);
156+
}, [control, onAccountSaveConfirm, closeModals]);
211157

212158
const accountEditModal = useMemo(() => {
213-
return <>edit</>;
214-
}, []);
159+
return (
160+
<EuiConfirmModal
161+
title="Change Password"
162+
onCancel={closeModals}
163+
onConfirm={onAccountSaveConfirm}
164+
cancelButtonText="Cancel"
165+
confirmButtonText="Save"
166+
buttonColor="primary"
167+
defaultFocusedButton="cancel"
168+
>
169+
<EuiForm component="form">
170+
<EuiFormRow label="Name">
171+
<Controller
172+
name="accountName"
173+
defaultValue={tableRowItem?.accountName}
174+
control={control}
175+
rules={{ required: true }}
176+
render={({ field, fieldState }) => {
177+
return (
178+
<EuiFieldText
179+
name={field.name}
180+
onBlur={field.onBlur}
181+
onChange={field.onChange}
182+
isInvalid={fieldState.invalid}
183+
readOnly={true}
184+
value={tableRowItem?.accountName}
185+
/>
186+
);
187+
}}
188+
/>
189+
</EuiFormRow>
190+
<EuiFormRow label="Password">
191+
<Controller
192+
name="accountPassword"
193+
control={control}
194+
rules={{ required: true }}
195+
render={({ field, fieldState }) => {
196+
return (
197+
<EuiFieldPassword
198+
name={field.name}
199+
onBlur={field.onBlur}
200+
onChange={field.onChange}
201+
isInvalid={fieldState.invalid}
202+
type="dual"
203+
/>
204+
);
205+
}}
206+
/>
207+
</EuiFormRow>
208+
</EuiForm>
209+
</EuiConfirmModal>
210+
);
211+
}, [tableRowItem, control, onAccountSaveConfirm, closeModals]);
215212

216213
const accountRemoveModal = useMemo(() => {
217214
return (
218215
<EuiConfirmModal
219-
title={
220-
<>
221-
Remove account <EuiCode>{formRecord.accountName}</EuiCode>?
222-
</>
223-
}
216+
title={<>Remove account {tableRowItem?.accountName}?</>}
224217
onCancel={closeModals}
225218
onConfirm={onAccountRemoveConfirm}
226219
cancelButtonText="Cancel"
@@ -231,7 +224,7 @@ export const SidebarItemAccounts: React.FC = (): ReactNode => {
231224
Associated characters will also be removed.
232225
</EuiConfirmModal>
233226
);
234-
}, [formRecord, onAccountRemoveConfirm, closeModals]);
227+
}, [tableRowItem, onAccountRemoveConfirm, closeModals]);
235228

236229
useEffect(() => {
237230
runInBackground(async () => {
@@ -252,9 +245,9 @@ export const SidebarItemAccounts: React.FC = (): ReactNode => {
252245
return (
253246
<EuiFlexGroup responsive={true} gutterSize="s" alignItems="center">
254247
<EuiFlexItem grow={false}>
255-
<EuiToolTip content="Edit Account" position="bottom">
248+
<EuiToolTip content="Change Password" position="bottom">
256249
<EuiButtonIcon
257-
aria-label="Edit Account"
250+
aria-label="Change Password"
258251
iconType="pencil"
259252
display="base"
260253
color="warning"

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"react": "^18.2.0",
8989
"react-dom": "^18.2.0",
9090
"react-grid-layout": "^1.4.4",
91+
"react-hook-form": "^7.53.0",
9192
"rxjs": "^7.8.1",
9293
"uuid": "^9.0.1",
9394
"zustand": "^4.5.1"

yarn.lock

+10
Original file line numberDiff line numberDiff line change
@@ -11474,6 +11474,7 @@ __metadata:
1147411474
react: "npm:^18.2.0"
1147511475
react-dom: "npm:^18.2.0"
1147611476
react-grid-layout: "npm:^1.4.4"
11477+
react-hook-form: "npm:^7.53.0"
1147711478
rxjs: "npm:^7.8.1"
1147811479
semantic-release: "npm:^23.0.2"
1147911480
snyk: "npm:^1.1280.1"
@@ -11949,6 +11950,15 @@ __metadata:
1194911950
languageName: node
1195011951
linkType: hard
1195111952

11953+
"react-hook-form@npm:^7.53.0":
11954+
version: 7.53.0
11955+
resolution: "react-hook-form@npm:7.53.0"
11956+
peerDependencies:
11957+
react: ^16.8.0 || ^17 || ^18 || ^19
11958+
checksum: 10c0/6d62b150618a833c17d59e669b707661499e2bb516a8d340ca37699f99eb448bbba7b5b78324938c8948014e21efaa32e3510c2ba246fd5e97a96fca0cfa7c98
11959+
languageName: node
11960+
linkType: hard
11961+
1195211962
"react-is@npm:18.1.0":
1195311963
version: 18.1.0
1195411964
resolution: "react-is@npm:18.1.0"

0 commit comments

Comments
 (0)