Skip to content

Commit e8add76

Browse files
committed
feat(change-password): add settings page with change password section
1 parent d9c5dd4 commit e8add76

File tree

11 files changed

+240
-2
lines changed

11 files changed

+240
-2
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"start:prod": "node --max_old_space_size=460 server.js",
4343
"start": "react-app-rewired start",
4444
"start": "react-scripts --openssl-legacy-provider start",
45+
"start_win": "react-scripts start",
4546
"build": "react-app-rewired --openssl-legacy-provider build",
4647
"test": "react-app-rewired test",
4748
"lint": "eslint 'src/**/*.{ts,tsx}'",

src/api/userAPI/userAPI.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2-
import { LoginFormType } from '../../models/apiTypes';
2+
import { LoginFormType, ChangePasswordFormType } from '../../models/apiTypes';
33
import { METHODS, request } from '../axiosInstances';
44

55
export default {
66
signIn: (params: LoginFormType) => request(METHODS.POST, '/sign-in/', { params }),
7+
changePassword: (params: ChangePasswordFormType) => request(METHODS.PUT, '/change-password/', { params }),
78
};

src/components/Drawer/Drawer.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useHistory } from 'react-router-dom';
66
import SwiftSSLogo from '../../assets/swiftss-logo.png';
77
import TSALogo from '../../assets/tsa-logo.png';
88
import { useIsLoggedIn } from '../../hooks/useIsLoggedIn';
9+
import urls from "../../routing/urls";
910
import { clearUserStorage } from '../../utils/storage';
1011
import { Container } from './Drawer.style';
1112
import Navigation from './Navigation/Navigation';
@@ -39,6 +40,10 @@ const Drawer: React.FC<Props> = (props) => {
3940
history.push('/');
4041
};
4142

43+
const handleSettings = () => {
44+
history.push(urls.settings());
45+
};
46+
4247
const handleClick = () => {
4348
props.setExpanded(false);
4449
};
@@ -63,6 +68,15 @@ const Drawer: React.FC<Props> = (props) => {
6368
{/*)}*/}
6469
{isLoggedIn && (
6570
<UserContainer>
71+
<Button
72+
transparent
73+
filled={false}
74+
buttonType={'button'}
75+
color={'lightGray-100'}
76+
onClick={() => handleSettings()}
77+
>
78+
Settings
79+
</Button>
6680
<Button
6781
transparent
6882
filled={false}

src/hooks/api/userHooks.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { setAxiosToken } from 'api/axiosInstances';
33
import userAPI from 'api/userAPI';
44
import { AxiosError } from 'axios';
55
import { useSetNotification } from 'hooks/useSetNotification';
6-
import { LoginFormType, LoginResponse } from 'models/apiTypes';
6+
import { LoginFormType, LoginResponse, ChangePasswordFormType, ChangePasswordResponse } from 'models/apiTypes';
77
import { resetNotifications } from 'providers/Notifications/actions';
88
import { useNotifications } from 'providers/Notifications/NotificationProvider';
99
import { useMutation } from 'react-query';
@@ -42,3 +42,39 @@ export const useSignIn = () => {
4242
}
4343
);
4444
};
45+
46+
export const useChangePassword = () => {
47+
const history = useHistory();
48+
const setNotification = useSetNotification();
49+
const [, notificationDispatch] = useNotifications();
50+
51+
return useMutation<ChangePasswordResponse, AxiosError, ChangePasswordFormType>(
52+
(params) => {
53+
const { request } = userAPI.single.changePassword({
54+
old_password: params.old_password,
55+
new_password1: params.new_password1,
56+
new_password2: params.new_password2,
57+
});
58+
return request();
59+
},
60+
{
61+
onSuccess: async (data) => {
62+
notificationDispatch(resetNotifications());
63+
setAxiosToken(data?.token ?? '');
64+
65+
// change token in either local or session
66+
// based on weather remember me was checked during login or not
67+
(
68+
localStorage.getItem(__TOKEN__) ? localStorage : sessionStorage
69+
).setItem(__TOKEN__, data?.token ?? '');
70+
71+
setNotification('Password changed.', 'success');
72+
history.replace(urls.settings());
73+
},
74+
onError: (errors) => {
75+
setNotification('Invalid credential combination.', 'error');
76+
console.log(errors);
77+
},
78+
}
79+
);
80+
};

src/models/apiTypes.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ export interface LoginResponse {
2828
};
2929
}
3030

