Skip to content

Commit 7df1edd

Browse files
authored
Merge pull request #19 from cherryontech/feat/signup-login-pages
Feat/signup login pages
2 parents 1e96287 + eb436a0 commit 7df1edd

7 files changed

Lines changed: 428 additions & 10 deletions

File tree

src/App.css

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
button:focus-visible {
1+
button:not(.toggle-btn):focus-visible {
22
outline: solid 4px var(--color-persianblue);
33
outline-offset: 8px;
44
}
5-
button:hover {
5+
button:not(.toggle-btn):hover {
66
background-color: var(--color-zinc);
77
color: white;
8+
cursor: pointer;
89
}
9-
button:active {
10+
button:not(.toggle-btn):active {
1011
background-color: var(--color-persianblue);
1112
color: white;
12-
}
13+
}

src/components/Baseinput.jsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
function Baseinput({
2+
id,
3+
type,
4+
name,
5+
label = '',
6+
required = false,
7+
placeholder = '',
8+
disabled = false,
9+
labelClassName = '',
10+
ariaInvalid,
11+
inputClassName = '',
12+
onChange,
13+
value = '',
14+
ariaDescribedBy,
15+
}) {
16+
return (
17+
<>
18+
<label
19+
htmlFor={name}
20+
className={`block font-medium text-stone-900 text-base mb-[12px] ${labelClassName}`}
21+
>
22+
{label}
23+
</label>
24+
<input
25+
id={id}
26+
value={value}
27+
onChange={onChange}
28+
type={type}
29+
name={name}
30+
required={required || undefined}
31+
placeholder={placeholder}
32+
className={`rounded-[5px]
33+
block w-full h-11 px-4 py-2
34+
bg-white text-black
35+
${inputClassName}
36+
disabled:bg-gray-100 disabled:text-gray-500`}
37+
aria-invalid={ariaInvalid}
38+
aria-describedby={ariaDescribedBy}
39+
disabled={disabled}
40+
/>
41+
</>
42+
);
43+
}
44+
45+
export default Baseinput;

src/components/Emailinput.jsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import Baseinput from './Baseinput';
2+
import { useState } from 'react';
3+
4+
function Emailinput({
5+
formValue = {},
6+
setFormValue,
7+
fieldName,
8+
label,
9+
required = false,
10+
setisValidEmail,
11+
isValidEmail,
12+
}) {
13+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14+
const [error, setError] = useState('');
15+
let inputClassName;
16+
const handleChange = (e) => {
17+
const newEmail = e.target.value;
18+
if (!newEmail && required) {
19+
setError('Email is required');
20+
setisValidEmail(false);
21+
} else if (emailRegex.test(newEmail) === false) {
22+
setError('Please enter a valid email address.');
23+
setisValidEmail(false);
24+
} else {
25+
setError('');
26+
setisValidEmail(true);
27+
}
28+
console.log('Error', error);
29+
setFormValue(fieldName, newEmail);
30+
};
31+
if (error) {
32+
inputClassName =
33+
'border-sangria ring-2 ring-sangria focus:ring-sangria focus:outline-none';
34+
} else if (isValidEmail) {
35+
inputClassName =
36+
'border-olivegreen ring-2 ring-olivegreen focus:ring-olivegreen focus:outline-none';
37+
} else {
38+
inputClassName =
39+
'border-zinc-300 ring-2 ring-zinc-300 focus:ring-persianblue focus:outline-none';
40+
}
41+
42+
return (
43+
<>
44+
<Baseinput
45+
id={fieldName}
46+
value={formValue[fieldName] || ''}
47+
onChange={handleChange}
48+
type="email"
49+
name={fieldName}
50+
required={required}
51+
label={label}
52+
inputClassName={inputClassName}
53+
ariaInvalid={isValidEmail === false ? true : undefined}
54+
ariaDescribedBy={error ? `${fieldName}-error` : undefined}
55+
/>
56+
{error && (
57+
<div
58+
id={`${fieldName}-error`}
59+
role="alert"
60+
className="text-sm mt-[8px]"
61+
>
62+
<p>{error}</p>
63+
</div>
64+
)}
65+
</>
66+
);
67+
}
68+
69+
export default Emailinput;

src/components/PasswordInput.jsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import Baseinput from './Baseinput';
2+
import { FaEye, FaEyeSlash } from 'react-icons/fa6';
3+
import { useState } from 'react';
4+
5+
function Passwordinput({
6+
formValue = {},
7+
setFormValue,
8+
fieldName,
9+
label,
10+
required = false,
11+
setisValidPassword,
12+
isValidPassword,
13+
pageType,
14+
}) {
15+
const [showPassword, setShowPassword] = useState(false);
16+
17+
const upperCaseRegex = /[A-Z]/;
18+
const lowerCaseRegex = /[a-z]/;
19+
const numberRegex = /[0-9]/;
20+
const specialCharactersRegex = /[!@#$%^&*()]/;
21+
const commonWords = ['password', '1234', 'qwerty', 'letmein', 'abc123'];
22+
const [error, setError] = useState('');
23+
let labelDynamicStyles = '';
24+
let inputClassName;
25+
let errorMessage = '';
26+
let valid = true;
27+
const togglePassword = () => setShowPassword((prev) => !prev);
28+
const handleChange = (e) => {
29+
const newPassword = e.target.value;
30+
31+
if (!newPassword && required) {
32+
errorMessage += 'Password is required <br>';
33+
valid = false;
34+
} else {
35+
setError('');
36+
}
37+
38+
if (newPassword && pageType === 'signup') {
39+
if (newPassword.length < 8) {
40+
errorMessage += 'Have at least 8 characters <br>';
41+
valid = false;
42+
}
43+
if (!upperCaseRegex.test(newPassword)) {
44+
errorMessage += 'One uppercase letter <br>';
45+
valid = false;
46+
}
47+
if (!lowerCaseRegex.test(newPassword)) {
48+
errorMessage += 'One lowercase letter<br>';
49+
valid = false;
50+
}
51+
if (
52+
!numberRegex.test(newPassword) &&
53+
!specialCharactersRegex.test(newPassword)
54+
) {
55+
errorMessage += 'One number or symbol ! @ # $ % ^ & *( )<br>';
56+
valid = false;
57+
}
58+
const isCommonPhrase = commonWords.some((w) =>
59+
newPassword.toLowerCase().includes(w)
60+
);
61+
62+
if (isCommonPhrase === true) {
63+
errorMessage += 'Avoid common phrases <br>';
64+
valid = false;
65+
}
66+
}
67+
setError(errorMessage);
68+
setisValidPassword(valid);
69+
setFormValue(fieldName, newPassword);
70+
};
71+
if (error) {
72+
inputClassName =
73+
'border-sangria ring-2 ring-sangria focus:ring-sangria focus:outline-none';
74+
} else if (isValidPassword) {
75+
inputClassName =
76+
'border-olivegreen ring-2 ring-olivegreen focus:ring-olivegreen focus:outline-none';
77+
} else {
78+
inputClassName =
79+
'border-zinc-300 ring-2 ring-zinc-300 focus:ring-persianblue focus:outline-none';
80+
}
81+
82+
return (
83+
<>
84+
<Baseinput
85+
id={fieldName}
86+
value={formValue[fieldName] || ''}
87+
onChange={handleChange}
88+
type={showPassword ? 'text' : 'password'}
89+
name={fieldName}
90+
required={required}
91+
label={label}
92+
inputClassName={inputClassName}
93+
labelClassName={labelDynamicStyles}
94+
ariaInvalid={isValidPassword === false ? true : undefined}
95+
ariaDescribedBy={error ? `${fieldName}-error` : undefined}
96+
/>
97+
<button
98+
type="button"
99+
onClick={togglePassword}
100+
aria-label={showPassword ? 'Hide password' : 'Show password'}
101+
aria-pressed={showPassword}
102+
className="toggle-btn absolute right-3 top-[46px] text-2xl text-black
103+
focus:outline-none focus:ring-2 focus:ring-persianblue focus:ring-offset-1 cursor-pointer"
104+
>
105+
{showPassword ? <FaEyeSlash /> : <FaEye />}
106+
</button>
107+
{error && (
108+
<div
109+
id={`${fieldName}-error`}
110+
role="alert"
111+
className="text-sm mt-[8px]"
112+
>
113+
<p>Password must:</p>
114+
{error.split('<br>').map((message, index) => (
115+
<p key={index}>{message}</p>
116+
))}
117+
</div>
118+
)}
119+
</>
120+
);
121+
}
122+
123+
export default Passwordinput;

src/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
--color-nyanza: #e1ffe4;
1010
--color-celeste: #bbfdff;
1111
--color-zinc: #888888;
12+
--color-olivegreen: #38791f;
1213

1314
--font-playfair: 'Playfair Display', serif;
1415
--font-poppins: 'Poppins', sans-serif;

src/pages/Login.jsx

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,101 @@
1+
import Emailinput from '../components/Emailinput';
2+
import Passwordinput from '../components/PasswordInput';
3+
import Button from '../components/Button';
4+
import { useState } from 'react';
5+
import { Link, useNavigate } from 'react-router-dom';
6+
17
function Login() {
8+
const navigate = useNavigate();
9+
const [formValues, setFormValues] = useState({});
10+
const [isValidEmail, setisValidEmail] = useState(false);
11+
const [isValidPassword, setisValidPassword] = useState(false);
12+
const [formSubmitMessage, setFormSubmitMessage] = useState('');
13+
const setFormValue = (fieldName, value) => {
14+
setFormValues((prevValue) => ({ ...prevValue, [fieldName]: value }));
15+
};
16+
const handleSubmit = (e) => {
17+
e.preventDefault();
18+
if (!isValidEmail || !isValidPassword) {
19+
setFormSubmitMessage('Fill form properly');
20+
} else {
21+
const loginFormData = new FormData();
22+
for (const key in formValues) {
23+
loginFormData.append(key, formValues[key]);
24+
}
25+
navigate('/dashboard');
26+
console.log('Login Submission Data:', loginFormData);
27+
}
28+
};
229
return (
3-
<div className="min-h-screen bg-emerald-100 flex items-center justify-center">
4-
<h1 className="text-4xl font-bold text-lime-500">Login Page! 🎉</h1>
5-
</div>
30+
<section className="flex justify-center items-center min-h-screen md:h-screen bg-gradient-to-b from-nyanza to-celeste md:bg-gradient-to-b md:from-nyanza md:via-celeste md:via-50% md:to-white md:to-50%">
31+
<form
32+
onSubmit={handleSubmit}
33+
className="flex flex-col bg-white w-full max-w-lg md:min-w-[30vw] md:h-auto mx-auto rounded-[10px] h-[68vh] shadow-[0px_4px_15px_8px_rgba(30,30,30,0.10)] p-14"
34+
noValidate
35+
>
36+
<h3 className="font-playfair text-2xl mb-[44px]">Sign In</h3>
37+
<div className="relative">
38+
<Link
39+
to=""
40+
className="underline absolute top-0 right-0 focus:outline-none focus:ring-2 focus:ring-persianblue focus:ring-offset-1"
41+
>
42+
Forgot Username
43+
</Link>
44+
<Emailinput
45+
formValue={formValues}
46+
setFormValue={setFormValue}
47+
fieldName="loginEmail"
48+
label="Email Address"
49+
setisValidEmail={setisValidEmail}
50+
isValidEmail={isValidEmail}
51+
pageType="login"
52+
/>
53+
</div>
54+
<div className="relative mt-[24px]">
55+
<Link
56+
to=""
57+
className="underline absolute top-0 right-0 focus:outline-none focus:ring-2 focus:ring-persianblue focus:ring-offset-1"
58+
>
59+
Reset Password
60+
</Link>
61+
<Passwordinput
62+
formValue={formValues}
63+
setFormValue={setFormValue}
64+
fieldName="loginPassword"
65+
label="Password"
66+
setisValidPassword={setisValidPassword}
67+
isValidPassword={isValidPassword}
68+
/>
69+
</div>
70+
<div className="block mt-[16px] mb-[44px]">
71+
<input
72+
type="checkbox"
73+
name="remember"
74+
id="remember"
75+
aria-disabled="true"
76+
className="mr-[8px] focus:outline-none focus:ring-2 focus:ring-persianblue focus:ring-offset-1"
77+
/>
78+
<label htmlFor="remember">Remember me</label>
79+
</div>
80+
{formSubmitMessage && (
81+
<p className="text-sm mb-[18px]" role="alert">
82+
{formSubmitMessage}
83+
</p>
84+
)}
85+
<div className="flex justify-center">
86+
<Button size="lg" color="primary" label="Sign in" />
87+
</div>
88+
<div className="mt-[12px]">
89+
New user?{' '}
90+
<Link
91+
to="/signup"
92+
className="text-persianblue focus:outline-none focus:ring-2 focus:ring-persianblue focus:ring-offset-1"
93+
>
94+
Create an Account
95+
</Link>
96+
</div>
97+
</form>
98+
</section>
699
);
7100
}
8101

0 commit comments

Comments
 (0)