diff --git a/package.json b/package.json index 5c4cac1..4eaef4a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "axios": "^0.19.2", "date-fns": "^2.0.0-beta.5", "date-fns-tz": "^1.0.7", - "history": "^4.9.0", + "history": "^4.10.1", "immer": "^6.0.2", "polished": "^3.5.1", "prop-types": "^15.7.2", diff --git a/src/components/Header/index.js b/src/components/Header/index.js index 91694bc..4fc8355 100644 --- a/src/components/Header/index.js +++ b/src/components/Header/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import { useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; import Notifications from '~/components/Notifications'; @@ -7,11 +8,13 @@ import logo from '~/assets/logo-purple.svg'; import { Container, Content, Profile } from './styles'; export default function Header() { + const profile = useSelector(state => state.user.profile); + return ( @@ -20,11 +23,14 @@ export default function Header() {
- Wellington Arantes - Meu Perfil + {profile.name} + Meu perfil
Wellington Arantes
diff --git a/src/components/Header/styles.js b/src/components/Header/styles.js index 81c281e..023a622 100644 --- a/src/components/Header/styles.js +++ b/src/components/Header/styles.js @@ -12,23 +12,19 @@ export const Content = styled.div` height: 64px; max-width: 900px; margin: 0 auto; - nav { display: flex; align-items: center; - img { margin-right: 20px; padding-right: 20px; border-right: 1px solid #eee; } - a { font-weight: bold; color: #7159c1; } } - aside { display: flex; align-items: center; @@ -40,16 +36,13 @@ export const Profile = styled.div` margin-left: 20px; padding-left: 20px; border-left: 1px solid #eee; - div { text-align: right; margin-right: 10px; - strong { display: block; color: #333; } - a { display: block; margin-top: 2px; @@ -58,6 +51,7 @@ export const Profile = styled.div` } } img { + width: 32px; height: 32px; border-radius: 50%; } diff --git a/src/components/Notifications/index.js b/src/components/Notifications/index.js index 1e6c36c..d237477 100644 --- a/src/components/Notifications/index.js +++ b/src/components/Notifications/index.js @@ -50,7 +50,7 @@ export default function Notifications() { setNotifications( notifications.map(notification => - notification._id == id ? { ...notification, read: true } : notification + notification._id === id ? { ...notification, read: true } : notification ) ); } diff --git a/src/pages/Profile/AvatarInput/index.js b/src/pages/Profile/AvatarInput/index.js new file mode 100644 index 0000000..1e98d81 --- /dev/null +++ b/src/pages/Profile/AvatarInput/index.js @@ -0,0 +1,59 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { useField } from '@rocketseat/unform'; +import api from '~/services/api'; + +import { Container } from './styles'; + +export default function AvatarInput() { + const { defaultValue, registerField } = useField('avatar'); + + const [file, setFile] = useState(defaultValue && defaultValue.id); + const [preview, setPreview] = useState(defaultValue && defaultValue.url); + + const ref = useRef(); + + useEffect(() => { + if (ref.current) { + registerField({ + name: 'avatar_id', + ref: ref.current, + path: 'dataset.file', + }); + } + }, [ref, registerField]); + + async function handleChange(e) { + const data = new FormData(); + + data.append('file', e.target.files[0]); + + const response = await api.post('files', data); + + const { id, url } = response.data; + + setFile(id); + setPreview(url); + } + + return ( + + + + ); +} diff --git a/src/pages/Profile/AvatarInput/styles.js b/src/pages/Profile/AvatarInput/styles.js new file mode 100644 index 0000000..00b2d9f --- /dev/null +++ b/src/pages/Profile/AvatarInput/styles.js @@ -0,0 +1,26 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + align-self: center; + margin-bottom: 30px; + + label { + cursor: pointer; + + &:hover { + opacity: 0.7; + } + + img { + height: 120px; + width: 120px; + border-radius: 50%; + border: 3px solid rgba(255, 255, 255, 0.3); + background: #eee; + } + + input { + display: none; + } + } +`; diff --git a/src/pages/Profile/index.js b/src/pages/Profile/index.js index 4bde172..c7bb38b 100644 --- a/src/pages/Profile/index.js +++ b/src/pages/Profile/index.js @@ -1,7 +1,53 @@ import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Form, Input } from '@rocketseat/unform'; -// import { Container } from './styles'; +import { logOut } from '~/store/modules/auth/actions'; +import { updateProfileRequest } from '~/store/modules/user/actions'; + +import AvatarInput from './AvatarInput'; + +import { Container } from './styles'; export default function Profile() { - return
; + const dispatch = useDispatch(); + const profile = useSelector(state => state.user.profile); + + function handleSubmit(data) { + dispatch(updateProfileRequest(data)); + } + + function handlelogOut() { + dispatch(logOut()); + } + + return ( + +
+ + + + +
+ + + + + + + + + +
+ ); } diff --git a/src/pages/Profile/styles.js b/src/pages/Profile/styles.js new file mode 100644 index 0000000..a26350b --- /dev/null +++ b/src/pages/Profile/styles.js @@ -0,0 +1,87 @@ +import styled from 'styled-components'; +import { darken } from 'polished'; + +export const Container = styled.div` + max-width: 600px; + margin: 50px auto; + + form { + display: flex; + flex-direction: column; + margin-top: 30px; + + input { + background: rgba(0, 0, 0, 0.1); + border: 0; + border-radius: 4px; + height: 44px; + padding: 0 15px; + color: #fff; + margin: 0 0 10px; + + &::placeholder { + color: rgba(255, 255, 255, 0.7); + } + } + + span { + color: #fb6f91; + align-self: flex-start; + margin: 0 0 10px; + font-weight: bold; + } + + hr { + border: 0; + height: 1px; + background: rgba(255, 255, 255, 0.2); + margin: 10px 0 20px; + } + + button { + margin: 5px 0 0; + height: 44px; + background: #3b9eff; + font-weight: bold; + color: #fff; + border: 0; + border-radius: 4px; + font-size: 16px; + transition: background 0.2s; + + &:hover { + background: ${darken(0.03, '#3b9eff')}; + } + } + + a { + color: #fff; + margin-top: 15px; + font-size: 16px; + opacity: 0.8; + + &:hover { + opacity: 1; + } + } + } + + > button { + width: 100%; + margin: 10px 0 0; + height: 44px; + background: #fb6f91; + font-weight: bold; + color: #fff; + border: 0; + border-radius: 4px; + font-size: 16px; + transition: background 0.2s; + + &:hover { + background: ${darken(0.08, '#fb6f91')}; + } + } +`; + +// export const Input = styled.div``; diff --git a/src/store/modules/auth/actions.js b/src/store/modules/auth/actions.js index 3b5da64..f37fd70 100644 --- a/src/store/modules/auth/actions.js +++ b/src/store/modules/auth/actions.js @@ -24,3 +24,9 @@ export function signFailure() { type: '@auth/SIGN_FAILURE', }; } + +export function logOut() { + return { + type: '@auth/SIGN_OUT', + }; +} diff --git a/src/store/modules/auth/reducer.js b/src/store/modules/auth/reducer.js index 27975d4..3a9193b 100644 --- a/src/store/modules/auth/reducer.js +++ b/src/store/modules/auth/reducer.js @@ -23,8 +23,12 @@ export default function auth(state = INITIAL_STATE, action) { draft.loading = false; break; } + case '@auth/SIGN_OUT': { + draft.token = null; + draft.signed = false; + break; + } default: - return state; } }); } diff --git a/src/store/modules/auth/sagas.js b/src/store/modules/auth/sagas.js index a5eff4a..d5ebf1d 100644 --- a/src/store/modules/auth/sagas.js +++ b/src/store/modules/auth/sagas.js @@ -62,8 +62,13 @@ export function setToken({ payload }) { } } +export function logOut() { + history.push('/'); +} + export default all([ takeLatest('persist/REHYDRATE', setToken), takeLatest('@auth/SIGN_IN_REQUEST', signIn), takeLatest('@auth/SIGN_UP_REQUEST', signUp), + takeLatest('@auth/SIGN_OUT', logOut), ]); diff --git a/src/store/modules/user/actions.js b/src/store/modules/user/actions.js index e69de29..f6df118 100644 --- a/src/store/modules/user/actions.js +++ b/src/store/modules/user/actions.js @@ -0,0 +1,19 @@ +export function updateProfileRequest(data) { + return { + type: '@user/UPDATE_PROFILE_REQUEST', + payload: { data }, + }; +} + +export function updateProfileSuccess(profile) { + return { + type: '@user/UPDATE_PROFILE_SUCCESS', + payload: { profile }, + }; +} + +export function updateProfileFailure() { + return { + type: '@user/UPDATE_PROFILE_FAILURE', + }; +} diff --git a/src/store/modules/user/reducer.js b/src/store/modules/user/reducer.js index b709f9c..f9906be 100644 --- a/src/store/modules/user/reducer.js +++ b/src/store/modules/user/reducer.js @@ -5,12 +5,21 @@ const INITIAL_STATE = { }; export default function user(state = INITIAL_STATE, action) { - switch (action.type) { - case '@auth/SIGN_IN_SUCCESS': - return produce(state, draft => { + return produce(state, draft => { + switch (action.type) { + case '@auth/SIGN_IN_SUCCESS': { draft.profile = action.payload.user; - }); - default: - return state; - } + break; + } + case '@user/UPDATE_PROFILE_SUCCESS': { + draft.profile = action.payload.profile; + break; + } + case '@auth/SIGN_OUT': { + draft.profile = null; + break; + } + default: + } + }); } diff --git a/src/store/modules/user/sagas.js b/src/store/modules/user/sagas.js index 9939d1c..c177c08 100644 --- a/src/store/modules/user/sagas.js +++ b/src/store/modules/user/sagas.js @@ -1,3 +1,28 @@ -import { all } from 'redux-saga/effects'; +/* eslint-disable prefer-object-spread */ +import { all, takeLatest, call, put } from 'redux-saga/effects'; +import { toast } from 'react-toastify'; -export default all([]); +import api from '~/services/api'; + +import { updateProfileSuccess, updateProfileFailure } from './actions'; + +export function* updateProfile({ payload }) { + try { + const { name, email, avatar_id, ...rest } = payload.data; + + const profile = Object.assign( + { name, email, avatar_id }, + rest.oldPassword ? rest : {} + ); + + const response = yield call(api.put, 'users', profile); + + toast.success('Perfil atualizado com sucesso!'); + yield put(updateProfileSuccess(response.data)); + } catch (err) { + toast.error('Erro ao atualizar perfil, confira seus dados.'); + yield put(updateProfileFailure()); + } +} + +export default all([takeLatest('@user/UPDATE_PROFILE_REQUEST', updateProfile)]); diff --git a/yarn.lock b/yarn.lock index 6d7f85b..57526fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5078,7 +5078,7 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -history@^4.9.0: +history@^4.10.1, history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==