Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"use-debounce": "^10.0.1",
"zod": "^3.24.1",
"zustand": "^4.5.2"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions src/app/(authentication)/auth/forgotpassword/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { withAuthRedirect } from '@/HOCs/withAuthRedirect';
import { useUsersApiAuthRequestReset } from '@/api/users-auth/users-auth';
import Button from '@/components/common/Button';
import FormInput from '@/components/common/FormInput';
import { emailSchema } from '@/constants/zod-schema';
import { showErrorToast } from '@/lib/toastHelpers';

interface IForgotPasswordForm {
Expand Down Expand Up @@ -91,8 +92,7 @@ const ForgotPasswordForm: React.FC = () => {
placeholder="Enter your email"
register={register}
requiredMessage="Email is required"
patternValue={/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/}
patternMessage="Enter a valid email address"
schema={emailSchema}
errors={errors}
/>
<div>
Expand Down
4 changes: 2 additions & 2 deletions src/app/(authentication)/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useUsersApiAuthLoginUser } from '@/api/users-auth/users-auth';
import Button from '@/components/common/Button';
import FormInput from '@/components/common/FormInput';
import { ArrowNarrowLeft } from '@/components/ui/Icons/common';
import { emailOrUsernameSchema } from '@/constants/zod-schema';
import { usePathTracker } from '@/hooks/usePathTracker';
import { showErrorToast } from '@/lib/toastHelpers';
import { useAuthStore } from '@/stores/authStore';
Expand Down Expand Up @@ -94,8 +95,7 @@ const LoginForm: React.FC = () => {
placeholder="Username or Email"
register={register}
requiredMessage="Username or Email is required"
patternValue={/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$|^\w+$/}
patternMessage="Enter a valid email or username"
schema={emailOrUsernameSchema}
errors={errors}
labelClassName="text-black/90"
helperTextClassName="text-black/60"
Expand Down
22 changes: 7 additions & 15 deletions src/app/(authentication)/auth/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useUsersApiAuthSignup } from '@/api/users-auth/users-auth';
import Button from '@/components/common/Button';
import FormInput from '@/components/common/FormInput';
import { ArrowNarrowLeft } from '@/components/ui/Icons/common';
import { emailSchema, matchPassword, passwordSchema, usernameSchema } from '@/constants/zod-schema';
import { showErrorToast } from '@/lib/toastHelpers';

import SignUpSuccess from './SignUpSuccess';
Expand Down Expand Up @@ -131,13 +132,8 @@ const RegisterForm: React.FC = () => {
type="text"
placeholder="e.g., john_doe"
register={register}
patternValue={/^[a-z0-9._]+$/}
patternMessage="Username must only contain lowercase letters, numbers, dots, and underscores."
schema={usernameSchema}
requiredMessage="Username is required"
minLengthValue={3}
minLengthMessage="Username must be at least 3 characters"
maxLengthValue={30}
maxLengthMessage="Username cannot exceed 30 characters"
errors={errors}
isSubmitting={isSubmitting}
helperText="3-30 characters. No spaces or special symbols."
Expand Down Expand Up @@ -169,35 +165,32 @@ const RegisterForm: React.FC = () => {
inputClassName="bg-white text-black"
/>
</div>

<FormInput
label="Email"
name="email"
type="email"
placeholder="e.g., john.doe@example.com"
register={register}
requiredMessage="Email is required"
patternValue={/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i}
patternMessage="Enter a valid email address."
schema={emailSchema}
errors={errors}
labelClassName="text-black/90"
inputClassName="bg-white text-black"
/>

<FormInput
label="Password"
name="password"
type="password"
placeholder="Create a password"
register={register}
requiredMessage="Password is required"
minLengthValue={8}
minLengthMessage="Password must be at least 8 characters long."
schema={passwordSchema}
//minLengthValue={8}
//minLengthMessage="Password must be at least 8 characters long."
errors={errors}
labelClassName="text-black/90"
inputClassName="bg-white text-black"
/>

<FormInput
label="Confirm Password"
name="confirm_password"
Expand All @@ -206,8 +199,7 @@ const RegisterForm: React.FC = () => {
register={register}
requiredMessage="Confirm Password is required"
errors={errors}
patternValue={new RegExp(watch('password'))}
patternMessage="Passwords must match."
schema={matchPassword(new RegExp(watch('password')))}
labelClassName="text-black/90"
inputClassName="bg-white text-black"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { withAuthRedirect } from '@/HOCs/withAuthRedirect';
import { useUsersApiAuthResendActivation } from '@/api/users-auth/users-auth';
import Button from '@/components/common/Button';
import FormInput from '@/components/common/FormInput';
import { emailSchema } from '@/constants/zod-schema';
import { showErrorToast } from '@/lib/toastHelpers';

interface IResendForm {
Expand Down Expand Up @@ -95,8 +96,7 @@ const ResendVerificationForm: React.FC = () => {
placeholder="Enter your email"
register={register}
requiredMessage="Email is required"
patternValue={/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/}
patternMessage="Enter a valid email address"
schema={emailSchema}
errors={errors}
/>
<div>
Expand Down
9 changes: 5 additions & 4 deletions src/app/(authentication)/auth/resetpassword/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { withAuthRedirect } from '@/HOCs/withAuthRedirect';
import { useUsersApiAuthResetPassword } from '@/api/users-auth/users-auth';
import Button from '@/components/common/Button';
import FormInput from '@/components/common/FormInput';
import { matchPassword, passwordSchema } from '@/constants/zod-schema';
import { showErrorToast } from '@/lib/toastHelpers';

interface IResetPasswordForm {
Expand Down Expand Up @@ -76,8 +77,9 @@ const ResetPasswordForm = ({ params }: { params: { token: string } }) => {
placeholder="Password"
register={register}
requiredMessage="Password is required"
minLengthValue={8}
minLengthMessage="Password must be at least 8 characters"
schema={passwordSchema}
//minLengthValue={8}
//minLengthMessage="Password must be at least 8 characters"
errors={errors}
/>
<FormInput<IResetPasswordForm>
Expand All @@ -88,8 +90,7 @@ const ResetPasswordForm = ({ params }: { params: { token: string } }) => {
register={register}
requiredMessage="Confirm Password is required"
errors={errors}
patternMessage="The passwords do not match"
patternValue={new RegExp(watch('password'))}
schema={matchPassword(new RegExp(watch('password')))}
/>
<Button type="submit" isPending={isPending}>
Reset Password
Expand Down
18 changes: 10 additions & 8 deletions src/app/(main)/(users)/myprofile/PersonalLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import React from 'react';
import { FieldErrors, useFormContext } from 'react-hook-form';

import FormInput from '@/components/common/FormInput';
import {
githubUrlSchema,
linkedInUrlSchema,
scholarUrlSchema,
urlSchema,
} from '@/constants/zod-schema';

import { IProfileForm } from './page';

Expand All @@ -29,8 +35,7 @@ const PersonalLinks: React.FC<PersonalLinksProps> = ({ errors, editMode }) => {
type="url"
register={register}
errors={errors}
patternValue={/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/}
patternMessage="Invalid URL format"
schema={urlSchema}
requiredMessage="Home page URL is required"
readOnly={!editMode}
/>
Expand All @@ -41,8 +46,7 @@ const PersonalLinks: React.FC<PersonalLinksProps> = ({ errors, editMode }) => {
type="url"
register={register}
errors={errors}
patternValue={/^https:\/\/[a-z]{2,3}\.linkedin\.com\/.*$/}
patternMessage="Invalid LinkedIn URL"
schema={linkedInUrlSchema}
requiredMessage="LinkedIn URL is required"
readOnly={!editMode}
/>
Expand All @@ -53,8 +57,7 @@ const PersonalLinks: React.FC<PersonalLinksProps> = ({ errors, editMode }) => {
type="url"
register={register}
errors={errors}
patternValue={/^https:\/\/github\.com\/.*$/}
patternMessage="Invalid GitHub URL"
schema={githubUrlSchema}
requiredMessage="GitHub URL is required"
readOnly={!editMode}
/>
Expand All @@ -65,8 +68,7 @@ const PersonalLinks: React.FC<PersonalLinksProps> = ({ errors, editMode }) => {
type="url"
register={register}
errors={errors}
patternValue={/^https:\/\/scholar\.google\.com\/.*$/}
patternMessage="Invalid Google Scholar URL"
schema={scholarUrlSchema}
requiredMessage="Google Scholar URL is required"
readOnly={!editMode}
/>
Expand Down
7 changes: 3 additions & 4 deletions src/app/(main)/(users)/myprofile/ProfessionalStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Plus, Trash2 } from 'lucide-react';
import { FieldErrors, useFieldArray, useFormContext } from 'react-hook-form';

import FormInput from '@/components/common/FormInput';
import { yearOrPresentSchema, yearSchema } from '@/constants/zod-schema';

import { IProfileForm } from './page';

Expand Down Expand Up @@ -50,8 +51,7 @@ const ProfessionalStatus: React.FC<ProfessionalStatusProps> = ({ errors, editMod
register={register}
errors={errors}
requiredMessage="Start year is required"
patternValue={/^\d{4}$/}
patternMessage="Invalid year format"
schema={yearSchema}
readOnly={!editMode}
/>
<FormInput
Expand All @@ -60,8 +60,7 @@ const ProfessionalStatus: React.FC<ProfessionalStatusProps> = ({ errors, editMod
type="text"
register={register}
errors={errors}
patternValue={/^\d{4}$|^Present$/i}
patternMessage="Invalid year format (use 'Present' for current positions)"
schema={yearOrPresentSchema}
readOnly={!editMode}
/>
<div className="flex justify-center space-x-2">
Expand Down
4 changes: 2 additions & 2 deletions src/app/(main)/(users)/myprofile/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Edit, Save } from 'lucide-react';
import { FieldErrors, useFormContext } from 'react-hook-form';

import FormInput from '@/components/common/FormInput';
import { emailSchema } from '@/constants/zod-schema';

import { IProfileForm } from './page';

Expand Down Expand Up @@ -117,8 +118,7 @@ const Profile: React.FC<ProfileProps> = ({ errors, editMode, setEditMode, profil
register={register}
errors={errors}
requiredMessage="Email is required"
patternValue={/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i}
patternMessage="Invalid email address"
schema={emailSchema}
readOnly={true}
/>
<FormInput
Expand Down
33 changes: 13 additions & 20 deletions src/components/common/FormInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';

import { Info } from 'lucide-react';
import { FieldErrors, FieldValues, Path, UseFormRegister } from 'react-hook-form';
import { ZodSchema } from 'zod';

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
Expand All @@ -11,8 +12,6 @@ interface InputProps<TFieldValues extends FieldValues> {
type: string;
placeholder?: string;
requiredMessage?: string;
patternMessage?: string;
patternValue?: RegExp;
minLengthValue?: number;
minLengthMessage?: string;
maxLengthValue?: number;
Expand All @@ -28,6 +27,7 @@ interface InputProps<TFieldValues extends FieldValues> {
inputClassName?: string;
labelClassName?: string;
helperTextClassName?: string;
schema?: ZodSchema<unknown>;
}

const FormInput = <TFieldValues extends FieldValues>({
Expand All @@ -37,12 +37,6 @@ const FormInput = <TFieldValues extends FieldValues>({
placeholder,
register,
requiredMessage,
patternMessage,
patternValue,
minLengthValue,
minLengthMessage,
maxLengthValue,
maxLengthMessage,
errors,
isSubmitting = false,
readOnly = false,
Expand All @@ -52,6 +46,7 @@ const FormInput = <TFieldValues extends FieldValues>({
inputClassName,
labelClassName,
helperTextClassName,
schema,
}: InputProps<TFieldValues>): JSX.Element => {
const error = errors[name];

Expand All @@ -61,18 +56,16 @@ const FormInput = <TFieldValues extends FieldValues>({
readOnly,
...register(name as Path<TFieldValues>, {
required: requiredMessage ? { value: true, message: requiredMessage } : undefined,
pattern:
patternValue && patternMessage
? { value: patternValue, message: patternMessage }
: undefined,
minLength:
minLengthValue && minLengthMessage
? { value: minLengthValue, message: minLengthMessage }
: undefined,
maxLength:
maxLengthValue && maxLengthMessage
? { value: maxLengthValue, message: maxLengthMessage }
: undefined,
validate: (value) => {
if (schema) {
const validationResult = schema.safeParse(value);
console.log(validationResult?.error);
if (!validationResult.success) {
return validationResult.error.errors[0]?.message || 'Invalid input';
}
}
return true;
},
}),
className: cn(
'mt-1 block w-full px-3 py-2 ring-1 ring-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-brand res-text-sm focus:ring-1',
Expand Down
Loading