email verfication after account creation, password component, added password special characters

This commit is contained in:
jackiettran
2025-10-10 14:36:09 -04:00
parent 513347e8b7
commit 0a9b875a9d
19 changed files with 1305 additions and 86 deletions

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import { useAuth } from "../contexts/AuthContext";
import PasswordStrengthMeter from "./PasswordStrengthMeter";
import PasswordInput from "./PasswordInput";
interface AuthModalProps {
show: boolean;
@@ -154,19 +155,18 @@ const AuthModal: React.FC<AuthModalProps> = ({
/>
</div>
<div className="mb-3">
<label className="form-label">Password</label>
<input
type="password"
className="form-control"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
{mode === "signup" && (
<PasswordInput
id="password"
label="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
{mode === "signup" && (
<div style={{ marginTop: '-0.75rem', marginBottom: '1rem' }}>
<PasswordStrengthMeter password={password} />
)}
</div>
</div>
)}
<button
type="submit"

View File

@@ -0,0 +1,58 @@
import React, { useState } from 'react';
interface PasswordInputProps {
id: string;
name?: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
required?: boolean;
placeholder?: string;
className?: string;
label?: string;
}
const PasswordInput: React.FC<PasswordInputProps> = ({
id,
name,
value,
onChange,
required = false,
placeholder,
className = 'form-control',
label
}) => {
const [showPassword, setShowPassword] = useState(false);
return (
<div className="mb-3">
{label && (
<label htmlFor={id} className="form-label">
{label}
</label>
)}
<div className="position-relative">
<input
type={showPassword ? 'text' : 'password'}
className={className}
id={id}
name={name || id}
value={value}
onChange={onChange}
required={required}
placeholder={placeholder}
/>
<button
type="button"
className="btn btn-link position-absolute end-0 top-50 translate-middle-y text-secondary p-0 pe-2"
onClick={() => setShowPassword(!showPassword)}
style={{ zIndex: 10, textDecoration: 'none' }}
tabIndex={-1}
>
<i className={`bi ${showPassword ? 'bi-eye' : 'bi-eye-slash'}`}></i>
</button>
</div>
</div>
);
};
export default PasswordInput;

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
interface PasswordStrengthMeterProps {
password: string;
@@ -13,60 +13,71 @@ interface PasswordRequirement {
const PasswordStrengthMeter: React.FC<PasswordStrengthMeterProps> = ({
password,
showRequirements = true
showRequirements = true,
}) => {
const requirements: PasswordRequirement[] = [
{
regex: /.{8,}/,
text: "At least 8 characters",
met: /.{8,}/.test(password)
met: /.{8,}/.test(password),
},
{
regex: /[a-z]/,
text: "One lowercase letter",
met: /[a-z]/.test(password)
met: /[a-z]/.test(password),
},
{
regex: /[A-Z]/,
text: "One uppercase letter",
met: /[A-Z]/.test(password)
text: "One uppercase letter",
met: /[A-Z]/.test(password),
},
{
regex: /\d/,
text: "One number",
met: /\d/.test(password)
met: /\d/.test(password),
},
{
regex: /[@$!%*?&]/,
text: "One special character (@$!%*?&)",
met: /[@$!%*?&]/.test(password)
}
regex: /[-@$!%*?&#^]/,
text: "One special character (-@$!%*?&#^)",
met: /[-@$!%*?&#^]/.test(password),
},
];
const getPasswordStrength = (): { score: number; label: string; color: string } => {
if (!password) return { score: 0, label: '', color: '' };
const getPasswordStrength = (): {
score: number;
label: string;
color: string;
} => {
if (!password) return { score: 0, label: "", color: "" };
const metRequirements = requirements.filter((req) => req.met).length;
const hasCommonPassword = [
"password",
"123456",
"123456789",
"qwerty",
"abc123",
"password123",
].includes(password.toLowerCase());
const metRequirements = requirements.filter(req => req.met).length;
const hasCommonPassword = ['password', '123456', '123456789', 'qwerty', 'abc123', 'password123'].includes(password.toLowerCase());
if (hasCommonPassword) {
return { score: 0, label: 'Too Common', color: 'danger' };
return { score: 0, label: "Too Common", color: "danger" };
}
switch (metRequirements) {
case 0:
case 1:
return { score: 1, label: 'Very Weak', color: 'danger' };
return { score: 1, label: "Very Weak", color: "danger" };
case 2:
return { score: 2, label: 'Weak', color: 'warning' };
return { score: 2, label: "Weak", color: "warning" };
case 3:
return { score: 3, label: 'Fair', color: 'info' };
return { score: 3, label: "Fair", color: "info" };
case 4:
return { score: 4, label: 'Good', color: 'primary' };
return { score: 4, label: "Good", color: "primary" };
case 5:
return { score: 5, label: 'Strong', color: 'success' };
return { score: 5, label: "Strong", color: "success" };
default:
return { score: 0, label: '', color: '' };
return { score: 0, label: "", color: "" };
}
};
@@ -87,7 +98,7 @@ const PasswordStrengthMeter: React.FC<PasswordStrengthMeterProps> = ({
</small>
)}
</div>
<div className="progress" style={{ height: '4px' }}>
<div className="progress" style={{ height: "4px" }}>
<div
className={`progress-bar bg-${strength.color}`}
role="progressbar"
@@ -102,17 +113,23 @@ const PasswordStrengthMeter: React.FC<PasswordStrengthMeterProps> = ({
{/* Requirements List */}
{showRequirements && (
<div className="password-requirements">
<small className="text-muted d-block mb-1">Password must contain:</small>
<ul className="list-unstyled mb-0" style={{ fontSize: '0.75rem' }}>
<small className="text-muted d-block mb-1">
Password must contain:
</small>
<ul className="list-unstyled mb-0" style={{ fontSize: "0.75rem" }}>
{requirements.map((requirement, index) => (
<li key={index} className="d-flex align-items-center mb-1">
<i
className={`bi ${
requirement.met ? 'bi-check-circle-fill text-success' : 'bi-circle text-muted'
requirement.met
? "bi-check-circle-fill text-success"
: "bi-circle text-muted"
} me-2`}
style={{ fontSize: '0.75rem' }}
style={{ fontSize: "0.75rem" }}
/>
<span className={requirement.met ? 'text-success' : 'text-muted'}>
<span
className={requirement.met ? "text-success" : "text-muted"}
>
{requirement.text}
</span>
</li>
@@ -124,4 +141,4 @@ const PasswordStrengthMeter: React.FC<PasswordStrengthMeterProps> = ({
);
};
export default PasswordStrengthMeter;
export default PasswordStrengthMeter;