Client - application translation (wip)
This commit is contained in:
parent
745d102ee2
commit
77bc32d4a5
@ -1,15 +1,18 @@
|
||||
import React from 'react'
|
||||
|
||||
import { translateSports } from '../../utils/activities'
|
||||
|
||||
export default class ActivitiesFilter extends React.PureComponent {
|
||||
render() {
|
||||
const { loadActivities, sports, updateParams } = this.props
|
||||
const { loadActivities, sports, t, updateParams } = this.props
|
||||
const translatedSports = translateSports(sports, t)
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-body activity-filter">
|
||||
<form onSubmit={event => event.preventDefault()}>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
From:
|
||||
{t('activities:From')}:
|
||||
<input
|
||||
className="form-control col-md"
|
||||
name="from"
|
||||
@ -18,7 +21,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
To:
|
||||
{t('activities:To')}:
|
||||
<input
|
||||
className="form-control col-md"
|
||||
name="to"
|
||||
@ -29,14 +32,14 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Sport:
|
||||
{t('common:Sport')}:
|
||||
<select
|
||||
className="form-control input-lg"
|
||||
name="sport_id"
|
||||
onChange={e => updateParams(e)}
|
||||
>
|
||||
<option value="" />
|
||||
{sports.map(sport => (
|
||||
{translatedSports.map(sport => (
|
||||
<option key={sport.id} value={sport.id}>
|
||||
{sport.label}
|
||||
</option>
|
||||
@ -46,7 +49,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Distance (km):
|
||||
{t('activities:Distance')} (km):
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -59,7 +62,9 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-2 align-middle text-center">to</div>
|
||||
<div className="col-2 align-middle text-center">
|
||||
{t('common:to')}
|
||||
</div>
|
||||
<div className="col-5">
|
||||
<input
|
||||
className="form-control"
|
||||
@ -76,7 +81,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Duration:
|
||||
{t('activities:Duration')}:
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -89,7 +94,9 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-2 align-middle text-center">to</div>
|
||||
<div className="col-2 align-middle text-center">
|
||||
{t('common:to')}
|
||||
</div>
|
||||
<div className="col-5">
|
||||
<input
|
||||
className="form-control"
|
||||
@ -106,7 +113,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Average speed (km/h):
|
||||
{t('activities:Average speed')} (km/h):
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -119,7 +126,9 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-2 align-middle text-center">to</div>
|
||||
<div className="col-2 align-middle text-center">
|
||||
{t('common:to')}
|
||||
</div>
|
||||
<div className="col-5">
|
||||
<input
|
||||
className="form-control"
|
||||
@ -136,7 +145,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Max speed (km/h):
|
||||
{t('activities:Max. speed')} (km/h):
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -149,7 +158,9 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-2 align-middle text-center">to</div>
|
||||
<div className="col-2 align-middle text-center">
|
||||
{t('common:to')}
|
||||
</div>
|
||||
<div className="col-5">
|
||||
<input
|
||||
className="form-control"
|
||||
@ -168,7 +179,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={() => loadActivities()}
|
||||
type="submit"
|
||||
value="Filter"
|
||||
value={t('activities:Filter')}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@ import { getDateWithTZ } from '../../utils'
|
||||
|
||||
export default class ActivitiesList extends React.PureComponent {
|
||||
render() {
|
||||
const { activities, sports, user } = this.props
|
||||
const { activities, sports, t, user } = this.props
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="card-body">
|
||||
@ -15,12 +15,12 @@ export default class ActivitiesList extends React.PureComponent {
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" />
|
||||
<th scope="col">Workout</th>
|
||||
<th scope="col">Date</th>
|
||||
<th scope="col">Distance</th>
|
||||
<th scope="col">Duration</th>
|
||||
<th scope="col">Ave. speed</th>
|
||||
<th scope="col">Max. speed</th>
|
||||
<th scope="col">{t('common:Workout')}</th>
|
||||
<th scope="col">{t('activities:Date')}</th>
|
||||
<th scope="col">{t('activities:Distance')}</th>
|
||||
<th scope="col">{t('activities:Duration')}</th>
|
||||
<th scope="col">{t('activities:Ave. speed')}</th>
|
||||
<th scope="col">{t('activities:Max. speed')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import ActivitiesFilter from './ActivitiesFilter'
|
||||
import ActivitiesList from './ActivitiesList'
|
||||
import NoActivities from '../Common/NoActivities'
|
||||
import { getOrUpdateData } from '../../actions'
|
||||
import { getMoreActivities } from '../../actions/activities'
|
||||
|
||||
@ -40,6 +41,7 @@ class Activities extends React.Component {
|
||||
loadMoreActivities,
|
||||
message,
|
||||
sports,
|
||||
t,
|
||||
user,
|
||||
} = this.props
|
||||
const { params } = this.state
|
||||
@ -50,7 +52,7 @@ class Activities extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Workouts</title>
|
||||
<title>FitTrackee - {t('common:Workouts')}</title>
|
||||
</Helmet>
|
||||
{message ? (
|
||||
<code>{message}</code>
|
||||
@ -61,6 +63,7 @@ class Activities extends React.Component {
|
||||
<ActivitiesFilter
|
||||
sports={sports}
|
||||
loadActivities={() => loadActivities(params)}
|
||||
t={t}
|
||||
updateParams={e => this.setParams(e)}
|
||||
/>
|
||||
</div>
|
||||
@ -68,6 +71,7 @@ class Activities extends React.Component {
|
||||
<ActivitiesList
|
||||
activities={activities}
|
||||
sports={sports}
|
||||
t={t}
|
||||
user={user}
|
||||
/>
|
||||
{!paginationEnd && (
|
||||
@ -82,16 +86,7 @@ class Activities extends React.Component {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activities.length === 0 && (
|
||||
<div className="card text-center">
|
||||
<div className="card-body">
|
||||
No workouts.{' '}
|
||||
<Link to={{ pathname: '/activities/add' }}>
|
||||
Upload one !
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activities.length === 0 && <NoActivities t={t} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,19 +96,21 @@ class Activities extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivities: params => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', params))
|
||||
},
|
||||
loadMoreActivities: params => {
|
||||
dispatch(getMoreActivities(params))
|
||||
},
|
||||
})
|
||||
)(Activities)
|
||||
export default withTranslation()(
|
||||
connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivities: params => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', params))
|
||||
},
|
||||
loadMoreActivities: params => {
|
||||
dispatch(getMoreActivities(params))
|
||||
},
|
||||
})
|
||||
)(Activities)
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import FormWithGpx from './ActivityForms/FormWithGpx'
|
||||
@ -23,13 +24,16 @@ class ActivityAddEdit extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activity, loading, message, sports } = this.props
|
||||
const { activity, loading, message, sports, t } = this.props
|
||||
const { withGpx } = this.state
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>
|
||||
FitTrackee - {activity ? 'Edit a workout' : 'Add a workout'}
|
||||
FitTrackee -{' '}
|
||||
{activity
|
||||
? t('activities:Edit a workout')
|
||||
: t('activities:Add a workout')}
|
||||
</title>
|
||||
</Helmet>
|
||||
<br />
|
||||
@ -41,14 +45,20 @@ class ActivityAddEdit extends React.Component {
|
||||
<div className="col-md-8">
|
||||
<div className="card add-activity">
|
||||
<h2 className="card-header text-center">
|
||||
{activity ? 'Edit a workout' : 'Add a workout'}
|
||||
{activity
|
||||
? t('activities:Edit a workout')
|
||||
: t('activities:Add a workout')}
|
||||
</h2>
|
||||
<div className="card-body">
|
||||
{activity ? (
|
||||
activity.with_gpx ? (
|
||||
<FormWithGpx activity={activity} sports={sports} />
|
||||
<FormWithGpx activity={activity} sports={sports} t={t} />
|
||||
) : (
|
||||
<FormWithoutGpx activity={activity} sports={sports} />
|
||||
<FormWithoutGpx
|
||||
activity={activity}
|
||||
sports={sports}
|
||||
t={t}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div>
|
||||
@ -66,7 +76,7 @@ class ActivityAddEdit extends React.Component {
|
||||
this.handleRadioChange(event)
|
||||
}
|
||||
/>
|
||||
with gpx file
|
||||
{t('activities:with gpx file')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
@ -81,15 +91,15 @@ class ActivityAddEdit extends React.Component {
|
||||
this.handleRadioChange(event)
|
||||
}
|
||||
/>
|
||||
without gpx file
|
||||
{t('activities:without gpx file')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{withGpx ? (
|
||||
<FormWithGpx sports={sports} />
|
||||
<FormWithGpx sports={sports} t={t} />
|
||||
) : (
|
||||
<FormWithoutGpx sports={sports} />
|
||||
<FormWithoutGpx sports={sports} t={t} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -104,6 +114,8 @@ class ActivityAddEdit extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
loading: state.loading,
|
||||
}))(ActivityAddEdit)
|
||||
export default withTranslation()(
|
||||
connect(state => ({
|
||||
loading: state.loading,
|
||||
}))(ActivityAddEdit)
|
||||
)
|
||||
|
@ -1,17 +1,21 @@
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { setLoading } from '../../../actions/index'
|
||||
import { addActivity, editActivity } from '../../../actions/activities'
|
||||
import { history } from '../../../index'
|
||||
import { fileSizeLimit, gpxLimit, zipSizeLimit } from '../../../utils'
|
||||
import { translateSports } from '../../../utils/activities'
|
||||
|
||||
function FormWithGpx(props) {
|
||||
const { activity, loading, onAddActivity, onEditActivity, sports } = props
|
||||
const { activity, loading, onAddActivity, onEditActivity, sports, t } = props
|
||||
const sportId = activity ? activity.sport_id : ''
|
||||
const translatedSports = translateSports(sports, t)
|
||||
// prettier-ignore
|
||||
const zipTooltip =
|
||||
`no folder inside, ${gpxLimit} files max, max size: ${zipSizeLimit}`
|
||||
`${t('activities:no folder inside')}, ${gpxLimit} ${
|
||||
t('activities:files max')}, ${t('activities:max size')}: ${zipSizeLimit}`
|
||||
return (
|
||||
<form
|
||||
encType="multipart/form-data"
|
||||
@ -20,7 +24,7 @@ function FormWithGpx(props) {
|
||||
>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Sport:
|
||||
{t('common:Sport')}:
|
||||
<select
|
||||
className="form-control input-lg"
|
||||
defaultValue={sportId}
|
||||
@ -29,7 +33,7 @@ function FormWithGpx(props) {
|
||||
required
|
||||
>
|
||||
<option value="" />
|
||||
{sports.map(sport => (
|
||||
{translatedSports.map(sport => (
|
||||
<option key={sport.id} value={sport.id}>
|
||||
{sport.label}
|
||||
</option>
|
||||
@ -40,7 +44,7 @@ function FormWithGpx(props) {
|
||||
{activity ? (
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Title:
|
||||
{t('activities:Title')}:
|
||||
<input
|
||||
name="title"
|
||||
defaultValue={activity ? activity.title : ''}
|
||||
@ -52,17 +56,21 @@ function FormWithGpx(props) {
|
||||
) : (
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<strong>gpx</strong> file
|
||||
<Trans i18nKey="activities:gpxFile">
|
||||
<strong>gpx</strong> file
|
||||
</Trans>
|
||||
<sup>
|
||||
<i
|
||||
className="fa fa-question-circle"
|
||||
aria-hidden="true"
|
||||
data-toggle="tooltip"
|
||||
title={`max size: ${fileSizeLimit}`}
|
||||
title={`${t('activities:max size')}: ${fileSizeLimit}`}
|
||||
/>
|
||||
</sup>{' '}
|
||||
or <strong> zip</strong> file containing <strong>gpx </strong>
|
||||
files
|
||||
<Trans i18nKey="activities:zipFile">
|
||||
or <strong> zip</strong> file containing <strong>gpx </strong>
|
||||
files
|
||||
</Trans>
|
||||
<sup>
|
||||
<i
|
||||
className="fa fa-question-circle"
|
||||
@ -86,7 +94,7 @@ function FormWithGpx(props) {
|
||||
)}
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Notes:
|
||||
{t('activities:Notes')}:
|
||||
<textarea
|
||||
name="notes"
|
||||
defaultValue={activity ? activity.notes : ''}
|
||||
@ -106,13 +114,13 @@ function FormWithGpx(props) {
|
||||
onClick={event =>
|
||||
activity ? onEditActivity(event, activity) : onAddActivity(event)
|
||||
}
|
||||
value="Submit"
|
||||
value={t('common:Submit')}
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={() => history.push('/')}
|
||||
value="Cancel"
|
||||
value={t('common:Cancel')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -6,10 +6,11 @@ import {
|
||||
editActivity,
|
||||
} from '../../../actions/activities'
|
||||
import { history } from '../../../index'
|
||||
import { formatActivityDate } from '../../../utils/activities'
|
||||
import { formatActivityDate, translateSports } from '../../../utils/activities'
|
||||
|
||||
function FormWithoutGpx(props) {
|
||||
const { activity, onAddOrEdit, sports } = props
|
||||
const { activity, onAddOrEdit, sports, t } = props
|
||||
const translatedSports = translateSports(sports, t)
|
||||
let activityDate,
|
||||
activityTime,
|
||||
sportId = ''
|
||||
@ -27,7 +28,7 @@ function FormWithoutGpx(props) {
|
||||
<form onSubmit={event => event.preventDefault()}>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Title:
|
||||
{t('activities:Title')}:
|
||||
<input
|
||||
name="title"
|
||||
defaultValue={activity ? activity.title : ''}
|
||||
@ -37,7 +38,7 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Sport:
|
||||
{t('common:Sport')}:
|
||||
<select
|
||||
className="form-control input-lg"
|
||||
defaultValue={sportId}
|
||||
@ -45,7 +46,7 @@ function FormWithoutGpx(props) {
|
||||
required
|
||||
>
|
||||
<option value="" />
|
||||
{sports.map(sport => (
|
||||
{translatedSports.map(sport => (
|
||||
<option key={sport.id} value={sport.id}>
|
||||
{sport.label}
|
||||
</option>
|
||||
@ -55,7 +56,7 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Activity Date:
|
||||
{t('activities:Activity Date')}:
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<input
|
||||
@ -78,7 +79,7 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Duration:
|
||||
{t('activities:Duration')}:
|
||||
<input
|
||||
name="duration"
|
||||
defaultValue={activity ? activity.duration : ''}
|
||||
@ -92,7 +93,7 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Distance (km):
|
||||
{t('activities:Distance')} (km):
|
||||
<input
|
||||
name="distance"
|
||||
defaultValue={activity ? activity.distance : ''}
|
||||
@ -106,7 +107,7 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Notes:
|
||||
{t('activities:Notes')}:
|
||||
<textarea
|
||||
name="notes"
|
||||
defaultValue={activity ? activity.notes : ''}
|
||||
@ -119,13 +120,13 @@ function FormWithoutGpx(props) {
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={event => onAddOrEdit(event, activity)}
|
||||
value="Submit"
|
||||
value={t('common:Submit')}
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={() => history.push('/')}
|
||||
value="Cancel"
|
||||
value={t('common:Cancel')}
|
||||
/>
|
||||
</form>
|
||||
)
|
||||
|
@ -400,7 +400,6 @@ label {
|
||||
.time-frame label {
|
||||
float: left;
|
||||
padding: 0 5px;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.time-frame label input {
|
||||
@ -413,7 +412,7 @@ label {
|
||||
color: #7b7b7b;
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 0;
|
||||
padding: 2px 6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
18
fittrackee_client/src/components/Common/NoActivities.jsx
Normal file
18
fittrackee_client/src/components/Common/NoActivities.jsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default class NoActivities extends React.PureComponent {
|
||||
render() {
|
||||
const { t } = this.props
|
||||
return (
|
||||
<div className="card text-center">
|
||||
<div className="card-body">
|
||||
{t('common:No workouts.')}{' '}
|
||||
<Link to={{ pathname: '/activities/add' }}>
|
||||
{t('dashboard:Upload one !')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -28,9 +28,9 @@ export default class StatsCharts extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
const { displayedData } = this.state
|
||||
const { sports, stats } = this.props
|
||||
const { sports, stats, t } = this.props
|
||||
if (Object.keys(stats).length === 0) {
|
||||
return 'No workouts'
|
||||
return t('common:No workouts.')
|
||||
}
|
||||
return (
|
||||
<div className="chart-stats">
|
||||
@ -42,7 +42,7 @@ export default class StatsCharts extends React.PureComponent {
|
||||
checked={displayedData === 'distance'}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
distance
|
||||
{t('statistics:distance')}
|
||||
</label>
|
||||
<label className="radioLabel col">
|
||||
<input
|
||||
@ -51,7 +51,7 @@ export default class StatsCharts extends React.PureComponent {
|
||||
checked={displayedData === 'duration'}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
duration
|
||||
{t('statistics:duration')}
|
||||
</label>
|
||||
<label className="radioLabel col">
|
||||
<input
|
||||
@ -60,7 +60,7 @@ export default class StatsCharts extends React.PureComponent {
|
||||
checked={displayedData === 'activities'}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
activities
|
||||
{t('statistics:activities')}
|
||||
</label>
|
||||
</div>
|
||||
<ResponsiveContainer height={300}>
|
||||
|
@ -37,10 +37,11 @@ class Statistics extends React.PureComponent {
|
||||
statistics,
|
||||
statsParams,
|
||||
displayEmpty,
|
||||
t,
|
||||
user,
|
||||
} = this.props
|
||||
if (!displayEmpty && Object.keys(statistics).length === 0) {
|
||||
return 'No workouts'
|
||||
return <span>{t('common:No workouts.')}</span>
|
||||
}
|
||||
const stats = formatStats(
|
||||
statistics,
|
||||
@ -49,7 +50,7 @@ class Statistics extends React.PureComponent {
|
||||
displayedSports,
|
||||
user.weekm
|
||||
)
|
||||
return <StatsChart sports={sports} stats={stats} />
|
||||
return <StatsChart sports={sports} stats={stats} t={t} />
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'
|
||||
import { formatRecord } from '../../utils/activities'
|
||||
|
||||
export default function RecordsCard(props) {
|
||||
const { records, sports, user } = props
|
||||
const { records, sports, t, user } = props
|
||||
const recordsBySport = records.reduce((sportList, record) => {
|
||||
const sport = sports.find(s => s.id === record.sport_id)
|
||||
if (sportList[sport.label] === void 0) {
|
||||
@ -22,7 +22,7 @@ export default function RecordsCard(props) {
|
||||
<div className="card-header">Personal records</div>
|
||||
<div className="card-body">
|
||||
{Object.keys(recordsBySport).length === 0
|
||||
? 'No records'
|
||||
? t('common:No records.')
|
||||
: Object.keys(recordsBySport).map(sportLabel => (
|
||||
<table
|
||||
className="table table-borderless table-sm record-table"
|
||||
|
@ -16,11 +16,12 @@ export default class Statistics extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t } = this.props
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="card-header">This month</div>
|
||||
<div className="card-header">{t('dashboard:This month')}</div>
|
||||
<div className="card-body">
|
||||
<Stats displayEmpty={false} statsParams={this.state} />
|
||||
<Stats displayEmpty={false} statsParams={this.state} t={t} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,10 +1,12 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function UserStatistics(props) {
|
||||
const { user } = props
|
||||
const { t, user } = props
|
||||
const days = user.total_duration.match(/day/g)
|
||||
? `${user.total_duration.split(',')[0]},`
|
||||
: '0 days,'
|
||||
? `${user.total_duration.split(' ')[0]} ${
|
||||
user.total_duration.match(/days/g) ? t('common:days') : t('common:day')
|
||||
}`
|
||||
: `0 ${t('common:days')},`
|
||||
let duration = user.total_duration.match(/day/g)
|
||||
? user.total_duration.split(', ')[1]
|
||||
: user.total_duration
|
||||
@ -19,7 +21,11 @@ export default function UserStatistics(props) {
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">{user.nb_activities}</div>
|
||||
<div>{`workout${user.nb_activities === 1 ? '' : 's'}`}</div>
|
||||
<div>{`${
|
||||
user.nb_activities === 1
|
||||
? t('common:workout')
|
||||
: t('common:workouts')
|
||||
}`}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,7 +66,9 @@ export default function UserStatistics(props) {
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">{user.nb_sports}</div>
|
||||
<div>{`sport${user.nb_sports === 1 ? '' : 's'}`}</div>
|
||||
<div>{`${
|
||||
user.nb_sports === 1 ? t('common:sport') : t('common:sports')
|
||||
}`}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import ActivityCard from './ActivityCard'
|
||||
import Calendar from './Calendar'
|
||||
import NoActivities from '../Common/NoActivities'
|
||||
import Records from './Records'
|
||||
import Statistics from './Statistics'
|
||||
import UserStatistics from './UserStatistics'
|
||||
@ -30,6 +31,7 @@ class DashBoard extends React.Component {
|
||||
message,
|
||||
records,
|
||||
sports,
|
||||
t,
|
||||
user,
|
||||
} = this.props
|
||||
const paginationEnd =
|
||||
@ -40,7 +42,7 @@ class DashBoard extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Dashboard</title>
|
||||
<title>FitTrackee - {t('common:Dashboard')}</title>
|
||||
</Helmet>
|
||||
{message ? (
|
||||
<code>{message}</code>
|
||||
@ -48,11 +50,16 @@ class DashBoard extends React.Component {
|
||||
activities &&
|
||||
sports.length > 0 && (
|
||||
<div className="container dashboard">
|
||||
<UserStatistics user={user} />
|
||||
<UserStatistics user={user} t={t} />
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<Statistics />
|
||||
<Records records={records} sports={sports} user={user} />
|
||||
<Statistics t={t} />
|
||||
<Records
|
||||
t={t}
|
||||
records={records}
|
||||
sports={sports}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-8">
|
||||
<Calendar weekm={user.weekm} />
|
||||
@ -66,14 +73,7 @@ class DashBoard extends React.Component {
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="card text-center">
|
||||
<div className="card-body">
|
||||
No workouts.{' '}
|
||||
<Link to={{ pathname: '/activities/add' }}>
|
||||
Upload one !
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<NoActivities t={t} />
|
||||
)}
|
||||
{!paginationEnd && (
|
||||
<input
|
||||
@ -96,21 +96,23 @@ class DashBoard extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
message: state.message,
|
||||
records: state.records.data,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivities: () => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', { page: 1 }))
|
||||
dispatch(getOrUpdateData('getData', 'records'))
|
||||
},
|
||||
loadMoreActivities: page => {
|
||||
dispatch(getMoreActivities({ page }))
|
||||
},
|
||||
})
|
||||
)(DashBoard)
|
||||
export default withTranslation()(
|
||||
connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
message: state.message,
|
||||
records: state.records.data,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivities: () => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', { page: 1 }))
|
||||
dispatch(getOrUpdateData('getData', 'records'))
|
||||
},
|
||||
loadMoreActivities: page => {
|
||||
dispatch(getMoreActivities({ page }))
|
||||
},
|
||||
})
|
||||
)(DashBoard)
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Translation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import LanguageDropdown from './LanguageDropdown'
|
||||
@ -8,163 +8,161 @@ import { apiUrl } from '../../utils'
|
||||
|
||||
class NavBar extends React.PureComponent {
|
||||
render() {
|
||||
const { id, isAuthenticated, picture, username } = this.props
|
||||
const { id, isAuthenticated, picture, t, username } = this.props
|
||||
return (
|
||||
<Translation>
|
||||
{t => (
|
||||
<header>
|
||||
<nav className="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div className="container">
|
||||
<span className="navbar-brand">FitTrackee</span>
|
||||
<button
|
||||
className="navbar-toggler"
|
||||
type="button"
|
||||
data-toggle="collapse"
|
||||
data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span className="navbar-toggler-icon" />
|
||||
</button>
|
||||
<div
|
||||
className="collapse navbar-collapse"
|
||||
id="navbarSupportedContent"
|
||||
>
|
||||
<ul className="navbar-nav mr-auto">
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/',
|
||||
}}
|
||||
>
|
||||
{t('common:Dashboard')}
|
||||
</Link>
|
||||
</li>
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/history',
|
||||
}}
|
||||
>
|
||||
{t('Workouts')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/statistics',
|
||||
}}
|
||||
>
|
||||
{t('common:Statistics')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/add',
|
||||
}}
|
||||
>
|
||||
<strong>{t('common:Add workout')}</strong>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{/* {user.admin && ( */}
|
||||
{/* <li className="nav-item"> */}
|
||||
{/* <Link */}
|
||||
{/* className="nav-link" */}
|
||||
{/* to={{ */}
|
||||
{/* pathname: '/admin', */}
|
||||
{/* }} */}
|
||||
{/* > */}
|
||||
{/* Admin */}
|
||||
{/* </Link> */}
|
||||
{/* </li> */}
|
||||
{/* )} */}
|
||||
</ul>
|
||||
{/* prettier-ignore */}
|
||||
<ul
|
||||
className="navbar-nav flex-row ml-md-auto d-none d-md-flex"
|
||||
<header>
|
||||
<nav className="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div className="container">
|
||||
<span className="navbar-brand">FitTrackee</span>
|
||||
<button
|
||||
className="navbar-toggler"
|
||||
type="button"
|
||||
data-toggle="collapse"
|
||||
data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span className="navbar-toggler-icon" />
|
||||
</button>
|
||||
<div
|
||||
className="collapse navbar-collapse"
|
||||
id="navbarSupportedContent"
|
||||
>
|
||||
<ul className="navbar-nav mr-auto">
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/',
|
||||
}}
|
||||
>
|
||||
{!isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/register',
|
||||
}}
|
||||
>
|
||||
{t('common:Register')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{!isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/login',
|
||||
}}
|
||||
>
|
||||
{t('common:Login')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{picture === true && (
|
||||
<img
|
||||
alt="Avatar"
|
||||
src={`${apiUrl}users/${id}/picture?${Date.now()}`}
|
||||
className="img-fluid App-nav-profile-img"
|
||||
/>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/profile',
|
||||
}}
|
||||
>
|
||||
{username}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/logout',
|
||||
}}
|
||||
>
|
||||
{t('common:Logout')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li><LanguageDropdown /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
)}
|
||||
</Translation>
|
||||
{t('common:Dashboard')}
|
||||
</Link>
|
||||
</li>
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/history',
|
||||
}}
|
||||
>
|
||||
{t('Workouts')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/statistics',
|
||||
}}
|
||||
>
|
||||
{t('common:Statistics')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/add',
|
||||
}}
|
||||
>
|
||||
<strong>{t('common:Add workout')}</strong>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{/* {user.admin && ( */}
|
||||
{/* <li className="nav-item"> */}
|
||||
{/* <Link */}
|
||||
{/* className="nav-link" */}
|
||||
{/* to={{ */}
|
||||
{/* pathname: '/admin', */}
|
||||
{/* }} */}
|
||||
{/* > */}
|
||||
{/* Admin */}
|
||||
{/* </Link> */}
|
||||
{/* </li> */}
|
||||
{/* )} */}
|
||||
</ul>
|
||||
{/* prettier-ignore */}
|
||||
<ul
|
||||
className="navbar-nav flex-row ml-md-auto d-none d-md-flex"
|
||||
>
|
||||
{!isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/register',
|
||||
}}
|
||||
>
|
||||
{t('common:Register')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{!isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/login',
|
||||
}}
|
||||
>
|
||||
{t('common:Login')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{picture === true && (
|
||||
<img
|
||||
alt="Avatar"
|
||||
src={`${apiUrl}users/${id}/picture?${Date.now()}`}
|
||||
className="img-fluid App-nav-profile-img"
|
||||
/>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/profile',
|
||||
}}
|
||||
>
|
||||
{username}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<li className="nav-item">
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/logout',
|
||||
}}
|
||||
>
|
||||
{t('common:Logout')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li><LanguageDropdown /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user }) => ({
|
||||
id: user.id,
|
||||
isAuthenticated: user.isAuthenticated,
|
||||
picture: user.picture,
|
||||
username: user.username,
|
||||
}))(NavBar)
|
||||
export default withTranslation()(
|
||||
connect(({ user }) => ({
|
||||
id: user.id,
|
||||
isAuthenticated: user.isAuthenticated,
|
||||
picture: user.picture,
|
||||
username: user.username,
|
||||
}))(NavBar)
|
||||
)
|
||||
|
@ -14,9 +14,10 @@ import {
|
||||
} from 'date-fns'
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { activityColors } from '../../utils/activities'
|
||||
import { activityColors, translateSports } from '../../utils/activities'
|
||||
import Stats from '../Common/Stats'
|
||||
|
||||
const durations = ['week', 'month', 'year']
|
||||
@ -114,15 +115,16 @@ class Statistics extends React.Component {
|
||||
|
||||
render() {
|
||||
const { displayedSports, statsParams } = this.state
|
||||
const { sports } = this.props
|
||||
const { sports, t } = this.props
|
||||
const translatedSports = translateSports(sports, t)
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Statistics</title>
|
||||
<title>FitTrackee - {t('statistics:Statistics')}</title>
|
||||
</Helmet>
|
||||
<div className="container dashboard">
|
||||
<div className="card activity-card">
|
||||
<div className="card-header">Statistics</div>
|
||||
<div className="card-header">{t('statistics:Statistics')}</div>
|
||||
<div className="card-body">
|
||||
<div className="chart-filters row">
|
||||
<div className="col chart-arrows">
|
||||
@ -145,7 +147,7 @@ class Statistics extends React.Component {
|
||||
checked={d === statsParams.duration}
|
||||
onChange={e => this.handleOnChangeDuration(e)}
|
||||
/>
|
||||
<span>{d}</span>
|
||||
<span>{t(`statistics:${d}`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
@ -164,9 +166,10 @@ class Statistics extends React.Component {
|
||||
displayEmpty
|
||||
displayedSports={displayedSports}
|
||||
statsParams={statsParams}
|
||||
t={t}
|
||||
/>
|
||||
<div className="row chart-activities">
|
||||
{sports.map(sport => (
|
||||
{translatedSports.map(sport => (
|
||||
<label className="col activity-label" key={sport.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -188,6 +191,8 @@ class Statistics extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
sports: state.sports.data,
|
||||
}))(Statistics)
|
||||
export default withTranslation()(
|
||||
connect(state => ({
|
||||
sports: state.sports.data,
|
||||
}))(Statistics)
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
@ -16,8 +17,10 @@ class Logout extends React.Component {
|
||||
<div className="card col-8">
|
||||
<div className="card-body">
|
||||
<div className="text-center">
|
||||
You are now logged out. Click <Link to="/login">here</Link> to
|
||||
log back in.
|
||||
<Trans i18nKey="user:loggedOut">
|
||||
You are now logged out. Click <Link to="/login">here</Link> to
|
||||
log back in.
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { format } from 'date-fns'
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { deletePicture, uploadPicture } from '../../actions/user'
|
||||
import { apiUrl, fileSizeLimit } from '../../utils'
|
||||
|
||||
function Profile({ message, onDeletePicture, onUploadPicture, user }) {
|
||||
function Profile({ message, onDeletePicture, onUploadPicture, t, user }) {
|
||||
const createdAt = user.created_at
|
||||
? format(new Date(user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
@ -17,11 +18,11 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Profile</title>
|
||||
<title>FitTrackee - {t('user:Profile')}</title>
|
||||
</Helmet>
|
||||
{message !== '' && <code>{message}</code>}
|
||||
<div className="container">
|
||||
<h1 className="page-title">Profile</h1>
|
||||
<h1 className="page-title">{t('user:Profile')}</h1>
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<div className="card">
|
||||
@ -38,15 +39,34 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col-md-8">
|
||||
<p>Email: {user.email}</p>
|
||||
<p>Registration Date: {createdAt}</p>
|
||||
<p>First Name: {user.first_name}</p>
|
||||
<p>Last Name: {user.last_name}</p>
|
||||
<p>Birth Date: {birthDate}</p>
|
||||
<p>Location: {user.location}</p>
|
||||
<p>Bio: {user.bio}</p>
|
||||
<p>Time zone: {user.timezone}</p>
|
||||
<p>First day of week: {user.weekm ? 'Monday' : 'Sunday'}</p>
|
||||
<p>
|
||||
{t('user:Email')}: {user.email}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:Registration Date')}: {createdAt}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:First Name')}: {user.first_name}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:Last Name')}: {user.last_name}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:Birth Date')}: {birthDate}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:Location')}: {user.location}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:Bio')}: {user.bio}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:Timezone')}: {user.timezone}
|
||||
</p>
|
||||
<p>
|
||||
{t('user:First day of week')}:{' '}
|
||||
{user.weekm ? t('user:Monday') : t('user:Sunday')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
{user.picture === true && (
|
||||
@ -61,7 +81,7 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
|
||||
/>
|
||||
<br />
|
||||
<button type="submit" onClick={() => onDeletePicture()}>
|
||||
Delete picture
|
||||
{t('user:Delete picture')}
|
||||
</button>
|
||||
<br />
|
||||
<br />
|
||||
@ -77,8 +97,8 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
|
||||
accept=".png,.jpg,.gif"
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">Send</button> (max. size:{' '}
|
||||
{fileSizeLimit})
|
||||
<button type="submit">{t('user:Send')}</button>
|
||||
{` (max. size: ${fileSizeLimit})`}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -91,17 +111,19 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
message: state.message,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
onDeletePicture: () => {
|
||||
dispatch(deletePicture())
|
||||
},
|
||||
onUploadPicture: event => {
|
||||
dispatch(uploadPicture(event))
|
||||
},
|
||||
})
|
||||
)(Profile)
|
||||
export default withTranslation()(
|
||||
connect(
|
||||
state => ({
|
||||
message: state.message,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
onDeletePicture: () => {
|
||||
dispatch(deletePicture())
|
||||
},
|
||||
onUploadPicture: event => {
|
||||
dispatch(uploadPicture(event))
|
||||
},
|
||||
})
|
||||
)(Profile)
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { format } from 'date-fns'
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import TimezonePicker from 'react-timezone'
|
||||
|
||||
@ -49,17 +50,17 @@ class ProfileEdit extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onHandleProfileFormSubmit, message, user } = this.props
|
||||
const { onHandleProfileFormSubmit, message, t, user } = this.props
|
||||
const { formData } = this.state
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Edit Profile</title>
|
||||
<title>FitTrackee - {t('user:Profile Edition')}</title>
|
||||
</Helmet>
|
||||
{message !== '' && <code>{message}</code>}
|
||||
{formData.isAuthenticated && (
|
||||
<div className="container">
|
||||
<h1 className="page-title">Profile Edition</h1>
|
||||
<h1 className="page-title">{t('user:Profile Edition')}</h1>
|
||||
<div className="row">
|
||||
<div className="col-md-2" />
|
||||
<div className="col-md-8">
|
||||
@ -76,7 +77,7 @@ class ProfileEdit extends React.Component {
|
||||
>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Email:
|
||||
{t('user:Email')}:
|
||||
<input
|
||||
name="email"
|
||||
className="form-control input-lg"
|
||||
@ -88,7 +89,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Registration Date:
|
||||
{t('user:Registration Date')}:
|
||||
<input
|
||||
name="createdAt"
|
||||
className="form-control input-lg"
|
||||
@ -100,7 +101,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Password:
|
||||
{t('user:Password')}:
|
||||
<input
|
||||
name="password"
|
||||
className="form-control input-lg"
|
||||
@ -111,7 +112,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Password Confirmation:
|
||||
{t('user:Password Confirmation')}:
|
||||
<input
|
||||
name="password_conf"
|
||||
className="form-control input-lg"
|
||||
@ -123,7 +124,7 @@ class ProfileEdit extends React.Component {
|
||||
<hr />
|
||||
<div className="form-group">
|
||||
<label>
|
||||
First Name:
|
||||
{t('user:First Name')}:
|
||||
<input
|
||||
name="first_name"
|
||||
className="form-control input-lg"
|
||||
@ -135,7 +136,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Last Name:
|
||||
{t('user:Last Name')}:
|
||||
<input
|
||||
name="last_name"
|
||||
className="form-control input-lg"
|
||||
@ -147,7 +148,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Birth Date
|
||||
{t('user:Birth Date')}
|
||||
<input
|
||||
name="birth_date"
|
||||
className="form-control input-lg"
|
||||
@ -159,7 +160,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Location:
|
||||
{t('user:Location')}:
|
||||
<input
|
||||
name="location"
|
||||
className="form-control input-lg"
|
||||
@ -171,7 +172,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Bio:
|
||||
{t('user:Bio')}:
|
||||
<textarea
|
||||
name="bio"
|
||||
className="form-control input-lg"
|
||||
@ -183,7 +184,7 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Timezone:
|
||||
{t('user:Timezone')}:
|
||||
<TimezonePicker
|
||||
className="form-control timezone-custom-height"
|
||||
onChange={tz => {
|
||||
@ -201,28 +202,32 @@ class ProfileEdit extends React.Component {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
First day of week:
|
||||
{t('user:First day of week')}:
|
||||
<select
|
||||
name="weekm"
|
||||
className="form-control input-lg"
|
||||
value={formData.weekm ? 'Monday' : 'Sunday'}
|
||||
onChange={e => this.handleFormChange(e)}
|
||||
>
|
||||
<option value="Sunday">Sunday</option>
|
||||
<option value="Monday">Monday</option>
|
||||
<option value="Sunday">
|
||||
{t('user:Sunday')}
|
||||
</option>
|
||||
<option value="Monday">
|
||||
{t('user:Monday')}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
value="Submit"
|
||||
value={t('common:Submit')}
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={() => history.push('/profile')}
|
||||
value="Cancel"
|
||||
value={t('common:Cancel')}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
@ -239,15 +244,17 @@ class ProfileEdit extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
location: state.router.location,
|
||||
message: state.message,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
onHandleProfileFormSubmit: formData => {
|
||||
dispatch(handleProfileFormSubmit(formData))
|
||||
},
|
||||
})
|
||||
)(ProfileEdit)
|
||||
export default withTranslation(
|
||||
connect(
|
||||
state => ({
|
||||
location: state.router.location,
|
||||
message: state.message,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
onHandleProfileFormSubmit: formData => {
|
||||
dispatch(handleProfileFormSubmit(formData))
|
||||
},
|
||||
})
|
||||
)(ProfileEdit)
|
||||
)
|
||||
|
@ -2,9 +2,17 @@ import i18n from 'i18next'
|
||||
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||
import XHR from 'i18next-xhr-backend'
|
||||
|
||||
import EnActivitiesTranslations from './locales/en/activities.json'
|
||||
import EnCommonTranslations from './locales/en/common.json'
|
||||
import EnDashboardTranslations from './locales/en/dashboard.json'
|
||||
import EnSportsTranslations from './locales/en/sports.json'
|
||||
import EnStatisticsTranslations from './locales/en/statistics.json'
|
||||
import EnUserTranslations from './locales/en/user.json'
|
||||
import FrActivitiesTranslations from './locales/fr/activities.json'
|
||||
import FrCommonTranslations from './locales/fr/common.json'
|
||||
import FrDashboardTranslations from './locales/fr/dashboard.json'
|
||||
import FrSportsTranslations from './locales/fr/sports.json'
|
||||
import FrStatisticsTranslations from './locales/fr/statistics.json'
|
||||
import FrUserTranslations from './locales/fr/user.json'
|
||||
|
||||
i18n
|
||||
@ -20,11 +28,19 @@ i18n
|
||||
},
|
||||
resources: {
|
||||
en: {
|
||||
activities: EnActivitiesTranslations,
|
||||
common: EnCommonTranslations,
|
||||
dashboard: EnDashboardTranslations,
|
||||
sports: EnSportsTranslations,
|
||||
statistics: EnStatisticsTranslations,
|
||||
user: EnUserTranslations,
|
||||
},
|
||||
fr: {
|
||||
activities: FrActivitiesTranslations,
|
||||
common: FrCommonTranslations,
|
||||
dashboard: FrDashboardTranslations,
|
||||
sports: FrSportsTranslations,
|
||||
statistics: FrStatisticsTranslations,
|
||||
user: FrUserTranslations,
|
||||
},
|
||||
},
|
||||
|
23
fittrackee_client/src/locales/en/activities.json
Normal file
23
fittrackee_client/src/locales/en/activities.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"Activity Date": "Activity Date",
|
||||
"Add a workout": "Add a workout",
|
||||
"Ave. speed": "Ave. speed",
|
||||
"Average speed": "Average speed",
|
||||
"Date": "Date",
|
||||
"Distance": "Distance",
|
||||
"Duration": "Duration",
|
||||
"Edit a workout": "Edit a workout",
|
||||
"Filter": "Filter",
|
||||
"From": "From",
|
||||
"gpxFile": "<strong>gpx</strong> file",
|
||||
"Max. speed": "Max. speed",
|
||||
"no folder inside": "no folder inside",
|
||||
"files max": "files max",
|
||||
"max size": "max size",
|
||||
"Notes": "Notes",
|
||||
"Title": "Title",
|
||||
"To": "To",
|
||||
"with gpx file": "with gpx file",
|
||||
"without gpx file": "without gpx file",
|
||||
"zipFile": "or <strong> zip</strong> file containing <strong>gpx </strong> files"
|
||||
}
|
@ -1,10 +1,23 @@
|
||||
{
|
||||
"Dashboard": "Dashboard",
|
||||
"Workouts": "Workouts",
|
||||
"Statistics": "Statistics",
|
||||
"Add workout": "Add workout",
|
||||
"Register": "Register",
|
||||
"Cancel": "Cancel",
|
||||
"Dashboard": "Dashboard",
|
||||
"day": "day",
|
||||
"days": "days",
|
||||
"Login": "Login",
|
||||
"Logout": "Logout",
|
||||
"Submit": "Submit"
|
||||
"No records.": "No records.",
|
||||
"No workouts.": "No workouts.",
|
||||
"Register": "Register",
|
||||
"Statistics": "Statistics",
|
||||
"Sport": "Sport",
|
||||
"sport": "sport",
|
||||
"Sports": "Sports",
|
||||
"sports": "sports",
|
||||
"Submit": "Submit",
|
||||
"to": "to",
|
||||
"Workout": "Workout",
|
||||
"Workouts": "Workouts",
|
||||
"workout": "workout",
|
||||
"workouts": "workouts"
|
||||
}
|
||||
|
5
fittrackee_client/src/locales/en/dashboard.json
Normal file
5
fittrackee_client/src/locales/en/dashboard.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"Personal records": "Personal records",
|
||||
"This month": "This month",
|
||||
"Upload one !": "Upload one !"
|
||||
}
|
8
fittrackee_client/src/locales/en/sports.json
Normal file
8
fittrackee_client/src/locales/en/sports.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Cycling (Sport)": "Cycling (Sport)",
|
||||
"Cycling (Transport)": "Cycling (Transport)",
|
||||
"Hiking": "Hiking",
|
||||
"Mountain Biking": "Mountain Biking",
|
||||
"Running": "Running",
|
||||
"Walking": "Walking"
|
||||
}
|
9
fittrackee_client/src/locales/en/statistics.json
Normal file
9
fittrackee_client/src/locales/en/statistics.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"activities": "activities",
|
||||
"distance": "distance",
|
||||
"duration": "duration",
|
||||
"month": "month",
|
||||
"Statistics": "Statistics",
|
||||
"year": "year",
|
||||
"week": "week"
|
||||
}
|
@ -1,8 +1,27 @@
|
||||
{
|
||||
"login": "login",
|
||||
"register": "register",
|
||||
"Bio": "Bio",
|
||||
"Birth Date": "Birth Date",
|
||||
"Delete picture": "Delete picture",
|
||||
"Edit Profile": "Edit Profile",
|
||||
"Email": "Email",
|
||||
"Enter a username": "Enter a username",
|
||||
"Enter an email address": "Enter an email address",
|
||||
"Enter a password": "Enter a password",
|
||||
"Enter the password confirmation": "Enter the password confirmation"
|
||||
"Enter the password confirmation": "Enter the password confirmation",
|
||||
"First day of week": "First day of week",
|
||||
"First Name": "First Name",
|
||||
"Last Name": "Last Name",
|
||||
"Location": "Location",
|
||||
"loggedOut": "You are now logged out. Click <1>here</1> to log back in.",
|
||||
"login": "login",
|
||||
"Monday": "Monday",
|
||||
"Password": "Password",
|
||||
"Password Confirmation": "Password Confirmation",
|
||||
"Profile": "Profile",
|
||||
"Profile Edition": "Profile Edition",
|
||||
"register": "register",
|
||||
"Registration Date": "Registration Date",
|
||||
"Send": "Send",
|
||||
"Sunday": "Sunday",
|
||||
"Timezone": "Timezone"
|
||||
}
|
||||
|
23
fittrackee_client/src/locales/fr/activities.json
Normal file
23
fittrackee_client/src/locales/fr/activities.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"Activity Date": "Date de l'activité",
|
||||
"Add a workout": "Ajouter une activité",
|
||||
"Ave. speed": "Vitesse moyenne",
|
||||
"Average speed": "Vitesse moyenne",
|
||||
"Date": "Date",
|
||||
"Distance": "Distance",
|
||||
"Duration": "Durée",
|
||||
"Edit a workout": "Editer une activité",
|
||||
"Filter": "Filtrer",
|
||||
"From": "A partir de",
|
||||
"gpxFile": "fichier <strong>gpx</strong>",
|
||||
"Max. speed": "Vitesse max",
|
||||
"no folder inside": "pas de répertoire",
|
||||
"files max": " fichiers max",
|
||||
"max size": "taille max",
|
||||
"Notes": "Notes",
|
||||
"Title": "Titre",
|
||||
"To": "Jusqu'au",
|
||||
"with gpx file": "avec un fichier gpx",
|
||||
"without gpx file": "sans fichier gpx",
|
||||
"zipFile": "ou un fichier <strong> zip</strong> contenant des fichiers <strong>gpx</strong>"
|
||||
}
|
@ -1,10 +1,23 @@
|
||||
{
|
||||
"Dashboard": "Tableau de Bord",
|
||||
"Workouts": "Activités",
|
||||
"Statistics": "Statistiques",
|
||||
"Add workout": "Ajouter une activité",
|
||||
"Register": "S'inscrire",
|
||||
"Cancel": "Annuler",
|
||||
"Dashboard": "Tableau de Bord",
|
||||
"day": "jour",
|
||||
"days": "jours",
|
||||
"Login": "Se connecter",
|
||||
"Logout": "Se déconnecter",
|
||||
"Submit": "Valider"
|
||||
"No records.": "Pas de records.",
|
||||
"No workouts.": "Pas d'activités.",
|
||||
"Register": "S'inscrire",
|
||||
"Statistics": "Statistiques",
|
||||
"Sport": "Sport",
|
||||
"sport": "sport",
|
||||
"Sports": "Sports",
|
||||
"sports": "sports",
|
||||
"Submit": "Valider",
|
||||
"to": "à",
|
||||
"Workout": "Activité",
|
||||
"Workouts": "Activités",
|
||||
"workout": "activité",
|
||||
"workouts": "activités"
|
||||
}
|
||||
|
5
fittrackee_client/src/locales/fr/dashboard.json
Normal file
5
fittrackee_client/src/locales/fr/dashboard.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"Personal records": "Mes records",
|
||||
"This month": "Ce mois",
|
||||
"Upload one !": "Ajoutez votre première activité !"
|
||||
}
|
8
fittrackee_client/src/locales/fr/sports.json
Normal file
8
fittrackee_client/src/locales/fr/sports.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Cycling (Sport)": "Vélo (Sport)",
|
||||
"Cycling (Transport)": "Vélo (Transport)",
|
||||
"Hiking": "Randonnée",
|
||||
"Mountain Biking": "VTT",
|
||||
"Running": "Course",
|
||||
"Walking": "Marche"
|
||||
}
|
9
fittrackee_client/src/locales/fr/statistics.json
Normal file
9
fittrackee_client/src/locales/fr/statistics.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"activities": "activités",
|
||||
"distance": "distance",
|
||||
"duration": "durée",
|
||||
"month": "mois",
|
||||
"Statistics": "Statistiques",
|
||||
"year": "année",
|
||||
"week": "semaine"
|
||||
}
|
@ -1,8 +1,27 @@
|
||||
{
|
||||
"login": "se connecter",
|
||||
"register": "s'nscrire",
|
||||
"Bio": "Bio",
|
||||
"Birth Date": "Date de naissance",
|
||||
"Delete picture": "Supprimer l'image",
|
||||
"Edit Profile": "Editer le profil",
|
||||
"Email": "Email",
|
||||
"Enter a username": "Saisir un nom",
|
||||
"Enter an email address": "Saisir une adresse e-mail",
|
||||
"Enter a password": "Saisir un mot de passe",
|
||||
"Enter the password confirmation": "Confirmer le mot de passe"
|
||||
"Enter the password confirmation": "Confirmer le mot de passe",
|
||||
"First day of week": "Premier jour de la semaine",
|
||||
"First Name": "Prénom",
|
||||
"Last Name": "Nom",
|
||||
"Location": "Lieu",
|
||||
"loggedOut": "Vous êtes déconnecté. Cliquez <1>ici</1> pour vous reconnecter.",
|
||||
"login": "se connecter",
|
||||
"Monday": "Lundi",
|
||||
"Password": "Mot de passe",
|
||||
"Password Confirmation": "Confirmation du mot de passe",
|
||||
"Profile": "Profil",
|
||||
"Profile Edition": "Edition du profil",
|
||||
"register": "s'inscrire",
|
||||
"Registration Date": "Date d'inscription",
|
||||
"Send": "Envoyer",
|
||||
"Sunday": "Dimanche",
|
||||
"Timezone": "Fuseau horaire"
|
||||
}
|
||||
|
@ -86,3 +86,17 @@ export const formatRecord = (record, tz) => {
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
const sortSports = (a, b) => {
|
||||
const sportALabel = a.label.toLowerCase()
|
||||
const sportBLabel = b.label.toLowerCase()
|
||||
return sportALabel > sportBLabel ? 1 : sportALabel < sportBLabel ? -1 : 0
|
||||
}
|
||||
|
||||
export const translateSports = (sports, t) =>
|
||||
sports
|
||||
.map(sport => ({
|
||||
...sport,
|
||||
label: t(`sports:${sport.label}`),
|
||||
}))
|
||||
.sort(sortSports)
|
||||
|
Loading…
Reference in New Issue
Block a user