diff --git a/backend/routes/user.py b/backend/routes/user.py
index 4ee7bcb..4dc0fbc 100755
--- a/backend/routes/user.py
+++ b/backend/routes/user.py
@@ -20,13 +20,7 @@ def get_profile():
user = User.query.get(user_id)
if not user:
return jsonify({"msg": "User not found"}), 404
- return jsonify({
- "id": user.id,
- "username": user.username,
- "email": user.email,
- "created_at": user.created_at.isoformat(),
- "is_verified": user.is_verified
- }), 200
+ return jsonify(user.to_dict()), 200
# UPDATE USER PROFILE
@user_bp.route('/profile', methods=['PUT'])
@@ -99,13 +93,7 @@ def get_user_profile(username):
user = User.query.filter_by(username=username).first()
if not user:
return jsonify({"msg": "User not found"}), 404
- return jsonify({
- "id": user.id,
- "username": user.username,
- "email": user.email,
- "created_at": user.created_at.isoformat(),
- "is_verified": user.is_verified
- }), 200
+ return jsonify(user.to_dict()), 200
# GET ALL USERS (with pagination and search)
@user_bp.route('/users', methods=['GET'])
diff --git a/frontend/src/components/features/auth/components/RegisterModal.tsx b/frontend/src/components/features/auth/components/RegisterModal.tsx
index 5a79354..bc84d1c 100644
--- a/frontend/src/components/features/auth/components/RegisterModal.tsx
+++ b/frontend/src/components/features/auth/components/RegisterModal.tsx
@@ -10,6 +10,7 @@ import { colors, shadows, transitions } from '../../../../theme/colors'
import { StyledModal } from '../../../common/StyledModal'
import { PrimaryButton, SecondaryButton } from '../../../common/StyledButton'
import StyledAlert from '../../../common/StyledAlert'
+import PasswordStrengthMeter from '../../../common/PasswordStrengthMeter'
import { useLocalStorage } from '../../../../hooks/useLocalStorage'
import logger from '../../../../utils/logger'
import { getErrorMessage, isErrorStatus } from '../../../../utils/errors'
@@ -75,67 +76,6 @@ const SwitchLink = styled.button`
}
`
-const PasswordStrengthMeter = styled.div`
- margin-top: 0.5rem;
- margin-bottom: 1rem;
-`
-
-const StrengthBar = styled.div<{ strength: number }>`
- height: 4px;
- background: ${props => {
- if (props.strength === 0) return 'rgba(255, 255, 255, 0.1)';
- if (props.strength === 1) return colors.danger; // Weak - red
- if (props.strength === 2) return '#ffa500'; // Fair - orange
- if (props.strength === 3) return colors.success; // Good - green
- if (props.strength === 4) return colors.success; // Strong - green
- return 'rgba(255, 255, 255, 0.1)';
- }};
- width: ${props => (props.strength / 4) * 100}%;
- transition: ${transitions.default};
- border-radius: 2px;
-`
-
-const StrengthBarContainer = styled.div`
- width: 100%;
- height: 4px;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 2px;
- overflow: hidden;
-`
-
-const StrengthText = styled.div<{ strength: number }>`
- font-size: 0.875rem;
- margin-top: 0.25rem;
- color: ${props => {
- if (props.strength === 0) return colors.text.muted;
- if (props.strength === 1) return colors.danger;
- if (props.strength === 2) return '#ffa500';
- if (props.strength === 3) return colors.success;
- if (props.strength === 4) return colors.success;
- return colors.text.muted;
- }};
-`
-
-const PasswordRequirements = styled.ul`
- list-style: none;
- padding: 0;
- margin: 0.5rem 0 0 0;
- font-size: 0.75rem;
-
- li {
- color: ${colors.text.muted};
- margin-bottom: 0.25rem;
-
- &.met {
- color: ${colors.success};
- }
-
- &::before {
- content: '• ';
- margin-right: 0.25rem;
- }
- }
-`
const RegisterModal = () => {
const [searchParams, setSearchParams] = useSearchParams()
@@ -163,46 +103,6 @@ const RegisterModal = () => {
const show = searchParams.get('register') === 'true'
- // Calculate password strength (matches cpta_app logic)
- const calculatePasswordStrength = (pwd: string) => {
- const requirements = {
- minLength8: pwd.length >= 8,
- minLength12: pwd.length >= 12,
- uppercase: /[A-Z]/.test(pwd),
- lowercase: /[a-z]/.test(pwd),
- number: /\d/.test(pwd),
- special: /[!@#$%^&*(),.?":{}|<>]/.test(pwd),
- }
-
- // If less than 8 chars, weak
- if (pwd.length < 8) {
- return { strength: 1, label: 'Weak', requirements, isValid: false }
- }
-
- // If 12+ characters, automatically strong
- if (requirements.minLength12) {
- return { strength: 4, label: 'Strong', requirements, isValid: true }
- }
-
- // Between 8-11 characters - check complexity
- const complexityCount = [
- requirements.uppercase,
- requirements.lowercase,
- requirements.number,
- requirements.special
- ].filter(Boolean).length
-
- if (complexityCount === 4) {
- return { strength: 3, label: 'Good', requirements, isValid: true }
- } else if (complexityCount >= 2) {
- return { strength: 2, label: 'Fair', requirements, isValid: false }
- } else {
- return { strength: 1, label: 'Weak', requirements, isValid: false }
- }
- }
-
- const { strength: passwordStrength, label, requirements } = calculatePasswordStrength(password)
-
// Countdown timer for rate limiting
useEffect(() => {
if (!rateLimitedUntil) {
@@ -468,32 +368,7 @@ const RegisterModal = () => {
- {password && (
-
-
-
-
-
- Password Strength: {label}
-
- {passwordStrength < 3 && (
-
-
- 12+ characters (recommended)
-
- {!requirements.minLength12 && (
- <>
- At least 8 characters
- One uppercase letter
- One lowercase letter
- One number
- One special character (!@#$%^&*)
- >
- )}
-
- )}
-
- )}
+
diff --git a/frontend/src/components/features/auth/pages/ResetPasswordPage.tsx b/frontend/src/components/features/auth/pages/ResetPasswordPage.tsx
index 4cf6f52..5e2a5aa 100644
--- a/frontend/src/components/features/auth/pages/ResetPasswordPage.tsx
+++ b/frontend/src/components/features/auth/pages/ResetPasswordPage.tsx
@@ -1,39 +1,30 @@
-import { useState } from 'react'
-import { useSearchParams, useNavigate, Link } from 'react-router-dom'
+import React, { useState } from 'react'
import { Container, Form, Card } from 'react-bootstrap'
+import { Link, useSearchParams, useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { authAPI } from '../../../../services/api'
-import { colors, gradients, shadows } from '../../../../theme/colors'
import logger from '../../../../utils/logger'
import { getErrorMessage } from '../../../../utils/errors'
import StyledAlert from '../../../common/StyledAlert'
import PasswordStrengthMeter from '../../../common/PasswordStrengthMeter'
+import { SubmitButton } from '../../../common/StyledButton'
import { PasswordInput } from '../../../common/PasswordInput'
-import { PrimaryButton } from '../../../common/StyledButton'
-import Footer from '../../../layout/Footer'
+import { colors, gradients } from '../../../../theme/colors'
const PageWrapper = styled.div`
min-height: 100vh;
background: linear-gradient(135deg, ${colors.backgroundDark} 0%, ${colors.background} 100%);
display: flex;
- flex-direction: column;
- padding-top: 70px; /* Account for navbar */
-`
-
-const ContentWrapper = styled.div`
- flex: 1;
- display: flex;
align-items: center;
padding: 40px 0;
`
const StyledCard = styled(Card)`
- background: ${colors.backgroundAlt};
- border: 1px solid ${colors.borderLight};
- box-shadow: ${shadows.large};
+ background: ${colors.background};
+ border: 1px solid ${colors.primary};
+ box-shadow: 0 8px 32px rgba(40, 167, 69, 0.15);
max-width: 500px;
margin: 0 auto;
- border-radius: 12px;
.card-header {
background: ${gradients.primary};
@@ -41,20 +32,17 @@ const StyledCard = styled(Card)`
color: ${colors.text.primary};
padding: 1.5rem;
text-align: center;
- border-radius: 12px 12px 0 0;
h2 {
margin: 0;
font-size: 1.75rem;
font-weight: 600;
- color: #000;
}
.subtitle {
margin-top: 0.5rem;
font-size: 0.95rem;
opacity: 0.9;
- color: #000;
}
}
@@ -62,6 +50,31 @@ const StyledCard = styled(Card)`
padding: 2rem;
}
+ .form-label {
+ color: ${colors.text.primary};
+ font-weight: 500;
+ margin-bottom: 0.5rem;
+ }
+
+ .form-control {
+ background: ${colors.backgroundLight};
+ border: 1px solid ${colors.borderInput};
+ color: ${colors.text.primary};
+ padding: 0.75rem;
+ border-radius: 8px;
+
+ &:focus {
+ background: ${colors.backgroundLight};
+ border-color: ${colors.primary};
+ box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+ color: ${colors.text.primary};
+ }
+
+ &::placeholder {
+ color: ${colors.text.muted};
+ }
+ }
+
.back-link {
color: ${colors.primary};
text-decoration: none;
@@ -76,9 +89,15 @@ const StyledCard = styled(Card)`
text-decoration: underline;
}
}
+
+ .password-requirements {
+ color: ${colors.text.muted};
+ font-size: 0.85rem;
+ margin-top: 0.5rem;
+ }
`
-const ResetPasswordPage = () => {
+const ResetPasswordPage: React.FC = () => {
const [searchParams] = useSearchParams()
const navigate = useNavigate()
const token = searchParams.get('token')
@@ -92,13 +111,6 @@ const ResetPasswordPage = () => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
- setSuccess(false)
-
- // Validate token exists
- if (!token) {
- setError('Invalid or missing reset token')
- return
- }
// Validate passwords match
if (password !== confirmPassword) {
@@ -108,7 +120,12 @@ const ResetPasswordPage = () => {
// Validate password length
if (password.length < 8) {
- setError('Password must be at least 8 characters')
+ setError('Password must be at least 8 characters long')
+ return
+ }
+
+ if (!token) {
+ setError('Invalid reset link. Please request a new password reset.')
return
}
@@ -118,140 +135,129 @@ const ResetPasswordPage = () => {
await authAPI.resetPassword(token, password)
setSuccess(true)
- // Redirect to login after 2 seconds
+ // Redirect to home after 3 seconds
setTimeout(() => {
- navigate('/?login=true')
- }, 2000)
+ navigate('/')
+ }, 3000)
} catch (error: unknown) {
logger.error('Reset password error:', error)
- setError(getErrorMessage(error, 'Failed to reset password. The link may have expired.'))
+ setError(getErrorMessage(error, 'An error occurred. The link may have expired. Please request a new password reset.'))
} finally {
setLoading(false)
}
}
- // Show error if no token
if (!token) {
return (
- <>
-
-
-
-
-
- Invalid Link
-
-
-
- Invalid Reset Link
- This password reset link is invalid or has expired. Please request a new one.
-
-
-
-
- Request New Reset Link
-
-
-
-
-
-
-
-
- >
+
+
+
+
+ Invalid Link
+
+
+
+ Invalid or Expired Link
+ This password reset link is invalid or has expired.
+ Please request a new password reset.
+
+
+
+
+ Request New Reset Link
+
+
+
+
+
+
)
}
return (
- <>
-
-
-
-
-
- Reset Password
- Choose a new secure password
-
-
- {error && (
-
- Reset Failed
- {error}
-
- )}
+
+
+
+
+ Reset Password
+ Enter your new password
+
+
+ {error && (
+
+ Password Reset Failed
+ {error}
+
+ )}
- {success ? (
-
- Success!
-
- Your password has been reset successfully! Redirecting to login...
-
-
-
-
- Go to Login
-
-
-
- ) : (
-
- setPassword(e.target.value)}
- label="New Password"
- placeholder="Enter your new password"
- required
- autoComplete="new-password"
- />
-
-
+ {success ? (
+
+ Password Reset!
+
+ Your password has been successfully reset. You can now log in with your new password.
+ Redirecting you to the home page...
+
+
+
+
+ Back to Home
+
+
+
+ ) : (
+
+ setPassword(e.target.value)}
+ label="New Password"
+ placeholder="Enter new password"
+ required
+ autoComplete="new-password"
+ />
+
+
-
- setConfirmPassword(e.target.value)}
- label="Confirm New Password"
- placeholder="Confirm your new password"
- required
- autoComplete="new-password"
- />
-
+ setConfirmPassword(e.target.value)}
+ label="Confirm Password"
+ placeholder="Confirm new password"
+ required
+ autoComplete="new-password"
+ />
-
-
- {loading ? (
- <>
-
- Resetting Password...
- >
- ) : (
- <>
-
- Reset Password
- >
- )}
-
-
+
+
+ {loading ? (
+ <>
+
+ Resetting...
+ >
+ ) : (
+ <>
+
+ Reset Password
+ >
+ )}
+
+
-
-
-
- Back to Login
-
-
-
- )}
-
-
-
-
-
-
- >
+
+
+
+ Back to Home
+
+
+
+ )}
+
+
+
+
)
}