feat: add change password tab to user profile settings
This commit is contained in:
parent
e64f6f8569
commit
6761d4571e
@ -4,8 +4,9 @@ import GlobalTopNavbar from 'App/TopNavbar';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { getAccessToken } from 'shared/utils/accessToken';
|
import { getAccessToken } from 'shared/utils/accessToken';
|
||||||
import Settings from 'shared/components/Settings';
|
import Settings from 'shared/components/Settings';
|
||||||
import { useMeQuery, useClearProfileAvatarMutation } from 'shared/generated/graphql';
|
import { useMeQuery, useClearProfileAvatarMutation, useUpdateUserPasswordMutation } from 'shared/generated/graphql';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { useCurrentUser } from 'App/context';
|
||||||
|
|
||||||
const MainContent = styled.div`
|
const MainContent = styled.div`
|
||||||
padding: 0 0 50px 80px;
|
padding: 0 0 50px 80px;
|
||||||
@ -16,10 +17,16 @@ const MainContent = styled.div`
|
|||||||
const Projects = () => {
|
const Projects = () => {
|
||||||
const $fileUpload = useRef<HTMLInputElement>(null);
|
const $fileUpload = useRef<HTMLInputElement>(null);
|
||||||
const [clearProfileAvatar] = useClearProfileAvatarMutation();
|
const [clearProfileAvatar] = useClearProfileAvatarMutation();
|
||||||
|
const { user } = useCurrentUser();
|
||||||
|
const [updateUserPassword] = useUpdateUserPasswordMutation();
|
||||||
const { loading, data, refetch } = useMeQuery();
|
const { loading, data, refetch } = useMeQuery();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = 'Profile | Taskcafé';
|
document.title = 'Profile | Taskcafé';
|
||||||
}, []);
|
}, []);
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
@ -57,6 +64,10 @@ const Projects = () => {
|
|||||||
$fileUpload.current.click();
|
$fileUpload.current.click();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onResetPassword={(password, done) => {
|
||||||
|
updateUserPassword({ variables: { userID: user.id, password } });
|
||||||
|
done();
|
||||||
|
}}
|
||||||
onProfileAvatarRemove={() => {
|
onProfileAvatarRemove={() => {
|
||||||
clearProfileAvatar();
|
clearProfileAvatar();
|
||||||
}}
|
}}
|
||||||
|
@ -27,6 +27,7 @@ export const Default = () => {
|
|||||||
<BaseStyles />
|
<BaseStyles />
|
||||||
<Settings
|
<Settings
|
||||||
profile={profile}
|
profile={profile}
|
||||||
|
onResetPassword={action('reset password')}
|
||||||
onProfileAvatarRemove={action('remove')}
|
onProfileAvatarRemove={action('remove')}
|
||||||
onProfileAvatarChange={action('profile avatar change')}
|
onProfileAvatarChange={action('profile avatar change')}
|
||||||
/>
|
/>
|
||||||
|
@ -3,6 +3,17 @@ import styled from 'styled-components';
|
|||||||
import { User } from 'shared/icons';
|
import { User } from 'shared/icons';
|
||||||
import Input from 'shared/components/Input';
|
import Input from 'shared/components/Input';
|
||||||
import Button from 'shared/components/Button';
|
import Button from 'shared/components/Button';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
const PasswordInput = styled(Input)`
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FormError = styled.span`
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(${props => props.theme.colors.warning});
|
||||||
|
`;
|
||||||
|
|
||||||
const ProfileContainer = styled.div`
|
const ProfileContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -218,6 +229,7 @@ const SettingActions = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
margin-top: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SaveButton = styled(Button)`
|
const SaveButton = styled(Button)`
|
||||||
@ -228,10 +240,73 @@ const SaveButton = styled(Button)`
|
|||||||
type SettingsProps = {
|
type SettingsProps = {
|
||||||
onProfileAvatarChange: () => void;
|
onProfileAvatarChange: () => void;
|
||||||
onProfileAvatarRemove: () => void;
|
onProfileAvatarRemove: () => void;
|
||||||
|
onResetPassword: (password: string, done: () => void) => void;
|
||||||
profile: TaskUser;
|
profile: TaskUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Settings: React.FC<SettingsProps> = ({ onProfileAvatarRemove, onProfileAvatarChange, profile }) => {
|
type TabProps = {
|
||||||
|
tab: number;
|
||||||
|
currentTab: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Tab: React.FC<TabProps> = ({ tab, currentTab, children }) => {
|
||||||
|
if (tab !== currentTab) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <TabContent>{children}</TabContent>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ResetPasswordTabProps = {
|
||||||
|
onResetPassword: (password: string, done: () => void) => void;
|
||||||
|
};
|
||||||
|
const ResetPasswordTab: React.FC<ResetPasswordTabProps> = ({ onResetPassword }) => {
|
||||||
|
const [active, setActive] = useState(true);
|
||||||
|
const { register, handleSubmit, errors, setError, reset } = useForm<{ password: string; password_confirm: string }>();
|
||||||
|
const done = () => {
|
||||||
|
reset();
|
||||||
|
setActive(true);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(data => {
|
||||||
|
console.log(`${data.password} !== ${data.password_confirm}`);
|
||||||
|
if (data.password !== data.password_confirm) {
|
||||||
|
setError('password', { message: 'Passwords must match!', type: 'error' });
|
||||||
|
setError('password_confirm', { message: 'Passwords must match!', type: 'error' });
|
||||||
|
} else {
|
||||||
|
onResetPassword(data.password, done);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<PasswordInput
|
||||||
|
width="100%"
|
||||||
|
ref={register({ required: 'Password is required' })}
|
||||||
|
label="Password"
|
||||||
|
name="password"
|
||||||
|
/>
|
||||||
|
{errors.password && <FormError>{errors.password.message}</FormError>}
|
||||||
|
<PasswordInput
|
||||||
|
width="100%"
|
||||||
|
ref={register({ required: 'Password is required' })}
|
||||||
|
label="Password (confirm)"
|
||||||
|
name="password_confirm"
|
||||||
|
/>
|
||||||
|
{errors.password_confirm && <FormError>{errors.password_confirm.message}</FormError>}
|
||||||
|
<SettingActions>
|
||||||
|
<SaveButton disabled={!active} type="submit">
|
||||||
|
Save Change
|
||||||
|
</SaveButton>
|
||||||
|
</SettingActions>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Settings: React.FC<SettingsProps> = ({
|
||||||
|
onProfileAvatarRemove,
|
||||||
|
onProfileAvatarChange,
|
||||||
|
onResetPassword,
|
||||||
|
profile,
|
||||||
|
}) => {
|
||||||
const [currentTab, setTab] = useState(0);
|
const [currentTab, setTab] = useState(0);
|
||||||
const [currentTop, setTop] = useState(0);
|
const [currentTop, setTop] = useState(0);
|
||||||
const $tabNav = useRef<HTMLDivElement>(null);
|
const $tabNav = useRef<HTMLDivElement>(null);
|
||||||
@ -257,7 +332,7 @@ const Settings: React.FC<SettingsProps> = ({ onProfileAvatarRemove, onProfileAva
|
|||||||
</TabNavContent>
|
</TabNavContent>
|
||||||
</TabNav>
|
</TabNav>
|
||||||
<TabContentWrapper>
|
<TabContentWrapper>
|
||||||
<TabContent>
|
<Tab tab={0} currentTab={currentTab}>
|
||||||
<AvatarSettings
|
<AvatarSettings
|
||||||
onProfileAvatarRemove={onProfileAvatarRemove}
|
onProfileAvatarRemove={onProfileAvatarRemove}
|
||||||
onProfileAvatarChange={onProfileAvatarChange}
|
onProfileAvatarChange={onProfileAvatarChange}
|
||||||
@ -275,7 +350,10 @@ const Settings: React.FC<SettingsProps> = ({ onProfileAvatarRemove, onProfileAva
|
|||||||
<SettingActions>
|
<SettingActions>
|
||||||
<SaveButton>Save Change</SaveButton>
|
<SaveButton>Save Change</SaveButton>
|
||||||
</SettingActions>
|
</SettingActions>
|
||||||
</TabContent>
|
</Tab>
|
||||||
|
<Tab tab={1} currentTab={currentTab}>
|
||||||
|
<ResetPasswordTab onResetPassword={onResetPassword} />
|
||||||
|
</Tab>
|
||||||
</TabContentWrapper>
|
</TabContentWrapper>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -1935,6 +1935,20 @@ export type DeleteUserAccountMutation = (
|
|||||||
) }
|
) }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type UpdateUserPasswordMutationVariables = {
|
||||||
|
userID: Scalars['UUID'];
|
||||||
|
password: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type UpdateUserPasswordMutation = (
|
||||||
|
{ __typename?: 'Mutation' }
|
||||||
|
& { updateUserPassword: (
|
||||||
|
{ __typename?: 'UpdateUserPasswordPayload' }
|
||||||
|
& Pick<UpdateUserPasswordPayload, 'ok'>
|
||||||
|
) }
|
||||||
|
);
|
||||||
|
|
||||||
export type UpdateUserRoleMutationVariables = {
|
export type UpdateUserRoleMutationVariables = {
|
||||||
userID: Scalars['UUID'];
|
userID: Scalars['UUID'];
|
||||||
roleCode: RoleCode;
|
roleCode: RoleCode;
|
||||||
@ -3975,6 +3989,39 @@ export function useDeleteUserAccountMutation(baseOptions?: ApolloReactHooks.Muta
|
|||||||
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
|
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
|
||||||
export type DeleteUserAccountMutationResult = ApolloReactCommon.MutationResult<DeleteUserAccountMutation>;
|
export type DeleteUserAccountMutationResult = ApolloReactCommon.MutationResult<DeleteUserAccountMutation>;
|
||||||
export type DeleteUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
|
export type DeleteUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
|
||||||
|
export const UpdateUserPasswordDocument = gql`
|
||||||
|
mutation updateUserPassword($userID: UUID!, $password: String!) {
|
||||||
|
updateUserPassword(input: {userID: $userID, password: $password}) {
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type UpdateUserPasswordMutationFn = ApolloReactCommon.MutationFunction<UpdateUserPasswordMutation, UpdateUserPasswordMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useUpdateUserPasswordMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useUpdateUserPasswordMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useUpdateUserPasswordMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [updateUserPasswordMutation, { data, loading, error }] = useUpdateUserPasswordMutation({
|
||||||
|
* variables: {
|
||||||
|
* userID: // value for 'userID'
|
||||||
|
* password: // value for 'password'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useUpdateUserPasswordMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateUserPasswordMutation, UpdateUserPasswordMutationVariables>) {
|
||||||
|
return ApolloReactHooks.useMutation<UpdateUserPasswordMutation, UpdateUserPasswordMutationVariables>(UpdateUserPasswordDocument, baseOptions);
|
||||||
|
}
|
||||||
|
export type UpdateUserPasswordMutationHookResult = ReturnType<typeof useUpdateUserPasswordMutation>;
|
||||||
|
export type UpdateUserPasswordMutationResult = ApolloReactCommon.MutationResult<UpdateUserPasswordMutation>;
|
||||||
|
export type UpdateUserPasswordMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateUserPasswordMutation, UpdateUserPasswordMutationVariables>;
|
||||||
export const UpdateUserRoleDocument = gql`
|
export const UpdateUserRoleDocument = gql`
|
||||||
mutation updateUserRole($userID: UUID!, $roleCode: RoleCode!) {
|
mutation updateUserRole($userID: UUID!, $roleCode: RoleCode!) {
|
||||||
updateUserRole(input: {userID: $userID, roleCode: $roleCode}) {
|
updateUserRole(input: {userID: $userID, roleCode: $roleCode}) {
|
||||||
|
11
frontend/src/shared/graphql/user/updateUserPassword.ts
Normal file
11
frontend/src/shared/graphql/user/updateUserPassword.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const UPDATE_USER_PASSWORD_MUTATION = gql`
|
||||||
|
mutation updateUserPassword($userID: UUID!, $password: String!) {
|
||||||
|
updateUserPassword(input: { userID: $userID, password: $password }) {
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default UPDATE_USER_PASSWORD_MUTATION;
|
Loading…
Reference in New Issue
Block a user