mirror of https://github.com/artifacthub/hub.git
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:
parent
b1956da197
commit
042f2932d1
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
Loading…
Reference in New Issue