Skip to content
Draft
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
8 changes: 7 additions & 1 deletion frontend/src/common/api/reportService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,15 @@ const determineCategory = (filename: string): ReportCategory => {
* @returns Promise with the latest reports
*/
export const fetchLatestReports = async (limit = 3): Promise<MedicalReport[]> => {

const headers = await getAuthConfig();
console.log('headers', JSON.stringify(headers));
console.log('API_URL', `${API_URL}/api/reports/latest?limit=${limit}`);

try {
const response = await axios.get(`${API_URL}/api/reports/latest?limit=${limit}`, await getAuthConfig());
const response = await axios.get(`${API_URL}/api/reports/latest?limit=${limit}`, headers);
console.log('response', response.data);
console.log('response headers', response.headers);
console.log('API_URL', API_URL);
return response.data;
} catch (error) {
Expand Down
30 changes: 28 additions & 2 deletions frontend/src/common/providers/AxiosProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PropsWithChildren, useEffect, useState } from 'react';
import { InternalAxiosRequestConfig } from 'axios';

import { AxiosContext, customAxios } from './AxiosContext';
import CognitoAuthService from 'common/services/auth/cognito-auth-service';

/**
* The `AxiosProvider` React component creates, maintains, and provides
Expand All @@ -12,12 +14,36 @@ const AxiosProvider = ({ children }: PropsWithChildren): JSX.Element => {
const [isReady, setIsReady] = useState(false);

useEffect(() => {
// use axios interceptors
// Add request interceptor to include auth token
const requestInterceptor = customAxios.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
try {
// Get tokens from Cognito
const tokens = await CognitoAuthService.getUserTokens();

// If tokens exist, add Authorization header
if (tokens?.access_token) {
// Make sure headers exists
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${tokens.access_token}`;
}

return config;
} catch (error) {
console.error('Error adding auth token to request:', error);
return config;
}
},
(error) => {
return Promise.reject(error);
}
);

setIsReady(true);

return () => {
// eject axios interceptors
// Eject axios interceptors when component unmounts
customAxios.interceptors.request.eject(requestInterceptor);
};
}, []);

Expand Down
11 changes: 9 additions & 2 deletions frontend/src/common/utils/i18n/resources/en/auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"remember-me": "Remember me",
"username": "Username",
"email": "Email Address",
"email_address": "Email Address",
"first-name": "First Name",
"last-name": "Last Name",
"confirm-password": "Confirm Password",
Expand All @@ -42,18 +43,24 @@
"confirm": "Confirm",
"resend-code": "Resend Code",
"forgot-password": "Forgot Password?",
"back.to.signin": "← Back to Log in",
"password-recovery": {
"title": "Password Recovery",
"message": "Enter your email address and we'll send you instructions to reset your password.",
"title": "Forgot your Password?",
"message": "Enter your account email, and we'll send you reset instructions.",
"success": "Password reset instructions sent to your email.",
"email-sent": "We've sent a verification code to your email.",
"enter-code": "Enter the verification code and your new password below."
},
"password-reset": {
"title": "Reset Password",
"message": "Set a new password for your account.",
"success": "Password reset successful!",
"button": "Reset Password"
},
"account-not-found": {
"title": "Account not found",
"message": "An account with this email address doesn't exist."
},
"password-requirements": "Password Requirements:",
"email-verification": {
"title": "Email Verification",
Expand Down
64 changes: 62 additions & 2 deletions frontend/src/pages/Auth/ForgotPassword/ForgotPasswordPage.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,69 @@
.ls-forgot-password-page {
&__container {
&__content {
--background: var(--ion-color-background);
}

&__background {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
height: 100%;
align-items: center;
padding: 2rem 1.5rem;
}

&__logo-container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 2.5rem;
}

&__logo {
width: 3.5rem;
height: auto;
margin-right: 0.75rem;
}

&__logo-text {
color: white;
font-size: 1.75rem;
font-weight: 600;
}

&__container {
display: flex;
flex-direction: column;
width: 100%;
max-width: 30rem;
}

&__card {
background-color: #ffffff;
border-radius: 1.5rem;
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.15);
padding: 2.5rem 2rem;
width: 100%;
max-width: 30rem;
margin: 0 auto;
}

&__header {
margin-bottom: 2.5rem;

h1 {
font-size: 2rem;
font-weight: 600;
color: #333;
margin-bottom: 0.5rem;
margin-top: 0;
}

p {
font-size: 1.125rem;
color: #666;
margin: 0;
}
}

&__form {
Expand Down
23 changes: 15 additions & 8 deletions frontend/src/pages/Auth/ForgotPassword/ForgotPasswordPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { IonContent, IonPage } from '@ionic/react';
import { IonContent, IonPage, IonImg } from '@ionic/react';
import { useTranslation } from 'react-i18next';

import './ForgotPasswordPage.scss';
import { PropsWithTestId } from 'common/components/types';
import ProgressProvider from 'common/providers/ProgressProvider';
import Header from 'common/components/Header/Header';
import ForgotPasswordForm from './components/ForgotPasswordForm';
import Container from 'common/components/Content/Container';
import logo from 'assets/logo_ls.png';

/**
* Properties for the `ForgotPasswordPage` component.
Expand All @@ -24,12 +24,19 @@ const ForgotPasswordPage = ({ testid = 'page-forgot-password' }: ForgotPasswordP
return (
<IonPage className="ls-forgot-password-page" data-testid={testid}>
<ProgressProvider>
<Header title={t('password-recovery.title', { ns: 'auth' })} />

<IonContent fullscreen className="ion-padding">
<Container className="ls-forgot-password-page__container" fixed>
<ForgotPasswordForm className="ls-forgot-password-page__form" />
</Container>
<IonContent fullscreen className="ls-forgot-password-page__content">
<div className="ls-forgot-password-page__background">
<div className="ls-forgot-password-page__logo-container">
<IonImg src={logo} alt="Logo" className="ls-forgot-password-page__logo" />
<span className="ls-forgot-password-page__logo-text">{t('app.name', { ns: 'common' })}</span>
</div>

<Container className="ls-forgot-password-page__container" fixed>
<div className="ls-forgot-password-page__card">
<ForgotPasswordForm className="ls-forgot-password-page__form" />
</div>
</Container>
</div>
</IonContent>
</ProgressProvider>
</IonPage>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,149 @@
.ls-forgot-password-form {
padding: 1rem;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;

&__icon-container {
display: flex;
justify-content: center;
margin-bottom: 2rem;
}

&__icon-circle {
width: 8.5rem;
height: 8.5rem;
border-radius: 50%;
background-color: rgba(252, 123, 244, 0.05);
display: flex;
justify-content: center;
align-items: center;
position: relative;

&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid rgba(252, 123, 244, 0.2);
box-sizing: border-box;
}
}

&__icon {
width: 3rem;
height: 3rem;
color: #fc4b95;
}

&__title {
font-size: 1.25rem;
font-weight: 700;
color: #31343f;
margin-bottom: 1rem;
text-align: center;
}

&__field {
margin-bottom: 16px;
}

&__label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}

&__input {
margin-bottom: 1rem;
width: 100%;

border: 1px solid #ccc;
border-radius: 0.75rem;
--padding-start: 1rem;
--padding-end: 1rem;
--padding-top: 0.875rem;
--padding-bottom: 0.875rem;
--highlight-color: var(--ion-color-primary);
font-size: 1rem;
height: 3.25rem;

&::part(native) {
padding: 0;
}
}

&__button {
margin-top: 1rem;
margin-top: 2rem;
width: 100%;
height: 3.75rem;
font-size: 1.125rem;
font-weight: 600;
--border-radius: 0.625rem;
--box-shadow: 0 0.5rem 0.75rem rgba(67, 96, 240, 0.08);
--background: var(--ion-color-primary);
--color: #ffffff;
}

&__message {
margin-bottom: 1rem;
margin-bottom: 2.5rem;
text-align: center;
color: #31343f;
max-width: 20rem;
font-size: 0.875rem;
line-height: 1.5;
opacity: 0.8;
}

&__success {
margin-bottom: 1rem;
text-align: center;
}

&__error {
margin-bottom: 1rem;
width: 100%;
}

&__custom-error {
margin-bottom: 1rem;
width: 100%;
border-radius: 0.5rem;
background-color: #f9edf0;
padding: 1rem;
}

&__error-title {
font-weight: 700;
font-size: 0.875rem;
color: #b01b3f;
margin-bottom: 0.25rem;
}

&__error-message {
font-size: 0.875rem;
color: #2d2d2d;
}

&__back-link {
margin-top: 2rem;
text-align: center;
cursor: pointer;
font-size: 0.875rem;

ion-text {
color: #666666;

.login-text {
color: #4360f0;
font-weight: 600;
}

&:hover {
text-decoration: underline;
}
}
}
}
Loading
Loading