Skip to content

Commit 3a71fbd

Browse files
committed
feat: consistent error messages and nonempty handling
https://harperdb.atlassian.net/browse/STUDIO-13
1 parent c94359d commit 3a71fbd

34 files changed

+176
-222
lines changed

src/features/auth/ClusterInstanceSignIn.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useInstanceClient } from '@/config/useInstanceClient';
1212
import { getClusterInfoQueryOptions } from '@/features/cluster/queries/getClusterInfoQuery';
1313
import { useInstanceLoginMutation } from '@/features/instance/operations/mutations/useInstanceLoginMutation';
1414
import { getInstanceUserInfo } from '@/features/instance/operations/queries/getInstanceUserInfo';
15+
import { SignInSchema } from '@/features/instance/operations/schemas/signInSchema';
1516
import { authStore, OverallAppSignIn } from '@/lib/authStore';
1617
import { CrossLocalhostIssueType, detectCrossLocalhostUrls } from '@/lib/urls/detectCrossLocalhostUrls';
1718
import { getOperationsUrlForCluster } from '@/lib/urls/getOperationsUrlForCluster';
@@ -25,20 +26,6 @@ import { useForm } from 'react-hook-form';
2526
import { toast } from 'sonner';
2627
import { z } from 'zod';
2728

28-
const SignInSchema = z.object({
29-
username: z
30-
.string({
31-
error: 'Please enter your username.',
32-
})
33-
.max(75, { error: 'Username must be less than 75 characters' }),
34-
password: z
35-
.string({
36-
error: 'Please enter your password',
37-
})
38-
.min(1, { error: 'Password is required' })
39-
.max(50, { error: 'Password must be less than 50 characters' }),
40-
});
41-
4229
export function ClusterInstanceSignIn() {
4330
const navigate = useNavigate();
4431
const router = useRouter();

src/features/auth/ForgotPassword.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import { toast } from 'sonner';
1616
const ForgotPasswordSchema = z.object({
1717
email: z
1818
.email({
19-
error: 'Please enter a valid email address',
19+
error: 'Please enter a valid email address.',
2020
})
21-
.max(75, { error: 'Email must be less than 75 characters' }),
21+
.max(75, { error: 'Email cannot be longer than 75 characters.' }),
2222
});
2323

2424
export function ForgotPassword() {

src/features/auth/ResetPassword.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@ import { useResetPasswordMutation } from './hooks/useResetPassword';
1717
const ResetPasswordSchema = z
1818
.object({
1919
password: z
20-
.string({
21-
error: 'Please enter your new password.',
22-
})
23-
.min(8, { error: 'Password must be at least 8 characters' })
24-
.max(50, { error: 'Password must be less than 50 characters.' }),
20+
.string()
21+
.min(8, { error: 'Password must be at least 8 characters long.' })
22+
.max(50, { error: 'Password cannot be longer than 50 characters.' }),
2523
confirmPassword: z.string(),
2624
})
2725
.refine((data) => data.password === data.confirmPassword, {
28-
error: 'Passwords do not match',
26+
error: 'Passwords do not match.',
2927
path: ['confirmPassword'],
3028
});
3129

src/features/auth/SignIn.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,10 @@ const SignInSchema = z.object({
2222
email: z
2323
.email({
2424
error: 'Please enter a valid email address.',
25-
})
26-
.max(75, { error: 'Email must be less than 75 characters' }),
25+
}),
2726
password: z
28-
.string({
29-
error: 'Please enter your password',
30-
})
31-
.min(1, { error: 'Password is required' })
32-
.max(50, { error: 'Password must be less than 50 characters' }),
27+
.string()
28+
.nonempty({ error: 'Please enter your password.' }),
3329
});
3430

