Skip to content

Commit fadc87c

Browse files
authored
Merge pull request #347 from CaptainFact/staging
Release 0.9.4
2 parents 25e1548 + 093a5d0 commit fadc87c

79 files changed

Lines changed: 1268 additions & 1076 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ parserOptions:
1010
ecmaFeatures:
1111
experimentalObjectRestSpread: true
1212
jsx: true
13+
legacyDecorators: true
1314
sourceType: module
1415
plugins:
1516
- react
@@ -29,6 +30,7 @@ rules:
2930
quotes: ['warn', 'single', { avoidEscape: true }]
3031
semi: ['error', 'never']
3132
import/first: ['warn']
33+
no-else-return: off
3234
no-trailing-spaces: ['warn']
3335
no-continue: off
3436
no-plusplus: off

app/API/graphql_queries.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,26 @@ export const VideosQuery = gql`
2020
}
2121
}
2222
`
23+
24+
export const VideosAddedByUserQuery = gql`
25+
query UserAddedVideosIndex($offset: Int! = 1, $limit: Int! = 16, $username: String!) {
26+
user(username: $username) {
27+
videosAdded(limit: $limit, offset: $offset) {
28+
pageNumber
29+
totalPages
30+
entries {
31+
hash_id: hashId
32+
youtube_id: youtubeId
33+
title
34+
insertedAt
35+
isPartner
36+
speakers {
37+
full_name: fullName
38+
id
39+
slug
40+
}
41+
}
42+
}
43+
}
44+
}
45+
`

app/API/http_api/current_user.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import HttpApi from '.'
2+
3+
/** Update user with given changes. Returns the updated user */
4+
export const updateUserInfo = userParams => {
5+
return HttpApi.put('users/me', userParams)
6+
}
7+
8+
/** Unlocks an achievement that is not protected */
9+
export const unlockPublicAchievement = achievementId => {
10+
return HttpApi.put(`users/me/achievements/${achievementId}`, achievementId)
11+
}
12+
13+
/** Sign in, then returns an object like {user, token} */
14+
export const signIn = (provider, userParams) => {
15+
return HttpApi.post(`auth/${provider}/callback`, userParams)
16+
}
17+
18+
/** Register user via identity provider. Use signIn for other providers. */
19+
export const signUp = (userParams, invitationToken) => {
20+
return HttpApi.post('users', { user: userParams, invitation_token: invitationToken })
21+
}
22+
23+
/** Unlink a third-party account. */
24+
export const unlinkProvider = provider => {
25+
return HttpApi.delete(`auth/${provider}/link`)
26+
}
27+
28+
/** Request a password reset for given email */
29+
export const resetPasswordRequest = email => {
30+
return HttpApi.post('users/reset_password/request', { email })
31+
}
32+
33+
/** Check a forgotten password token, returns the user if the token is valid */
34+
export const resetPasswordVerify = confirmToken => {
35+
return HttpApi.get(`users/reset_password/verify/${confirmToken}`)
36+
}
37+
38+
/** Update user password using forgotten password token */
39+
export const resetPasswordConfirm = (confirmToken, newPassword) => {
40+
return HttpApi.post('users/reset_password/confirm', {
41+
token: confirmToken,
42+
password: newPassword
43+
})
44+
}
45+
46+
/** Confirm user email */
47+
export const confirmEmail = token => {
48+
return HttpApi.put(`users/me/confirm_email/${token}`)
49+
}
50+
51+
/** Delete user account (dangerous!) */
52+
export const deleteUserAccount = () => {
53+
return HttpApi.delete('users/me')
54+
}
55+
56+
/** Request invitation */
57+
export const requestInvitation = (email, locale) => {
58+
return HttpApi.post('users/request_invitation', { email, locale })
59+
}
Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import 'isomorphic-fetch'
22
import trimRight from 'voca/trim_right'
33

4-
import SocketApi from './socket_api'
5-
import { HTTP_API_URL } from '../config'
6-
import parseServerError from './server_error'
7-
import flashNoInternetError from './no_internet_error'
8-
import { optionsToQueryString } from '../lib/url_utils'
4+
import { HTTP_API_URL } from '../../config'
5+
import parseServerError from '../server_error'
6+
import flashNoInternetError from '../no_internet_error'
7+
import { optionsToQueryString } from '../../lib/url_utils'
98

