Skip to content

Commit 22b18d4

Browse files
committedFeb 21, 2017
Port Fitness over from main website to standalone app
1 parent b932b4f commit 22b18d4

34 files changed

+2589
-273
lines changed
 

‎.babelrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"presets": ["env", "stage-1", "react"],
3-
"plugins": ["transform-class-properties", "syntax-dynamic-import"]
3+
"plugins": ["transform-class-properties", "syntax-dynamic-import", "./babelPluginRelay"]
44
}

‎babelPluginRelay.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
var babelRelayPlugin = require('babel-relay-plugin')
2+
var schemaData = require('./fitnessSchema.json').data
3+
var plugin = babelRelayPlugin(schemaData)
4+
5+
module.exports = plugin

‎bootstrap.sh

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
#!/usr/bin/env bash
2-
docker exec postgres-starter /bin/bash -c "psql -U postgres links < schema.sql"
3-
docker exec postgres-starter /bin/bash -c "psql -U postgres links < seed.sql"
4-
export NODE_ENV="development"
2+
docker exec postgres-fitness /bin/bash -c "psql -U postgres fitness < schema.sql"
3+
docker exec postgres-fitness /bin/bash -c "psql -U postgres fitness < seed.sql"

‎client.webpack.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ module.exports = {
4646
module: {
4747
rules: [
4848
{
49-
use: 'babel-loader',
5049
test: /\.js$/,
50+
use: 'babel-loader',
5151
exclude: /node_modules/
5252
},
5353
{
54-
use: ['style-loader', 'css-loader'],
55-
test: /\.css$/
54+
test: /\.css$/,
55+
use: ['style-loader', 'css-loader?modules'],
56+
exclude: /node_modules/
5657
}
5758
]
5859
},

‎database/pgdb.js

+31-22
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,45 @@
11
import humps from 'humps'
22

33
export default (pgPool) => {
4+
// queries here
45
return {
5-
getLinks() {
6+
getWorkouts() {
67
return pgPool.query(`
7-
select * from links
8-
`).then(res => humps.camelizeKeys(res.rows))
8+
select * from workouts
9+
`).then(res => {
10+
return humps.camelizeKeys(res.rows)
11+
})
912
},
10-
addLink({ link, linkTitle, createdAt }) {
13+
getWorkoutsByDate(date) {
1114
return pgPool.query(`
12-
insert into links("link", "link_title", "created_at")
15+
select * from workouts
16+
where workout_date = $1
17+
`, [date]).then(res => {
18+
return humps.camelizeKeys(res.rows)
19+
})
20+
},
21+
addNewWorkout(
22+
{
23+
workout, workoutDate, duration, calories, fatBurnTime, fitnessTime, avgHeartRate, maxHeartRate, workoutType
24+
}) {
25+
return pgPool.query(`
26+
insert into workouts("workout",
27+
"workout_date",
28+
"duration",
29+
"calories",
30+
"fat_burn_time",
31+
"fitness_time",
32+
"avg_heart_rate",
33+
"max_heart_rate",
34+
"workout_type")
1335
values
14-
($1, $2, $3)
36+
($1, $2, $3, $4, $5, $6, $7, $8, $9)
1537
returning *
16-
`, [link, linkTitle, createdAt]).then(res => {
38+
`, [workout, workoutDate, duration, calories, fatBurnTime, fitnessTime, avgHeartRate, maxHeartRate,
39+
workoutType]).then(res => {
1740
return humps.camelizeKeys(res.rows[0])
1841
})
19-
},
20-
deleteLink({ id }) {
21-
return pgPool.query(`
22-
delete from links where id=$1
23-
returning *
24-
`, [id]).then(res => humps.camelizeKeys(res.rows[0]))
25-
},
26-
updateLink({ id, link, linkTitle }) {
27-
return pgPool.query(`
28-
update links
29-
set link=$1,
30-
link_title=$2
31-
where id=$3
32-
returning *
33-
`, [link, linkTitle, id]).then(res => humps.camelizeKeys(res.rows[0]))
42+
3443
}
3544
}
3645
}

‎docker-compose.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
version: "2"
22

33
services:
4-
postgres_starter:
5-
image: postgres:starter
6-
container_name: "postgres-starter"
4+
postgres_fitness:
5+
image: postgres:fitness
6+
container_name: "postgres-fitness"
77
ports:
88
- 5432:5432
99
environment:
10-
- POSTGRES_DB=links
10+
- POSTGRES_DB=fitness
1111

‎fitnessSchema.json

+1,673
Large diffs are not rendered by default.

‎package.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"babel-preset-env": "^1.1.8",
2727
"babel-preset-react": "^6.23.0",
2828
"babel-preset-stage-1": "^6.22.0",
29+
"babel-relay-plugin": "^0.11.0",
2930
"css-loader": "^0.26.1",
3031
"html-webpack-plugin": "^2.28.0",
3132
"nodemon": "^1.11.0",
@@ -45,10 +46,16 @@
4546
"graphql-relay": "^0.5.1",
4647
"humps": "^2.0.0",
4748
"lodash": "^4.17.4",
49+
"material-ui": "^0.17.0",
4850
"moment": "^2.17.1",
4951
"pg": "^6.1.2",
5052
"react": "^15.4.2",
53+
"react-countup": "^1.3.0",
5154
"react-dom": "^15.4.2",
52-
"react-router-dom": "next"
55+
"react-fontawesome": "^1.5.0",
56+
"react-relay": "^0.10.0",
57+
"react-router-dom": "next",
58+
"react-tap-event-plugin": "^2.0.1",
59+
"recharts": "^0.20.8"
5360
}
5461
}