3531
export function SignIn() {

src/features/auth/SignUp.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,22 @@ import { z } from 'zod';
1515

1616
const SignInSchema = z.object({
1717
firstname: z
18-
.string({
19-
error: 'Please enter your first name.',
20-
})
21-
.min(2, { error: 'First name is required.' })
22-
.max(50, { error: 'First name must be less than 50 characters.' }),
18+
.string()
19+
.min(2, { error: 'Please enter your first name.' })
20+
.max(50, { error: 'First name cannot be longer than 50 characters.' }),
2321
lastname: z
24-
.string({
25-
error: 'Please enter your last name.',
26-
})
27-
.min(2, { error: 'Last name is required.' })
28-
.max(50, { error: 'Last name must be less than 50 characters.' }),
22+
.string()
23+
.min(2, { error: 'Please enter your last name.' })
24+
.max(50, { error: 'Last name cannot be longer than 50 characters.' }),
2925
email: z
3026
.email({
3127
error: 'Please enter a valid email address.',
3228
})
33-
.max(75, { error: 'Email must be less than 75 characters.' }),
29+
.max(75, { error: 'Email cannot be longer than 75 characters.' }),
3430
password: z
35-
.string({
36-
error: 'Please enter your password.',
37-
})
38-
.min(8, { error: 'Password must be 8 characters or more.' })
39-
.max(50, { error: 'Password must be less than 50 characters.' }),
31+
.string()
32+
.min(8, { error: 'Password must be at least 8 characters long.' })
33+
.max(50, { error: 'Password cannot be longer than 50 characters.' }),
4034
});
4135

4236
export function SignUp() {

src/features/auth/VerifyEmail.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1+
import { Button } from '@/components/ui/button';
12
import { Form } from '@/components/ui/form/Form';
23
import { FormControl } from '@/components/ui/form/FormControl';
34
import { FormField } from '@/components/ui/form/FormField';
45
import { FormItem } from '@/components/ui/form/FormItem';
56
import { FormLabel } from '@/components/ui/form/FormLabel';
67
import { FormMessage } from '@/components/ui/form/FormMessage';
7-
import { useNavigate, useSearch } from '@tanstack/react-router';
8+
import { Input } from '@/components/ui/input';
89
import { useVerifyEmailMutation, VerifyEmailToken } from '@/features/auth/hooks/useVerifyEmail';
10+
import { zodResolver } from '@hookform/resolvers/zod';
11+
import { useNavigate, useSearch } from '@tanstack/react-router';
912
import { useCallback, useEffect } from 'react';
10-
import { Input } from '@/components/ui/input';
11-
import { Button } from '@/components/ui/button';
1213
import { useForm } from 'react-hook-form';
13-
import { zodResolver } from '@hookform/resolvers/zod';
14+
import { toast } from 'sonner';
1415
import { z } from 'zod';
1516
import { useResendEmailVerification } from './hooks/useResendEmailVerification';
16-
import { toast } from 'sonner';
1717

1818
const VerifyEmailSchema = z.object({
1919
email: z
2020
.email({
21-
error: 'Please enter a valid email address',
21+
error: 'Please enter a valid email address.',
2222
})
23-
.max(75, { error: 'Email must be less than 75 characters' }),
23+
.max(75, { error: 'Email cannot be longer than 75 characters.' }),
2424
});
2525

2626
function SendEmailVerification() {
@@ -101,7 +101,7 @@ export function VerifyEmail() {
101101
},
102102
});
103103
},
104-
[submitEmailVerificationToken, navigate]
104+
[submitEmailVerificationToken, navigate],
105105
);
106106

