Skip to content
Open
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
3 changes: 3 additions & 0 deletions client/src/app/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { Routes } from 'react-router-dom';
import getRoutesV1 from '../routes/auth-routes/v1';

export const LoginLazyComponent = lazy(() => import('../../modules/auth/v1'));
export const ResetPasswordLazyComponent = lazy(
() => import('../../modules/auth/v1/reset-password')
);
export const HomePageLazyComponent = lazy(
() => import('../../modules/home/v1')
);
Expand Down
18 changes: 17 additions & 1 deletion client/src/app/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,27 @@ import { Suspense } from 'react';
import { Route, Routes } from 'react-router-dom';
import Loader from '../../shared/components/molecules/loader';
import { LOADING } from './constants';
import { LoginLazyComponent } from '../components';
import { LoginLazyComponent, ResetPasswordLazyComponent } from '../components';

export function AppUnProtectedRoutes() {
return (
<Routes>
<Route
path="/"
element={
<Suspense fallback={<Loader size={32} secondary={LOADING} />}>
<LoginLazyComponent />
</Suspense>
}
/>
<Route
path="/reset-password"
element={
<Suspense fallback={<Loader size={32} secondary={LOADING} />}>
<ResetPasswordLazyComponent />
</Suspense>
}
/>
<Route
path="*"
element={
Expand Down
24 changes: 24 additions & 0 deletions client/src/modules/auth/v1/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import A2ZButton from '../../../shared/components/atoms/button';
import { useUserAuthForm } from './hooks';
import { useState } from 'react';
import A2ZTypography from '../../../shared/components/atoms/typography';
import { useNavigate } from 'react-router-dom';

const StyledSection = styled('section')(({ theme }) => ({
paddingTop: theme.spacing(4),
Expand Down Expand Up @@ -44,6 +45,7 @@ const StyledFooter = styled('p')(({ theme }) => ({
const UserAuthForm = () => {
const [formType, setFormType] = useState<string>('login');
const { loading, handleSubmit } = useUserAuthForm({ type: formType });
const navigate = useNavigate();

return (
<StyledSection>
Expand Down Expand Up @@ -90,6 +92,28 @@ const UserAuthForm = () => {
icon={<PasswordIcon />}
/>

{/* Forgot Password Link - Only shown on login */}
{formType === 'login' && (
<Box sx={{ width: '100%', textAlign: 'right', mt: 0.5 }}>
<A2ZTypography
text="Forgot Password?"
component="span"
props={{
onClick: () => navigate('/reset-password'),
sx: {
fontSize: '0.9rem',
textDecoration: 'underline',
color: 'text.secondary',
cursor: 'pointer',
'&:hover': {
opacity: 0.8,
},
},
}}
/>
</Box>
)}

<A2ZButton
type="submit"
sx={{
Expand Down
197 changes: 197 additions & 0 deletions client/src/modules/auth/v1/reset-password/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// client/src/modules/auth/v1/reset-password/index.tsx

import { Box, styled, Typography } from '@mui/material';
import InputBox from '../../../../shared/components/atoms/input-box';
import EmailIcon from '@mui/icons-material/Email';
import A2ZButton from '../../../../shared/components/atoms/button';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import A2ZTypography from '../../../../shared/components/atoms/typography';

const StyledSection = styled('section')(({ theme }) => ({
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
paddingLeft: '5vw',
paddingRight: '5vw',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
}));

const StyledForm = styled('form')(() => ({
width: '80%',
maxWidth: 400,
}));

const StyledTitle = styled(Typography)(({ theme }) => ({
fontSize: '2.5rem',
fontFamily: 'Gelasio, serif',
textTransform: 'capitalize',
textAlign: 'center',
marginBottom: theme.spacing(6),
}));

const StyledFooter = styled('p')(({ theme }) => ({
marginTop: theme.spacing(6),
color: theme.palette.text.secondary,
fontSize: '1.125rem',
textAlign: 'center',
}));

const ResetPassword = () => {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState(false);
const navigate = useNavigate();

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
setError('');
setSuccess(false);

try {
const response = await fetch('http://localhost:8000/api/auth/forgot-password', {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write the api call inside /infra/rest folder - according to auth directory.

Before writing, please take a look on implementation / use case of api call function.

method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});

const data = await response.json();

if (response.ok) {
setSuccess(true);
setEmail(''); // Clear email after success
} else {
setError(data.message || 'Failed to send reset link. Please try again.');
}
} catch (err) {
setError('Something went wrong. Please try again later.');
console.error('Error:', err);
} finally {
setLoading(false);
}
};

const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
setError(''); // Clear error when user types
setSuccess(false); // Clear success when user types
};

return (
<StyledSection>
<StyledForm onSubmit={handleSubmit}>
<StyledTitle>Reset Password</StyledTitle>

<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 1,
}}
>
<Typography
sx={{
mb: 2,
textAlign: 'center',
color: 'text.secondary',
}}
>
Enter your email address to receive a link to reset your password.
</Typography>

{success && (
<Typography
sx={{
color: 'success.main',
fontSize: '0.9rem',
mb: 1,
width: '100%',
textAlign: 'center',
backgroundColor: 'success.light',
padding: '8px 12px',
borderRadius: '4px',
}}
>
Password reset link sent successfully! Check your email.
</Typography>
)}

{error && (
<Typography
sx={{
color: 'error.main',
fontSize: '0.9rem',
mb: 1,
width: '100%',
textAlign: 'center',
backgroundColor: 'error.light',
padding: '8px 12px',
borderRadius: '4px',
}}
>
{error}
</Typography>
)}

<InputBox
id="reset-email"
name="email"
type="email"
placeholder="Email"
fullWidth
icon={<EmailIcon />}
slotProps={{
input: {
value: email,
onChange: handleEmailChange,
},
}}
/>

<A2ZButton
type="submit"
sx={{
mt: 2,
width: '100%',
}}
loading={loading}
disabled={!email || loading}
>
Send Reset Link
</A2ZButton>
</Box>

<StyledFooter>
Remember your password?{' '}
<A2ZTypography
text="Sign in here"
component="span"
props={{
onClick: () => navigate('/'),
sx: {
fontSize: '1.125rem',
marginLeft: 1,
textDecoration: 'underline',
color: 'inherit',
cursor: 'pointer',
'&:hover': {
opacity: 0.8,
},
},
}}
/>
</StyledFooter>
</StyledForm>
</StyledSection>
);
};

export default ResetPassword;
7 changes: 7 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ CLOUDINARY_API_SECRET=
# Admin Credentials (for sending emails)
ADMIN_EMAIL=
RESEND_API_KEY=

# Email Configuration (for password reset)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=youremailid
EMAIL_PASSWORD=your-16-char-app-password-here
FRONTEND_URL=http://localhost:5173
3 changes: 3 additions & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
""
"# Environment variables"
".env"
8 changes: 4 additions & 4 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"morgan": "^1.10.1",
"multer": "^1.4.5-lts.1",
"nanoid": "^5.1.2",
"nodemailer": "^7.0.5",
"nodemailer": "^7.0.13",
"rate-limiter-flexible": "^7.3.0",
"resend": "^6.1.2",
"sanitize-html": "^2.17.0",
Expand Down
Loading
Loading