Skip to content

Commit 0335da9

Browse files
committed
refactor: improve errors to user
1 parent 9f1a64f commit 0335da9

File tree

2 files changed

+103
-90
lines changed

2 files changed

+103
-90
lines changed

apps/client/src/features/app-settings/panel/project-panel/ProjectForm.tsx

+25-31
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { useEffect } from 'react';
22
import { useForm } from 'react-hook-form';
33
import { Button, Input } from '@chakra-ui/react';
44

5-
import * as Panel from '../PanelUtils';
6-
75
import style from './ProjectPanel.module.scss';
86

97
export type ProjectFormValues = {
@@ -15,10 +13,9 @@ interface ProjectFormProps {
1513
filename: string;
1614
onCancel: () => void;
1715
onSubmit: (values: ProjectFormValues) => Promise<void>;
18-
submitError: string | null;
1916
}
2017

21-
export default function ProjectForm({ action, filename, onSubmit, onCancel, submitError }: ProjectFormProps) {
18+
export default function ProjectForm({ action, filename, onSubmit, onCancel }: ProjectFormProps) {
2219
const {
2320
handleSubmit,
2421
register,
@@ -37,34 +34,31 @@ export default function ProjectForm({ action, filename, onSubmit, onCancel, subm
3734
}, [setFocus]);
3835

3936
return (
40-
<>
41-
<form onSubmit={handleSubmit(onSubmit)} className={style.form}>
42-
<Input
43-
className={style.formInput}
44-
id='filename'
37+
<form onSubmit={handleSubmit(onSubmit)} className={style.form}>
38+
<Input
39+
className={style.formInput}
40+
id='filename'
41+
size='sm'
42+
type='text'
43+
variant='ontime-filled'
44+
placeholder='Enter new name'
45+
autoComplete='off'
46+
{...register('filename', { required: true })}
47+
/>
48+
<div className={style.actionButtons}>
49+
<Button onClick={onCancel} size='sm' variant='ontime-ghosted' disabled={isSubmitting}>
50+
Cancel
51+
</Button>
52+
<Button
4553
size='sm'
46-
type='text'
4754
variant='ontime-filled'
48-
placeholder='Enter new name'
49-
autoComplete='off'
50-
{...register('filename')}
51-
/>
52-
<div className={style.actionButtons}>
53-
<Button onClick={onCancel} size='sm' variant='ontime-ghosted' disabled={isSubmitting}>
54-
Cancel
55-
</Button>
56-
<Button
57-
size='sm'
58-
variant='ontime-filled'
59-
isDisabled={!isDirty || !isValid || isSubmitting}
60-
type='submit'
61-
className={style.saveButton}
62-
>
63-
{action}
64-
</Button>
65-
</div>
66-
</form>
67-
{submitError && <Panel.Error>{submitError}</Panel.Error>}
68-
</>
55+
isDisabled={!isDirty || !isValid || isSubmitting}
56+
type='submit'
57+
className={style.saveButton}
58+
>
59+
{action}
60+
</Button>
61+
</div>
62+
</form>
6963
);
7064
}

apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx

+78-59
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
renameProject,
1212
} from '../../../../common/api/db';
1313
import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/utils';
14+
import * as Panel from '../PanelUtils';
1415

1516
import ProjectForm, { ProjectFormValues } from './ProjectForm';
1617

@@ -40,36 +41,53 @@ export default function ProjectListItem({
4041
onToggleEditMode,
4142
}: ProjectListItemProps) {
4243
const [submitError, setSubmitError] = useState<string | null>(null);
44+
const [loading, setLoading] = useState(false);
4345

44-
const handleSubmitRename = async (values: ProjectFormValues) => {
45-
try {
46+
const handleSubmitAction = (actionType: 'rename' | 'duplicate') => {
47+
return async (values: ProjectFormValues) => {
48+
setLoading(true);
4649
setSubmitError(null);
47-
48-
if (!values.filename) {
49-
setSubmitError('Filename cannot be blank');
50-
return;
50+
try {
51+
if (!values.filename) {
52+
setSubmitError('Filename cannot be blank');
53+
return;
54+
}
55+
const action = actionType === 'rename' ? renameProject : duplicateProject;
56+
await action(filename, values.filename);
57+
await onRefetch();
58+
onSubmit();
59+
} catch (error) {
60+
setSubmitError(maybeAxiosError(error));
61+
} finally {
62+
setLoading(false);
5163
}
52-
await renameProject(filename, values.filename);
64+
};
65+
};
66+
67+
const handleLoad = async (filename: string) => {
68+
setLoading(true);
69+
setSubmitError(null);
70+
try {
71+
await loadProject(filename);
5372
await onRefetch();
54-
onSubmit();
73+
await invalidateAllCaches();
5574
} catch (error) {
5675
setSubmitError(maybeAxiosError(error));
76+
} finally {
77+
setLoading(false);
5778
}
5879
};
5980

60-
const handleSubmitDuplicate = async (values: ProjectFormValues) => {
81+
const handleDelete = async (filename: string) => {
82+
setLoading(true);
83+
setSubmitError(null);
6184
try {
62-
setSubmitError(null);
63-
64-
if (!values.filename) {
65-
setSubmitError('Filename cannot be blank');
66-
return;
67-
}
68-
await duplicateProject(filename, values.filename);
85+
await deleteProject(filename);
6986
await onRefetch();
70-
onSubmit();
7187
} catch (error) {
7288
setSubmitError(maybeAxiosError(error));
89+
} finally {
90+
setLoading(false);
7391
}
7492
};
7593

@@ -86,50 +104,55 @@ export default function ProjectListItem({
86104
const classes = current && !isCurrentlyBeingEdited ? style.current : undefined;
87105

88106
return (
89-
<tr key={filename} className={classes}>
90-
{isCurrentlyBeingEdited ? (
91-
<td colSpan={99}>
92-
<ProjectForm
93-
action={editingMode}
94-
filename={filename}
95-
onSubmit={editingMode === 'duplicate' ? handleSubmitDuplicate : handleSubmitRename}
96-
onCancel={handleCancel}
97-
submitError={submitError}
98-
/>
99-
</td>
100-
) : (
101-
<>
102-
<td className={style.containCell}>{filename}</td>
103-
<td>{new Date(updatedAt).toLocaleString()}</td>
104-
<td className={style.actionButton}>
105-
<ActionMenu
106-
current={current}
107+
<>
108+
{submitError && (
109+
<tr key='filename-error'>
110+
<td colSpan={99}>
111+
<Panel.Error>{submitError}</Panel.Error>
112+
</td>
113+
</tr>
114+
)}
115+
<tr key={filename} className={classes}>
116+
{isCurrentlyBeingEdited ? (
117+
<td colSpan={99}>
118+
<ProjectForm
119+
action={editingMode}
107120
filename={filename}
108-
onChangeEditMode={handleToggleEditMode}
109-
onRefetch={onRefetch}
121+
onSubmit={editingMode === 'duplicate' ? handleSubmitAction('duplicate') : handleSubmitAction('rename')}
122+
onCancel={handleCancel}
110123
/>
111124
</td>
112-
</>
113-
)}
114-
</tr>
125+
) : (
126+
<>
127+
<td className={style.containCell}>{filename}</td>
128+
<td>{new Date(updatedAt).toLocaleString()}</td>
129+
<td className={style.actionButton}>
130+
<ActionMenu
131+
current={current}
132+
filename={filename}
133+
onChangeEditMode={handleToggleEditMode}
134+
onDelete={handleDelete}
135+
onLoad={handleLoad}
136+
isDisabled={loading}
137+
/>
138+
</td>
139+
</>
140+
)}
141+
</tr>
142+
</>
115143
);
116144
}
117145

118-
function ActionMenu({
119-
current,
120-
filename,
121-
onChangeEditMode,
122-
onRefetch,
123-
}: {
146+
interface ActionMenuProps {
124147
current?: boolean;
125148
filename: string;
149+
isDisabled: boolean;
126150
onChangeEditMode: (editMode: EditMode, filename: string) => void;
127-
onRefetch: () => Promise<void>;
128-
}) {
129-
const handleLoad = async () => {
130-
await loadProject(filename);
131-
await invalidateAllCaches();
132-
};
151+
onDelete: (filename: string) => void;
152+
onLoad: (filename: string) => void;
153+
}
154+
function ActionMenu(props: ActionMenuProps) {
155+
const { current, filename, isDisabled, onChangeEditMode, onDelete, onLoad } = props;
133156

134157
const handleRename = () => {
135158
onChangeEditMode('rename', filename);
@@ -139,11 +162,6 @@ function ActionMenu({
139162
onChangeEditMode('duplicate', filename);
140163
};
141164

142-
const handleDelete = async () => {
143-
await deleteProject(filename);
144-
await onRefetch();
145-
};
146-
147165
const handleDownload = async () => {
148166
await downloadProject(filename);
149167
};
@@ -161,16 +179,17 @@ function ActionMenu({
161179
color='#e2e2e2' // $gray-200
162180
variant='ontime-ghosted'
163181
size='sm'
182+
isDisabled={isDisabled}
164183
/>
165184
<MenuList>
166-
<MenuItem onClick={handleLoad} isDisabled={current}>
185+
<MenuItem onClick={() => onLoad(filename)} isDisabled={current}>
167186
Load
168187
</MenuItem>
169188
<MenuItem onClick={handleRename}>Rename</MenuItem>
170189
<MenuItem onClick={handleDuplicate}>Duplicate</MenuItem>
171190
<MenuItem onClick={handleDownload}>Download</MenuItem>
172191
<MenuItem onClick={handleExportCSV}>Export CSV Rundown</MenuItem>
173-
<MenuItem isDisabled={current} onClick={handleDelete}>
192+
<MenuItem isDisabled={current} onClick={() => onDelete(filename)}>
174193
Delete
175194
</MenuItem>
176195
</MenuList>

0 commit comments

Comments
 (0)