107107
useEffect(() => {

src/features/cluster/ClusterSetPassword.tsx

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
useInstanceResetPasswordMutation,
1515
} from '@/features/instance/operations/mutations/useInstanceResetPasswordMutation';
1616
import { getInstanceUserInfo } from '@/features/instance/operations/queries/getInstanceUserInfo';
17+
import { AddUserFormSchema } from '@/features/instance/operations/schemas/addUserFormSchema';
1718
import { authStore } from '@/lib/authStore';
1819
import { getOperationsUrlForCluster } from '@/lib/urls/getOperationsUrlForCluster';
1920
import { zodResolver } from '@hookform/resolvers/zod';
@@ -24,25 +25,6 @@ import { useForm } from 'react-hook-form';
2425
import { toast } from 'sonner';
2526
import { z } from 'zod';
2627

27-
const ClusterSetPasswordSchema = z
28-
.object({
29-
username: z.string({
30-
error: 'Please enter a username.',
31-
// TODO: usernames must have only letters, numbers, hyphens, and underscores
32-
}).min(1, { error: 'Please enter a username.' }),
33-
password: z
34-
.string({
35-
error: 'Please enter your password',
36-
})
37-
.min(1, { error: 'Password is required' })
38-
.max(50, { error: 'Password must be less than 50 characters' }),
39-
confirmPassword: z.string(),
40-
})
41-
.refine((data) => data.password === data.confirmPassword, {
42-
error: 'Passwords do not match',
43-
path: ['confirmPassword'],
44-
});
45-
4628
const route = getRouteApi('');
4729

4830
export function ClusterSetPassword() {
@@ -59,18 +41,19 @@ export function ClusterSetPassword() {
5941
const router = useRouter();
6042

6143
const form = useForm({
62-
resolver: zodResolver(ClusterSetPasswordSchema),
44+
resolver: zodResolver(AddUserFormSchema),
6345
defaultValues: {
64-
username: '',
65-
password: '',
6646
confirmPassword: '',
47+
password: '',
48+
role: 'super_user',
49+
username: '',
6750
},
6851
});
6952
const tempPassword = cluster?.instances?.find(i => i.tempPassword)?.tempPassword;
7053

7154
const { mutate: submitInstanceResetPassword, isPending } = useInstanceResetPasswordMutation();
7255

73-
const submitForm = useCallback(async (formData: z.infer<typeof ClusterSetPasswordSchema>) => {
56+
const submitForm = useCallback(async (formData: z.infer<typeof AddUserFormSchema>) => {
7457
if (!operationsUrl) {
7558
toast.error('Cluster is not yet fully loaded, please wait a moment before trying to sign in.');
7659
return;
@@ -87,7 +70,7 @@ export function ClusterSetPassword() {
8770
toast.success(response.message);
8871
const user = await getInstanceUserInfo({ instanceClient });
8972
authStore.setUserForEntity(cluster || null, user);
90-
router.invalidate();
73+
void router.invalidate();
9174
await navigate({ to: redirect?.startsWith('/') ? redirect : defaultInstanceRouteUpOne });
9275
},
9376
});
@@ -97,7 +80,6 @@ export function ClusterSetPassword() {
9780
return <Navigate to="../sign-in" replace={true} />;
9881
}
9982

100-
// TODO: There's a lot we can DRY up between the sign in form variants.
10183
return (
10284
<>
10385
<nav className="fixed top-20 w-full h-12 z-39 px-4 md:px-12 bg-grey-700 flex items-center">

src/features/clusters/upsert/upsertClusterSchema.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { z } from 'zod';
44

55
export const UpsertClusterSchema = z.object({
66
systemName: z.string()
7-
.min(1, 'Must be at least 1 character long.')
8-
.max(255, 'Must be at most 255 characters long.'),
7+
.nonempty('Please enter a system name.')
8+
.max(255, 'System name cannot be longer than 255 characters long.'),
99
abbreviatedName: z
1010
.string()
1111
.max(10, 'Must be at most 10 characters long.')
@@ -15,8 +15,8 @@ export const UpsertClusterSchema = z.object({
1515
.regex(hostNameRegex, 'Please enter a valid host name without the port or any path.')
1616
.optional(),
1717

18-
deploymentDescription: z.string().min(1, 'Please select a deployment tier.'),
19-
performanceDescription: z.string().min(1, 'Please select a performance tier.'),
18+
deploymentDescription: z.string().nonempty('Please select a deployment tier.'),
19+
performanceDescription: z.string().nonempty('Please select a performance tier.'),
2020

2121
regionPlans: z.array(
2222
z.object({

src/features/instance/applications/modals/AddFolderFileModal.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ export function AddFolderFileModal({
3434
}) {
3535
const NewFileFolderSchema = z.object({
3636
name: z
37-
.string({
38-
error: 'Please enter a valid name',
37+
.string()
38+
.nonempty({ error: 'Please enter a valid name.' })
39+
.regex(/^[a-zA-Z0-9_\- .]*$/, {
40+
error: 'Names can only contain letters, numbers, underscores, hyphens, periods, and spaces.',
3941
})
40-
.min(1, { error: 'Must be at least 1 character long' })
41-
.regex(/^[a-zA-Z0-9_\- .]+$/, {
42-
error: 'Names can only contain letters, numbers, underscores, hyphens, periods, and spaces',
43-
})
44-
.max(50, { error: 'Must be less than 50 characters' })
42+
.max(50, { error: 'Names cannot be longer than 50 characters.' })
4543
.trim(),
4644
});
4745

src/features/instance/applications/new/CreateNewProjectForm.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import { z } from 'zod';
2020
const NewProjectSchema = z.object({
2121
newApplicationName: z
2222
.string()
23-
.min(1, { error: 'Project name is required' })
24-
.max(75, { error: 'Project name must be less than 75 characters' })
25-
.regex(/^[a-zA-Z0-9-_]+$/, { error: 'Can only contain letters, numbers, dashes and underscores' }),
23+
.nonempty({ error: 'Project name is required.' })
24+
.max(75, { error: 'Project name cannot be longer than 75 characters.' })
25+
.regex(/^[a-zA-Z0-9-_]*$/, { error: 'Can only contain letters, numbers, dashes and underscores.' }),
2626
replicated: z.boolean(),
2727
});
2828

0 commit comments

Comments
 (0)