‎postgres/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
FROM postgres:9.6
22

33
ADD schema.sql /
4-
ADD seed.sql /
4+
ADD seed.sql /

‎postgres/build.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/bin/bash
2-
docker build -t postgres:starter .
2+
docker build -t postgres:fitness .

‎postgres/config.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default {
22
development: {
3-
database: 'links',
3+
database: 'fitness',
44
user: 'postgres'
55
}
6-
}
6+
}

‎postgres/schema.sql

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
create table links (
1+
create table workouts (
22
id serial primary key,
3-
link varchar(255) not null,
4-
link_title varchar(255) not null,
5-
created_at timestamp not null
3+
workout varchar(128) not null,
4+
workout_date date not null,
5+
duration interval not null,
6+
calories int not null,
7+
fat_burn_time interval not null,
8+
fitness_time interval not null,
9+
avg_heart_rate int not null,
10+
max_heart_rate int not null,
11+
workout_type varchar(8) not null
612
);
13+

‎postgres/seed.sql

+52-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,53 @@
1-
insert into "links" (
2-
"link",
3-
"link_title",
4-
"created_at")
1+
insert into "workouts" ("workout",
2+
"workout_date",
3+
"duration",
4+
"calories",
5+
"fat_burn_time",
6+
"fitness_time",
7+
"avg_heart_rate",
8+
"max_heart_rate",
9+
"workout_type")
510
values
6-
('https://reacttraining.com/react-router/','React Router','2017-01-08 04:05:06 -8:00'),
7-
('https://facebook.github.io/relay/','Relay Docs','2017-01-09 05:28:33 -8:00'),
8-
('http://exploringjs.com/es6/index.html','Exploring ES6','2017-01-10 06:35:38 -8:00'),
9-
('https://www.postgresql.org/docs/9.6/static/datatype-datetime.html','Postgres Date Type','2017-01-11 02:01:19 -8:00');
11+
('CHEST_ABS','12/28/16','PT57M39S',481,'PT36M42S','PT20M48S',123,172,'RES'),
12+
('BACK_CALVES','12/30/16','PT58M33S',629,'PT32M37S','PT26M6S',139,179,'RES'),
13+
('CARDIO_ELLIPTICAL','12/28/16','PT27M46S',379,'PT2M19S','PT25M18S',161,190,'CARDIO'),
14+
('SHOULDER_ABS', '01/01/17', 'PT34M9S', 322, 'PT23M19S', 'PT11M10S', 129, 173, 'RES'),
15+
('INSANITY_PLYOMETRIC_CARDIO_CIRCUIT', '01/03/17', 'PT42M31S', 634, 'PT0M53S', 'PT41M30S', 169, 191, 'CARDIO'),
16+
('INSANITY_CARDIO_POWER_AND_RES', '01/04/17', 'PT39M46S', 599, 'PT1M16S', 'PT38M21S', 170, 193, 'CARDIO'),
17+
('INSANITY_CARDIO_RECOVERY', '01/05/17', 'PT52M17S', 385, 'PT35M50S', 'PT15M47S', 116, 163, 'CARDIO'),
18+
('INSANITY_PURE_CARDIO', '01/06/17', 'PT37M09S', 552, 'PT0M22S', 'PT36M32S', 169, 190, 'CARDIO'),
19+
('INSANITY_PLYOMETRIC_CARDIO_CIRCUIT', '01/07/17', 'PT41M38S', 611, 'PT0M24S', 'PT40M56S', 169, 188, 'CARDIO'),
20+
('LEGS', '01/07/17', 'PT23M32S', 271, 'PT6M16S', 'PT17M06S', 146, 181, 'RES'),
21+
('ARMS_ABS', '01/08/17', 'PT48M00S', 486, 'PT25M24S', 'PT22M27S', 135, 175, 'RES'),
22+
('INSANITY_CARDIO_POWER_AND_RES', '01/10/17', 'PT39M15S', 585, 'PT0M47S', 'PT38M28S', 169, 186, 'CARDIO'),
23+
('INSANITY_PLYOMETRIC_CARDIO_CIRCUIT', '01/17/17', 'PT41M55S', 585, 'PT3M45S', 'PT37M51S', 163, 184, 'CARDIO'),
24+
('INSANITY_INSANE_ABS', '01/18/17', 'PT31M03S', 307, 'PT24M36S', 'PT6M16S', 133, 168, 'CARDIO'),
25+
('INSANITY_PURE_CARDIO', '01/18/17', 'PT38M15S', 547, 'PT4M36S', 'PT33M37S', 165, 187, 'CARDIO'),
26+
('INSANITY_CARDIO_POWER_AND_RES', '01/20/17', 'PT39M15S', 567, 'PT0M46S', 'PT38M23S', 167, 188, 'CARDIO'),
27+
('INSANITY_PLYOMETRIC_CARDIO_CIRCUIT', '01/21/17', 'PT41M47S', 601, 'PT4M12S', 'PT37M26S', 166, 186, 'CARDIO'),
28+
('UPPER_CALVES', '01/21/17', 'PT1H4M7S', 569, 'PT55M07S', 'PT8M51S', 126, 170, 'RES'),
29+
('LEGS_FOUR_DAY', '01/22/17', 'PT57M27S', 593, 'PT19M30S', 'PT37M47S', 137, 172, 'RES'),
30+
('CARDIO_CYCLING', '01/23/17', 'PT47M09S', 432, 'PT20M26S', 'PT26M34S', 129, 155, 'CARDIO'),
31+
('INSANITY_PURE_CARDIO', '01/24/17', 'PT38M12S', 533, 'PT5M15S', 'PT32M49S', 163, 186, 'CARDIO'),
32+
('INSANITY_CARDIO_POWER_AND_RES', '01/25/17', 'PT39M14S', 547, 'PT2M40S', 'PT36M25S', 163, 182, 'CARDIO'),
33+
('INSANITY_PLYOMETRIC_CARDIO_CIRCUIT', '01/26/17', 'PT41M44S', 601, 'PT2M02S', 'PT39M39S', 166, 188, 'CARDIO'),
34+
('CARDIO_ELLIPTICAL', '01/27/17', 'PT36M19S', 389, 'PT8M22S', 'PT27M47S', 140, 163, 'CARDIO'),
35+
('INSANITY_PURE_CARDIO', '01/28/17', 'PT38M17S', 507, 'PT6M38S', 'PT31M26S', 159, 183, 'CARDIO'),
36+
('CHEST_TRI_DAY_4', '01/28/17', 'PT1H1M48S', 554, 'PT42M04S', 'PT19M35S', 127, 171, 'RES'),
37+
('INSANITY_PLYOMETRIC_CARDIO_CIRCUIT', '01/29/17', 'PT41M39S', 560, 'PT6M18S', 'PT35M09S', 160, 184, 'CARDIO'),
38+
('BACK_BI_DAY_4', '01/29/17', 'PT1H3M46S', 651, 'PT37M16S', 'PT25M54S', 136, 176, 'RES'),
39+
('INSANITY_CORE_CARDIO_AND_BALANCE', '01/31/17', 'PT37M26S', 431, 'PT9M33S', 'PT27M53S', 145, 174, 'CARDIO'),
40+
('INSANITY_CORE_CARDIO_AND_BALANCE', '02/01/17', 'PT36M59S', 435, 'PT7M39S', 'PT29M20S', 147, 175, 'CARDIO'),
41+
('INSANITY_CORE_CARDIO_AND_BALANCE', '02/02/17', 'PT37M07S', 466, 'PT7M46S', 'PT29M18S', 154, 184, 'CARDIO'),
42+
('LEGS_FOUR_DAY', '02/03/17', 'PT53M11S', 539, 'PT25M04S', 'PT28M00S', 135, 176, 'RES'),
43+
('INSANITY_MAX_INTERVAL_CIRCUIT', '02/06/17', 'PT1H0M13S', 786, 'PT10M59S', 'PT49M14S', 157, 183, 'CARDIO'),
44+
('INSANITY_MAX_INTERVAL_PLYO', '02/07/17', 'PT55M03S', 697, 'PT14M39S', 'PT40M24S', 154, 177, 'CARDIO'),
45+
('INSANITY_MAX_CARDIO_CONDITIONING', '02/08/17', 'PT47M47S', 630, 'PT9M25S', 'PT38M00S', 158, 179, 'CARDIO'),
46+
('CARDIO_CYCLING_DUMBELLS', '02/09/17', 'PT36M48S', 429, 'PT10M27S', 'PT26M11S', 147, 173, 'CARDIO'),
47+
('INSANITY_MAX_INTERVAL_CIRCUIT', '02/10/17', 'PT59M50S', 812, 'PT7M50S', 'PT51M55S', 161, 183, 'CARDIO'),
48+
('INSANITY_MAX_INTERVAL_PLYO', '02/14/17', 'PT55M05S', 755, 'PT8M04S', 'PT47M01S', 161, 184, 'CARDIO'),
49+
('INSANITY_MAX_INTERVAL_PLYO', '02/14/17', 'PT55M05S', 755, 'PT8M04S', 'PT47M01S', 161, 184, 'CARDIO'),
50+
('INSANITY_MAX_CARDIO_CONDITIONING', '02/15/17', 'PT47M31S', 573, 'PT15M16S', 'PT32M15S', 149, 181, 'CARDIO'),
51+
('INSANITY_MAX_INTERVAL_PLYO', '02/16/17', 'PT59M45S', 810, 'PT8M8S', 'PT51M36S', 160, 182, 'CARDIO'),
52+
('INSANITY_MAX_CARDIO_CONDITIONING', '02/18/17', 'PT47M44S', 644, 'PT5M9S', 'PT42M14S', 161, 185, 'CARDIO'),
53+
('UPPER_CALVES', '02/19/17', 'PT51M40S', 503, 'PT25M16S', 'PT26M24S', 132, 172, 'RES');

‎schema/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
GraphQLSchema,
3-
GraphQLObjectType
3+
GraphQLObjectType,
44
} from 'graphql'
55

