diff --git a/app/components/Users/PublicAchievementUnlocker.jsx b/app/components/Users/PublicAchievementUnlocker.jsx
index 88f816d00..1532393d6 100644
--- a/app/components/Users/PublicAchievementUnlocker.jsx
+++ b/app/components/Users/PublicAchievementUnlocker.jsx
@@ -4,30 +4,35 @@ import PropTypes from 'prop-types'
import { withNamespaces } from 'react-i18next'
import isPromise from 'is-promise'
-import { isAuthenticated } from '../../state/users/current_user/selectors'
-import { unlockPublicAchievement } from '../../state/users/current_user/effects'
import { flashSuccessMsg } from '../../state/flashes/reducer'
+import { withLoggedInUser } from '../LoggedInUser/UserProvider'
+import { unlockPublicAchievement } from '../../API/http_api/current_user'
/**
- * Check if user has given achievement. If not, calls `meetConditions` and trigger effect if the result is true.
- * If no meetConditions is passed, component will just unlock achievement on mount / update.
+ * Check if user has given achievement. If not, calls `meetConditions` and
+ * trigger effect if the result is true.
+ * If no meetConditions is passed, component will just unlock achievement on
+ * mount / update.
*/
@connect(
- state => ({
- isAuthenticated: isAuthenticated(state),
- achievements: state.CurrentUser.data.achievements,
- user: state.CurrentUser.data
- }),
- { unlockPublicAchievement, flashSuccessMsg }
+ null,
+ { flashSuccessMsg }
)
@withNamespaces('achievements')
+@withLoggedInUser
class PublicAchievementUnlocker extends React.PureComponent {
componentDidMount() {
this.unlockIfNecessary()
}
componentDidUpdate(oldProps) {
- if (this.props.achievements !== oldProps.achievements) this.unlockIfNecessary()
+ const { isAuthenticated, loggedInUser } = this.props
+ if (
+ isAuthenticated
+ && loggedInUser.achievements !== oldProps.loggedInUser.achievements
+ ) {
+ this.unlockIfNecessary()
+ }
}
render() {
@@ -35,10 +40,13 @@ class PublicAchievementUnlocker extends React.PureComponent {
}
unlockIfNecessary = () => {
- if (!this.props.isAuthenticated || this.hasAchievement()) return false
+ if (!this.props.isAuthenticated || this.hasAchievement()) {
+ return false
+ }
- if (!this.props.meetConditionsFunc) this.doUnlockAchievement()
- else {
+ if (!this.props.meetConditionsFunc) {
+ this.doUnlockAchievement()
+ } else {
const funcResult = this.props.meetConditionsFunc()
if (funcResult === true) this.doUnlockAchievement()
else if (isPromise(funcResult))
@@ -49,14 +57,17 @@ class PublicAchievementUnlocker extends React.PureComponent {
return true
}
- hasAchievement = () => this.props.achievements.includes(this.props.achievementId)
+ hasAchievement = () => {
+ return this.props.loggedInUser.achievements.includes(this.props.achievementId)
+ }
doUnlockAchievement = () => {
- this.props.unlockPublicAchievement(this.props.achievementId).then(() => {
+ unlockPublicAchievement(this.props.achievementId).then(user => {
+ this.props.updateLoggedInUser(user)
const achievementTitle = this.props.t(`${this.props.achievementId}.title`)
this.props.flashSuccessMsg('achievements:unlocked', {
i18nParams: { achievement: achievementTitle },
- infoUrl: `/u/${this.props.user.username}`,
+ infoUrl: `/u/${this.props.loggedInUser.username}`,
iconName: 'trophy'
})
})
diff --git a/app/components/Users/ResetPasswordConfirmForm.jsx b/app/components/Users/ResetPasswordConfirmForm.jsx
index 6e252601c..d288d8727 100644
--- a/app/components/Users/ResetPasswordConfirmForm.jsx
+++ b/app/components/Users/ResetPasswordConfirmForm.jsx
@@ -1,21 +1,15 @@
import React from 'react'
import { reduxForm } from 'redux-form'
import { withNamespaces } from 'react-i18next'
-import { connect } from 'react-redux'
import { passwordField, passwordRepeatField } from './UserFormFields'
-import {
- resetPasswordRequest,
- resetPasswordVerify,
- resetPasswordConfirm,
- login
-} from '../../state/users/current_user/effects'
import { LoadingFrame } from '../Utils/LoadingFrame'
import { ErrorView } from '../Utils/ErrorView'
import UserPicture from './UserPicture'
import { USER_PICTURE_XLARGE } from '../../constants'
import UserAppellation from './UserAppellation'
import Notification from '../Utils/Notification'
-import { handleEffectResponse } from '../../lib/handle_effect_response'
+import { withLoggedInUser } from '../LoggedInUser/UserProvider'
+import * as userAPI from '../../API/http_api/current_user'
// Fields are auto-validated, only validate password and repeat are the same
const validate = params => {
@@ -28,57 +22,53 @@ const validate = params => {
@reduxForm({ form: 'resetPassword', validate })
@withNamespaces('user')
-@connect(
- null,
- { resetPasswordRequest, resetPasswordVerify, resetPasswordConfirm, login }
-)
+@withLoggedInUser
export default class ResetPasswordConfirmForm extends React.PureComponent {
constructor(props) {
super(props)
- this.state = { status: 'waiting_verification', payload: null }
+ this.state = { status: 'waiting_verification', user: null }
}
componentDidMount() {
- this.props.resetPasswordVerify(this.props.params.token).then(
- handleEffectResponse({
- onSuccess: user => this.setState({ status: 'confirm', payload: user }),
- onError: () => this.setState({ status: 'error', payload: 'invalid_token' })
+ userAPI
+ .resetPasswordVerify(this.props.params.token)
+ .then(user => {
+ this.setState({ status: 'confirm', user })
+ })
+ .catch(() => {
+ this.setState({ status: 'error', user: 'invalid_token' })
})
- )
}
submitForm(e) {
- this.props
- .resetPasswordConfirm({
- password: e.password,
- token: this.props.params.token
+ userAPI
+ .resetPasswordConfirm(this.props.params.token, e.password)
+ .then(user => {
+ userAPI
+ .signIn('identity', { ...user, password: e.password })
+ .then(({ user, token }) => {
+ this.props.updateLoggedInUser(user, token)
+ })
+ this.setState({ status: 'confirm_success' })
+ })
+ .catch(e => {
+ this.setState({ status: 'error', user: 'reset_failed' })
})
- .then(
- handleEffectResponse({
- onSuccess: () => {
- this.props.login({
- provider: 'identity',
- params: { email: this.state.payload.email, password: e.password }
- })
- this.setState({ status: 'confirm_success' })
- },
- onError: () => this.setState({ status: 'error', payload: 'reset_failed' })
- })
- )
}
renderContent() {
- if (this.state.status === 'error')
+ if (this.state.status === 'error') {
return (
-
+
)
- if (this.state.status === 'verify') return
+ }
+
+ if (this.state.status === 'verify') {
+ return
+ }
+
if (this.state.status === 'confirm') {
- const user = this.state.payload
+ const user = this.state.user
return (
@@ -93,8 +83,9 @@ export default class ResetPasswordConfirmForm extends React.PureComponent {
)
}
- if (this.state.status === 'confirm_success')
+ if (this.state.status === 'confirm_success') {
return
{this.props.t('resetPasswordSuccess')}
+ }
}
render() {
diff --git a/app/components/Users/ResetPasswordRequestForm.jsx b/app/components/Users/ResetPasswordRequestForm.jsx
index e2d8488bd..a47c7191b 100644
--- a/app/components/Users/ResetPasswordRequestForm.jsx
+++ b/app/components/Users/ResetPasswordRequestForm.jsx
@@ -1,16 +1,10 @@
import React from 'react'
import { reduxForm } from 'redux-form'
import { withNamespaces } from 'react-i18next'
-import { connect } from 'react-redux'
import { emailField } from './UserFormFields'
-import {
- resetPasswordRequest,
- resetPasswordVerify,
- resetPasswordConfirm
-} from '../../state/users/current_user/effects'
import { ErrorView } from '../Utils/ErrorView'
import Notification from '../Utils/Notification'
-import { handleEffectResponse } from '../../lib/handle_effect_response'
+import { resetPasswordRequest } from '../../API/http_api/current_user'
// Fields are auto-validated, only validate password and repeat are the same
const validate = params => {
@@ -23,10 +17,6 @@ const validate = params => {
@reduxForm({ form: 'resetPassword', validate })
@withNamespaces('user')
-@connect(
- null,
- { resetPasswordRequest, resetPasswordVerify, resetPasswordConfirm }
-)
export default class ResetPasswordRequestForm extends React.PureComponent {
constructor(props) {
super(props)
@@ -34,11 +24,9 @@ export default class ResetPasswordRequestForm extends React.PureComponent {
}
submitForm(e) {
- this.props.resetPasswordRequest(e).then(
- handleEffectResponse({
- onSuccess: () => this.setState({ status: 'done' }),
- onError: () => this.setState({ status: 'error', payload: 'reset_failed' })
- })
+ resetPasswordRequest(e.email).then(
+ () => this.setState({ status: 'done' }),
+ () => this.setState({ status: 'error', payload: 'reset_failed' })
)
}
diff --git a/app/components/Users/SignupForm.jsx b/app/components/Users/SignupForm.jsx
index f8b1e3b8e..e50106065 100644
--- a/app/components/Users/SignupForm.jsx
+++ b/app/components/Users/SignupForm.jsx
@@ -1,10 +1,9 @@
import React from 'react'
-import { reduxForm } from 'redux-form'
+import { reduxForm, SubmissionError } from 'redux-form'
import { connect } from 'react-redux'
import { withRouter, Link } from 'react-router'
import { withNamespaces } from 'react-i18next'
-import { register, requestInvitation } from '../../state/users/current_user/effects'
import {
renderAllUserFields,
submitButton,
@@ -15,8 +14,9 @@ import Notification from '../Utils/Notification'
import Message from '../Utils/Message'
import InvitationRequestForm from './InvitationRequestForm'
import { errorToFlash } from '../../state/flashes/reducer'
-import { handleFormEffectResponse } from '../../lib/handle_effect_response'
import { INVITATION_SYSTEM } from '../../config'
+import { withLoggedInUser } from '../LoggedInUser/UserProvider'
+import { signUp } from '../../API/http_api/current_user'
const SignupForm = ({ location, t }) => {
if (!INVITATION_SYSTEM || location.query.invitation_token) {
@@ -47,30 +47,30 @@ export default withRouter(withNamespaces('user')(SignupForm))
@withNamespaces('user')
@reduxForm({ form: 'signupForm', validate: validatePasswordRepeat })
@connect(
- ({ CurrentUser: { data }, UserPreferences: { locale } }) => ({
- CurrentUser: data,
- locale
- }),
- { register, requestInvitation, errorToFlash }
+ state => ({ locale: state.UserPreferences.locale }),
+ { errorToFlash }
)
+@withLoggedInUser
class RealSignupForm extends React.PureComponent {
componentWillReceiveProps(props) {
// Redirect to user profile when logged in
- if (props.CurrentUser.id) {
- this.props.router.push(`/u/${props.CurrentUser.username}`)
+ if (props.isAuthenticated) {
+ props.router.push(`/u/${props.loggedInUser.username}`)
}
}
submit({ invitation_token, ...user }) {
- const registerParams = {
- invitation_token,
- user: { ...user, locale: this.props.locale }
- }
- return this.props.register(registerParams).then(
- handleFormEffectResponse({
- onError: msg => (typeof msg === 'string' ? this.props.errorToFlash(msg) : null)
+ return signUp({ ...user, locale: this.props.locale }, invitation_token)
+ .then(({ user, token }) => {
+ this.props.updateLoggedInUser(user, token)
+ })
+ .catch(e => {
+ if (typeof e === 'string') {
+ this.props.errorToFlash(e)
+ } else {
+ throw new SubmissionError(e)
+ }
})
- )
}
render() {
diff --git a/app/components/Users/ThirdPartyAccountLinker.jsx b/app/components/Users/ThirdPartyAccountLinker.jsx
index 1e77e53c3..7ae53a50a 100644
--- a/app/components/Users/ThirdPartyAccountLinker.jsx
+++ b/app/components/Users/ThirdPartyAccountLinker.jsx
@@ -1,16 +1,14 @@
import React from 'react'
-import { connect } from 'react-redux'
import { withNamespaces } from 'react-i18next'
-import { unlinkProvider } from '../../state/users/current_user/effects'
+import { withLoggedInUser } from '../LoggedInUser/UserProvider'
+import { unlinkProvider } from '../../API/http_api/current_user'
import Button from '../Utils/Button'
-const mapDispatchToProps = { unlinkProvider }
-
const ThirdPartyAccountLinker = ({
t,
- unlinkProvider,
title,
+ updateLoggedInUser,
provider,
isLinked,
authURL
@@ -24,7 +22,11 @@ const ThirdPartyAccountLinker = ({
@@ -37,9 +39,4 @@ const ThirdPartyAccountLinker = ({
)
-export default withNamespaces('user')(
- connect(
- null,
- mapDispatchToProps
- )(ThirdPartyAccountLinker)
-)
+export default withNamespaces('user')(withLoggedInUser(ThirdPartyAccountLinker))
diff --git a/app/components/Users/ThirdPartyCallback.jsx b/app/components/Users/ThirdPartyCallback.jsx
index 5e8ca702d..31d7bcfc3 100644
--- a/app/components/Users/ThirdPartyCallback.jsx
+++ b/app/components/Users/ThirdPartyCallback.jsx
@@ -1,43 +1,39 @@
import React from 'react'
-import { connect } from 'react-redux'
import { withNamespaces } from 'react-i18next'
import { LoadingFrame } from '../Utils/LoadingFrame'
import { ErrorView } from '../Utils/ErrorView'
-import { login } from '../../state/users/current_user/effects'
-import { isAuthenticated } from '../../state/users/current_user/selectors'
+import { withLoggedInUser } from '../LoggedInUser/UserProvider'
+import { signIn } from '../../API/http_api/current_user'
-@connect(
- state => ({
- user: state.CurrentUser.data,
- isLoading: state.CurrentUser.isLoading || state.CurrentUser.isPosting,
- error: state.CurrentUser.error,
- isAuthenticated: isAuthenticated(state)
- }),
- { login }
-)
@withNamespaces('user')
+@withLoggedInUser
export default class ThirdPartyCallback extends React.PureComponent {
+ state = { error: null }
+
componentDidMount() {
if (!this.props.location.query.error) {
- this.props.login({
- provider: this.props.params.provider,
- params: {
- code: this.props.location.query.code,
- invitation_token: this.props.location.query.state
- }
+ signIn(this.props.params.provider, {
+ code: this.props.location.query.code,
+ invitation_token: this.props.location.query.state
})
+ .then(({ user, token }) => {
+ this.props.updateLoggedInUser(user, token)
+ })
+ .catch(e => {
+ this.setState({ error: e })
+ })
}
}
componentDidUpdate() {
- if (this.props.isAuthenticated && this.props.user.username)
- this.props.router.push(`/u/${this.props.user.username}/settings`)
+ if (this.props.isAuthenticated && this.props.loggedInUser.username) {
+ this.props.router.push(`/u/${this.props.loggedInUser.username}/settings`)
+ }
}
render() {
- if (this.props.isLoading) return
- if (this.props.error)
+ if (this.state.error)
return
if (this.props.location.query.error)
return (
diff --git a/app/components/Users/User.jsx b/app/components/Users/User.jsx
index 7a417e463..b11e97d66 100644
--- a/app/components/Users/User.jsx
+++ b/app/components/Users/User.jsx
@@ -14,10 +14,10 @@ import { TimeSince } from '../Utils/TimeSince'
import { USER_PICTURE_XLARGE } from '../../constants'
import { fetchUser } from '../../state/users/displayed_user/effects'
import { resetUser } from '../../state/users/displayed_user/reducer'
+import { withLoggedInUser } from '../LoggedInUser/UserProvider';
@connect(
- ({ CurrentUser, DisplayedUser: { isLoading, errors, data } }) => ({
- isSelf: CurrentUser.data.id === data.id,
+ ({ DisplayedUser: { isLoading, errors, data } }) => ({
isLoading,
errors,
user: data
@@ -25,6 +25,7 @@ import { resetUser } from '../../state/users/displayed_user/reducer'
{ fetchUser, resetUser }
)
@withNamespaces('main')
+@withLoggedInUser
export default class User extends React.PureComponent {
componentDidMount() {
this.props.fetchUser(this.props.params.username)
@@ -63,13 +64,17 @@ export default class User extends React.PureComponent {
)
}
+ isSelf() {
+ return this.props.isAuthenticated && this.props.loggedInUser.id === this.props.user.id
+ }
+
render() {
if (this.props.errors) return
if (this.props.isLoading) return
const user = this.props.user
const prettyUsername = `@${user.username}`
- const isSelf = this.props.isSelf
+
return (
@@ -103,8 +108,9 @@ export default class User extends React.PureComponent {
{this.getActiveTab('', 'user-circle', 'menu.profile')}
+ {this.getActiveTab('videos', 'television', 'menu.addedVideos')}
{this.getActiveTab('activity', 'tasks', 'menu.activity')}
- {isSelf && this.getActiveTab('settings', 'cog', 'menu.settings')}
+ {this.isSelf() && this.getActiveTab('settings', 'cog', 'menu.settings')}
{this.props.children}
diff --git a/app/components/Users/UserAppellation.jsx b/app/components/Users/UserAppellation.jsx
index e6a17977c..6b746de7c 100644
--- a/app/components/Users/UserAppellation.jsx
+++ b/app/components/Users/UserAppellation.jsx
@@ -10,6 +10,7 @@ const UserAppellation = ({
compact = false,
defaultComponent = 'div'
}) => {
+ const name = user && user.name
const prettyUsername = user ? `@${user.username}` : t('deletedAccount')
const hasLink = user && !withoutActions
const Component = hasLink ? Link : defaultComponent
diff --git a/app/components/Users/UserPicture.jsx b/app/components/Users/UserPicture.jsx
index e2a67c103..ad17d47a0 100644
--- a/app/components/Users/UserPicture.jsx
+++ b/app/components/Users/UserPicture.jsx
@@ -1,8 +1,13 @@
import React from 'react'
+import { UserCircle } from 'styled-icons/fa-regular/UserCircle'
const UserPicture = ({ user: { id, picture_url, mini_picture_url }, size }) => (
)
diff --git a/app/components/Users/UserSettings.jsx b/app/components/Users/UserSettings.jsx
index 6e8aac160..8d625eb7f 100644
--- a/app/components/Users/UserSettings.jsx
+++ b/app/components/Users/UserSettings.jsx
@@ -1,35 +1,28 @@
import React from 'react'
import { connect } from 'react-redux'
import { withNamespaces } from 'react-i18next'
+import { Flex } from '@rebass/grid'
import { facebookAuthUrl } from '../../lib/third_party_auth'
-import { deleteAccount } from '../../state/users/current_user/effects'
-import { addModal } from '../../state/modals/reducer'
+import { addModal, popModal } from '../../state/modals/reducer'
import DeleteUserModal from './DeleteUserModal'
import EditUserForm from './EditUserForm'
import { LoadingFrame } from '../Utils/LoadingFrame'
import Button from '../Utils/Button'
import ThirdPartyAccountLinker from './ThirdPartyAccountLinker'
-import LanguageSelector from '../App/LanguageSelector'
-import i18n from '../../i18n/i18n'
-
-const mapStateToProps = state => ({
- user: state.CurrentUser.data,
- isLoading: state.CurrentUser.isLoading || state.CurrentUser.isPosting,
- locale: state.UserPreferences.locale
-})
-
-const mapDispatchToProps = { deleteAccount, addModal }
+import { withLoggedInUser } from '../LoggedInUser/UserProvider'
+import UserLanguageSelector from '../LoggedInUser/UserLanguageSelector'
+import { deleteUserAccount } from '../../API/http_api/current_user'
@connect(
- mapStateToProps,
- mapDispatchToProps
+ state => ({ locale: state.UserPreferences.locale }),
+ { addModal, popModal }
)
@withNamespaces('user')
+@withLoggedInUser
export default class UserSettings extends React.PureComponent {
render() {
- const { t, deleteAccount, addModal, user, locale } = this.props
-
+ const { t, addModal, loggedInUser, locale, logout } = this.props
return this.props.isLoading ? (
) : (
@@ -42,13 +35,9 @@ export default class UserSettings extends React.PureComponent {
{t('main:menu.language')}
- i18n.changeLanguage(v)}
- value={i18n.language}
- size="medium"
- withIcon
- />
+
+
+
@@ -58,7 +47,7 @@ export default class UserSettings extends React.PureComponent {
@@ -80,11 +69,17 @@ export default class UserSettings extends React.PureComponent {
{t('dangerZone')}
}
+ trigger={
+