Skip to content

Commit

Permalink
chore(LoggedInUser): Move logged in user to react context
Browse files Browse the repository at this point in the history
  • Loading branch information
Betree committed Dec 23, 2018
1 parent 9225349 commit 77dcc90
Show file tree
Hide file tree
Showing 51 changed files with 704 additions and 744 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ parserOptions:
ecmaFeatures:
experimentalObjectRestSpread: true
jsx: true
legacyDecorators: true
sourceType: module
plugins:
- react
Expand All @@ -29,6 +30,7 @@ rules:
quotes: ['warn', 'single', { avoidEscape: true }]
semi: ['error', 'never']
import/first: ['warn']
no-else-return: off
no-trailing-spaces: ['warn']
no-continue: off
no-plusplus: off
Expand Down
59 changes: 59 additions & 0 deletions app/API/http_api/current_user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import HttpApi from '.'

/** Update user with given changes. Returns the updated user */
export const updateUserInfo = userParams => {
return HttpApi.put('users/me', userParams)
}

/** Unlocks an achievement that is not protected */
export const unlockPublicAchievement = achievementId => {
return HttpApi.put(`users/me/achievements/${achievementId}`, achievementId)
}

/** Sign in, then returns an object like {user, token} */
export const signIn = (provider, userParams) => {
return HttpApi.post(`auth/${provider}/callback`, userParams)
}

/** Register user via identity provider. Use signIn for other providers. */
export const signUp = (userParams, invitationToken) => {
return HttpApi.post('users', { user: userParams, invitation_token: invitationToken })
}

/** Unlink a third-party account. */
export const unlinkProvider = provider => {
return HttpApi.delete(`auth/${provider}/link`)
}

/** Request a password reset for given email */
export const resetPasswordRequest = email => {
return HttpApi.post('users/reset_password/request', { email })
}

/** Check a forgotten password token, returns the user if the token is valid */
export const resetPasswordVerify = confirmToken => {
return HttpApi.get(`users/reset_password/verify/${confirmToken}`)
}

/** Update user password using forgotten password token */
export const resetPasswordConfirm = (confirmToken, newPassword) => {
return HttpApi.post('users/reset_password/confirm', {
token: confirmToken,
password: newPassword
})
}

/** Confirm user email */
export const confirmEmail = token => {
return HttpApi.put(`users/me/confirm_email/${token}`)
}

/** Delete user account (dangerous!) */
export const deleteUserAccount = () => {
return HttpApi.delete('users/me')
}

/** Request invitation */
export const requestInvitation = (email, locale) => {
return HttpApi.post('users/request_invitation', { email, locale })
}
19 changes: 8 additions & 11 deletions app/API/http_api.js → app/API/http_api/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import 'isomorphic-fetch'
import trimRight from 'voca/trim_right'

import SocketApi from './socket_api'
import { HTTP_API_URL } from '../config'
import parseServerError from './server_error'
import flashNoInternetError from './no_internet_error'
import { optionsToQueryString } from '../lib/url_utils'
import { HTTP_API_URL } from '../../config'
import parseServerError from '../server_error'
import flashNoInternetError from '../no_internet_error'
import { optionsToQueryString } from '../../lib/url_utils'

