diff --git a/frontend/src/Profile/index.tsx b/frontend/src/Profile/index.tsx index 8434dbb..c582c7e 100644 --- a/frontend/src/Profile/index.tsx +++ b/frontend/src/Profile/index.tsx @@ -4,8 +4,9 @@ import GlobalTopNavbar from 'App/TopNavbar'; import { Link } from 'react-router-dom'; import { getAccessToken } from 'shared/utils/accessToken'; 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 { useCurrentUser } from 'App/context'; const MainContent = styled.div` padding: 0 0 50px 80px; @@ -16,10 +17,16 @@ const MainContent = styled.div` const Projects = () => { const $fileUpload = useRef(null); const [clearProfileAvatar] = useClearProfileAvatarMutation(); + const { user } = useCurrentUser(); + const [updateUserPassword] = useUpdateUserPasswordMutation(); const { loading, data, refetch } = useMeQuery(); useEffect(() => { document.title = 'Profile | Taskcafé'; }, []); + if (!user) { + return null; + } + return ( <> { $fileUpload.current.click(); } }} + onResetPassword={(password, done) => { + updateUserPassword({ variables: { userID: user.id, password } }); + done(); + }} onProfileAvatarRemove={() => { clearProfileAvatar(); }} diff --git a/frontend/src/shared/components/Settings/Settings.stories.tsx b/frontend/src/shared/components/Settings/Settings.stories.tsx index bfa2577..6f97048 100644 --- a/frontend/src/shared/components/Settings/Settings.stories.tsx +++ b/frontend/src/shared/components/Settings/Settings.stories.tsx @@ -27,6 +27,7 @@ export const Default = () => { diff --git a/frontend/src/shared/components/Settings/index.tsx b/frontend/src/shared/components/Settings/index.tsx index 81fec51..86254c3 100644 --- a/frontend/src/shared/components/Settings/index.tsx +++ b/frontend/src/shared/components/Settings/index.tsx @@ -3,6 +3,17 @@ import styled from 'styled-components'; import { User } from 'shared/icons'; import Input from 'shared/components/Input'; 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` display: flex; @@ -218,6 +229,7 @@ const SettingActions = styled.div` display: flex; align-items: center; justify-content: flex-end; + margin-top: 12px; `; const SaveButton = styled(Button)` @@ -228,10 +240,73 @@ const SaveButton = styled(Button)` type SettingsProps = { onProfileAvatarChange: () => void; onProfileAvatarRemove: () => void; + onResetPassword: (password: string, done: () => void) => void; profile: TaskUser; }; -const Settings: React.FC = ({ onProfileAvatarRemove, onProfileAvatarChange, profile }) => { +type TabProps = { + tab: number; + currentTab: number; +}; + +const Tab: React.FC = ({ tab, currentTab, children }) => { + if (tab !== currentTab) { + return null; + } + return {children}; +}; + +type ResetPasswordTabProps = { + onResetPassword: (password: string, done: () => void) => void; +}; +const ResetPasswordTab: React.FC = ({ onResetPassword }) => { + const [active, setActive] = useState(true); + const { register, handleSubmit, errors, setError, reset } = useForm<{ password: string; password_confirm: string }>(); + const done = () => { + reset(); + setActive(true); + }; + return ( +
{ + 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); + } + })} + > + + {errors.password && {errors.password.message}} + + {errors.password_confirm && {errors.password_confirm.message}} + + + Save Change + + + + ); +}; + +const Settings: React.FC = ({ + onProfileAvatarRemove, + onProfileAvatarChange, + onResetPassword, + profile, +}) => { const [currentTab, setTab] = useState(0); const [currentTop, setTop] = useState(0); const $tabNav = useRef(null); @@ -257,7 +332,7 @@ const Settings: React.FC = ({ onProfileAvatarRemove, onProfileAva - + = ({ onProfileAvatarRemove, onProfileAva Save Change - + + + + ); diff --git a/frontend/src/shared/generated/graphql.tsx b/frontend/src/shared/generated/graphql.tsx index a58a556..4bc7e86 100644 --- a/frontend/src/shared/generated/graphql.tsx +++ b/frontend/src/shared/generated/graphql.tsx @@ -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 + ) } +); + export type UpdateUserRoleMutationVariables = { userID: Scalars['UUID']; roleCode: RoleCode; @@ -3975,6 +3989,39 @@ export function useDeleteUserAccountMutation(baseOptions?: ApolloReactHooks.Muta export type DeleteUserAccountMutationHookResult = ReturnType; export type DeleteUserAccountMutationResult = ApolloReactCommon.MutationResult; export type DeleteUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions; +export const UpdateUserPasswordDocument = gql` + mutation updateUserPassword($userID: UUID!, $password: String!) { + updateUserPassword(input: {userID: $userID, password: $password}) { + ok + } +} + `; +export type UpdateUserPasswordMutationFn = ApolloReactCommon.MutationFunction; + +/** + * __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) { + return ApolloReactHooks.useMutation(UpdateUserPasswordDocument, baseOptions); + } +export type UpdateUserPasswordMutationHookResult = ReturnType; +export type UpdateUserPasswordMutationResult = ApolloReactCommon.MutationResult; +export type UpdateUserPasswordMutationOptions = ApolloReactCommon.BaseMutationOptions; export const UpdateUserRoleDocument = gql` mutation updateUserRole($userID: UUID!, $roleCode: RoleCode!) { updateUserRole(input: {userID: $userID, roleCode: $roleCode}) { diff --git a/frontend/src/shared/graphql/user/updateUserPassword.ts b/frontend/src/shared/graphql/user/updateUserPassword.ts new file mode 100644 index 0000000..07865e9 --- /dev/null +++ b/frontend/src/shared/graphql/user/updateUserPassword.ts @@ -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;