Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhance Sign-In/Sign-Up Form with Professional UI and Zod Valid… #23

Closed
wants to merge 1 commit into from
Closed
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
212 changes: 212 additions & 0 deletions apps/PetMe-Frontend/src/components/Authentication/SignIn.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import React, { useState } from 'react';
import { signInSchema, signUpSchema } from '../../helpers/validation';

const SignIn = () => {
const [isSignUp, setIsSignUp] = useState(false);
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: '',
});
const [errors, setErrors] = useState({});

const toggleForm = () => {
setIsSignUp(!isSignUp);
setFormData({
username: '',
email: '',
password: '',
confirmPassword: '',
});
setErrors({});
};

const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value,
});
};

const handleSubmit = (e) => {
e.preventDefault();
const schema = isSignUp ? signUpSchema : signInSchema;
try {
schema.parse(formData);
setErrors({});
console.log('Form submitted successfully', formData);
} catch (err) {
const formattedErrors = err.errors.reduce((acc, curr) => {
acc[curr.path[0]] = curr.message;
return acc;
}, {});
setErrors(formattedErrors);
}
};

return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-r from-blue-500 to-teal-400 p-4">
<div className="max-w-md w-full bg-white p-8 rounded-lg shadow-lg">
<div className="text-center mb-6">
<img className="mx-auto h-12 w-auto" src="/apps/PetMe-Frontend/src/assets/images/Pet Care Logo.png" alt="Logo" />
<h2 className="mt-4 text-3xl font-extrabold text-gray-900">
{isSignUp ? 'Create Your Account' : 'Sign In to Your Account'}
</h2>
</div>
<form className="space-y-6" onSubmit={handleSubmit}>
{isSignUp && (
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
Username
</label>
<div className="mt-1">
<input
id="username"
name="username"
type="text"
value={formData.username}
onChange={handleChange}
required
className={`appearance-none rounded-md relative block w-full px-3 py-2 border ${errors.username ? 'border-red-500' : 'border-gray-300'} placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm`}
placeholder="Username"
/>
{errors.username && <p className="text-red-500 text-xs mt-1">{errors.username}</p>}
</div>
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email address
</label>
<div className="mt-1">
<input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
autoComplete="email"
required
className={`appearance-none rounded-md relative block w-full px-3 py-2 border ${errors.email ? 'border-red-500' : 'border-gray-300'} placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm`}
placeholder="Email address"
/>
{errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
</div>
</div>

<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<div className="mt-1">
<input
id="password"
name="password"
type="password"
value={formData.password}
onChange={handleChange}
autoComplete="current-password"
required
className={`appearance-none rounded-md relative block w-full px-3 py-2 border ${errors.password ? 'border-red-500' : 'border-gray-300'} placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm`}
placeholder="Password"
/>
{errors.password && <p className="text-red-500 text-xs mt-1">{errors.password}</p>}
</div>
</div>

{isSignUp && (
<div>
<label htmlFor="confirm-password" className="block text-sm font-medium text-gray-700">
Confirm Password
</label>
<div className="mt-1">
<input
id="confirm-password"
name="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={handleChange}
required
className={`appearance-none rounded-md relative block w-full px-3 py-2 border ${errors.confirmPassword ? 'border-red-500' : 'border-gray-300'} placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm`}
placeholder="Confirm Password"
/>
{errors.confirmPassword && <p className="text-red-500 text-xs mt-1">{errors.confirmPassword}</p>}
</div>
</div>
)}

{!isSignUp && (
<div className="flex items-center justify-between">
<div className="flex items-center">
<input
id="remember-me"
name="remember-me"
type="checkbox"
className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
/>
<label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900">
Remember me
</label>
</div>

<div className="text-sm">
<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">
Forgot your password?
</a>
</div>
</div>
)}

<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{isSignUp ? 'Sign Up' : 'Sign In'}
</button>
</div>
</form>

{isSignUp && (
<div className="mt-6">
<div className="relative mb-4">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Or sign up with</span>
</div>
</div>
<div className="flex justify-center space-x-4">
<button
type="button"
className="w-full inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
Sign Up with Google
</button>
<button
type="button"
className="w-full inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Sign Up with Facebook
</button>
</div>
</div>
)}

<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
{isSignUp ? 'Already have an account?' : "Don't have an account?"}{' '}
<button onClick={toggleForm} className="font-medium text-indigo-600 hover:text-indigo-500">
{isSignUp ? 'Sign In' : 'Sign Up'}
</button>
</p>
</div>
</div>
</div>
);
};

export default SignIn;
16 changes: 16 additions & 0 deletions apps/PetMe-Frontend/src/helpers/validation.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { z } from 'zod';

export const signInSchema = z.object({
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters long"),
});

export const signUpSchema = z.object({
username: z.string().min(3, "Username must be at least 3 characters long"),
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters long"),
confirmPassword: z.string().min(6, "Password must be at least 6 characters long"),
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});
24 changes: 13 additions & 11 deletions apps/PetMe-Frontend/tailwind.config.cjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/* stylelint-disable */
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
/* eslint-disable no-undef */
export const purge = ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'];
export const darkMode = false;
export const theme = {
extend: {},
};
export const variants = {
extend: {},
};
export const plugins = [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
];
Loading