109
class CaptainFactHttpApi {
1110
constructor(baseUrl, token) {
@@ -16,17 +15,15 @@ class CaptainFactHttpApi {
1615
}
1716

1817
setAuthorizationToken(token) {
19-
this.hasToken = true
20-
localStorage.token = token
21-
if (token) this.headers.authorization = `Bearer ${token}`
22-
SocketApi.setAuthorizationToken(token)
18+
if (token) {
19+
this.hasToken = true
20+
this.headers.authorization = `Bearer ${token}`
21+
}
2322
}
2423

2524
resetToken() {
2625
this.hasToken = false
2726
delete this.headers.authorization
28-
localStorage.removeItem('token')
29-
SocketApi.resetToken()
3027
}
3128

3229
prepareResponse(promise) {

app/components/App/LanguageSelector.jsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import React from 'react'
22
import { Map } from 'immutable'
33
import classNames from 'classnames'
44
import { withNamespaces } from 'react-i18next'
5-
6-
import { Icon } from '../Utils/Icon'
5+
import { Flex, Box } from '@rebass/grid'
6+
import { Globe } from 'styled-icons/fa-solid/Globe'
77

88
const defaultLocales = new Map({
99
en: 'English',
@@ -12,16 +12,6 @@ const defaultLocales = new Map({
1212

1313
@withNamespaces() // Force waiting for translations to be loaded
1414
export default class LanguageSelector extends React.PureComponent {
15-
render() {
16-
const sizeClass = this.props.size ? `is-${this.props.size}` : null
17-
return (
18-
<div className={classNames('language-selector', this.props.className)}>
19-
{this.props.withIcon && <Icon name="language" size={this.props.size} />}
20-
<span className={classNames('select', sizeClass)}>{this.renderSelect()}</span>
21-
</div>
22-
)
23-
}
24-
2515
renderSelect() {
2616
const options = defaultLocales
2717
.merge(this.props.additionalOptions || {})
@@ -44,4 +34,28 @@ export default class LanguageSelector extends React.PureComponent {
4434
</option>
4535
))
4636
}
37+
38+
renderIcon() {
39+
const { value, size } = this.props
40+
if (value === 'fr') {
41+
return '🇫🇷'
42+
}
43+
if (value === 'en') {
44+
return '🇬🇧'
45+
}
46+
return <Globe size={!size ? '2em' : '1em'} />
47+
}
48+
49+
render() {
50+
const sizeClass = this.props.size ? `is-${this.props.size}` : null
51+
return (
52+
<Flex
53+
className={classNames('language-selector', this.props.className)}
54+
alignItems="center"
55+
>
56+
{this.props.withIcon && <Box mr="0.5em">{this.renderIcon()}</Box>}
57+
<span className={classNames('select', sizeClass)}>{this.renderSelect()}</span>
58+
</Flex>
59+
)
60+
}
4761
}

app/components/App/Sidebar.jsx

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,20 @@ import {
1414
import { LoadingFrame } from '../Utils/LoadingFrame'
1515
import RawIcon from '../Utils/RawIcon'
1616
import ReputationGuard from '../Utils/ReputationGuard'
17-
import LanguageSelector from './LanguageSelector'
1817
import ScoreTag from '../Users/ScoreTag'
19-
import { logout } from '../../state/users/current_user/effects'
2018
import { closeSidebar, toggleSidebar } from '../../state/user_preferences/reducer'
2119
import UserPicture from '../Users/UserPicture'
22-
import i18n from '../../i18n/i18n'
2320
import Logo from './Logo'
2421
import Button from '../Utils/Button'
22+
import { withLoggedInUser } from '../LoggedInUser/UserProvider'
23+
import UserLanguageSelector from '../LoggedInUser/UserLanguageSelector'
2524

2625
@connect(
27-
state => ({
28-
CurrentUser: state.CurrentUser.data,
29-
isLoadingUser: state.CurrentUser.isLoading,
30-
sidebarExpended: state.UserPreferences.sidebarExpended
31-
}),
32-
{ logout, toggleSidebar, closeSidebar }
26+
state => ({ sidebarExpended: state.UserPreferences.sidebarExpended }),
27+
{ toggleSidebar, closeSidebar }
3328
)
3429
@withNamespaces('main')
30+
@withLoggedInUser
3531
export default class Sidebar extends React.PureComponent {
3632
constructor(props) {
3733
super(props)
@@ -65,7 +61,7 @@ export default class Sidebar extends React.PureComponent {
6561

6662
renderUserLinks() {
6763
const {
68-
CurrentUser: { username, reputation },
64+
loggedInUser: { username, reputation },
6965
t
7066
} = this.props
7167
const baseLink = `/u/${username}`
@@ -75,7 +71,7 @@ export default class Sidebar extends React.PureComponent {
7571
<div className="level-left menu-list">
7672
<this.MenuLink to={baseLink} className="my-profile-link" onlyActiveOnIndex>
7773
<div className="current-user-link">
78-
<UserPicture size={USER_PICTURE_SMALL} user={this.props.CurrentUser} />
74+
<UserPicture size={USER_PICTURE_SMALL} user={this.props.loggedInUser} />
7975
<span className="username" style={{ fontSize: this.usernameFontSize() }}>
8076
{username}
8177
</span>
@@ -121,14 +117,15 @@ export default class Sidebar extends React.PureComponent {
121117
}
122118

123119
renderUserSection() {
124-
if (this.props.isLoadingUser)
120+
if (this.props.loggedInUserLoading) {
125121
return (
126122
<div className="user-section">
127123
<LoadingFrame size="mini" />
128124
</div>
129125
)
130-
if (this.props.CurrentUser.id !== 0) return this.renderUserLinks()
131-
return this.renderConnectLinks()
126+
}
127+
128+
return this.props.isAuthenticated ? this.renderUserLinks() : this.renderConnectLinks()
132129
}
133130

134131
render() {
@@ -153,13 +150,7 @@ export default class Sidebar extends React.PureComponent {
153150
<div className="menu-content">
154151
{this.renderUserSection()}
155152
<p className="menu-label hide-when-collapsed">{t('menu.language')}</p>
156-
<LanguageSelector
157-
className="hide-when-collapsed"
158-
handleChange={v => i18n.changeLanguage(v)}
159-
value={i18n.language}
160-
size="small"
161-
withIcon
162-
/>
153+
<UserLanguageSelector className="hide-when-collapsed" size="small" />
163154
<p className="menu-label">{t('menu.content')}</p>
164155
{this.renderMenuContent()}
165156
<p className="menu-label">{t('menu.other')}</p>
@@ -212,6 +203,6 @@ export default class Sidebar extends React.PureComponent {
212203
}
213204

214205
usernameFontSize() {
215-
return `${1.4 - this.props.CurrentUser.username.length / 30}em`
206+
return `${1.4 - this.props.loggedInUser.username.length / 30}em`
216207
}
217208
}

app/components/App/index.jsx

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,40 @@
11
import React from 'react'
22
import { connect } from 'react-redux'
3-
import { I18nextProvider } from 'react-i18next'
3+
44
import { Helmet } from 'react-helmet'
55

6-
import i18n from '../../i18n/i18n'
76
import { FlashMessages } from '../Utils'
8-
import { fetchCurrentUser } from '../../state/users/current_user/effects'
97
import Sidebar from './Sidebar'
108
import { MainModalContainer } from '../Modal/MainModalContainer'
119
import PublicAchievementUnlocker from '../Users/PublicAchievementUnlocker'
12-
import { isAuthenticated } from '../../state/users/current_user/selectors'
1310
import BackgroundNotifier from './BackgroundNotifier'
1411

15-
@connect(
16-
state => ({
17-
locale: state.UserPreferences.locale,
18-
sidebarExpended: state.UserPreferences.sidebarExpended,
19-
isAuthenticated: isAuthenticated(state)
20-
}),
21-
{ fetchCurrentUser }
22-
)
12+
@connect(state => ({
13+
locale: state.UserPreferences.locale,
14+
sidebarExpended: state.UserPreferences.sidebarExpended
15+
}))
2316
export default class App extends React.PureComponent {
24-
componentDidMount() {
25-
if (!this.props.isAuthenticated) {
26-
this.props.fetchCurrentUser()
27-
}
28-
}
29-
3017
render() {
3118
const { locale, sidebarExpended, children } = this.props
3219
const mainContainerClass = sidebarExpended ? undefined : 'expended'
3320

3421
return (
35-
<I18nextProvider i18n={i18n}>
36-
<div lang={locale}>
37-
<Helmet>
38-
<title>CaptainFact</title>
39-
</Helmet>
40-
<MainModalContainer />
41-
<FlashMessages />
42-
<Sidebar />
43-
<div id="main-container" className={mainContainerClass}>
44-
{children}
45-
</div>
46-
<BackgroundNotifier />
47-
<PublicAchievementUnlocker
48-
achievementId={4}
49-
meetConditionsFunc={this.checkExtensionInstall}
50-
/>
22+
<div lang={locale}>
23+
<Helmet>
24+
<title>CaptainFact</title>
25+
</Helmet>
26+
<MainModalContainer />
27+
<FlashMessages />
28+
<Sidebar />
29+
<div id="main-container" className={mainContainerClass}>
30+
{children}
5131
</div>
52-
</I18nextProvider>
32+
<BackgroundNotifier />
33+
<PublicAchievementUnlocker
34+
achievementId={4}
35+
meetConditionsFunc={this.checkExtensionInstall}
36+
/>
37+
</div>
5338
)
5439
}
5540

0 commit comments

Comments
 (0)