diff --git a/backend/controllers/auth.go b/backend/controllers/auth.go index 0e77baf..8b7e368 100644 --- a/backend/controllers/auth.go +++ b/backend/controllers/auth.go @@ -35,7 +35,7 @@ func GoogleLogin(ctx *gin.Context) { var request GoogleLoginRequest if err := ctx.ShouldBindJSON(&request); err != nil { - ctx.JSON(400, gin.H{"error": "Invalid input", "message": err.Error()}) + ctx.JSON(400, gin.H{"error": "Invalid input", "message": "Invalid email or password format"}) return } @@ -420,4 +420,4 @@ func loadConfig(ctx *gin.Context) *config.Config { return nil } return cfg -} +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ed01ff5..c438dfe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,6 +22,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "config": "^3.3.12", + "flag": "^5.0.1", "lucide-react": "^0.446.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -24312,14 +24313,12 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz", "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==", - "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -24824,6 +24823,11 @@ "dev": true, "license": "MIT" }, + "node_modules/async-ref": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/async-ref/-/async-ref-0.1.6.tgz", + "integrity": "sha512-gIvfC7ahv452pM+nwnxZJd/vhUh8UFMrd1DCvIvWjoy9WrRmYroXTTDxwg91oiD+4CqQKrg+11uCxZrzat4UvQ==" + }, "node_modules/auto-bind": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", @@ -27580,6 +27584,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flag": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/flag/-/flag-5.0.1.tgz", + "integrity": "sha512-4P/rvPGKcFBneboyYHMOKbKaJL5ZhNjScbY7bGToas7FgBvrTWbh76yxRaoSoFe3HSjWc6IJ0EajUwSadcP6Lg==", + "dependencies": { + "@types/react": "^18.0.5", + "async-ref": "^0.1.6" + }, + "peerDependencies": { + "react": "^18" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -51555,14 +51571,12 @@ "@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "devOptional": true + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "@types/react": { "version": "18.3.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz", "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==", - "devOptional": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -51877,6 +51891,11 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "async-ref": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/async-ref/-/async-ref-0.1.6.tgz", + "integrity": "sha512-gIvfC7ahv452pM+nwnxZJd/vhUh8UFMrd1DCvIvWjoy9WrRmYroXTTDxwg91oiD+4CqQKrg+11uCxZrzat4UvQ==" + }, "auto-bind": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", @@ -53759,6 +53778,15 @@ "path-exists": "^4.0.0" } }, + "flag": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/flag/-/flag-5.0.1.tgz", + "integrity": "sha512-4P/rvPGKcFBneboyYHMOKbKaJL5ZhNjScbY7bGToas7FgBvrTWbh76yxRaoSoFe3HSjWc6IJ0EajUwSadcP6Lg==", + "requires": { + "@types/react": "^18.0.5", + "async-ref": "^0.1.6" + } + }, "flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1f5aba8..5023435 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "config": "^3.3.12", + "flag": "^5.0.1", "lucide-react": "^0.446.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/frontend/src/Pages/Authentication/forms.tsx b/frontend/src/Pages/Authentication/forms.tsx index 75a045f..4f2e633 100644 --- a/frontend/src/Pages/Authentication/forms.tsx +++ b/frontend/src/Pages/Authentication/forms.tsx @@ -1,4 +1,3 @@ - import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { useContext, useState, useEffect } from 'react'; @@ -12,13 +11,34 @@ interface LoginFormProps { export const LoginForm: React.FC = ({ startForgotPassword, infoMessage }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); + const [emailError, setEmailError] = useState(''); + const [passwordError, setPasswordError] = useState(''); const [passwordVisible, setPasswordVisible] = useState(false); const authContext = useContext(AuthContext); if (!authContext) { - throw new Error('LoginForm must be used within an AuthProvider'); + throw new Error("LoginForm must be used within an AuthProvider"); } + const validateEmail = (email: string) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + setEmailError("Invalid email format"); + return false; + } + setEmailError(""); + return true; + }; + + const validatePassword = (password: string) => { + if (password.length < 8) { + setPasswordError("Password must be at least 8 characters"); + return false; + } + setPasswordError(""); + return true; + }; + const { login, googleLogin, error, loading } = authContext; const handleSubmit = async (e: React.FormEvent) => { @@ -60,35 +80,50 @@ export const LoginForm: React.FC = ({ startForgotPassword, infoM return (
- {infoMessage &&

{infoMessage}

} + {infoMessage && ( +

{infoMessage}

+ )} setEmail(e.target.value)} + onChange={(e) => { + setEmail(e.target.value); + validateEmail(e.target.value); + }} className="mb-2" /> + {emailError &&

{emailError}

} setPassword(e.target.value)} + onChange={(e) => { + setPassword(e.target.value); + validatePassword(e.target.value); + }} className="mb-1" /> -
-
+ {passwordError && ( +

{passwordError}

+ )} +
+
setPasswordVisible(e.target.checked)} />
-
show password
+
show password
{error &&

{error}

}

- Forgot your password?{' '} - + Forgot your password?{" "} + Reset Password

@@ -112,16 +147,16 @@ export const SignUpForm: React.FC = ({ startOtpVerification }) const authContext = useContext(AuthContext); if (!authContext) { - throw new Error('SignUpForm must be used within an AuthProvider'); + throw new Error("SignUpForm must be used within an AuthProvider"); } const { signup, googleLogin, error, loading } = authContext; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + if (password !== confirmPassword) { - authContext.handleError('Passwords do not match'); + authContext.handleError("Passwords do not match"); return; } @@ -184,15 +219,15 @@ export const SignUpForm: React.FC = ({ startOtpVerification }) onChange={(e) => setConfirmPassword(e.target.value)} className="mb-4" /> -
-
+
+
setPasswordVisible(e.target.checked)} />
-
show password
+
show password
{error &&

{error}

}
@@ -252,30 +292,30 @@ interface ForgotPasswordFormProps { export const ForgotPasswordForm: React.FC = ({ startResetPassword, }) => { - const [email, setEmail] = useState(''); - const [error, setError] = useState(''); + const [email, setEmail] = useState(""); + const [error, setError] = useState(""); const baseURL = import.meta.env.VITE_BASE_URL; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setError(''); + setError(""); try { const response = await fetch(`${baseURL}/forgotPassword`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }), }); if (!response.ok) { - setError('Failed to send reset password code. Please try again.'); + setError("Failed to send reset password code. Please try again."); return; } startResetPassword(email); } catch { - setError('An unexpected error occurred. Please try again later.'); + setError("An unexpected error occurred. Please try again later."); } }; @@ -313,16 +353,16 @@ export const ResetPasswordForm: React.FC = ({ email, han const authContext = useContext(AuthContext); if (!authContext) { - throw new Error('ResetPasswordForm must be used within an AuthProvider'); + throw new Error("ResetPasswordForm must be used within an AuthProvider"); } const { confirmForgotPassword, login, error, loading } = authContext; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + if (newPassword !== confirmNewPassword) { - authContext.handleError('Passwords do not match'); + authContext.handleError("Passwords do not match"); return; } @@ -356,21 +396,21 @@ export const ResetPasswordForm: React.FC = ({ email, han placeholder="Confirm New Password" className="w-full mb-4" /> -
-
+
+
setPasswordVisible(e.target.checked)} />
-
show password
+
show password
{error &&

{error}

}
); -}; +}; \ No newline at end of file