class CaptainFactHttpApi {
constructor(baseUrl, token) {
Expand All @@ -16,17 +15,15 @@ class CaptainFactHttpApi {
}

setAuthorizationToken(token) {
this.hasToken = true
localStorage.token = token
if (token) this.headers.authorization = `Bearer ${token}`
SocketApi.setAuthorizationToken(token)
if (token) {
this.hasToken = true
this.headers.authorization = `Bearer ${token}`
}
}

resetToken() {
this.hasToken = false
delete this.headers.authorization
localStorage.removeItem('token')
SocketApi.resetToken()
}

prepareResponse(promise) {
Expand Down
38 changes: 26 additions & 12 deletions app/components/App/LanguageSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react'
import { Map } from 'immutable'
import classNames from 'classnames'
import { withNamespaces } from 'react-i18next'

import { Icon } from '../Utils/Icon'
import { Flex, Box } from '@rebass/grid'
import { Globe } from 'styled-icons/fa-solid/Globe'

const defaultLocales = new Map({
en: 'English',
Expand All @@ -12,16 +12,6 @@ const defaultLocales = new Map({

@withNamespaces() // Force waiting for translations to be loaded
export default class LanguageSelector extends React.PureComponent {
render() {
const sizeClass = this.props.size ? `is-${this.props.size}` : null
return (
<div className={classNames('language-selector', this.props.className)}>
{this.props.withIcon && <Icon name="language" size={this.props.size} />}
<span className={classNames('select', sizeClass)}>{this.renderSelect()}</span>
</div>
)
}

renderSelect() {
const options = defaultLocales
.merge(this.props.additionalOptions || {})
Expand All @@ -44,4 +34,28 @@ export default class LanguageSelector extends React.PureComponent {
</option>
))
}

renderIcon() {
const { value, size } = this.props
if (value === 'fr') {
return '🇫🇷'
}
if (value === 'en') {
return '🇬🇧'
}
return <Globe size={!size ? '2em' : '1em'} />
}

render() {
const sizeClass = this.props.size ? `is-${this.props.size}` : null
return (
<Flex
className={classNames('language-selector', this.props.className)}
alignItems="center"
>
{this.props.withIcon && <Box mr="0.5em">{this.renderIcon()}</Box>}
<span className={classNames('select', sizeClass)}>{this.renderSelect()}</span>
</Flex>
)
}
}
35 changes: 13 additions & 22 deletions app/components/App/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,20 @@ import {
import { LoadingFrame } from '../Utils/LoadingFrame'
import RawIcon from '../Utils/RawIcon'
import ReputationGuard from '../Utils/ReputationGuard'
import LanguageSelector from './LanguageSelector'
import ScoreTag from '../Users/ScoreTag'
import { logout } from '../../state/users/current_user/effects'
import { closeSidebar, toggleSidebar } from '../../state/user_preferences/reducer'
import UserPicture from '../Users/UserPicture'
import i18n from '../../i18n/i18n'
import Logo from './Logo'
import Button from '../Utils/Button'
import { withLoggedInUser } from '../LoggedInUser/UserProvider'
import UserLanguageSelector from '../LoggedInUser/UserLanguageSelector'

@connect(
state => ({
CurrentUser: state.CurrentUser.data,
isLoadingUser: state.CurrentUser.isLoading,
sidebarExpended: state.UserPreferences.sidebarExpended
}),
{ logout, toggleSidebar, closeSidebar }
state => ({ sidebarExpended: state.UserPreferences.sidebarExpended }),
{ toggleSidebar, closeSidebar }
)
@withNamespaces('main')
@withLoggedInUser
export default class Sidebar extends React.PureComponent {
constructor(props) {
super(props)
Expand Down Expand Up @@ -65,7 +61,7 @@ export default class Sidebar extends React.PureComponent {

renderUserLinks() {
const {
CurrentUser: { username, reputation },
loggedInUser: { username, reputation },
t
} = this.props
const baseLink = `/u/${username}`
Expand All @@ -75,7 +71,7 @@ export default class Sidebar extends React.PureComponent {
<div className="level-left menu-list">
<this.MenuLink to={baseLink} className="my-profile-link" onlyActiveOnIndex>
<div className="current-user-link">
<UserPicture size={USER_PICTURE_SMALL} user={this.props.CurrentUser} />
<UserPicture size={USER_PICTURE_SMALL} user={this.props.loggedInUser} />
<span className="username" style={{ fontSize: this.usernameFontSize() }}>
{username}
</span>
Expand Down Expand Up @@ -121,14 +117,15 @@ export default class Sidebar extends React.PureComponent {
}

renderUserSection() {
if (this.props.isLoadingUser)
if (this.props.loggedInUserLoading) {
return (
<div className="user-section">
<LoadingFrame size="mini" />
</div>
)
if (this.props.CurrentUser.id !== 0) return this.renderUserLinks()
return this.renderConnectLinks()
}

return this.props.isAuthenticated ? this.renderUserLinks() : this.renderConnectLinks()
}

render() {
Expand All @@ -153,13 +150,7 @@ export default class Sidebar extends React.PureComponent {
<div className="menu-content">
{this.renderUserSection()}
<p className="menu-label hide-when-collapsed">{t('menu.language')}</p>
<LanguageSelector
className="hide-when-collapsed"
handleChange={v => i18n.changeLanguage(v)}
value={i18n.language}
size="small"
withIcon
/>
<UserLanguageSelector className="hide-when-collapsed" size="small" />
<p className="menu-label">{t('menu.content')}</p>
{this.renderMenuContent()}
<p className="menu-label">{t('menu.other')}</p>
Expand Down Expand Up @@ -212,6 +203,6 @@ export default class Sidebar extends React.PureComponent {
}

usernameFontSize() {
return `${1.4 - this.props.CurrentUser.username.length / 30}em`
return `${1.4 - this.props.loggedInUser.username.length / 30}em`
}
}
55 changes: 20 additions & 35 deletions app/components/App/index.jsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,40 @@
import React from 'react'
import { connect } from 'react-redux'
import { I18nextProvider } from 'react-i18next'

import { Helmet } from 'react-helmet'

import i18n from '../../i18n/i18n'
import { FlashMessages } from '../Utils'
import { fetchCurrentUser } from '../../state/users/current_user/effects'
import Sidebar from './Sidebar'
import { MainModalContainer } from '../Modal/MainModalContainer'
import PublicAchievementUnlocker from '../Users/PublicAchievementUnlocker'
import { isAuthenticated } from '../../state/users/current_user/selectors'
import BackgroundNotifier from './BackgroundNotifier'

@connect(
state => ({
locale: state.UserPreferences.locale,
sidebarExpended: state.UserPreferences.sidebarExpended,
isAuthenticated: isAuthenticated(state)
}),
{ fetchCurrentUser }
)
@connect(state => ({
locale: state.UserPreferences.locale,
sidebarExpended: state.UserPreferences.sidebarExpended
}))
export default class App extends React.PureComponent {
componentDidMount() {
if (!this.props.isAuthenticated) {
this.props.fetchCurrentUser()
}
}

render() {
const { locale, sidebarExpended, children } = this.props
const mainContainerClass = sidebarExpended ? undefined : 'expended'

return (
<I18nextProvider i18n={i18n}>
<div lang={locale}>
<Helmet>
<title>CaptainFact</title>
</Helmet>
<MainModalContainer />
<FlashMessages />
<Sidebar />
<div id="main-container" className={mainContainerClass}>
{children}
</div>
<BackgroundNotifier />
<PublicAchievementUnlocker
achievementId={4}
meetConditionsFunc={this.checkExtensionInstall}
/>
<div lang={locale}>
<Helmet>
<title>CaptainFact</title>
</Helmet>
<MainModalContainer />
<FlashMessages />
<Sidebar />
<div id="main-container" className={mainContainerClass}>
{children}
</div>
</I18nextProvider>
<BackgroundNotifier />
<PublicAchievementUnlocker
achievementId={4}
meetConditionsFunc={this.checkExtensionInstall}
/>
</div>
)
}

Expand Down
9 changes: 4 additions & 5 deletions app/components/Comments/CommentDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import {
deleteComment,
flagComment
} from '../../state/video_debate/comments/effects'
import { isAuthenticated } from '../../state/users/current_user/selectors'
import { isOwnComment } from '../../state/video_debate/comments/selectors'
import { flashErrorUnauthenticated } from '../../state/flashes/reducer'
import MediaLayout from '../Utils/MediaLayout'
import Vote from './Vote'
Expand All @@ -22,11 +20,10 @@ import { COLLAPSE_REPLIES_AT_NESTING } from '../../constants'
import ModalFlag from './ModalFlag'
import ModalDeleteComment from './ModalDeleteComment'
import { CommentsList } from './CommentsList'
import { withLoggedInUser } from '../LoggedInUser/UserProvider'

@connect(
(state, { comment }) => ({
isOwnComment: isOwnComment(state, comment),
isAuthenticated: isAuthenticated(state),
myVote: state.VideoDebate.comments.voted.get(comment.id, 0),
isVoting: state.VideoDebate.comments.voting.has(comment.id),
replies: state.VideoDebate.comments.replies.get(comment.id),
Expand All @@ -42,6 +39,7 @@ import { CommentsList } from './CommentsList'
}
)
@withNamespaces('main')
@withLoggedInUser
export class CommentDisplay extends React.PureComponent {
constructor(props) {
super(props)
Expand Down Expand Up @@ -96,6 +94,7 @@ export class CommentDisplay extends React.PureComponent {
renderCommentContent() {
const { repliesCollapsed } = this.state
const { comment, withoutActions, replies, richMedias = true } = this.props
const isOwnComment = comment.user && this.props.loggedInUser.id === comment.user.id

return (
<React.Fragment>
Expand All @@ -110,7 +109,7 @@ export class CommentDisplay extends React.PureComponent {
/>
{!withoutActions && (
<CommentActions
isOwnComment={this.props.isOwnComment}
isOwnComment={isOwnComment}
isFlagged={this.props.isFlagged}
nbReplies={replies ? replies.size : 0}
repliesCollapsed={repliesCollapsed}
Expand Down
Loading

0 comments on commit 77dcc90

Please sign in to comment.