31+
export interface ChangePasswordFormType {
32+
old_password: string;
33+
new_password1: string;
34+
new_password2: string;
35+
}
36+
37+
export interface ChangePasswordResponse {
38+
token?: string;
39+
}
40+
3141
export type HospitalsAPI = {
3242
id: number;
3343
hospital_id?: number;

src/pages/Settings/Settings.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/** @jsxImportSource @emotion/react */
2+
import React, { useState } from 'react';
3+
4+
import { Icon } from '@orfium/ictinus';
5+
import { useHistory } from 'react-router-dom';
6+
7+
import { PageTitle, PageWrapper } from '../../common.style';
8+
import { Tabs } from '../../components/Tabs';
9+
import { useResponsiveLayout } from '../../hooks/useResponsiveSidebar';
10+
import urls from '../../routing/urls';
11+
import { ComponentWrapper } from '../PatientDetails/PatientDetails.style';
12+
import ChangePasswordForm from "./components/ChangePasswordForm";
13+
14+
const tabs = [
15+
{ label: 'Change Password', value: 'change-password' },
16+
];
17+
18+
const Settings: React.FC = () => {
19+
const { isDesktop } = useResponsiveLayout();
20+
21+
const [activeTab, setActiveTab] = useState('change-password');
22+
const history = useHistory();
23+
24+
return (
25+
<PageWrapper isDesktop={isDesktop}>
26+
<PageTitle>
27+
<Icon
28+
name="fatArrowLeft"
29+
size={24}
30+
color={'lightGray-700'}
31+
onClick={() => {
32+
history.push(urls.settings());
33+
}}
34+
/>
35+
Settings
36+
</PageTitle>
37+
<Tabs
38+
matchActiveDataType={activeTab}
39+
onTabClick={(tabId) => {
40+
setActiveTab(tabId);
41+
}}
42+
tabs={tabs}
43+
shouldDisplayTabs
44+
/>
45+
<ComponentWrapper>
46+
{activeTab === 'change-password' ? (
47+
<ChangePasswordForm />
48+
) : (
49+
<div />
50+
)}
51+
</ComponentWrapper>
52+
</PageWrapper>
53+
);
54+
};
55+
56+
export default Settings;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/** @jsxImportSource @emotion/react */
2+
import React from 'react';
3+
4+
import { Button, TextField } from '@orfium/ictinus';
5+
import { FieldsContainer, FieldWrapper, LongFieldWrapper } from 'common.style';
6+
import { useChangePassword } from 'hooks/api/userHooks';
7+
import { ChangePasswordFormType } from 'models/apiTypes';
8+
import { Field, Form } from 'react-final-form';
9+
10+
import { ButtonContainer } from '../../../Login/components/LoginForm/LoginForm.style';
11+
12+
13+
const ChangePasswordForm: React.FC = () => {
14+
const { mutate, isLoading } = useChangePassword();
15+
16+
const handleSubmit = (form: ChangePasswordFormType) => {
17+
mutate(form);
18+
};
19+
20+
return (
21+
<Form onSubmit={handleSubmit}>
22+
{({ handleSubmit, valid, submitting }) => (
23+
<form style={{ height: '100%' }} onSubmit={handleSubmit}>
24+
<FieldsContainer withMargin>
25+
<LongFieldWrapper>
26+
<FieldWrapper>
27+
<Field name="old_password" parse={(value) => value}>
28+
{(props) => {
29+
const hasError = props.meta.touched && props.meta.invalid;
30+
31+
return (
32+
<TextField
33+
id="old_password"
34+
label="Old password"
35+
styleType="outlined"
36+
size="md"
37+
status={hasError && 'error'}
38+
hintMsg={hasError && props.meta.error}
39+
type="password"
40+
{...props.input}
41+
/>
42+
);
43+
}}
44+
</Field>
45+
</FieldWrapper>
46+
</LongFieldWrapper>
47+
</FieldsContainer>
48+
49+
<FieldsContainer withMargin>
50+
<LongFieldWrapper>
51+
<FieldWrapper>
52+
<Field name="new_password1" parse={(value) => value}>
53+
{(props) => {
54+
const hasError = props.meta.touched && props.meta.invalid;
55+
return (
56+
<TextField
57+
id="new_password1"
58+
label="New password"
59+
styleType="outlined"
60+
size="md"
61+
status={hasError && 'error'}
62+
hintMsg={hasError && props.meta.error}
63+
type="password"
64+
{...props.input}
65+
/>
66+
);
67+
}}
68+
</Field>
69+
</FieldWrapper>
70+
</LongFieldWrapper>
71+
</FieldsContainer>
72+
73+
<FieldsContainer withMargin>
74+
<LongFieldWrapper>
75+
<FieldWrapper>
76+
<Field name="new_password2" parse={(value) => value}>
77+
{(props) => {
78+
const hasError = props.meta.touched && props.meta.invalid;
79+
return (
80+
<TextField
81+
id="new_password2"
82+
label="New password (again)"
83+
styleType="outlined"
84+
size="md"
85+
status={hasError && 'error'}
86+
hintMsg={hasError && props.meta.error}
87+
type="password"
88+
{...props.input}
89+
/>
90+
);
91+
}}
92+
</Field>
93+
</FieldWrapper>
94+
</LongFieldWrapper>
95+
</FieldsContainer>
96+
97+
<ButtonContainer>
98+
<Button
99+
block
100+
color={'blue-500'}
101+
disabled={isLoading || !valid || submitting}
102+
filled
103+
size="lg"
104+
buttonType="submit"
105+
>
106+
Change Password
107+
</Button>
108+
</ButtonContainer>
109+
</form>
110+
)}
111+
</Form>
112+
);
113+
};
114+
115+
export default ChangePasswordForm;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './ChangePasswordForm';

src/pages/Settings/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './Settings';

src/routing/Routes.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from 'react';
33
import Login from 'pages/Login';
44
import PatientDirectory from 'pages/PatientDirectory';
55
import RegisterPatient from 'pages/RegisterPatient';
6+
import Settings from 'pages/Settings';
67
import { Redirect, Switch } from 'react-router-dom';
78

89
import EpisodeDetails from '../pages/EpisodeDetails';
@@ -15,6 +16,7 @@ import urls from './urls';
1516
const Routes: React.FC = () => (
1617
<Switch>
1718
<PublicRoute exact path={urls.login()} component={Login} />
19+
<PrivateRoute exact path={urls.settings()} component={Settings} />
1820
<PrivateRoute exact path={urls.registerPatient()} component={RegisterPatient} />
1921
<PrivateRoute
2022
exact

0 commit comments

Comments
 (0)