66
import storeType from './types/store'
@@ -19,4 +19,4 @@ const RootSchema = new GraphQLSchema({
1919
mutation: mutations
2020
})
2121

22-
export default RootSchema
22+
export default RootSchema

‎schema/mutations/add-workout.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
GraphQLInputObjectType,
3+
GraphQLNonNull,
4+
GraphQLString,
5+
GraphQLInt
6+
} from 'graphql'
7+
8+
import {
9+
mutationWithClientMutationId
10+
} from 'graphql-relay'
11+
12+
import pgPool from '../../src/server/pgPool'
13+
14+
import pgdbCreator from '../../database/pgdb'
15+
import WorkoutConnectionType from '../types/workoutConnection'
16+
import storeType from '../types/store'
17+
18+
19+
const createWorkoutMutation = mutationWithClientMutationId({
20+
name: 'CreateWorkout',
21+
inputFields: {
22+
workout: { type: new GraphQLNonNull(GraphQLString) },
23+
workoutDate: { type: new GraphQLNonNull(GraphQLString) },
24+
duration: { type: new GraphQLNonNull(GraphQLString) },
25+
calories: { type: new GraphQLNonNull(GraphQLInt) },
26+
fatBurnTime: { type: new GraphQLNonNull(GraphQLString) },
27+
fitnessTime: { type: new GraphQLNonNull(GraphQLString) },
28+
avgHeartRate: { type: new GraphQLNonNull(GraphQLInt) },
29+
maxHeartRate: { type: new GraphQLNonNull(GraphQLInt) },
30+
workoutType: { type: new GraphQLNonNull(GraphQLString) }
31+
},
32+
outputFields: {
33+
workout: {
34+
type: WorkoutConnectionType.edgeType,
35+
resolve: (obj) => ({ node: obj, cursor: obj.id })
36+
},
37+
store: {
38+
type: storeType,
39+
resolve: () => ({})
40+
}
41+
},
42+
mutateAndGetPayload: (input) => {
43+
const pgdb = pgdbCreator(pgPool)
44+
return pgdb.addNewWorkout(input)
45+
}
46+
})
47+
48+
// const WorkoutInputType = new GraphQLInputObjectType({
49+
// name: 'WorkoutInput',
50+
// fields: {
51+
// workout: { type: new GraphQLNonNull(GraphQLString) },
52+
// workoutDate: { type: new GraphQLNonNull(GraphQLString) },
53+
// duration: { type: new GraphQLNonNull(GraphQLString) },
54+
// calories: { type: new GraphQLNonNull(GraphQLInt) },
55+
// fatBurnTime: { type: new GraphQLNonNull(GraphQLString) },
56+
// fitnessTime: { type: new GraphQLNonNull(GraphQLString) },
57+
// avgHeartRate: { type: new GraphQLNonNull(GraphQLInt) },
58+
// maxHeartRate: { type: new GraphQLNonNull(GraphQLInt) },
59+
// workoutType: { type: new GraphQLNonNull(GraphQLString) }
60+
// }
61+
// })
62+
//
63+
// const WorkoutInput = {
64+
// type: WorkoutType,
65+
// args: {
66+
// input: { type: new GraphQLNonNull(WorkoutInputType) }
67+
// },
68+
// resolve(obj, { input }, { pgPool }) {
69+
// const pgdb = pgdbCreator(pgPool)
70+
// return pgdb.addNewWorkout(input)
71+
// }
72+
// }
73+
74+
export default createWorkoutMutation

