Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move logged in user to react context #330

Merged
merged 1 commit into from
Dec 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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