Skip to content

Commit da24f66

Browse files
committed
Finish code for updating email
1 parent 438f1b0 commit da24f66

File tree

6 files changed

+134
-20
lines changed

6 files changed

+134
-20
lines changed
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
@import url('//fonts.googleapis.com/css?family=Rubik:300,400,500&display=swap');
22
@import url('https://fonts.googleapis.com/css?family=IBM+Plex+Sans:500&display=swap');
33

4+
body {
5+
@apply .text-neutral-200;
6+
letter-spacing: 0.015em;
7+
}
8+
49
h1, h2, h3, h4, h5, h6 {
510
@apply .font-medium;
11+
letter-spacing: 0;
612
font-family: 'IBM Plex Sans', -apple-system, '"Roboto"', 'system-ui', 'sans-serif';
713
}
814

915
p {
1016
@apply .text-neutral-200;
11-
letter-spacing: 0.015em;
1217
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import http from '@/api/http';
2+
3+
export default (email: string, password: string): Promise<void> => {
4+
return new Promise((resolve, reject) => {
5+
http.put('/api/client/account/email', { email, password })
6+
.then(() => resolve())
7+
.catch(reject);
8+
});
9+
};

resources/scripts/components/account/AccountOverviewContainer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import ContentBox from '@/components/elements/ContentBox';
33
import UpdatePasswordForm from '@/components/account/forms/UpdatePasswordForm';
4+
import UpdateEmailAddressForm from '@/components/account/forms/UpdateEmailAddressForm';
45

56
export default () => {
67
return (
@@ -9,7 +10,8 @@ export default () => {
910
<UpdatePasswordForm/>
1011
</ContentBox>
1112
<div className={'flex-1 ml-4'}>
12-
<ContentBox title={'Update Email Address'}>
13+
<ContentBox title={'Update Email Address'} showFlashes={'account:email'}>
14+
<UpdateEmailAddressForm/>
1315
</ContentBox>
1416
<ContentBox title={'Update Identity'} className={'mt-8'}>
1517
</ContentBox>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React from 'react';
2+
import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
3+
import { ApplicationState } from '@/state/types';
4+
import { Form, Formik, FormikActions } from 'formik';
5+
import * as Yup from 'yup';
6+
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
7+
import Field from '@/components/elements/Field';
8+
import { httpErrorToHuman } from '@/api/http';
9+
10+
interface Values {
11+
email: string;
12+
password: string;
13+
}
14+
15+
const schema = Yup.object().shape({
16+
email: Yup.string().email().required(),
17+
password: Yup.string().required('You must provide your current account password.'),
18+
});
19+
20+
export default () => {
21+
const user = useStoreState((state: State<ApplicationState>) => state.user.data);
22+
const updateEmail = useStoreActions((state: Actions<ApplicationState>) => state.user.updateUserEmail);
23+
24+
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationState>) => actions.flashes);
25+
26+
const submit = (values: Values, { resetForm, setSubmitting }: FormikActions<Values>) => {
27+
clearFlashes('account:email');
28+
29+
updateEmail({ ...values })
30+
.then(() => addFlash({
31+
type: 'success',
32+
key: 'account:email',
33+
message: 'Your primary email has been updated.',
34+
}))
35+
.catch(error => addFlash({
36+
type: 'error',
37+
key: 'account:email',
38+
title: 'Error',
39+
message: httpErrorToHuman(error),
40+
}))
41+
.then(() => {
42+
resetForm();
43+
setSubmitting(false);
44+
});
45+
};
46+
47+
return (
48+
<Formik
49+
onSubmit={submit}
50+
validationSchema={schema}
51+
initialValues={{ email: user!.email, password: '' }}
52+
>
53+
{
54+
({ isSubmitting, isValid }) => (
55+
<React.Fragment>
56+
<SpinnerOverlay large={true} visible={isSubmitting}/>
57+
<Form className={'m-0'}>
58+
<Field
59+
id={'current_email'}
60+
type={'email'}
61+
name={'email'}
62+
label={'Email'}
63+
/>
64+
<div className={'mt-6'}>
65+
<Field
66+
id={'confirm_password'}
67+
type={'password'}
68+
name={'password'}
69+
label={'Confirm Password'}
70+
/>
71+
</div>
72+
<div className={'mt-6'}>
73+
<button className={'btn btn-sm btn-primary'} disabled={isSubmitting || !isValid}>
74+
Update Email
75+
</button>
76+
</div>
77+
</Form>
78+
</React.Fragment>
79+
)
80+
}
81+
</Formik>
82+
);
83+
};
Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
1-
import { UserState } from '@/state/types';
2-
import { action } from 'easy-peasy';
1+
import { Action, action, Thunk, thunk } from 'easy-peasy';
2+
import updateAccountEmail from '@/api/account/updateAccountEmail';
3+
4+
export interface UserData {
5+
uuid: string;
6+
username: string;
7+
email: string;
8+
language: string;
9+
rootAdmin: boolean;
10+
useTotp: boolean;
11+
createdAt: Date;
12+
updatedAt: Date;
13+
}
14+
15+
export interface UserState {
16+
data?: UserData;
17+
setUserData: Action<UserState, UserData>;
18+
updateUserData: Action<UserState, Partial<UserData>>;
19+
updateUserEmail: Thunk<UserState, { email: string; password: string }, any, {}, Promise<void>>;
20+
}
321

422
const user: UserState = {
523
data: undefined,
624
setUserData: action((state, payload) => {
725
state.data = payload;
826
}),
27+
28+
updateUserData: action((state, payload) => {
29+
// Limitation of Typescript, can't do much about that currently unfortunately.
30+
// @ts-ignore
31+
state.data = { ...state.data, ...payload };
32+
}),
33+
34+
updateUserEmail: thunk(async (actions, payload) => {
35+
await updateAccountEmail(payload.email, payload.password);
36+
37+
actions.updateUserData({ email: payload.email });
38+
}),
939
};
1040

1141
export default user;

resources/scripts/state/types.d.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FlashMessageType } from '@/components/MessageBox';
22
import { Action } from 'easy-peasy';
3+
import { UserState } from '@/state/models/user';
34

45
export interface ApplicationState {
56
flashes: FlashState;
@@ -12,22 +13,6 @@ export interface FlashState {
1213
clearFlashes: Action<FlashState, string | void>;
1314
}
1415

15-
export interface UserState {
16-
data?: UserData;
17-
setUserData: Action<UserState, UserData>;
18-
}
19-
20-
export interface UserData {
21-
uuid: string;
22-
username: string;
23-
email: string;
24-
language: string;
25-
rootAdmin: boolean;
26-
useTotp: boolean;
27-
createdAt: Date;
28-
updatedAt: Date;
29-
}
30-
3116
export interface FlashMessage {
3217
id?: string;
3318
key?: string;

0 commit comments

Comments
 (0)