‎schema/mutations/addLink.js

-40
This file was deleted.

‎schema/mutations/deleteLink.js

-41
This file was deleted.

‎schema/mutations/index.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ import {
22
GraphQLObjectType
33
} from 'graphql'
44

5-
import CreateLinkMutation from './addLink'
6-
import DeleteLinkMutation from './deleteLink'
7-
import UpdateLinkMutation from './updateLink'
5+
import AddWorkoutMutation from './add-workout'
86

97
const RootMutationType = new GraphQLObjectType({
108
name: 'RootMutationType',
119
fields: () => ({
12-
AddLink: CreateLinkMutation,
13-
DeleteLink: DeleteLinkMutation,
14-
UpdateLink: UpdateLinkMutation
10+
AddWorkout: AddWorkoutMutation
1511
})
1612
})
1713

‎schema/mutations/updateLink.js

-39
This file was deleted.

‎schema/types/duration.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {
2+
GraphQLObjectType,
3+
GraphQLInt
4+
} from 'graphql'
5+
6+
const DurationType = new GraphQLObjectType({
7+
name: 'DurationType',
8+
fields: {
9+
minutes: { type: GraphQLInt },
10+
seconds: { type: GraphQLInt },
11+
hours: { type: GraphQLInt }
12+
}
13+
})
14+
15+
export default DurationType

