Allow users to update their username (#296)

Closes #293

Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>

Co-authored-by: Sergio Castaño Arteaga <tegioz@icloud.com>
This commit is contained in:
Cynthia S. Garcia 2020-04-13 17:28:28 +02:00 committed by GitHub
parent b1956da197
commit 042f2932d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 92 deletions

View File

@ -3,6 +3,7 @@
create or replace function update_user_profile(p_requesting_user_id uuid, p_user jsonb)
returns void as $$
update "user" set
alias = p_user->>'alias',
first_name = nullif(p_user->>'first_name', ''),
last_name = nullif(p_user->>'last_name', '')
where user_id = p_requesting_user_id;

View File

@ -25,6 +25,7 @@ insert into "user" (
-- Update user profile
select update_user_profile(:'user1ID', '
{
"alias": "user1 updated",
"first_name": "firstname updated",
"last_name": "lastname updated"
}
@ -43,7 +44,7 @@ select results_eq(
$$,
$$
values (
'user1',
'user1 updated',
'firstname updated',
'lastname updated',
'user1@email.com',

View File

@ -1,7 +1,6 @@
import { fireEvent, render, wait } from '@testing-library/react';
import { render } from '@testing-library/react';
import React from 'react';
import { API } from '../../../api';
import { Profile } from '../../../types';
import UpdateProfile from './UpdateProfile';
jest.mock('../../../api');
@ -40,24 +39,5 @@ describe('Update profile - user settings', () => {
expect(getByDisplayValue(profile.firstName!)).toBeInTheDocument();
expect(getByDisplayValue(profile.lastName!)).toBeInTheDocument();
});
it('updates firstName and calls updateProfile', () => {
const { getByDisplayValue, getByTestId } = render(<UpdateProfile {...defaultProps} />);
const input = getByDisplayValue(profile.firstName!) as HTMLInputElement;
fireEvent.change(input, { target: { value: 'testing' } });
const btn = getByTestId('updateProfileBtn');
expect(btn).toBeInTheDocument();
fireEvent.click(btn);
wait();
expect(API.updateUserProfile).toBeCalledTimes(1);
expect(API.updateUserProfile).toHaveBeenCalledWith({
firstName: 'testing',
lastName: profile.lastName,
});
});
});
});

View File

@ -1,11 +1,12 @@
import classnames from 'classnames';
import every from 'lodash/every';
import isNull from 'lodash/isNull';
import isUndefined from 'lodash/isUndefined';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { API } from '../../../api';
import { AppCtx, updateUser } from '../../../context/AppCtx';
import { Profile, UserFullName } from '../../../types';
import { Profile, RefInputField, ResourceKind, UserFullName } from '../../../types';
import alertDispatcher from '../../../utils/alertDispatcher';
import InputField from '../../common/InputField';
@ -15,18 +16,20 @@ interface Props {
}
interface User {
alias: string;
firstName?: string;
lastName?: string;
}
interface FormValidation {
isValid: boolean;
user: User;
user: User | null;
}
const UpdateProfile = (props: Props) => {
const { dispatch } = useContext(AppCtx);
const form = useRef<HTMLFormElement>(null);
const usernameInput = useRef<RefInputField>(null);
const [isSending, setIsSending] = useState(false);
const [isValidated, setIsValidated] = useState(false);
const [profile, setProfile] = useState<Profile | null | undefined>(props.profile);
@ -58,32 +61,42 @@ const UpdateProfile = (props: Props) => {
const submitForm = () => {
setIsSending(true);
if (form.current) {
const { isValid, user } = validateForm(form.current);
if (isValid) {
updateProfile(user);
} else {
setIsSending(false);
}
validateForm(form.current).then((validation: FormValidation) => {
if (validation.isValid && !isNull(validation.user)) {
updateProfile(validation.user);
} else {
setIsSending(false);
}
});
}
};
const validateForm = (form: HTMLFormElement): FormValidation => {
let user: User = {};
const isValid = form.checkValidity();
const validateForm = (form: HTMLFormElement): Promise<FormValidation> => {
let user: User | null = null;
if (isValid) {
const formData = new FormData(form);
if (formData.get('firstName') !== '') {
user['firstName'] = formData.get('firstName') as string;
return validateAllFields().then((isValid: boolean) => {
if (isValid) {
const formData = new FormData(form);
user = {
alias: formData.get('alias') as string,
};
if (formData.get('firstName') !== '') {
user['firstName'] = formData.get('firstName') as string;
}
if (formData.get('lastName') !== '') {
user['lastName'] = formData.get('lastName') as string;
}
}
setIsValidated(true);
return { isValid, user };
});
};
if (formData.get('lastName') !== '') {
user['lastName'] = formData.get('lastName') as string;
}
}
setIsValidated(true);
return { isValid, user };
const validateAllFields = async (): Promise<boolean> => {
return Promise.all([usernameInput.current!.checkIsValid()]).then((res: boolean[]) => {
return every(res, (isValid: boolean) => isValid);
});
};
return (
@ -94,14 +107,6 @@ const UpdateProfile = (props: Props) => {
autoComplete="on"
noValidate
>
<InputField
type="text"
label="Username"
name="alias"
value={!isUndefined(profile) && !isNull(profile) ? profile.alias : ''}
readOnly
/>
<InputField
type="email"
label="Email address"
@ -110,6 +115,23 @@ const UpdateProfile = (props: Props) => {
readOnly
/>
<InputField
ref={usernameInput}
type="text"
label="Username"
labelLegend={<small className="ml-1 font-italic">(Required)</small>}
name="alias"
value={!isUndefined(profile) && !isNull(profile) ? profile.alias : ''}
invalidText={{
default: 'This field is required',
customError: 'Username not available',
}}
checkAvailability={ResourceKind.userAlias}
validateOnBlur
autoComplete="username"
required
/>
<InputField
type="text"
label="First Name"

View File

@ -8,26 +8,6 @@ exports[`Update profile - user settings creates snapshot 1`] = `
data-testid="updateProfileForm"
novalidate=""
>
<div
class="form-group mb-4 position-relative undefined"
>
<label
for="alias"
>
Username
</label>
<input
class="form-control"
data-testid="aliasInput"
name="alias"
readonly=""
type="text"
value="userAlias"
/>
<div
class="invalid-feedback mt-0 inputFeedback"
/>
</div>
<div
class="form-group mb-4 position-relative undefined"
>
@ -48,6 +28,34 @@ exports[`Update profile - user settings creates snapshot 1`] = `
class="invalid-feedback mt-0 inputFeedback"
/>
</div>
<div
class="form-group mb-4 position-relative undefined"
>
<label
for="alias"
>
Username
<small
class="ml-1 font-italic"
>
(Required)
</small>
</label>
<input
autocomplete="username"
class="form-control"
data-testid="aliasInput"
name="alias"
required=""
type="text"
value="userAlias"
/>
<div
class="invalid-feedback mt-0 inputFeedback"
>
This field is required
</div>
</div>
<div
class="form-group mb-4 position-relative undefined"
>

View File

@ -38,26 +38,6 @@ exports[`User settings index creates snapshot 1`] = `
data-testid="updateProfileForm"
novalidate=""
>
<div
class="form-group mb-4 position-relative undefined"
>
<label
for="alias"
>
Username
</label>
<input
class="form-control"
data-testid="aliasInput"
name="alias"
readonly=""
type="text"
value="userAlias"
/>
<div
class="invalid-feedback mt-0 inputFeedback"
/>
</div>
<div
class="form-group mb-4 position-relative undefined"
>
@ -78,6 +58,34 @@ exports[`User settings index creates snapshot 1`] = `
class="invalid-feedback mt-0 inputFeedback"
/>
</div>
<div
class="form-group mb-4 position-relative undefined"
>
<label
for="alias"
>
Username
<small
class="ml-1 font-italic"
>
(Required)
</small>
</label>
<input
autocomplete="username"
class="form-control"
data-testid="aliasInput"
name="alias"
required=""
type="text"
value="userAlias"
/>
<div
class="invalid-feedback mt-0 inputFeedback"
>
This field is required
</div>
</div>
<div
class="form-group mb-4 position-relative undefined"
>