‎schema/types/link.js

-18
This file was deleted.

‎schema/types/linkConnection.js

-12
This file was deleted.

‎schema/types/store.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
1-
import {
2-
GraphQLObjectType,
3-
GraphQLList
4-
} from 'graphql'
1+
import workoutConnection from './workoutConnection'
2+
import pgdbCreator from '../../database/pgdb'
53
import {
64
connectionArgs,
75
connectionFromPromisedArray,
86
globalIdField
97
} from 'graphql-relay'
8+
import {
9+
GraphQLObjectType,
10+
GraphQLString,
11+
} from 'graphql'
1012

11-
import linkConnection from './linkConnection'
12-
import pgdbCreator from '../../database/pgdb'
1313

1414
const storeType = new GraphQLObjectType({
1515
name: 'Store',
1616
fields: {
1717
id: globalIdField("Store"),
18-
linkConnection: {
19-
type: linkConnection.connectionType,
20-
args: connectionArgs,
21-
resolve: (obj, args, { pgPool }) => {
18+
workoutConnection: {
19+
type: workoutConnection.connectionType,
20+
args: {
21+
...connectionArgs,
22+
date: { type: GraphQLString },
23+
query: { type: GraphQLString }
24+
},
25+
resolve: (obj, args, { unused, pgPool }) => {
2226
const pgdb = pgdbCreator(pgPool)
23-
return connectionFromPromisedArray(pgdb.getLinks(), args)
27+
const promisedArray = args.date ? pgdb.getWorkoutsByDate(args.date) : pgdb.getWorkouts()
28+
return connectionFromPromisedArray(promisedArray, args)
2429
}
2530
}
2631
}

‎schema/types/workout.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {
2+
GraphQLObjectType,
3+
GraphQLString,
4+
GraphQLInt,
5+
GraphQLID,
6+
GraphQLNonNull
7+
} from 'graphql'
8+
import DurationType from './duration'
9+
10+
const WorkoutType = new GraphQLObjectType({
11+
name: 'WorkoutType',
12+
fields: {
13+
id: { type: GraphQLID },
14+
workout: { type: new GraphQLNonNull(GraphQLString) },
15+
workoutDate: { type: new GraphQLNonNull(GraphQLString) },
16+
duration: { type: new GraphQLNonNull(DurationType) },
17+
calories: { type: new GraphQLNonNull(GraphQLInt) },
18+
fatBurnTime: { type: new GraphQLNonNull(DurationType) },
19+
fitnessTime: { type: new GraphQLNonNull(DurationType) },
20+
avgHeartRate: { type: new GraphQLNonNull(GraphQLInt) },
21+
maxHeartRate: { type: new GraphQLNonNull(GraphQLInt) },
22+
workoutType: { type: new GraphQLNonNull(GraphQLString) }
23+
}
24+
})
25+
26+
export default WorkoutType

‎schema/types/workoutConnection.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {
2+
connectionDefinitions,
3+
} from 'graphql-relay'
4+
import WorkoutType from './workout'
5+
6+
const workoutConnection = connectionDefinitions({
7+
name: 'Workout',
8+
nodeType: WorkoutType
9+
})
10+
11+
export default workoutConnection
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import React, { Component } from 'react'
2+
import Relay from 'react-relay'
3+
import { Card, CardHeader, CardText } from 'material-ui/Card'
4+
import { PieChart, Pie, Tooltip, Cell } from 'recharts'
5+
import { heartContent, workoutContents, calStyle, timeStyle, heartStyle, hbStyle } from './styles.css'
6+
import CountUp from 'react-countup'
7+
import FontAwesome from 'react-fontawesome'
8+
9+
10+
function getTimeInMinutes({ hours, minutes }) {
11+
return (hours * 60) + minutes
12+
}
13+
14+
function getTotalTimeString({ hours, minutes, seconds }) {
15+
if (!hours) {
16+
hours = 0
17+
}
18+
return `${hours} h ${minutes} m ${seconds} s`
19+
}
20+
21+
class WorkoutCard extends Component {
22+
23+
render() {
24+
const { workout } = this.props
25+
const fitnessTime = getTimeInMinutes(workout.fitnessTime)
26+
const fatBurnTime = getTimeInMinutes(workout.fatBurnTime)
27+
const timeData = [{ name: 'Fitness Time', value: fitnessTime }, { name: 'Fat Burn Time', value: fatBurnTime }]
28+
const colors = ['orangered', 'darkorange']
29+
const totalTime = getTotalTimeString(workout.duration)
30+
const avgAndMax = `${workout.avgHeartRate} / ${workout.maxHeartRate}`
31+
32+
33+
return (
34+
<Card>
35+
<CardHeader
36+
title={workout.workout}
37+
subtitle={workout.workoutType}
38+
avatar={
39+
<FontAwesome
40+
name={workout.workoutType === "RES" ? 'fire' : 'bolt'}
41+
size='2x'
42+
/>
43+
}
44+
actAsExpander={true}
45+
showExpandableButton={true}
46+
/>
47+
<CardText expandable={true}>
48+
<div className={workoutContents}>
49+
<div>
50+
<CountUp
51+
className={calStyle}
52+
start={0}
53+
end={workout.calories}
54+
duration={1.6}
55+
useEasing={true}
56+
suffix=" Cal"
57+
/>
58+
</div>
59+
<div className={heartContent}>
60+
<FontAwesome
61+
className={heartStyle}
62+
name='heartbeat'
63+
size='3x'
64+
/>
65+
<span className={hbStyle}>
66+
{ avgAndMax}
67+
</span>
68+
</div>
69+
<PieChart width={300} height={250}>
70+
<Pie isAnimationActive={true} data={timeData} cx={150} cy={125} outerRadius={80} fill="#82ca9d" label>
71+
{
72+
timeData.map((entry, index) => (
73+
<Cell key={`cell-${index}`} fill={colors[index]}/>
74+
))
75+
}
76+
</Pie>
77+
<Tooltip/>
78+
</PieChart>
79+
<div className={timeStyle}>
80+
{totalTime}
81+
</div>
82+
</div>
83+
</CardText>
84+
</Card> )
85+
}
86+
}
87+
88+
89+
export default Relay.createContainer(WorkoutCard, {
90+
fragments: {
91+
workout: () => Relay.QL`
92+
fragment on WorkoutType {
93+
workout
94+
workoutDate
95+
calories
96+
duration {
97+
minutes
98+
seconds
99+
hours
100+
}
101+
fatBurnTime {
102+
minutes
103+
seconds
104+
hours
105+
}
106+
fitnessTime {
107+
minutes
108+
seconds
109+
hours
110+
}
111+
avgHeartRate
112+
maxHeartRate
113+
workoutType
114+
}`
115+
}
116+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
.workoutContents {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
}
6+
7+
.calStyle {
8+
font-family: 'Droid Serif', serif;
9+
font-size: 3em;
10+
}
11+
12+
.timeStyle {
13+
font-family: 'Open Sans', sans-serif;
14+
font-size: 1.6em;
15+
}
16+
17+
.heartStyle {
18+
color: red;
19+
margin-right: 20px;
20+
}
21+
22+
.heartContent {
23+
width: 100%;
24+
display: flex;
25+
align-items: center;
26+
justify-content: center;
27+
}
28+
29+
.hbStyle {
30+
font-family: 'Open Sans', sans-serif;
31+
font-size: 1.6em;
32+
color: firebrick;
33+
}

‎src/client/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react'
22
import ReactDOM from 'react-dom'
33
import App from './views'
4+
import injectTapEventPlugin from 'react-tap-event-plugin'
5+
injectTapEventPlugin();
46

57
ReactDOM.render(<App/>, document.getElementById('app'))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Relay from 'react-relay'
2+
3+
class CreateWorkoutMutation extends Relay.Mutation {
4+
getMutation() {
5+
return Relay.QL`
6+
mutation { AddWorkout }
7+
`
8+
}
9+
10+
getVariables() {
11+
return {
12+
workout: this.props.workout,
13+
workoutDate: this.props.workoutDate,
14+
duration: this.props.duration,
15+
calories: this.props.calories,
16+
fatBurnTime: this.props.fatBurnTime,
17+
fitnessTime: this.props.fitnessTime,
18+
avgHeartRate: this.props.avgHeartRate,
19+
maxHeartRate: this.props.maxHeartRate,
20+
workoutType: this.props.workoutType,
21+
}
22+
}
23+
24+
getFatQuery() {
25+
return Relay.QL`
26+
fragment on CreateWorkoutPayload {
27+
workout,
28+
store { workoutConnection }
29+
}
30+
`
31+
}
32+
33+
getConfigs() {
34+
return [{
35+
type: 'RANGE_ADD',
36+
parentName: 'store',
37+
parentID: this.props.store.id,
38+
connectionName: 'workoutConnection',
39+
edgeName: 'workout',
40+
rangeBehaviors: {
41+
'': 'append'
42+
}
43+
}]
44+
45+
46+
}
47+
48+
}
49+
50+
export default CreateWorkoutMutation

‎src/client/views/Fitness/index.js

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React, { Component } from 'react'
2+
import Relay from 'react-relay'
3+
import styles from './styles.css'
4+
import WorkoutCard from '../../components/WorkoutCard'
5+
import CreateWorkoutMutation from '../../mutations/CreateWorkoutMutation'
6+
7+
class FitnessRoute extends Relay.Route {
8+
static routeName = 'Fitness'
9+
static queries = {
10+
store: (Component) => Relay.QL`
11+
query MainQuery {
12+
store { ${Component.getFragment('store')} }
13+
}
14+
`
15+
}
16+
}
17+
18+
19+
class Fitness extends Component {
20+
21+
handleSubmit = (e) => {
22+
e.preventDefault()
23+
Relay.Store.update(
24+
new CreateWorkoutMutation({
25+
workout: this.refs.Workout.value,
26+
workoutDate: this.refs.WorkoutDate.value,
27+
duration: this.refs.Duration.value,
28+
calories: this.refs.Calories.value,
29+
fatBurnTime: this.refs.FatBurnTime.value,
30+
fitnessTime: this.refs.FitnessTime.value,
31+
avgHeartRate: this.refs.AvgHeartRate.value,
32+
maxHeartRate: this.refs.MaxHeartRate.value,
33+
workoutType: this.refs.WorkoutType.value,
34+
store: this.props.store
35+
})
36+
)
37+
}
38+
39+
40+
render() {
41+
return (
42+
<div className={styles.fitnessBody}>
43+
<div className={styles.fitnessContent}>
44+
<div className={styles.mainText}>
45+
<form onSubmit={this.handleSubmit}>
46+
<input type="text" placeholder="Workout" ref="Workout"/>
47+
<input type="text" placeholder="WorkoutDate" ref="WorkoutDate"/>
48+
<input type="text" placeholder="Duration" ref="Duration"/>
49+
<input type="text" placeholder="Calories" ref="Calories"/>
50+
<input type="text" placeholder="FatBurnTime" ref="FatBurnTime"/>
51+
<input type="text" placeholder="FitnessTime" ref="FitnessTime"/>
52+
<input type="text" placeholder="AvgHeartRate" ref="AvgHeartRate"/>
53+
<input type="text" placeholder="MaxHeartRate" ref="MaxHeartRate"/>
54+
<input type="text" placeholder="WorkoutType" ref="WorkoutType"/>
55+
<button type="submit">Add Workout</button>
56+
</form>
57+
{
58+
this.props.store.workoutConnection.edges.map((edge) => <WorkoutCard key={edge.node.id}
59+
id={edge.node.id}
60+
workout={edge.node}/>)
61+
}
62+
</div>
63+
</div>
64+
</div>
65+
)
66+
}
67+
}
68+
69+
const FitnessContainer = Relay.createContainer(Fitness, {
70+
initialVariables: {
71+
limit: 100,
72+
},
73+
fragments: {
74+
store: () => Relay.QL`
75+
fragment on Store {
76+
id,
77+
workoutConnection(first: $limit) {
78+
edges {
79+
node {
80+
id
81+
${WorkoutCard.getFragment('workout')}
82+
}
83+
}
84+
}
85+
}
86+
`
87+
}
88+
})
89+
90+
91+
export {
92+
FitnessContainer,
93+
FitnessRoute
94+
}
95+

‎src/client/views/Fitness/styles.css

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@media (max-width: 1005px) {
2+
.fitnessBody {
3+
height: calc(100% - 64px);
4+
min-height: 100vh;
5+
}
6+
}
7+
8+
.fitnessBody {
9+
display: flex;
10+
flex-grow: 1;
11+
background-color: #E8DDB5;
12+
flex-direction: column;
13+
}
14+
15+
.fitnessContent {
16+
display: flex;
17+
flex-direction: column;
18+
align-items: center;
19+
}
20+
21+
@media (max-width: 530px) {
22+
.mainText {
23+
margin-top: 1.5rem;
24+
}
25+
}
26+
27+
.mainText {
28+
max-width: 45rem;
29+
margin-left: 2rem;
30+
margin-right: 2rem;
31+
margin-bottom: 1.5rem;
32+
color: #373a3c;
33+
font-family: 'Open Sans', sans-serif;
34+
font-size: 1.2rem;
35+
line-height: 2.2rem;
36+
}

‎src/client/views/index.js

+14-39
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,26 @@
11
import React, { Component } from 'react'
2+
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
3+
import Relay from 'react-relay'
24
import {
35
BrowserRouter as Router,
46
Route
57
} from 'react-router-dom'
6-
7-
class Home extends Component {
8-
9-
// constructor(props) {
10-
// super(props)
11-
// this.handleClick = this.handleClick.bind(this)
12-
// }
13-
14-
// async handleClick(event) {
15-
// try {
16-
// const moment = await import('moment')
17-
// console.log(moment().format('LLLL'))
18-
// } catch(err) {
19-
// console.log(err)
20-
// }
21-
// }
22-
state = {
23-
numClicks: 0
24-
}
25-
26-
handleClick = () => {
27-
this.setState({ numClicks: ++this.state.numClicks })
28-
}
29-
30-
render() {
31-
return (
32-
<div>
33-
<button onClick={this.handleClick}>
34-
Click Me
35-
</button>
36-
<div>
37-
{this.state.numClicks}
38-
</div>
39-
</div>)
40-
}
41-
}
8+
import { FitnessContainer, FitnessRoute } from './Fitness'
429

4310
class App extends Component {
4411
render() {
4512
return (
46-
<Router>
47-
<Route exact path="/" component={Home}/>
48-
</Router>
13+
<MuiThemeProvider>
14+
<Router>
15+
<Route exact path="/" children={({ match }) => {
16+
return match ?
17+
<Relay.RootContainer
18+
Component={FitnessContainer}
19+
route={new FitnessRoute()}
20+
/> : null
21+
}}/>
22+
</Router>
23+
</MuiThemeProvider>
4924
)
5025
}
5126
}

‎src/server/index.html

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
<head>
55
<meta charset="utf-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css" rel="stylesheet">
8+
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
9+
<link href="https://fonts.googleapis.com/css?family=Droid+Serif" rel="stylesheet">
10+
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
711
</head>
812

913
<body>

‎yarn.lock

+298-15
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.