229 | );
230 | }
231 | }
232 |
233 | AddItemPage.propTypes = {
234 | close: PropTypes.func,
235 | openClass: PropTypes.string,
236 | addItem: PropTypes.func.isRequired
237 | };
238 |
239 | export default AddItemPage;
240 |
241 |
--------------------------------------------------------------------------------
/src/components/AddItemPage/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | $base-line-height: 24px
4 | $white: #eee
5 | $spin-duration: 3s
6 | $pulse-duration: 1000ms
7 |
8 | @keyframes pulse
9 | 50%
10 | background: $white
11 |
12 | .addItemWrapper
13 | z-index: 9999
14 | .hider
15 | position: fixed
16 | top: 0
17 | left: 0
18 | width: 100%
19 | height: 100%
20 | z-index: 999999
21 | +hider-gradient
22 | opacity: 0
23 | transition: opacity .5s cubic-bezier(.82,0,.12,1)
24 |
25 | .modal
26 | overflow: hidden
27 | background: $modal-background
28 | max-width: 900px
29 | height: 85vh
30 | width: 90vw
31 | position: fixed
32 | top: 50%
33 | left: 50%
34 | border: solid 1px $modal-border-color
35 | box-shadow: 0 4px 29px rgba(0,0,0,.22), 0 0 0 1px rgba(0,0,0,.04)
36 | transform: translate(-50%, 50%)
37 | z-index: 9999999
38 | box-sizing: border-box
39 | opacity: 0
40 | transition: transform .5s cubic-bezier(.82,0,.12,1), opacity .5s cubic-bezier(.82,0,.12,1)
41 |
42 | .wmWrapper
43 | position: absolute
44 | width: 100%
45 | height: 100%
46 | background: rgba(252, 252, 252, .95)
47 | z-index: 99999999999
48 | div
49 | position: relative
50 | width: 100%
51 | height: 100%
52 | h3
53 | width: auto
54 | position: absolute
55 | top: 30%
56 | left: 50%
57 | width: 80%
58 | transform: translateX(-50%)
59 | color: $black
60 | padding: 0
61 | div
62 | position: absolute
63 | top: 50%
64 | left: 50%
65 | transform: translate(-50%, -50%)
66 | div
67 | display: inline-block
68 | position: relative
69 | width: ($base-line-height / 4)
70 | height: $base-line-height
71 | background: $black
72 | animation: pulse $pulse-duration infinite
73 | animation-delay: ($pulse-duration / 3)
74 | border-radius: 2px
75 | &:before, &:after
76 | border-radius: 2px
77 | content: ''
78 | position: absolute
79 | display: block
80 | height: ($base-line-height / 1.5)
81 | width: ($base-line-height / 4)
82 | background: $black
83 | top: 50%
84 | transform: translateY(-50%)
85 | animation: pulse $pulse-duration infinite
86 |
87 | &:before
88 | left: -($base-line-height / 2)
89 |
90 | &:after
91 | left: ($base-line-height / 2)
92 | animation-delay: ($pulse-duration / 1.5)
93 |
94 |
95 | .heading
96 | top: 0
97 | left: 0
98 | position: fixed
99 | width: 100%
100 | z-index: 999
101 |
102 | h3
103 | font-size: 1.3rem
104 | padding-bottom: .5rem
105 | border-bottom: solid 2px transparent
106 | text-align: center
107 | padding: .7rem 1.5rem
108 | border-bottom: solid 2px $modal-border-color
109 | width: 100%
110 |
111 | .priceWrapper
112 | display: flex
113 | .inputWrapper
114 | flex: 1
115 | .input
116 | width: 100%
117 | &:first-child
118 | margin-right: 1rem
119 | &:last-child
120 | margin-left: 1rem
121 |
122 | .itemWrapper
123 | overflow-y: auto
124 | height: calc(85vh - 137px)
125 | margin-top: 50px
126 | overflow-y: auto
127 | padding: 2.5rem 1.5rem 0 1.5rem
128 | .errorMsgWrapper
129 | display: flex
130 | .flexWrapper
131 | display: flex
132 | flex-direction: column
133 | .itemPicWrapper
134 | flex: 1
135 | position: relative
136 | #itemPicInput
137 | width: 0.1px
138 | height: 0.1px
139 | opacity: 0
140 | overflow: hidden
141 | position: absolute
142 | left: 46%
143 | z-index: -1
144 | .img
145 | margin: auto
146 | display: block
147 | height: 5rem
148 | width: 5rem
149 | border-radius: 50%
150 | cursor: pointer
151 | background: url('../../assets/images/fileUpload.svg') no-repeat
152 | background-size: 100% 100%
153 | background-position: 50% 50%
154 | border: solid 2rem #eee
155 | transition: background-image .2s
156 | .imgLoaded
157 | border: none
158 | width: 9rem
159 | height: 9rem
160 | background-size: 100% 100% !important
161 | background-position: 50% 50% !important
162 | .imgLoadErrors
163 | font-weight: 700
164 | color: $red
165 |
166 | .imgText
167 | color: $black
168 | font-weight: 500
169 | font-size: .9rem
170 | text-align: center
171 | display: inline-block
172 | cursor: pointer
173 | margin: 2rem 0
174 | line-height: 1.5
175 | padding: 0 10px
176 | position: relative
177 | span
178 | color: $light-grey
179 | position: relative
180 | &:before
181 | content: '*'
182 | position: absolute
183 | top: -5px
184 | font-size: .9rem
185 | left: -15px
186 | .itemInfoWrapper
187 | flex: 2
188 | margin-top: 2rem
189 | .inputWrapper
190 | .itemDescription, .itemTags
191 | padding: .5rem 1rem
192 | resize: none
193 | height: 8rem
194 | box-sizing: border-box
195 | width: 100% !important
196 | .itemTags
197 | margin-bottom: 2.5rem
198 | h3.fullWidthFlexItem
199 | border: none
200 | margin: 0 0 2rem 0
201 | color: #fafafa
202 | background: lighten($red, 20%)
203 | border-left: solid 4px $red
204 | font-weight: 700 !important
205 | letter-spacing: 1px
206 | font-size: 1.1rem
207 | text-align: left
208 | width: 100%
209 | .buttonWrapper
210 | display: flex
211 | z-index: 999
212 | button
213 | flex: 1
214 | height: 50px
215 | padding: 0
216 | margin: 0
217 | font-size: .9rem
218 | color: $light-grey
219 | font-weight: 700
220 | text-transform: uppercase
221 | background: none
222 | transition: color .3s
223 | cursor: pointer
224 | border-top: solid 2px $modal-border-color
225 | &:last-child
226 | border-left: solid 2px $modal-border-color
227 | &:hover
228 | color: $black
229 | cursor: pointer
230 | &:active
231 | color: $black
232 |
233 | .addItemWrapper.open
234 | .hider
235 | opacity: 1
236 | .modal
237 | transform: translate(-50%, -50%)
238 | opacity: 1
239 |
--------------------------------------------------------------------------------
/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
3 |
4 | import Header from '../../containers/Header/index';
5 | import Footer from '../Footer/index';
6 | import './styles.sass';
7 | import '../../styles/animation.sass';
8 |
9 | class App extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | userLoggedIn: false
14 | };
15 | }
16 |
17 | getContent() {
18 | const mainContent = (
19 |
31 | );
32 |
33 | return mainContent;
34 | }
35 |
36 | render() {
37 | return this.getContent();
38 | }
39 | }
40 |
41 | App.propTypes = {
42 | children: PropTypes.element,
43 | location: PropTypes.object,
44 | "location.pathname": PropTypes.string
45 | };
46 |
47 | export default App;
48 |
--------------------------------------------------------------------------------
/src/components/App/styles.sass:
--------------------------------------------------------------------------------
1 | .wrapper
2 | padding: 20px
3 | overflow-x: hidden
4 |
--------------------------------------------------------------------------------
/src/components/BasicInfo/index.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | import './styles.sass';
4 | import userImage from '../../assets/images/user.svg';
5 |
6 | const BasicInfo = ({data}) => {
7 | return(
8 |
16 | );
17 | };
18 |
19 | BasicInfo.propTypes = {
20 | data: PropTypes.object.isRequired
21 | };
22 |
23 | export default BasicInfo;
24 |
--------------------------------------------------------------------------------
/src/components/BasicInfo/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .basicInfo
4 | flex: 1
5 | margin-bottom: 40px
6 |
7 | .profilePic
8 | width: 120px
9 | height: 120px
10 | border-radius: 50%
11 | background: #eee
12 | margin: 1rem auto 2rem auto
13 | img
14 | background-image: url('../../assets/images/plain.png')
15 | width: 120px
16 | height: 120px
17 | border-radius: 50%
18 | transition: background .2s
19 | background-position: 50%, 50%
20 | background-size: 100% 100%
21 |
22 |
23 | .nameWrapper
24 | h3
25 | text-align: center
26 | font-size: 1.3rem
27 |
--------------------------------------------------------------------------------
/src/components/ErrorPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import './styles.sass';
4 | import loadPageProps from '../../utils/loadPageProps';
5 |
6 |
7 | class ErrorPage extends Component {
8 | componentDidMount() {
9 | loadPageProps('404 - Page Not Found!');
10 | }
11 | render() {
12 | return (
13 |
14 | );
15 | }
16 | }
17 |
18 | export default ErrorPage;
19 |
--------------------------------------------------------------------------------
/src/components/ErrorPage/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | h3.error
4 | margin: 18vh 0
5 | font-weight: 500
6 | font-size: 2.5rem
7 | color: lighten($light-grey, 20%)
8 | text-transform: none
9 |
--------------------------------------------------------------------------------
/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './styles.sass';
3 |
4 | class Footer extends Component {
5 | render() {
6 | return (
7 |
12 | );
13 | }
14 | }
15 |
16 | export default Footer;
17 |
--------------------------------------------------------------------------------
/src/components/Footer/styles.sass:
--------------------------------------------------------------------------------
1 | .footer
2 | padding: 80px 0 20px 0
3 |
--------------------------------------------------------------------------------
/src/components/Item/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { browserHistory } from 'react-router';
3 |
4 | import './styles.sass';
5 |
6 | class Item extends Component {
7 |
8 | openItem(url) {
9 | browserHistory.push(url);
10 | }
11 |
12 | render() {
13 | const {name, price, itemId, pic} = this.props;
14 | const url = `/item/${itemId}`;
15 | const openItem = this.openItem.bind(this, url);
16 |
17 | return(
18 |
this.item = node}
20 | onClick={openItem}
21 | >
22 |
this.pic = node}
24 | data-bg={`${pic}`}
25 | />
26 |
27 |
28 | {name}
29 |
30 |
31 |
32 | {price}
33 |
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | Item.propTypes = {
41 | name: PropTypes.string.isRequired,
42 | price: PropTypes.string.isRequired,
43 | itemId: PropTypes.number.isRequired,
44 | pic: PropTypes.string.isRequired
45 | };
46 |
47 | export default Item;
48 |
--------------------------------------------------------------------------------
/src/components/Item/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .item
4 | flex: 0 1 48%
5 | align-items: stretch
6 | transition: opacity .3s, filter .3s
7 | cursor: pointer
8 | &:hover
9 | cursor: pointer
10 | opacity: .8
11 | &:empty
12 | height: 0
13 | .itemName
14 | margin-top: .7rem
15 | overflow: hidden
16 | color: $black
17 | font-size: .9rem
18 | display: inline-block
19 | .itemCost
20 | color: lighten($black, 20%)
21 | font-size: 1rem
22 | margin: 0 0 1.5rem 0
23 | line-height: 1
24 | letter-spacing: 1px
25 | font-weight: 500
26 | font-size: .9rem
27 | display: inline-block
28 | .content
29 | background-image: url('../../assets/images/plain.png')
30 | cursor: pointer
31 | padding-bottom: 100%
32 | transition: background-image .2s
33 | .content, .itemCost, .itemName
34 | &, &:hover
35 | cursor: pointer
36 |
--------------------------------------------------------------------------------
/src/components/Login/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | import './styles.sass';
4 | import loadPageProps from '../../utils/loadPageProps';
5 |
6 | class Login extends Component {
7 | componentDidMount() {
8 | loadPageProps('Login - Trader');
9 | }
10 |
11 | render() {
12 | let last_item = this.props.location.query.l_i;
13 | let query = "";
14 | if(last_item) {
15 | query = `?l_i=${last_item}`;
16 | }
17 | return (
18 |
19 |
Login with your social account to add trade items or propose a trade
20 |
25 |
26 | );
27 | }
28 | }
29 |
30 | Login.propTypes = {
31 | location: PropTypes.object.isRequired
32 | };
33 |
34 | export default Login;
35 |
--------------------------------------------------------------------------------
/src/components/Login/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .loginWrapper
4 | min-height: 40vh
5 | margin: 1rem auto 2rem auto
6 | .loginHeading
7 | margin: 0 auto 4rem auto
8 | max-width: 570px
9 | line-height: 1.7
10 | .btnWrapper
11 | margin: 2rem 0 0 0
12 | display: flex
13 | flex-direction: column
14 | justify-content: flex-start
15 | a
16 | margin: 0 auto 2rem auto
17 | width: 100%
18 | max-width: 400px
19 | button
20 | flex: 0
21 | width: 100%
22 | padding: .8rem 1rem
23 | transition: all .2s
24 | font-size: .8rem
25 | font-weight: 700
26 | background: transparent
27 | border: solid 2px
28 | opacity: .75
29 | text-transform: uppercase
30 | &:hover
31 | cursor: pointer
32 | opacity: 1
33 | &:active
34 | transform: translateY(2px)
35 | &:focus
36 | opacity: .75
37 |
38 | .fbBtn
39 | color: $fbColor
40 | .twitterBtn
41 | color: $twitterColor
42 | .googleBtn
43 | color: $googleColor
44 |
--------------------------------------------------------------------------------
/src/components/OtherInfo/index.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 | import objectAssign from 'object-assign';
3 | import './styles.sass';
4 |
5 | class OtherInfo extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | locationEditing: false,
10 | contactEditing: false
11 | };
12 | }
13 |
14 | componentWillReceiveProps(props) {
15 | if(JSON.stringify(this.props) !== JSON.stringify(props)) {
16 | const newAddress = objectAssign({}, props.data.address);
17 | if(JSON.stringify(newAddress) !== JSON.stringify(this.props.data.address)) {
18 | this.locationUpdated = true;
19 | } else {
20 | this.contactUpdated = true;
21 | }
22 | }
23 | }
24 |
25 | handleCFSubmit(event) {
26 | event.preventDefault();
27 | const email = document.getElementById('email').value;
28 | const phoneNo = document.getElementById('phoneNo').value;
29 | this.props.updateProfileState({ email, phoneNo }, "contact");
30 | this.setState({ contactEditing: false });
31 | }
32 |
33 | handleLFSubmit(event) {
34 | event.preventDefault();
35 | const localAddress = document.getElementById('localAddress').value;
36 | const state = document.getElementById('state').value;
37 | const city = document.getElementById('city').value;
38 | const landmark = document.getElementById('landmark').value;
39 | const country = document.getElementById('country').value;
40 | const pinCode = document.getElementById('pinCode').value;
41 | this.props.updateProfileState({ localAddress, state, city, landmark, country, pinCode }, "location");
42 | this.setState({ locationEditing: false });
43 | }
44 |
45 | getLocationData() {
46 | const { localAddress, city, state, landmark, country, pinCode} = this.props.data.address;
47 | if (this.state.locationEditing) {
48 | return (
49 |
77 | );
78 | } else {
79 | const updatedClass = this.locationUpdated ? 'updated' : '';
80 | this.locationUpdated = false;
81 | return (
82 |
83 |
84 |
Local Address:
85 |
{localAddress ? localAddress : "Local Address"}
86 |
87 |
88 |
City:
89 |
{city ? city : "City"}
90 |
91 |
92 |
State:
93 |
{state ? state : "State"}
94 |
95 |
96 |
Landmark:
97 |
{landmark ? landmark : "Landmark"}
98 |
99 |
100 |
Country:
101 |
{country ? country : "Country"}
102 |
103 |
104 |
Pin Code:
105 |
{pinCode ? pinCode : "Pincode"}
106 |
107 |
108 | );
109 | }
110 | }
111 |
112 | getContactData() {
113 | const { email, phoneNo } = this.props.data;
114 | if (this.state.contactEditing) {
115 | return (
116 |
128 | );
129 | } else {
130 | const updatedClass = this.contactUpdated ? 'updated' : '';
131 | this.contactUpdated = false;
132 | return (
133 |
134 |
135 |
Email:
136 |
{email ? email : "Email"}
137 |
138 |
139 |
Phone No:
140 |
{phoneNo ? phoneNo : "Phone No"}
141 |
142 |
143 | );
144 | }
145 | }
146 |
147 | getButtons(info) {
148 | if (!this.state.locationEditing && info === 'LOCATION') {
149 | return (
150 |
{
152 | this.setState({ locationEditing: true });
153 | }}>
154 | Edit
155 |
156 | );
157 | } else if (!this.state.contactEditing && info === 'CONTACT') {
158 | return (
159 |
{
161 | this.setState({ contactEditing: true });
162 | }}>
163 | Edit
164 |
165 | );
166 | } else {
167 | let buttons;
168 | switch (info) {
169 | case 'LOCATION':
170 | buttons = ([
171 |
{
174 | // this will trigger synthetic form submission event
175 | // refs can't be used here as they will trigger native event
176 | // and then page will reload
177 | this.submitLFBtn.click();
178 | // dont put setState here, let the form first submit
179 | // and then change state
180 | }}>
181 | Save
182 | ,
183 |
{
186 | this.setState({ locationEditing: false });
187 | }}>
188 | Cancel
189 |
190 | ]);
191 |
192 | break;
193 | case 'CONTACT':
194 | buttons = ([
195 |
{
198 | // this will trigger synthetic form submission event
199 | // refs can't be used here as they will trigger native event
200 | // and then page will reload
201 | this.submitCFBtn.click();
202 | // dont put setState here, let the form first submit
203 | // and then change state
204 | }}>
205 | Save
206 | ,
207 |
{
210 | this.setState({ contactEditing: false });
211 | }}>
212 | Cancel
213 |
214 | ]);
215 | break;
216 | }
217 | return buttons;
218 | }
219 | }
220 |
221 | render() {
222 | return (
223 |
224 |
225 |
226 |
Location Info
227 | {this.getButtons('LOCATION')}
228 |
229 | {this.getLocationData()}
230 |
231 |
232 |
233 |
Contact Info
234 | {this.getButtons('CONTACT')}
235 |
236 | {this.getContactData()}
237 |
238 |
239 | );
240 | }
241 | }
242 |
243 | OtherInfo.propTypes = {
244 | data: PropTypes.object.isRequired,
245 | updateProfileState: PropTypes.func.isRequired
246 | };
247 |
248 | export default OtherInfo;
249 |
--------------------------------------------------------------------------------
/src/components/OtherInfo/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .otherInfo
4 | flex: 3
5 |
6 | .lIWrapper, .cIWrapper
7 | display: flex
8 | flex-wrap: wrap
9 | justify-content: space-between
10 |
11 | & > *
12 | flex: 0 1 calc(50% - 1rem)
13 |
14 | .contactInfo
15 | margin-top: 2rem
16 | padding-bottom: 1rem
17 |
18 | .heading
19 | display: flex
20 | h3
21 | flex: 1
22 | button
23 | padding: 0 !important
24 | background: transparent
25 | color: $light-grey
26 | border: none
27 | line-height: 1
28 | font-weight: 500
29 | font-size: .9rem
30 | text-transform: uppercase
31 | transition: color .3s
32 | cursor: pointer
33 | &:hover
34 | color: $black
35 |
36 | .inputWrapper
37 | display: flex
38 | flex-direction: column
39 | margin-bottom: 1rem
40 |
41 | p.inputData
42 | padding: .5rem 1rem
43 | color: $grey
44 | margin: 1rem 0
45 | font-size: .9rem
46 | font-weight: 400
47 | border-bottom: solid 2px transparent
48 | background: #fff
49 | transition: border-color 1s
50 |
51 | p.inputData.updated
52 | animation: flickr 1s
53 |
54 | .cancelBtn
55 | margin-left: 1rem
56 |
57 | @keyframes flickr
58 | 0%
59 | border-color: $green
60 | 90%
61 | border-color: $green
62 | 100%
63 | border-color: transparent
64 |
--------------------------------------------------------------------------------
/src/components/ProposedTrade/index.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 | import './styles.sass';
4 |
5 | const ProposedTrade = ({ itemName, itemPic, itemId, reqStatus, ownerInfo, itemOwner, cancelProposal }) => {
6 | let cancelBtn, wrapper;
7 | const contactInfo = [];
8 |
9 | if(ownerInfo[0]) {
10 | contactInfo.push(
11 |
12 | Email: {ownerInfo[0]}
13 |
14 | );
15 | }
16 |
17 | if(ownerInfo[1]) {
18 | contactInfo.push(
19 |
20 | Phone No: {ownerInfo[1]}
21 |
22 | );
23 | }
24 |
25 | if(!ownerInfo[0] && !ownerInfo[1]) {
26 | contactInfo.push(
27 |
28 | Sorry, Item owner hasn't provided any contact details.
29 |
30 | );
31 | }
32 |
33 | return (
34 |
wrapper = node}>
35 |
36 |
37 |
38 |
39 | {
40 | reqStatus === 'PENDING' ?
41 |
42 | You have proposed {itemOwner} for trading {itemName}.
43 | :
44 |
45 | Your proposal to {itemOwner} for trading {itemName} has been accepted.
46 |
47 | }
48 |
49 |
50 | {
51 | reqStatus === 'PENDING' ?
52 |
cancelBtn = node}
54 | className="cancelBtn normalBtn"
55 | onClick={() => {
56 | cancelBtn.disabled = true;
57 | cancelBtn.classList.add('disabled');
58 | wrapper.classList.add('blacklisted');
59 | cancelProposal(itemId, wrapper, cancelBtn);
60 | }}
61 | >
62 | Cancel Proposal
63 | :
64 |
65 |
Owner Contact Info
66 | {contactInfo}
67 |
68 | }
69 |
70 |
71 | );
72 | };
73 |
74 | ProposedTrade.propTypes = {
75 | itemName: PropTypes.string.isRequired,
76 | itemId: PropTypes.string.isRequired,
77 | itemPic: PropTypes.string.isRequired,
78 | itemOwner: PropTypes.string.isRequired,
79 | cancelProposal: PropTypes.func.isRequired,
80 | reqStatus: PropTypes.string.isRequired,
81 | ownerInfo: PropTypes.array.isRequired
82 | };
83 |
84 | export default ProposedTrade;
85 |
--------------------------------------------------------------------------------
/src/components/ProposedTrade/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .ptWrapper
4 | transition: .15s opacity, .3s margin
5 | margin-bottom: 2rem
6 | .contactInfoOwner
7 | h4
8 | margin: .5rem 0
9 | &:first-child
10 | margin-bottom: 1rem
11 | span.underline
12 | padding-bottom: 3px
13 | border-bottom: solid 2px
14 | span
15 | font-weight: 700
16 | color: $black
17 | .upper
18 | padding: 1rem 0
19 | display: flex
20 | .userImg
21 | flex: 0 0 3rem
22 | height: 3rem
23 | width: 3rem
24 | background-image: url('../../assets/images/plain.png')
25 | border-radius: 0
26 | margin-right: 1rem
27 | transition: background-image .2s, opacity .2s
28 | &:hover
29 | opacity: .8
30 | .lower
31 | display: flex
32 | h4
33 | line-height: 1.5
34 | font-size: .9rem
35 | letter-spacing: 1px
36 | a
37 | font-size: .9rem
38 | font-weight: 500
39 | color: $grey
40 | transition: all .3s
41 | &:hover
42 | color: $black
43 | cursor: pointer
44 | button
45 | font-size: .8rem
46 | font-weight: 600
47 | text-transform: uppercase
48 | padding: 5px 1rem
49 | cursor: pointer
50 | transition: opacity .3s
51 | &:hover
52 | opacity: .8
53 | .cancelBtn
54 | margin: 0
55 | background: none
56 | border: solid 2px
57 | &:hover
58 | opacity: .7
59 |
60 |
--------------------------------------------------------------------------------
/src/components/TradeRequest/index.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 | import './styles.sass';
4 |
5 | const TradeRequest = ({itemName, itemPic, reqMaker, docId, reqStatus, acceptRequest, declineRequest, itemId }) => {
6 | let wrapper, acceptBtn, declineBtn;
7 | return (
8 |
wrapper = node}>
9 |
10 |
11 |
12 |
13 |
14 | {reqMaker} wants to trade with your item- {itemName}
15 |
16 |
17 |
18 | {
19 | reqStatus === 'PENDING' ?
20 |
21 | acceptBtn = node}
23 | onClick={() => {
24 | acceptBtn.classList.add('disabled');
25 | declineBtn.classList.add('disabled');
26 | acceptRequest(itemId, docId, acceptBtn, declineBtn);
27 | }}
28 | >
29 | Accept
30 |
31 | declineBtn = node}
33 | onClick={() => {
34 | wrapper.classList.add('blacklisted');
35 | acceptBtn.classList.add('disabled');
36 | declineBtn.classList.add('disabled');
37 | declineRequest(itemId, docId, wrapper, acceptBtn, declineBtn);
38 | }}
39 | >
40 | Decline
41 |
42 |
:
43 |
45 | Accepted
46 |
47 | }
48 |
49 |
50 |
51 | );
52 |
53 | };
54 |
55 | TradeRequest.propTypes = {
56 | itemName: PropTypes.string.isRequired,
57 | itemId: PropTypes.string.isRequired,
58 | docId: PropTypes.string.isRequired,
59 | itemPic: PropTypes.string.isRequired,
60 | reqMaker: PropTypes.string.isRequired,
61 | reqStatus: PropTypes.string.isRequired,
62 | declineRequest: PropTypes.func.isRequired,
63 | acceptRequest: PropTypes.func.isRequired
64 | };
65 |
66 | export default TradeRequest;
67 |
--------------------------------------------------------------------------------
/src/components/TradeRequest/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .trWrapper
4 | transition: .15s opacity, .3s margin
5 | margin-bottom: 2rem
6 | .upper
7 | padding: 1rem 0
8 | display: flex
9 | .userImg
10 | flex: 0 0 3rem
11 | height: 3rem
12 | width: 3rem
13 | background-image: url('../../assets/images/plain.png')
14 | border-radius: 0
15 | margin-right: 1rem
16 | transition: background .2s, opacity .2s
17 | &:hover
18 | opacity: .8
19 | .lower
20 | display: flex
21 | h4
22 | line-height: 1.5
23 | font-size: .9rem
24 | letter-spacing: 1px
25 | a
26 | font-size: .9rem
27 | font-weight: 500
28 | color: $grey
29 | transition: all .3s
30 | &:hover
31 | color: $black
32 | cursor: pointer
33 | button
34 | font-size: .8rem
35 | font-weight: 600
36 | text-transform: uppercase
37 | padding: 5px 1rem
38 | cursor: pointer
39 | transition: opacity .3s
40 | &:hover
41 | opacity: .8
42 | .acceptBtn
43 | background: $black
44 | color: #fff
45 | border: solid 2px $black
46 | .declineBtn
47 | margin-left: 1rem
48 | background: none
49 | border: solid 2px
50 | &:hover
51 | opacity: .7
52 | .noLinkBtn
53 | cursor: default
54 | &:hover
55 | opacity: 1
56 |
57 |
--------------------------------------------------------------------------------
/src/components/UserItem/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 | import './styles.sass';
4 |
5 | class UserItem extends Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | componentDidMount() {
11 | this.displayWidthWiseImages();
12 | // window.addEventListener('resize', () => {
13 | // clearTimeout(window.reloadImages);
14 | // window.reloadImages = setTimeout(() => {
15 | // this.displayWidthWiseImages();
16 | // }, 500);
17 | // });
18 | }
19 |
20 | displayWidthWiseImages() {
21 | Array.from(document.querySelectorAll('[data-bg]')).forEach(image => {
22 | const { clientWidth, clientHeight } = image;
23 | const imageParams = `w_${clientWidth},h_${clientHeight},f_auto,q_80`;
24 | const [head, end] = image.dataset.bg.split('upload');
25 | image.style.backgroundImage = `url('${head}upload/${imageParams}${end}')`;
26 | });
27 | }
28 |
29 | render() {
30 | const { data } = this.props;
31 | return (
32 |
this.itemNode = node}>
33 |
34 |
35 |
37 |
38 |
39 |
40 | {data.itemName}
41 |
42 |
{`${data.itemCurrency.slice(0,1)}${data.itemPrice}`}
43 |
{data.itemAdditionDate}
44 |
45 | {data.itemDescription}
46 |
47 |
48 | Tags:
49 | {
50 | data.itemTags.trim().split(',').map(
51 | (elem, i) => {elem}
52 | )
53 | }
54 |
55 |
this.rbw = node}
56 | className="tradeBtnWrapper lower removeBtnWrapper">
57 |
58 |
Are you sure?
59 |
{
60 | this.itemNode.classList.add('blacklisted');
61 | this.props.deleteItem(data.key, this.itemNode);
62 | }}>
63 | Yes
64 |
65 |
{
66 | this.rbw.classList.remove('open');
67 | }}>
68 | No
69 |
70 |
71 |
{
73 | this.rbw.classList.add('open');
74 | }}
75 | >
76 | Remove Item
77 |
78 |
79 |
80 |
81 |
82 | );
83 | }
84 | }
85 |
86 | UserItem.propTypes = {
87 | data: PropTypes.object.isRequired,
88 | deleteItem: PropTypes.func.isRequired
89 | };
90 |
91 | export default UserItem;
92 |
--------------------------------------------------------------------------------
/src/components/UserItem/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .uIWrapper
4 | transition: opacity .15s, margin .3s
5 | margin: 0 0 3rem 0
6 | .removeBtnWrapper.open
7 | transform: translateX(0)
8 | div
9 | z-index: 1
10 | opacity: 1
11 | visibility: visible
12 | .deleteBtn
13 | opacity: 0
14 | visibility: hidden
15 | z-index: -9999
16 | .removeBtnWrapper
17 | transition: transform .3s
18 | transform: translateX(-267px)
19 | min-width: 410px
20 | div
21 | z-index: -9999
22 | opacity: 0
23 | visibility: hidden
24 | transition: opacity .2s, visibility .3s
25 | display: flex
26 | align-items: center
27 | p
28 | font-weight: 500
29 | margin-right: 5px
30 | button
31 | margin-left: 1rem
32 | color: #fcfcfc
33 | padding: .5rem 1.5rem
34 | background: $black
35 | border: solid 2px $black
36 | &:last-child
37 | color: $black
38 | background: transparent
39 | border: solid 2px $black
40 | .itemCost
41 | color: lighten($black, 20%)
42 | font-size: 1.2rem
43 | margin: .7rem 0
44 | font-weight: 500
45 | .addDate
46 | margin: .5rem 0
47 | font-weight: 500
48 | color: $grey
49 | letter-spacing: 1px
50 | .itemDescription
51 | margin: .5rem 0 1.5rem 0
52 | .itemTags
53 | margin-bottom: 3rem
54 | font-weight: 700
55 | font-size: 1rem
56 | .tags
57 | padding: .5rem 1rem
58 | border-radius: 30px
59 | color: $black
60 | border: solid 2px
61 | font-family: roboto
62 | display: inline-block
63 | margin: 1rem 1rem 0 0
64 | &:first-child
65 | margin-left: 1rem
66 | .upper
67 | margin: 0 0 1rem 0
68 | display: flex
69 | flex-direction: column
70 | .userImg
71 | flex: 0 0 10rem
72 | height: 10rem
73 | width: 10rem
74 | background-image: url('../../assets/images/plain.png')
75 | border-radius: 0
76 | margin: 1.5rem auto
77 | transition: opacity .2s, background-image .2s
78 | &:hover
79 | opacity: .8
80 | .lower
81 | display: flex
82 | h3.itemName
83 | line-height: 1.2
84 | font-size: 1.2rem
85 | letter-spacing: 2px
86 | a
87 | transition: none
88 | color: inherit
89 | font-size: inherit
90 | border-bottom: solid 2px transparent
91 | &:hover
92 | border-bottom: solid 2px
93 | a
94 | font-size: .9rem
95 | font-weight: 500
96 | color: $grey
97 | cursor: pointer
98 | transition: all .3s
99 | &:hover
100 | color: $black
101 | cursor: pointer
102 | button
103 | font-size: .8rem
104 | font-weight: 600
105 | text-transform: uppercase
106 | padding: .5rem 1rem
107 | cursor: pointer
108 | &:hover
109 | opacity: .8
110 | .deleteBtn
111 | background: $black
112 | color: #fff
113 | z-index: 1
114 | transition: opacity .2s, visibility .3s
115 | border: solid 2px $black
116 | .editBtn
117 | margin-left: 1rem
118 | background: none
119 | border: solid 2px
120 | &:hover
121 | opacity: .7
122 |
--------------------------------------------------------------------------------
/src/constants/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const UPDATE_PROFILE_STATE = 'UPDATE_PROFILE_INFO';
2 | export const GET_INITIAL_PROFILE_STATE = 'GET_INITIAL_PROFILE_STATE';
3 | export const UPDATE_APP_STATE = 'UPDATE_APP_STATE';
4 | export const ADD_MY_ITEM = 'ADD_MY_ITEM';
5 | export const DELETE_MY_ITEM = 'DELETE_MY_ITEM';
6 | export const GET_INITIAL_MYITEMS_STATE = 'GET_INITIAL_MYITEMS_STATE';
7 | export const GET_INITIAL_ALLITEMS_STATE = 'GET_INITIAL_ALLITEMS_STATE';
8 | export const GET_INITIAL_INDIVIDUALITEM_STATE = 'GET_INITIAL_INDIVIDUALITEM_STATE';
9 | export const UPDATE_INDIVIDUALITEM_STATE = 'UPDATE_INDIVIDUALITEM_STATE';
10 | export const ACCEPT_TRADE_REQ = 'ACCEPT_TRADE_REQ';
11 | export const DECLINE_TRADE_REQ = 'DECLINE_TRADE_REQ';
12 | export const CANCEL_TRADE_PROPOSED = 'CANCEL_TRADE_PROPOSED';
13 | export const GET_INITIAL_TRADES_STATE = 'GET_INITIAL_TRADES_STATE';
14 | export const UPDATE_TRADE_STATE = 'UPDATE_TRADE_STATE';
15 | export const UPDATE_TRADEREQUESTS_STATE = 'UPDATE_TRADEREQUESTS_STATE';
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/containers/Header/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { Link, browserHistory } from 'react-router';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators } from 'redux';
5 |
6 | import * as appActions from '../../actions/appActions';
7 |
8 | import './styles.sass';
9 |
10 | class Header extends Component {
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = {};
15 | }
16 |
17 | componentWillMount() {
18 | this.previousWidth = 0;
19 | this.menuButton = (
20 |
{
23 | document.querySelector(".menu").classList.toggle("open");
24 | }
25 | }
26 | >
27 | MENU
28 |
29 | );
30 |
31 | this.loggedInMenu = (
32 |
33 |
36 | Home
37 |
38 |
41 | Profile
42 |
43 |
46 | My Items
47 |
48 |
51 | Trades
52 | {
53 | this.props.app.notificationsCount > 0 ?
54 | `(${this.props.app.notificationsCount})` :
55 | ''
56 | }
57 |
58 | {
62 | this.collapseMenu();
63 | fetch('/logout', {method: 'POST', credentials: 'same-origin'})
64 | .then(res => {
65 | if(res.status === 200) {
66 | this.props.actions.updateAppState({ loggedIn: false });
67 | browserHistory.push('/');
68 | }
69 | })
70 |
71 | .catch(err => {
72 | /*eslint-disable no-alert, no-console */
73 | console.error(`Error Happened while logging out- ${err}`);
74 | });
75 | }
76 | }
77 | >
78 | Logout
79 |
80 |
81 | );
82 |
83 | this.loggedOutMenu = (
84 |
85 |
88 | Home
89 |
90 |
93 | LogIn | Sign Up
94 |
95 |
96 | );
97 |
98 | this.setMenuState(window.innerWidth);
99 | this.previousWidth = window.innerWidth;
100 |
101 | }
102 |
103 | componentDidMount() {
104 | window.addEventListener('resize', () => {
105 | this.setMenuState(window.innerWidth);
106 | });
107 | }
108 |
109 | collapseMenu() {
110 | document.querySelector('.menu').classList.remove('open');
111 | }
112 |
113 | setMenuState(width) {
114 | if (this.previousWidth !== width) {
115 | if (width > 768) {
116 | const menu = document.querySelector('div.menu');
117 | if (menu) {
118 | menu.classList.remove("open");
119 | }
120 | this.setState({ menuActive: false });
121 | } else {
122 | this.setState({ menuActive: true });
123 | }
124 | this.previousWidth = width;
125 | }
126 | }
127 |
128 | getNav() {
129 | // check for auth here
130 | if (this.props.app.loggedIn) {
131 | return this.loggedInMenu;
132 | } else {
133 | return this.loggedOutMenu;
134 | }
135 | }
136 |
137 | render() {
138 | return (
139 |
140 |
141 |
142 | Trader
143 |
144 |
145 | {this.state.menuActive ? this.menuButton : ""}
146 | {this.getNav()}
147 |
148 | );
149 | }
150 | }
151 |
152 | Header.propTypes = {
153 | app: PropTypes.object.isRequired,
154 | actions: PropTypes.object.isRequired
155 | };
156 |
157 | const mapStateToProps = (state) => {
158 | return {
159 | app: state.appData
160 | };
161 | };
162 |
163 | const mapDispatchToProps = (dispatch) => {
164 | return {
165 | actions: bindActionCreators(appActions, dispatch)
166 | };
167 | };
168 |
169 | export default connect(
170 | mapStateToProps,
171 | mapDispatchToProps
172 | )(Header);
173 |
--------------------------------------------------------------------------------
/src/containers/Header/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .header
4 | display: flex
5 | padding: 0 0 30px 0
6 |
7 | .header > h1
8 | text-align: center
9 | flex: 1
10 | a
11 | text-decoration: none
12 | color: $black
13 | letter-spacing: 4px
14 | transition: color .3s
15 | font-size: 1.8rem
16 | &:active, &:hover
17 | color: #444
18 |
19 |
20 | .menuBtn
21 | margin: 0
22 | background: transparent
23 | border: none
24 | color: $light-grey
25 | font-size: 1rem
26 | line-height: 1.5
27 | // change it if making fixed header
28 | margin-bottom: 30px
29 | &:hover
30 | cursor: pointer
31 | &:focus, &:active
32 | padding: 0
33 | outline: none
34 | img
35 | width: 1.3rem
36 | height: 1.3rem
37 |
38 | .logoWrapper
39 | color: $black
40 |
41 | header
42 | flex-direction: column-reverse
43 |
44 | .menu
45 | width: 100%
46 | box-sizing: border-box
47 | transition: height .3s
48 | height: 0
49 | overflow: hidden
50 |
51 | .menu.open
52 | height: 192px
53 |
54 | .loginMenu.open
55 | height: 85px
56 |
57 | .navLink:last-child
58 | padding-bottom: 10px
59 | border-bottom: solid 1px $light-grey
60 |
61 | .navLink
62 | transition: color .3s
63 | color: $light-grey
64 | display: flex
65 | align-items: center
66 | justify-content: center
67 | font-size: 1rem
68 | line-height: 2.5
69 | text-transform: uppercase
70 | &:hover
71 | cursor: pointer
72 | color: $black
73 |
74 | .activeNavLink
75 | color: $black
76 | text-decoration: none
77 |
--------------------------------------------------------------------------------
/src/containers/ItemPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { Link, browserHistory } from 'react-router';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators } from 'redux';
5 |
6 | import './styles.sass';
7 | import loadPageProps from '../../utils/loadPageProps';
8 | import * as individualItemActions from '../../actions/individualItemActions';
9 |
10 | class ItemPage extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | errMsg: ''
15 | };
16 | }
17 |
18 | componentDidMount() {
19 | loadPageProps(`${this.props.app.itemName} - Trader`);
20 | this.displayWidthWiseImages();
21 | // window.addEventListener('resize', () => {
22 | // clearTimeout(window.reloadImages);
23 | // window.reloadImages = setTimeout(() => {
24 | // this.displayWidthWiseImages();
25 | // }, 500);
26 | // });
27 | }
28 |
29 | displayWidthWiseImages() {
30 | Array.from(document.querySelectorAll('[data-bg]')).forEach(image => {
31 | const { clientWidth, clientHeight } = image;
32 | const imageParams = `w_${clientWidth},h_${clientHeight},f_auto,q_80`;
33 | const [head, end] = image.dataset.bg.split('upload');
34 | image.style.backgroundImage = `url('${head}upload/${imageParams}${end}')`;
35 | });
36 | }
37 |
38 | getButton() {
39 | if (this.props.app.ownItem) {
40 | return (
41 |
42 |
43 | Back to your items
44 |
45 |
46 | );
47 | } else if(this.props.app.isSoldOut) {
48 | return (
49 |
50 | Sold Out
51 |
52 | );
53 | } else if (this.props.app.itemRequestedByCurrentUser) {
54 | return (
55 |
56 | Requested
57 |
58 | );
59 | } else {
60 | return (
61 |
{
65 | if (this.props.user.loggedIn) {
66 | document.querySelector('.reqTradeBtn').classList.add('disabled');
67 | document.querySelector('.reqTradeBtn').disabled = true;
68 | this.props
69 | .actions.requestItem(
70 | this.props.app.key, this.showErrMsg.bind(this)
71 | );
72 | } else {
73 | browserHistory.push(`/login?l_i=${window.location.pathname}`);
74 | }
75 | }
76 | }
77 | >
78 | Request Trade
79 |
80 | );
81 | }
82 | }
83 |
84 | showErrMsg(str) {
85 | this.setState({ 'errMsg': str });
86 | }
87 |
88 | getMsg() {
89 | if (this.state.errMsg) {
90 | return (
91 |
{this.state.errMsg}
92 | );
93 | }
94 | return;
95 | }
96 |
97 | render() {
98 | const data = this.props.app;
99 | return (
100 |
101 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | All Items
114 |
115 |
{data.itemName}
116 |
{`${data.itemCurrency.slice(0, 1)}${data.itemPrice}`}
117 |
{data.itemAdditionDate}
118 |
119 | {data.itemDescription}
120 |
121 |
122 | Tags:
123 | {
124 | data.itemTags.trim().split(',').map(
125 | (elem, i) => {elem}
126 | )
127 | }
128 |
129 |
By {data.itemOwner}
130 | {this.getMsg()}
131 | {this.getButton()}
132 |
133 |
134 | );
135 | }
136 | }
137 |
138 | ItemPage.propTypes = {
139 | app: PropTypes.object.isRequired,
140 | user: PropTypes.object.isRequired,
141 | actions: PropTypes.object.isRequired
142 | };
143 |
144 | const mapStateToProps = (state) => {
145 | return {
146 | app: state.individualItemData,
147 | user: state.appData
148 | };
149 | };
150 |
151 | const mapDispatchToProps = (dispatch) => {
152 | return {
153 | actions: bindActionCreators(individualItemActions, dispatch)
154 | };
155 | };
156 |
157 | export default connect(
158 | mapStateToProps,
159 | mapDispatchToProps
160 | )(ItemPage);
161 |
162 |
--------------------------------------------------------------------------------
/src/containers/ItemPage/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .itemPageWrapper
4 | padding: 1rem 0 2rem 0
5 | display: flex
6 | flex-direction: column
7 | .itemImgWrapper
8 | flex: 1
9 | .itemImg
10 | background-image: url('../../assets/images/plain.png')
11 | transition: background-image .2s
12 | padding-bottom: 100%
13 | .itemInfoWrapper
14 | margin-top: 2rem
15 | flex: 1
16 | .itemName
17 | margin: 2rem 0 .5rem 0
18 | .itemTags
19 | margin: 2rem 0 3rem 0
20 | font-weight: 700
21 | font-size: 1rem
22 | .tags
23 | padding: .5rem 1rem
24 | border-radius: 30px
25 | color: $black
26 | border: solid 2px
27 | font-family: roboto
28 | display: inline-block
29 | margin: 1rem 1rem 0 0
30 | &:first-child
31 | margin-left: 1rem
32 | .seller
33 | font-size: 1rem
34 | letter-spacing: 1px
35 | color: $grey
36 | span
37 | font-weight: 800
38 | color: $black
39 | .itemCost
40 | color: lighten($black, 20%)
41 | font-size: 1.5rem
42 | margin: 1rem 0
43 | font-weight: 500
44 | .addDate
45 | color: $grey
46 | margin: .5rem 0 1rem 0
47 |
48 | .backLink
49 | font-size: .9rem
50 | color: $light-grey
51 | cursor: pointer
52 | padding-bottom: 3px
53 | transition: color .2s
54 | font-weight: 700
55 | span.small
56 | svg
57 | transform: rotateZ(90deg)
58 | transition: color .2s
59 | fill: $light-grey
60 | font-weight: 800
61 | &:hover, &:active
62 | color: $black
63 | span svg
64 | fill: $black
65 |
66 | .warningMsg
67 | font-weight: 700
68 | color: $red
69 | margin-top: 2rem
70 |
71 | button
72 | font-size: .8rem
73 | font-weight: 600
74 | text-transform: uppercase
75 | padding: .5rem 1rem
76 | cursor: pointer
77 | transition: opacity .3s
78 | &:hover
79 | opacity: .8
80 | .reqTradeBtn, .removeTradeBtn
81 | margin: 3rem 0 0 0
82 | background: $black
83 | color: #fff
84 | display: block
85 | .optionsWrapper
86 | margin-top: 3rem
87 | span.small
88 | svg
89 | transform: rotateZ(-90deg) translateX(-4px)
90 | .backLink
91 | border-bottom: solid 2px
92 |
--------------------------------------------------------------------------------
/src/containers/Main/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import './styles.sass';
5 | import Item from '../../components/Item/index';
6 | import loadPageProps from '../../utils/loadPageProps';
7 |
8 | class Homepage extends Component {
9 | componentDidMount() {
10 | loadPageProps('Home - Trader');
11 | this.displayWidthWiseImages();
12 | // window.addEventListener('resize', () => {
13 | // clearTimeout(window.reloadImages);
14 | // window.reloadImages = setTimeout(() => {
15 | // this.displayWidthWiseImages();
16 | // }, 500);
17 | // });
18 | }
19 |
20 | displayWidthWiseImages() {
21 | Array.from(document.querySelectorAll('[data-bg]')).forEach(image => {
22 | const { clientWidth, clientHeight } = image;
23 | const imageParams = `w_${clientWidth},h_${clientHeight},f_auto,q_80`;
24 | const [head, end] = image.dataset.bg.split('upload');
25 | image.style.backgroundImage = `url('${head}upload/${imageParams}${end}')`;
26 | });
27 | }
28 |
29 | getAllItemsData() {
30 | const data = this.props.app;
31 | if(data.length > 0) {
32 | return data.map((e) =>
33 |
);
35 | } else {
36 | return
No items found! ;
37 | }
38 | }
39 |
40 | render() {
41 | return (
42 |
43 | {this.getAllItemsData()}
44 | {
45 | this.props.app.length > 0 ?
46 |
: ""
47 | }
48 |
49 | );
50 | }
51 | }
52 |
53 | Homepage.propTypes = {
54 | app: PropTypes.array.isRequired
55 | };
56 |
57 | const mapStateToProps = (state) => {
58 | return {
59 | app: state.allItemsData
60 | };
61 | };
62 |
63 |
64 | export default connect(
65 | mapStateToProps
66 | )(Homepage);
67 |
--------------------------------------------------------------------------------
/src/containers/Main/styles.sass:
--------------------------------------------------------------------------------
1 | .main
2 | padding: 1rem 0
3 | display: flex
4 | flex-wrap: wrap
5 | justify-content: space-between
6 | min-height: 40vh
7 | .noItemHeading
8 | max-width: 570px
9 | line-height: 1.7
10 | text-align: center
11 | margin: 9rem auto 0 auto
12 |
13 |
--------------------------------------------------------------------------------
/src/containers/MyItems/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 | import { bindActionCreators } from 'redux';
4 |
5 | import * as myItemsActions from '../../actions/myItemsActions';
6 |
7 | import UserItem from '../../components/UserItem/index';
8 | import AddItemPage from '../../components/AddItemPage/index';
9 | import './styles.sass';
10 | import loadPageProps from '../../utils/loadPageProps';
11 |
12 | class MyItems extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | modalOpened: false
17 | };
18 | }
19 |
20 | componentDidMount() {
21 | loadPageProps('My Items - Trader');
22 | this.displayWidthWiseImages();
23 | // window.addEventListener('resize', () => {
24 | // clearTimeout(window.reloadImages);
25 | // window.reloadImages = setTimeout(() => {
26 | // this.displayWidthWiseImages();
27 | // }, 500);
28 | // });
29 | }
30 |
31 | displayWidthWiseImages() {
32 | Array.from(document.querySelectorAll('[data-bg]')).forEach(image => {
33 | const { clientWidth, clientHeight } = image;
34 | const imageParams = `w_${clientWidth},h_${clientHeight},f_auto,q_80`;
35 | const [head, end] = image.dataset.bg.split('upload');
36 | image.style.backgroundImage = `url('${head}upload/${imageParams}${end}')`;
37 | });
38 | }
39 |
40 | closeModal() {
41 | this.setState({ modalOpened: false });
42 | document.body.classList.remove('modal-opened');
43 | document.body.style.marginRight = 0;
44 | }
45 |
46 | getModal() {
47 | if (this.state.modalOpened) {
48 | return (
49 |
52 | );
53 | } else {
54 | return;
55 | }
56 | }
57 |
58 | openModal() {
59 | const scrollBar = document.querySelector('.scrollbar-measure');
60 | const scrollBarWidth = scrollBar.offsetWidth - scrollBar.clientWidth;
61 | document.body.classList.add('modal-opened');
62 | document.body.style.marginRight = `${scrollBarWidth}px`;
63 | this.setState({ modalOpened: true });
64 | }
65 |
66 | getItems() {
67 | if (this.props.items.length > 0) {
68 | return this.props.items.map(
69 | item =>
70 |
71 | );
72 | } else {
73 | return You haven't added any item yet! Click on ADD ITEM button to add item ;
74 | }
75 | }
76 |
77 | render() {
78 | return (
79 |
80 | {this.getModal()}
81 |
82 | {
84 | this.openModal();
85 | }}
86 | className="tradeBtn addItemBtn">
87 | + Add Item
88 |
89 |
90 | {this.getItems()}
91 |
92 | );
93 | }
94 | }
95 |
96 | MyItems.propTypes = {
97 | items: PropTypes.array.isRequired,
98 | actions: PropTypes.object.isRequired
99 | };
100 |
101 | const mapStateToProps = (state) => {
102 | return {
103 | items: state.myItemsData
104 | };
105 | };
106 |
107 | const mapDispatchToProps = (dispatch) => {
108 | return {
109 | actions: bindActionCreators(myItemsActions, dispatch)
110 | };
111 | };
112 |
113 | export default connect(
114 | mapStateToProps,
115 | mapDispatchToProps
116 | )(MyItems);
117 |
--------------------------------------------------------------------------------
/src/containers/MyItems/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .myItemsWrapper
4 | padding: 1rem 0 2rem 0
5 | min-height: 40vh
6 | // added so that scrollbar doesnt show up
7 | // on removing the last item
8 | overflow-y: hidden
9 | .addTradeWrapper
10 | display: flex
11 | justify-content: flex-end
12 | margin: 0 0 3rem 0
13 | .allItemsBtn
14 | border-bottom: solid 2px
15 | .tradeBtn
16 | color: $light-grey
17 | font-size: .9rem
18 | font-weight: 700
19 | padding: 0
20 | background: transparent
21 | text-transform: uppercase
22 | transition: all, .3s
23 | cursor: pointer
24 | &:hover
25 | color: $black
26 | .noItemHeading
27 | max-width: 570px
28 | line-height: 1.7
29 | text-align: center
30 | margin: 9rem auto 0 auto
31 |
32 |
--------------------------------------------------------------------------------
/src/containers/Profile/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 | import { bindActionCreators } from 'redux';
4 | import objectAssign from 'object-assign';
5 |
6 | import * as profileActions from '../../actions/profileActions';
7 |
8 | import BasicInfo from '../../components/BasicInfo/index';
9 | import OtherInfo from '../../components/OtherInfo/index';
10 | import './styles.sass';
11 | import loadPageProps from '../../utils/loadPageProps';
12 |
13 | class Profile extends Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | showMessage: true
18 | };
19 | }
20 |
21 | componentDidMount() {
22 | loadPageProps('Profile - Trader');
23 | Array.from(document.querySelectorAll('[data-bg]')).forEach(image => {
24 | image.style.backgroundImage = `url('${image.dataset.bg}')`;
25 | });
26 | }
27 |
28 | updateProfileState(changedState, editSection) {
29 | this.props.actions.updateProfileState(changedState, editSection);
30 | }
31 |
32 | getMessage() {
33 | if (this.state.showMessage && this.props.location.query.message === "fillProfileInfo") {
34 | return (
35 | (this.message = node)}>
36 |
Please fill your profile informations. You info will be shared with the other user when you propose/accept trade.
37 |
{
40 | objectAssign(this.message.style,{opacity:"0",marginBottom: `-${this.message.offsetHeight}px`, zIndex: '-99'});
41 | setTimeout(() => { this.setState({ showMessage: false }); }, 250);
42 | }
43 | }
44 | >
45 | x
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | render() {
53 | return (
54 |
55 | {this.getMessage()}
56 | {/*
57 | no need to pass update method to basic info as for this appm picture and
58 | and name of user will not be mutable and their value will be based upon
59 | the social account which user uses to sign in.
60 | */}
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | Profile.propTypes = {
69 | profile: PropTypes.object.isRequired,
70 | actions: PropTypes.object.isRequired,
71 | location: PropTypes.object.isRequired
72 | };
73 |
74 | const mapStateToProps = (state) => {
75 | return {
76 | profile: state.profileData
77 | };
78 | };
79 |
80 | const mapDispatchToProps = (dispatch) => {
81 | return {
82 | actions: bindActionCreators(profileActions, dispatch)
83 | };
84 | };
85 |
86 | export default connect(
87 | mapStateToProps,
88 | mapDispatchToProps
89 | )(Profile);
90 |
--------------------------------------------------------------------------------
/src/containers/Profile/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .infoWrapper
4 | display: flex
5 | min-height: 40vh
6 | flex-direction: column
7 | padding: 1rem 0
8 | flex-wrap: wrap
9 | .message
10 | flex: 1 1 100%
11 | padding: 15px
12 | margin-bottom: 3rem
13 | background: lighten($black, 0%)
14 | border-left: solid 5px lighten($black, 10%)
15 | display: flex
16 | transition: all .2s
17 | p, span
18 | font-size: 1rem
19 | letter-spacing: 1px
20 | color: $background-color
21 | opacity: 1
22 | font-weight: 500
23 | font-family: inherit
24 | p
25 | flex: 1
26 | line-height: 1.4
27 | span
28 | cursor: pointer
29 | transition: all .1s cubic-bezier(1, 0.03, 0.28, 0.07)
30 | background: transparent
31 | width: 20px
32 | height: 20px
33 | text-align: center
34 | line-height: 1.1
35 | border-radius: 50%
36 | user-select: none
37 | font-weight: 700
38 | &:hover
39 | background: lighten($black, 10%)
40 |
41 |
--------------------------------------------------------------------------------
/src/containers/Trades/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators } from 'redux';
5 |
6 | import * as tradeActions from '../../actions/tradeActions';
7 |
8 | import TradeRequest from '../../components/TradeRequest/index';
9 | import ProposedTrade from '../../components/ProposedTrade/index';
10 | import './styles.sass';
11 | import loadPageProps from '../../utils/loadPageProps';
12 |
13 | class Trades extends Component {
14 | constructor(props) {
15 | super(props);
16 | }
17 |
18 | componentDidMount() {
19 | loadPageProps('Trades - Trader');
20 | this.displayWidthWiseImages();
21 | // window.addEventListener('resize', () => {
22 | // clearTimeout(window.reloadImages);
23 | // window.reloadImages = setTimeout(() => {
24 | // this.displayWidthWiseImages();
25 | // }, 500);
26 | // });
27 | }
28 |
29 | displayWidthWiseImages() {
30 | Array.from(document.querySelectorAll('[data-bg]')).forEach(image => {
31 | const { clientWidth, clientHeight } = image;
32 | const imageParams = `w_${clientWidth},h_${clientHeight},f_auto,q_80`;
33 | const [head, end] = image.dataset.bg.split('upload');
34 | image.style.backgroundImage = `url('${head}upload/${imageParams}${end}')`;
35 | });
36 | }
37 |
38 | getAllProposedTrades() {
39 | const trades = this.props.trades.proposedTrades;
40 | if(trades.length > 0) {
41 | return trades.map(
42 | elem =>
43 |
53 | );
54 | } else {
55 | return ( No trade proposal sent! );
56 | }
57 |
58 | }
59 |
60 | getAllTradeRequests() {
61 | const trades = this.props.trades.tradeRequests;
62 | if(trades.length > 0) {
63 | return trades.map(
64 | elem =>
65 | elem.itemRequests.map(
66 | req => (
67 |
78 | )
79 | )
80 | );
81 | } else {
82 | return ( No trade requests! );
83 | }
84 | }
85 |
86 | cancelTradeRequest(id, node, btn) {
87 | this.props.tradeActions.cancelTradeProposed(id, node, btn);
88 | }
89 |
90 | declineRequest(id, docId, node, btn1, btn2) {
91 | this.props.tradeActions.declineTradeReq(id, docId, node, btn1, btn2);
92 | }
93 |
94 | acceptRequest(id, docId, btn1, btn2) {
95 | this.props.tradeActions.acceptTrade(id, docId, btn1, btn2);
96 | }
97 |
98 | render() {
99 | return (
100 |
101 |
102 | My Items
103 |
104 |
105 |
106 |
Trade Requests
107 |
108 | {this.getAllTradeRequests()}
109 |
110 |
111 |
112 |
Trades Proposed
113 |
114 | {this.getAllProposedTrades()}
115 |
116 |
117 |
118 |
119 | );
120 | }
121 | }
122 |
123 | Trades.propTypes = {
124 | trades: PropTypes.object.isRequired,
125 | tradeActions: PropTypes.object.isRequired
126 | };
127 |
128 | const mapStateToProps = (state) => {
129 | return {
130 | trades: state.tradesData
131 | };
132 | };
133 |
134 | const mapDispatchToProps = (dispatch) => {
135 | return {
136 | tradeActions: bindActionCreators(tradeActions, dispatch)
137 | };
138 | };
139 |
140 | export default connect(
141 | mapStateToProps,
142 | mapDispatchToProps
143 | )(Trades);
144 |
--------------------------------------------------------------------------------
/src/containers/Trades/styles.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .tradesWrapper
4 | min-height: 40vh
5 |
6 | .addTradeWrapper
7 | display: flex
8 | justify-content: space-between
9 | margin: 1rem 0 3rem 0
10 | .allItemsBtn
11 | border-bottom: solid 2px
12 | .tradeBtn
13 | color: $light-grey
14 | font-size: .9rem
15 | font-weight: 700
16 | padding: 0
17 | background: transparent
18 | text-transform: uppercase
19 | transition: all, .3s
20 | cursor: pointer
21 | &:hover
22 | color: $black
23 | .tradesInfoWrapper
24 | display: flex
25 | flex-direction: column
26 | h3
27 | letter-spacing: 1px
28 | margin-bottom: .5rem
29 | .allTradeRequestsWrapper
30 | margin-bottom: 2rem
31 | overflow-y: hidden
32 |
33 | .allProposedTradesWrapper
34 | margin-bottom: 2rem
35 | overflow-y: hidden
36 |
37 | .name
38 | font-weight: 700
39 | color: $black
40 |
41 | .noitemHeading
42 | margin: 1rem 0
43 | color: $grey
44 | line-height: 1.2
45 | font-size: .9rem
46 | letter-spacing: 1px
47 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ar5had/trader/4bec083791afc117cbc034bb41d26b572c643106/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
13 | <% if (htmlWebpackPlugin.options.trackJSToken) { %>
14 |
15 |
16 | <% } %>
17 |
18 |
19 |
20 |
21 | Trader
22 |
23 |
24 |
25 |
26 |
27 |
35 |
36 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Router, browserHistory } from 'react-router';
4 | import { Provider } from 'react-redux';
5 |
6 | import routes from './routes';
7 | import configureStore from './store/configureStore';
8 |
9 | import './styles/global.sass';
10 | import './favicon.ico';
11 |
12 | import { syncHistoryWithStore } from 'react-router-redux';
13 |
14 | const store = configureStore();
15 |
16 | const history = syncHistoryWithStore(browserHistory, store);
17 |
18 | render(
19 |
20 |
21 | {/*Passing dispatch method so that actions can be called in onEnter hook*/}
22 | {routes(store.dispatch)}
23 |
24 | , document.getElementById('app')
25 | );
26 |
--------------------------------------------------------------------------------
/src/reducers/allItemsReducer.js:
--------------------------------------------------------------------------------
1 | import {GET_INITIAL_ALLITEMS_STATE } from '../constants/actionTypes';
2 | import initialState from './initialState';
3 |
4 | export default function allItemsReducer(state = initialState.allItems, action) {
5 | switch (action.type) {
6 | case GET_INITIAL_ALLITEMS_STATE:
7 | return [...action.payload];
8 | default:
9 | return state;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/reducers/appDataReducer.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_APP_STATE } from '../constants/actionTypes';
2 | import objectAssign from 'object-assign';
3 | import initialState from './initialState';
4 |
5 | export default function appDataReducer(state = initialState.app, action) {
6 | switch (action.type) {
7 | case UPDATE_APP_STATE:
8 | return objectAssign({}, state, action.payload);
9 | default:
10 | return state;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerReducer } from 'react-router-redux';
3 |
4 | import profileDataReducer from './profileDataReducer';
5 | import appDataReducer from './appDataReducer';
6 | import myItemsReducer from './myItemsReducer';
7 | import allItemsReducer from './allItemsReducer';
8 | import individualItemReducer from './individualItemReducer';
9 | import tradesDataReducer from './tradesDataReducer';
10 |
11 |
12 | const rootReducer = combineReducers({
13 | profileData: profileDataReducer,
14 | appData: appDataReducer,
15 | myItemsData: myItemsReducer,
16 | allItemsData: allItemsReducer,
17 | individualItemData: individualItemReducer,
18 | tradesData: tradesDataReducer,
19 | routing: routerReducer
20 | });
21 |
22 | export default rootReducer;
23 |
--------------------------------------------------------------------------------
/src/reducers/individualItemReducer.js:
--------------------------------------------------------------------------------
1 | import { GET_INITIAL_INDIVIDUALITEM_STATE, UPDATE_INDIVIDUALITEM_STATE} from '../constants/actionTypes';
2 | import objectAssign from 'object-assign';
3 | import initialState from './initialState';
4 |
5 | export default function individualItemReducer(state = initialState.individualItemData, action) {
6 | switch (action.type) {
7 | case GET_INITIAL_INDIVIDUALITEM_STATE:
8 | return objectAssign({}, state, action.payload);
9 | case UPDATE_INDIVIDUALITEM_STATE:
10 | return objectAssign({}, state, action.payload);
11 | default:
12 | return state;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/reducers/initialState.js:
--------------------------------------------------------------------------------
1 | export default {
2 | profile: {
3 | dp: '',
4 | name: '',
5 | address: {
6 | localAddress: '',
7 | city: '',
8 | state: '',
9 | country: '',
10 | landmark: '',
11 | pinCode: ''
12 | },
13 | email: '',
14 | phoneNo: ''
15 | },
16 | app: {
17 | loggedIn: false
18 | },
19 | myItems: [],
20 | allItems: [],
21 | individualItemData: {},
22 | trades: {
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/src/reducers/myItemsReducer.js:
--------------------------------------------------------------------------------
1 | import { ADD_MY_ITEM, DELETE_MY_ITEM, GET_INITIAL_MYITEMS_STATE } from '../constants/actionTypes';
2 | // import objectAssign from 'object-assign';
3 | import initialState from './initialState';
4 |
5 | export default function myItemsReducer(state = initialState.myItems, action) {
6 | switch (action.type) {
7 | case DELETE_MY_ITEM:
8 | return state.filter(elem => elem.key !== action.payload);
9 | case ADD_MY_ITEM:
10 | return [action.payload, ...state];
11 | case GET_INITIAL_MYITEMS_STATE:
12 | return [...action.payload];
13 | default:
14 | return state;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/reducers/profileDataReducer.js:
--------------------------------------------------------------------------------
1 | import { GET_INITIAL_PROFILE_STATE, UPDATE_PROFILE_STATE } from '../constants/actionTypes';
2 | import objectAssign from 'object-assign';
3 | import initialState from './initialState';
4 |
5 | export default function profileDataReducer(state = initialState.profile, action) {
6 | switch (action.type) {
7 | case GET_INITIAL_PROFILE_STATE:
8 | return objectAssign({}, state, action.payload);
9 | case UPDATE_PROFILE_STATE:
10 | return objectAssign({}, state, action.payload);
11 | default:
12 | return state;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/reducers/tradesDataReducer.js:
--------------------------------------------------------------------------------
1 | import { GET_INITIAL_TRADES_STATE, ACCEPT_TRADE_REQ, UPDATE_TRADE_STATE, UPDATE_TRADEREQUESTS_STATE } from '../constants/actionTypes';
2 | import objectAssign from 'object-assign';
3 | import initialState from './initialState';
4 |
5 | export default function tradesDataReducer(state = initialState.trades, action) {
6 | let tradeRequests, item, newState;
7 |
8 | switch (action.type) {
9 | case GET_INITIAL_TRADES_STATE:
10 | return objectAssign({}, state, action.payload);
11 | case UPDATE_TRADE_STATE:
12 | return objectAssign({}, state, action.payload);
13 | case UPDATE_TRADEREQUESTS_STATE:
14 | tradeRequests = state.tradeRequests.filter(elem => {
15 | item = elem.itemRequests.filter(
16 | elemItem => elemItem.docId.toString() !== action.payload
17 | );
18 | return item.length > 0;
19 | });
20 | return objectAssign({}, state, { tradeRequests });
21 | case ACCEPT_TRADE_REQ:
22 | newState = objectAssign({}, state);
23 | newState.tradeRequests = newState.tradeRequests.map(elem => {
24 | const arr = objectAssign({}, elem);
25 | arr.itemRequests = arr.itemRequests.map(
26 | elemItem => {
27 | const itemRequest = objectAssign({}, elemItem);
28 | if (elemItem.docId.toString() === action.payload) {
29 | itemRequest.reqStatus = 'ACCEPTED';
30 | }
31 | return itemRequest;
32 | }
33 | );
34 | return arr;
35 | });
36 | return {
37 | proposedTrades: newState.proposedTrades,
38 | tradeRequests: newState.tradeRequests
39 | };
40 | default:
41 | return state;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, IndexRoute } from 'react-router';
3 |
4 | import App from './components/App/index';
5 | import Main from './containers/Main/index';
6 | import Profile from './containers/Profile/index';
7 | import Login from './components/Login/index';
8 | import Trades from './containers/Trades/index';
9 | import ItemPage from './containers/ItemPage/index';
10 | import MyItems from './containers/MyItems/index';
11 | import ErrorPage from './components/ErrorPage/index';
12 | import CheckAuth from './utils/checkAuth';
13 |
14 | import { getInitialState } from './actions/commonActions';
15 | import { loadIndividualItemState } from './actions/individualItemActions';
16 | import { updateAppState } from './actions/appActions';
17 |
18 |
19 | export default function AllRoutes(dispatch) {
20 |
21 | const loadAppState = (nextState, replace, cb) => {
22 | document.body.style.cursor = 'wait';
23 | CheckAuth(
24 | (notificationsCount) => {
25 | updateAppState({ loggedIn: true, notificationsCount })(dispatch);
26 | cb();
27 | },
28 | () => {
29 | updateAppState({ loggedIn: false})(dispatch);
30 | cb();
31 | }
32 | );
33 | };
34 |
35 | const requireAuthAndLoad = (nextState, replace, cb) => {
36 | document.body.style.cursor = 'wait';
37 | // CheckAuth take two function as parameter
38 | // one for authorized req
39 | // other for unauthorized req
40 | // If user is authorized then load initial state
41 | CheckAuth(
42 | () => {
43 | const path = nextState.location.pathname;
44 | getInitialState(cb, path.slice(1))(dispatch);
45 | },
46 | () => {
47 | replace({
48 | pathname: '/login',
49 | state: { nextPathname: nextState.location.pathname }
50 | });
51 | cb();
52 | }
53 | );
54 | };
55 |
56 | const loadAllItems = (nextState, replace, cb) => {
57 | document.body.style.cursor = 'wait';
58 | getInitialState(cb, 'allItems')(dispatch);
59 | };
60 |
61 | const loadIndividualItem = (nextState, replace, cb) => {
62 | document.body.style.cursor = 'wait';
63 | // fetch the id from /item/123 like url
64 | const id = nextState.location.pathname.split('/')[2];
65 | loadIndividualItemState(cb, replace, 'individualItem', id)(dispatch);
66 | };
67 |
68 | const requireNoAuth = (nextState, replace, cb) => {
69 | document.body.style.cursor = 'wait';
70 | // CheckAuth take two function as parameter
71 | // one for authorized req
72 | // other for unauthorized req
73 |
74 | // If user is already authorized then
75 | // send him back to home(/)
76 | // else let him goto login page
77 | CheckAuth(
78 | () => {
79 | replace({
80 | pathname: '/',
81 | state: { nextPathname: nextState.location.pathname }
82 | });
83 | cb();
84 | },
85 | () => {
86 | cb();
87 | },
88 | );
89 | };
90 |
91 | return (
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux';
2 | import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
3 | import thunk from 'redux-thunk';
4 | import logger from 'redux-logger';
5 |
6 | import rootReducer from '../reducers';
7 |
8 | function configureStoreProd(initialState) {
9 |
10 | const middlewares = [thunk];
11 |
12 | return createStore(rootReducer, initialState, compose(
13 | applyMiddleware(...middlewares)
14 | )
15 | );
16 | }
17 |
18 | function configureStoreDev(initialState) {
19 | const middlewares = [
20 | // Add other middleware on this line...
21 | logger(),
22 | // Redux middleware that spits an error on you when you try to mutate your state either inside a dispatch or between dispatches.
23 | reduxImmutableStateInvariant(),
24 |
25 | // thunk middleware can also accept an extra argument to be passed to each thunk action
26 | // https://github.com/gaearon/redux-thunk#injecting-a-custom-argument
27 | thunk,
28 | ];
29 |
30 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // add support for Redux dev tools
31 | const store = createStore(rootReducer, initialState, composeEnhancers(
32 | applyMiddleware(...middlewares)
33 | )
34 | );
35 |
36 | if (module.hot) {
37 | // Enable Webpack hot module replacement for reducers
38 | module.hot.accept('../reducers', () => {
39 | const nextReducer = require('../reducers').default; // eslint-disable-line global-require
40 | store.replaceReducer(nextReducer);
41 | });
42 | }
43 |
44 | return store;
45 | }
46 |
47 | const configureStore = process.env.NODE_ENV === 'production' ? configureStoreProd : configureStoreDev;
48 |
49 | export default configureStore;
50 |
--------------------------------------------------------------------------------
/src/styles/animation.sass:
--------------------------------------------------------------------------------
1 | // For Pages Content ********************************************************************************
2 |
3 | .content-enter
4 | opacity: 0.01
5 | transform: scale(.96)
6 |
7 | .content-enter.content-enter-active
8 | opacity: 1
9 | transform: scale(1)
10 | transition: transform .4s cubic-bezier(.82,0,.12,1),opacity .4s cubic-bezier(.82,0,.12,1), filter .4s cubic-bezier(.82,0,.12,1)
11 |
12 | .content-leave
13 | opacity: 0.01
14 | transform: scale(.96)
15 |
16 | .content-leave.content-leave-active
17 | transform: scale(.96)
18 | opacity: 0.01
19 |
--------------------------------------------------------------------------------
/src/styles/fonts.sass:
--------------------------------------------------------------------------------
1 | @font-face
2 | font-family: 'robotoblack_italic'
3 | src: url('../assets/fonts/roboto-blackitalic-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-blackitalic-webfont.woff') format('woff')
4 | font-weight: 900
5 | font-style: italic
6 |
7 |
8 | @font-face
9 | font-family: 'roboto'
10 | src: url('../assets/fonts/roboto-bold-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-bold-webfont.woff') format('woff')
11 | font-weight: 700
12 | font-style: normal
13 |
14 |
15 | @font-face
16 | font-family: 'roboto'
17 | src: url('../assets/fonts/roboto-bolditalic-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-bolditalic-webfont.woff') format('woff')
18 | font-weight: 700
19 | font-style: italic
20 |
21 |
22 | @font-face
23 | font-family: 'roboto'
24 | src: url('../assets/fonts/roboto-italic-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-italic-webfont.woff') format('woff')
25 | font-weight: 400
26 | font-style: italic
27 |
28 |
29 | @font-face
30 | font-family: 'roboto'
31 | src: url('../assets/fonts/roboto-regular-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-regular-webfont.woff') format('woff')
32 | font-weight: 400
33 | font-style: normal
34 |
35 |
36 | @font-face
37 | font-family: 'roboto'
38 | src: url('../assets/fonts/roboto-light-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-light-webfont.woff') format('woff')
39 | font-weight: 300
40 | font-style: normal
41 |
42 |
43 | @font-face
44 | font-family: 'roboto'
45 | src: url('../assets/fonts/roboto-lightitalic-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-lightitalic-webfont.woff') format('woff')
46 | font-weight: 300
47 | font-style: italic
48 |
49 |
50 | @font-face
51 | font-family: 'roboto'
52 | src: url('../assets/fonts/roboto-medium-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-medium-webfont.woff') format('woff')
53 | font-weight: 500
54 | font-style: normal
55 |
56 |
57 | @font-face
58 | font-family: 'roboto'
59 | src: url('../assets/fonts/roboto-mediumitalic-webfont.woff2') format('woff2'), url('../assets/fonts/roboto-mediumitalic-webfont.woff') format('woff')
60 | font-weight: 500
61 | font-style: italic
62 |
63 |
64 | @font-face
65 | font-family: 'roboto_mono'
66 | src: url('../assets/fonts/robotomono-bold-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-bold-webfont.woff') format('woff')
67 | font-weight: 700
68 | font-style: normal
69 |
70 |
71 | @font-face
72 | font-family: 'roboto_mono'
73 | src: url('../assets/fonts/robotomono-bolditalic-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-bolditalic-webfont.woff') format('woff')
74 | font-weight: 700
75 | font-style: italic
76 |
77 |
78 | @font-face
79 | font-family: 'roboto_mono'
80 | src: url('../assets/fonts/robotomono-light-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-light-webfont.woff') format('woff')
81 | font-weight: 300
82 | font-style: normal
83 |
84 |
85 | @font-face
86 | font-family: 'roboto_mono'
87 | src: url('../assets/fonts/robotomono-lightitalic-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-lightitalic-webfont.woff') format('woff')
88 | font-weight: 500
89 | font-style: italic
90 |
91 |
92 | @font-face
93 | font-family: 'roboto_monomedium'
94 | src: url('../assets/fonts/robotomono-medium-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-medium-webfont.woff') format('woff')
95 | font-weight: 500
96 | font-style: normal
97 |
98 |
99 | @font-face
100 | font-family: 'roboto_monomedium_italic'
101 | src: url('../assets/fonts/robotomono-mediumitalic-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-mediumitalic-webfont.woff') format('woff')
102 | font-weight: 500
103 | font-style: italic
104 |
105 |
106 | @font-face
107 | font-family: 'roboto_mono'
108 | src: url('../assets/fonts/robotomono-regular-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-regular-webfont.woff') format('woff')
109 | font-weight: 400
110 | font-style: normal
111 |
112 |
113 | @font-face
114 | font-family: 'roboto_mono'
115 | src: url('../assets/fonts/robotomono-italic-webfont.woff2') format('woff2'), url('../assets/fonts/robotomono-italic-webfont.woff') format('woff')
116 | font-weight: 400
117 | font-style: italic
118 |
--------------------------------------------------------------------------------
/src/styles/global.sass:
--------------------------------------------------------------------------------
1 | @import './fonts.sass'
2 | @import './mediaqueries.sass'
3 | @import './variables.sass'
4 |
5 | // http://meyerweb.com/eric/tools/css/reset/
6 | // v2.0 | 20110126
7 | // License: none (public domain)
8 |
9 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video
10 | margin: 0
11 | padding: 0
12 | border: 0
13 | font-size: 100%
14 | font: inherit
15 | vertical-align: baseline
16 |
17 | // HTML5 display-role reset for older browsers
18 |
19 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section
20 | display: block
21 |
22 | body
23 | line-height: 1
24 | // set max height to more than 100vh to remove shift produced by scrollbar
25 | min-height: calc(100vh + 2px)
26 |
27 | ol, ul
28 | list-style: none
29 |
30 | blockquote, q
31 | quotes: none
32 |
33 | blockquote
34 | &:before, &:after
35 | content: ''
36 | content: none
37 |
38 | q
39 | &:before, &:after
40 | content: ''
41 | content: none
42 |
43 | table
44 | border-collapse: collapse
45 | border-spacing: 0
46 |
47 | // Global styles starts
48 |
49 | html
50 | font-size: 14px
51 | height: 100%
52 |
53 | body
54 | background: $background-color
55 | overflow: auto
56 | height: 100%
57 |
58 | *, p, h4, h5, h6, a
59 | font-size: 1rem
60 | letter-spacing: 1px
61 | font-family: roboto, helvetica, sans-serif
62 | line-height: 2
63 | text-decoration: none
64 | outline: none
65 | word-break: break-word
66 |
67 |
68 | h1, h2, h3
69 | text-transform: uppercase
70 | font-weight: 900
71 | letter-spacing: 4px
72 | line-height: 1.5
73 | font-family: roboto_mono, helvetica, sans-serif
74 |
75 | h1
76 | font-size: 1.5rem
77 |
78 | h2
79 | letter-spacing: 3px
80 | font-size: 1.5rem
81 | font-weight: 400
82 |
83 | h3
84 | font-size: 1.2rem
85 | letter-spacing: 2px
86 |
87 | h4
88 | letter-spacing: 2px
89 | font-size: 1.2rem
90 | font-weight: 400
91 |
92 | h5
93 | letter-spacing: 2px
94 | font-size: 1.2rem
95 | font-family: roboto_mono, helvetica, sans-serif
96 |
97 | h6
98 | font-size: 1rem
99 | font-weight: 800
100 | text-transform: uppercase
101 | letter-spacing: 1px
102 |
103 | p
104 | font-weight: 400
105 | letter-spacing: 1px
106 |
107 | .frm
108 | font-family: roboto_mono, helvetica, sans-serif
109 |
110 | .marB10
111 | margin-bottom: 10px
112 |
113 | .marT10
114 | margin-top: 10px
115 |
116 | .marB20
117 | margin-bottom: 20px
118 |
119 | .marT20
120 | margin-top: 20px
121 |
122 | .text-center
123 | text-align: center
124 |
125 | .text-left
126 | text-align: left
127 |
128 | .text-right
129 | text-align: right
130 |
131 | .has-link
132 | color: $grey
133 |
134 | .has-link > a
135 | text-decoration: none
136 | color: $black
137 | border-bottom: 1px dotted $black
138 | &:hover
139 | border-bottom: 1px solid $black
140 |
141 | .normal
142 | text-transform: none
143 | letter-spacing: 1px
144 | font-size: 1rem
145 |
146 | input, textarea
147 | padding: .5rem 1rem
148 | border: none
149 | color: $grey
150 | margin: 1rem 0
151 | font-size: .9rem
152 | font-weight: 400
153 | border-bottom: solid 2px $light-grey
154 | transition: color .3s, border-color .3s
155 | background: #fff
156 | &:invalid
157 | box-shadow: none
158 | &:active, &:focus
159 | outline: none
160 | color: $black
161 | border-color: $black
162 |
163 | label
164 | font-size: .9rem
165 | line-height: 1
166 | font-weight: 500
167 | font-family: roboto_mono, helvetica, sans-serif
168 |
169 | button
170 | outline: none
171 | border: none
172 | border-radius: 0
173 | &:focus, &:active
174 | outline: none
175 | &::-moz-focus-inner
176 | border: 0
177 |
178 | .unCap
179 | text-transform: none
180 |
181 | a
182 | white-space: nowrap
183 | cursor: pointer
184 | outline: none
185 | &:active, &:focus
186 | outline: none
187 |
188 | input
189 | outline: none
190 | box-sizing: border-box
191 | width: 100%
192 |
193 | .modal-opened
194 | overflow: hidden
195 |
196 | .scrollbar-measure
197 | width: 100px !important
198 | height: 100px !important
199 | overflow-y: scroll
200 | position: absolute
201 | top: -9999px
202 |
203 | .bkdPic
204 | background-repeat: no-repeat !important
205 | background-position: 50% 50% !important
206 | background-size: 100% 100% !important
207 | filter: grayscale(30%)
208 |
209 | button.disabled
210 | opacity: .6
211 | cursor: not-allowed
212 | &:hover
213 | opacity: .6
214 |
215 | .blacklisted
216 | opacity: .5
217 |
--------------------------------------------------------------------------------
/src/styles/mediaqueries.sass:
--------------------------------------------------------------------------------
1 | @import './variables.sass'
2 |
3 | @media screen and (max-width: 400px)
4 | .lIWrapper, .cIWrapper
5 | & > *
6 | flex-basis: 100%
7 | flex: 0 1 calc(50% -2rem)
8 | h3.error
9 | font-size: 2rem
10 | .priceWrapper
11 | flex-direction: column
12 | .inputWrapper
13 | flex: 1
14 | .input
15 | width: 100%
16 | &:first-child
17 | margin-right: 0 !important
18 | &:last-child
19 | margin-left: 0 !important
20 |
21 | @media screen and (min-width: 768px)
22 | button.normalBtn
23 | padding: .5rem 1rem !important
24 | .wrapper
25 | max-width: 1100px
26 | margin: 10px auto
27 | padding: 75px !important
28 | .footer
29 | margin: 90px 0 0 0
30 | padding: 0
31 | .header
32 | margin: 0px 0 20px 0
33 | display: flex
34 | flex-direction: row
35 | .header > h1
36 | flex: 1
37 | text-align: left
38 | .logo
39 | font-size: 2.5rem !important
40 | .menu
41 | flex: none
42 | height: auto
43 | display: flex
44 | width: auto
45 | .navLink
46 | margin-left: 20px
47 | font-size: .9rem
48 | &:last-child
49 | border: none
50 | padding: 0
51 | .item
52 | flex: 0 1 31%
53 |
54 | .otherInfo
55 | margin: 0 2rem
56 |
57 | .basicInfo
58 | padding: 0 1.5rem
59 |
60 | .profilePic, .profilePic > img
61 | width: 150px
62 | height: 150px
63 |
64 | .infoWrapper
65 | flex-direction: row
66 |
67 | h3.error
68 | margin: 25vh 0
69 |
70 | .tradesInfoWrapper
71 | flex-direction: row
72 | justify-content: space-between
73 | .tradeReqWrapper, .tradeProposedWrapper
74 | flex: 0 1 calc(50% - 1rem)
75 | .noitemHeading
76 | font-size: 1rem
77 | .trWrapper, .ptWrapper, .uIWrapper
78 | .userImg
79 | flex: 0 0 4rem
80 | height: 4rem
81 | width: 4rem
82 | .lower
83 | display: flex
84 | h4
85 | font-size: 1rem
86 | a
87 | font-size: 1rem
88 | .tradeProposedWrapper
89 | margin-top: 0
90 |
91 | .itemPageWrapper
92 | flex-direction: row
93 | .itemInfoWrapper
94 | margin-left: 2rem
95 | margin-top: 0
96 | .reqTradeBtn, removeTradeBtn
97 | margin: 3rem 0 2rem 0
98 | border: solid 2px $black
99 | .uIWrapper
100 | margin: 0 0 4rem 0
101 | .upper
102 | flex-direction: row
103 | .itemCost
104 | color: lighten($black, 20%)
105 | font-size: 1.2rem
106 | margin: 1rem 0
107 | font-weight: 500
108 | .addDate
109 | margin: .5rem 0
110 | font-weight: 500
111 | .itemDescription
112 | margin: 2rem 0
113 | .itemInfo
114 | margin-left: 3rem
115 | flex: 1
116 | .userImg
117 | margin-top: .2rem
118 | flex: 0 0 12rem
119 | height: 12rem
120 | width: 12rem
121 | .myItemsWrapper
122 | padding: 0 0 2rem 0
123 |
124 | .addItemWrapper
125 | .itemWrapper
126 | .flexWrapper
127 | display: flex
128 | flex-direction: row !important
129 | .itemPicWrapper
130 | .img
131 | height: 7rem !important
132 | width: 7rem !important
133 | .imgLoaded
134 | border: none
135 | width: 11rem !important
136 | height: 11rem !important
137 | .itemInfoWrapper
138 | margin-left: 2rem
139 | margin-top: 0 !important
140 |
141 |
--------------------------------------------------------------------------------
/src/styles/variables.sass:
--------------------------------------------------------------------------------
1 | $background-color: #fcfcfc
2 | $modal-background: #fafafa
3 | $modal-border-color: #f2f2f2
4 | $hider-background: #fcfcfc
5 | $black: #222
6 | $light-grey: rgba(0, 0, 0, .3)
7 | $grey: rgba(34, 34, 34, .55)
8 | $fbColor: rgba(59, 89, 152, 0.9)
9 | $twitterColor: rgba(85, 172, 238, 0.9)
10 | $googleColor: rgba(221, 75, 57, 0.9)
11 | $red: #d25c5c
12 | $green: #73b15d
13 |
14 | =hider-gradient
15 | background: #f2f2f2
16 | background: -webkit-gradient(radial,50% 25%,0,50% 25%,800,from(rgba(252, 252, 252,.9)),to(#f2f2f2)) transparent
17 | background: -moz-radial-gradient(center 45deg,circle cover,rgba(252, 252, 252,.9) 0%,#f2f2f2 100%) transparent
18 |
19 | @function strip-units($number)
20 | @return $number / ($number * 0 + 1)
21 |
22 | @function get-vw($target)
23 | $vw-context: (1000*.01) * 1px
24 | @return ($target/$vw-context) * 1vw
25 |
26 | @function getMaxWidth($a, $b)
27 | @warn "a= #{strip-units($a)}, b= #{strip-units($b)}"
28 | @if $a > $b
29 | @return $b+px
30 | @else
31 | @return $a+px
32 |
--------------------------------------------------------------------------------
/src/utils/checkAuth.js:
--------------------------------------------------------------------------------
1 | import fetch from 'unfetch';
2 |
3 | export default function CheckAuth(success, failure) {
4 | fetch('/isUserLoggedIn', {
5 | method: 'GET',
6 | headers: {
7 | 'Accept': 'application/json',
8 | 'Content-Type': 'application/json',
9 | 'Cache': 'no-cache'
10 | },
11 | credentials: 'same-origin'
12 | })
13 | .then(res => res.json())
14 | .then(res => {
15 | if (res.error === 'UNAUTHORIZED') {
16 | failure();
17 | } else {
18 | success(res.notificationsCount);
19 | }
20 | })
21 | .catch(() => {
22 | /* eslint-disable no-console */
23 | failure();
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/loadPageProps.js:
--------------------------------------------------------------------------------
1 | export default function loadPageProps(title) {
2 | document.title = title;
3 | document.querySelector('.menu').classList.remove('open');
4 | document.body.style.cursor = 'default';
5 | // document.body doesnt not work in IE and firefox
6 | document.documentElement.scrollTop = 0;
7 | document.body.scrollTop = 0;
8 | }
9 |
--------------------------------------------------------------------------------
/src/webpack-public-path.js:
--------------------------------------------------------------------------------
1 | // Dynamically set the webpack public path at runtime below
2 | // This magic global is used by webpack to set the public path at runtime.
3 | // The public path is set dynamically to avoid the following issues:
4 | // 1. https://github.com/coryhouse/react-slingshot/issues/205
5 | // 2. https://github.com/coryhouse/react-slingshot/issues/181
6 | // 3. https://github.com/coryhouse/react-slingshot/pull/125
7 | // Documentation: http://webpack.github.io/docs/configuration.html#output-publicpath
8 | // eslint-disable-next-line no-undef
9 | __webpack_public_path__ = window.location.protocol + "//" + window.location.host + "/";
10 |
--------------------------------------------------------------------------------
/tools/.yarnclean:
--------------------------------------------------------------------------------
1 | !browser-sync-ui/lib/plugins/history # need this for now because of https://github.com/yarnpkg/yarn/issues/1396#issuecomment-255965666
2 |
--------------------------------------------------------------------------------
/tools/analyzeBundle.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer';
3 | import config from '../webpack.config.prod';
4 |
5 | config.plugins.push(new BundleAnalyzerPlugin());
6 |
7 | const compiler = webpack(config);
8 |
9 | compiler.run((error, stats) => {
10 | if (error) {
11 | throw new Error(error);
12 | }
13 |
14 | console.log(stats); // eslint-disable-line no-console
15 | });
16 |
--------------------------------------------------------------------------------
/tools/build.js:
--------------------------------------------------------------------------------
1 | // More info on Webpack's Node API here: https://webpack.github.io/docs/node.js-api.html
2 | // Allowing console calls below since this is a build file.
3 | /* eslint-disable no-console */
4 | import webpack from 'webpack';
5 | import config from '../webpack.config.prod';
6 | import {chalkError, chalkSuccess, chalkWarning, chalkProcessing} from './chalkConfig';
7 |
8 | process.env.NODE_ENV = 'production'; // this assures React is built in prod mode and that the Babel dev config doesn't apply.
9 |
10 | console.log(chalkProcessing('Generating minified bundle. This will take a moment...'));
11 |
12 | webpack(config).run((error, stats) => {
13 | if (error) { // so a fatal error occurred. Stop here.
14 | console.log(chalkError(error));
15 | return 1;
16 | }
17 |
18 | const jsonStats = stats.toJson();
19 |
20 | if (jsonStats.hasErrors) {
21 | return jsonStats.errors.map(error => console.log(chalkError(error)));
22 | }
23 |
24 | if (jsonStats.hasWarnings) {
25 | console.log(chalkWarning('Webpack generated the following warnings: '));
26 | jsonStats.warnings.map(warning => console.log(chalkWarning(warning)));
27 | }
28 |
29 | console.log(`Webpack stats: ${stats}`);
30 |
31 | // if we got this far, the build succeeded.
32 | console.log(chalkSuccess('Your app is compiled in production mode in /dist. It\'s ready to roll!'));
33 |
34 | return 0;
35 | });
36 |
--------------------------------------------------------------------------------
/tools/chalkConfig.js:
--------------------------------------------------------------------------------
1 | // Centralized configuration for chalk, which is used to add color to console.log statements.
2 | import chalk from 'chalk';
3 | export const chalkError = chalk.red;
4 | export const chalkSuccess = chalk.green;
5 | export const chalkWarning = chalk.yellow;
6 | export const chalkProcessing = chalk.blue;
7 |
--------------------------------------------------------------------------------
/tools/distServer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const express = require('express');
3 | const http = require('http');
4 | const mongoose = require('mongoose');
5 | const passport = require('passport');
6 | const session = require('express-session');
7 | const bodyParser = require('body-parser');
8 | const path = require('path');
9 | const authRoutes = require('../server/authRoutes');
10 | const logoutRoute = require('../server/logoutRoute');
11 |
12 | const api = require('../server/api');
13 | const environment = process.env.NODE_ENV;
14 | const app = express();
15 | const server = http.createServer(app);
16 |
17 | require('dotenv').load({path: path.resolve(process.cwd() ,".env")});
18 |
19 |
20 | if (environment === "productiion") {
21 | require('dotenv').load({path: path.resolve(process.cwd() ,".env")});
22 | }
23 |
24 | require('../config/passport')(passport);
25 |
26 | app.use(bodyParser.json());
27 | app.use(bodyParser.urlencoded({extended: true}));
28 |
29 | app.use(session({
30 | secret: process.env.SESSION_SECRET,
31 | resave: false,
32 | saveUninitialized: true
33 | }));
34 |
35 | app.use(passport.initialize());
36 | app.use(passport.session());
37 |
38 | const options = {
39 | server: {
40 | socketOptions: { keepAlive: 300000, connectTimeoutMS: 30000 }
41 | },
42 | replset: {
43 | socketOptions: { keepAlive: 300000, connectTimeoutMS: 30000 }
44 | }
45 | };
46 |
47 | mongoose.connect(process.env.MONGO_URI, options, err => {
48 | if (err) {
49 | console.error(`Some error happened while connecting to db - ${err}`);
50 | } else {
51 | console.log(`db connected successfully!`);
52 | }
53 | });
54 |
55 | mongoose.Promise = require('bluebird');
56 | const conn = mongoose.connection;
57 |
58 | conn.on('error', console.error.bind(console, 'connection error:'));
59 |
60 | conn.once('open', () => {
61 | // passport auth routes
62 | authRoutes(app, passport);
63 | logoutRoute(app);
64 | api(app);
65 | app.use(express.static('dist'));
66 | app.get('*', (_req, res) => {
67 | res.sendFile(path.join(process.cwd(), 'dist', 'index.html'));
68 | });
69 |
70 | });
71 |
72 | server.listen(process.env.PORT);
73 | /* eslint-disable no-console */
74 | console.log('Express server is listening on port: ' + server.address().port);
75 |
--------------------------------------------------------------------------------
/tools/fileMock.js:
--------------------------------------------------------------------------------
1 | // Return an empty string or other mock path to emulate the url that
2 | // webpack provides via the file-loader
3 | module.exports = '';
4 |
--------------------------------------------------------------------------------
/tools/nodeVersionCheck.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var exec = require('child_process').exec;
3 |
4 | exec('node -v', function (err, stdout) {
5 | if (err) throw err;
6 |
7 | if (parseFloat(stdout.slice(1)) < 4) {
8 | throw new Error('React Slingshot requires node 4.0 or greater.');
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/tools/server.js:
--------------------------------------------------------------------------------
1 | // common server for both production and development
2 | import historyApiFallback from 'connect-history-api-fallback';
3 | import webpack from 'webpack';
4 | import webpackDevMiddleware from 'webpack-dev-middleware';
5 | import webpackHotMiddleware from 'webpack-hot-middleware';
6 | import { chalkSuccess } from './chalkConfig';
7 | import config from '../webpack.config.dev';
8 | import express from 'express';
9 | import http from 'http';
10 | import mongoose from 'mongoose';
11 | import passport from 'passport';
12 | import session from 'express-session';
13 | import bodyParser from 'body-parser';
14 | import path from 'path';
15 | import authRoutes from '../server/authRoutes';
16 | import logoutRoute from '../server/logoutRoute';
17 |
18 | const api = require('../server/api');
19 | const environment = process.argv[2];
20 | const app = express();
21 | const server = http.createServer(app);
22 |
23 | /* eslint-disable no-console */
24 | console.log(chalkSuccess(`Starting Express server in ${environment} mode...`));
25 |
26 | const runWebpack = () => {
27 | if (environment !== "production") {
28 | const bundler = webpack(config);
29 |
30 | app.use(express.static('src/*.html'));
31 | app.use(historyApiFallback());
32 | app.use(webpackHotMiddleware(bundler));
33 | app.use(webpackDevMiddleware(bundler, {
34 | // Dev middleware can't access config, so we provide publicPath
35 | publicPath: config.output.publicPath,
36 |
37 | // These settings suppress noisy webpack output so only errors are displayed to the console.
38 | noInfo: false,
39 | quiet: false,
40 | stats: {
41 | assets: false,
42 | colors: true,
43 | version: false,
44 | hash: false,
45 | timings: false,
46 | chunks: false,
47 | chunkModules: false
48 | }
49 |
50 | // for other settings see
51 | // http://webpack.github.io/docs/webpack-dev-middleware.html
52 | }));
53 | } else {
54 | app.use(express.static('dist'));
55 | }
56 | };
57 |
58 | if (environment !== "production") {
59 | require('dotenv').load({path: path.resolve(process.cwd() ,".env")});
60 | }
61 |
62 | require('../config/passport')(passport);
63 |
64 | app.use(bodyParser.json());
65 | app.use(bodyParser.urlencoded({extended: true}));
66 |
67 | app.use(session({
68 | secret: process.env.SESSION_SECRET,
69 | resave: false,
70 | saveUninitialized: true
71 | }));
72 |
73 | app.use(passport.initialize());
74 | app.use(passport.session());
75 |
76 | const options = {
77 | server: {
78 | socketOptions: { keepAlive: 300000, connectTimeoutMS: 30000 }
79 | },
80 | replset: {
81 | socketOptions: { keepAlive: 300000, connectTimeoutMS: 30000 }
82 | }
83 | };
84 |
85 | mongoose.connect(process.env.MONGO_URI, options, err => {
86 | if (err) {
87 | console.error(`Some error happened while connecting to db - ${err}`);
88 | } else {
89 | console.log(`db connected successfully!`);
90 | }
91 | });
92 |
93 | mongoose.Promise = require('bluebird');
94 | const conn = mongoose.connection;
95 |
96 | conn.on('error', console.error.bind(console, 'connection error:'));
97 |
98 | conn.once('open', () => {
99 | // passport auth routes
100 | authRoutes(app, passport);
101 | logoutRoute(app);
102 | api(app);
103 | runWebpack();
104 | });
105 |
106 | server.listen(process.env.PORT);
107 | /* eslint-disable no-console */
108 | console.log(chalkSuccess('Express server is listening on port: ' + server.address().port));
109 |
--------------------------------------------------------------------------------
/tools/srcServer.js:
--------------------------------------------------------------------------------
1 | // This file configures the development web server
2 | // which supports hot reloading and synchronized testing.
3 |
4 | // Require Browsersync along with webpack and middleware for it
5 | import browserSync from 'browser-sync';
6 | // Required for react-router browserHistory
7 | // see https://github.com/BrowserSync/browser-sync/issues/204#issuecomment-102623643
8 | import historyApiFallback from 'connect-history-api-fallback';
9 | import webpack from 'webpack';
10 | import webpackDevMiddleware from 'webpack-dev-middleware';
11 | import webpackHotMiddleware from 'webpack-hot-middleware';
12 | import config from '../webpack.config.dev';
13 |
14 | const bundler = webpack(config);
15 |
16 | // Run Browsersync and use middleware for Hot Module Replacement
17 | browserSync({
18 | port: 3000,
19 | ui: {
20 | port: 3001
21 | },
22 | server: {
23 | baseDir: 'src',
24 |
25 | middleware: [
26 | historyApiFallback(),
27 |
28 | webpackDevMiddleware(bundler, {
29 | // Dev middleware can't access config, so we provide publicPath
30 | publicPath: config.output.publicPath,
31 |
32 | // These settings suppress noisy webpack output so only errors are displayed to the console.
33 | noInfo: false,
34 | quiet: false,
35 | stats: {
36 | assets: false,
37 | colors: true,
38 | version: false,
39 | hash: false,
40 | timings: false,
41 | chunks: false,
42 | chunkModules: false
43 | },
44 |
45 | // for other settings see
46 | // http://webpack.github.io/docs/webpack-dev-middleware.html
47 | }),
48 |
49 | // bundler should be the same as above
50 | webpackHotMiddleware(bundler)
51 | ]
52 | },
53 |
54 | // no need to watch '*.js' here, webpack will take care of it for us,
55 | // including full page reloads if HMR won't work
56 | files: [
57 | 'src/*.html'
58 | ]
59 | });
60 |
--------------------------------------------------------------------------------
/tools/startMessage.js:
--------------------------------------------------------------------------------
1 | import {chalkSuccess} from './chalkConfig';
2 |
3 | /* eslint-disable no-console */
4 | console.log(chalkSuccess('Starting app in dev mode...'));
5 |
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import HtmlWebpackPlugin from 'html-webpack-plugin';
3 | import autoprefixer from 'autoprefixer';
4 | import path from 'path';
5 |
6 | export default {
7 | resolve: {
8 | extensions: ['*', '.js', '.jsx', '.json']
9 | },
10 | devtool: 'eval-source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool
11 | entry: [
12 | // must be first entry to properly set public path
13 | './src/webpack-public-path',
14 | 'webpack-hot-middleware/client?reload=true',
15 | path.resolve(__dirname, 'src/index.js') // Defining path seems necessary for this to work consistently on Windows machines.
16 | ],
17 | target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test
18 | output: {
19 | path: path.resolve(__dirname, 'dist'), // Note: Physical files are only output by the production build task `npm run build`.
20 | publicPath: '/',
21 | filename: 'bundle.js'
22 | },
23 | plugins: [
24 | new webpack.DefinePlugin({
25 | 'process.env.NODE_ENV': JSON.stringify('development'), // Tells React to build in either dev or prod modes. https://facebook.github.io/react/downloads.html (See bottom)
26 | __DEV__: true
27 | }),
28 | new webpack.HotModuleReplacementPlugin(),
29 | new webpack.NoEmitOnErrorsPlugin(),
30 | new HtmlWebpackPlugin({ // Create HTML file that includes references to bundled CSS and JS.
31 | template: 'src/index.ejs',
32 | minify: {
33 | removeComments: true,
34 | collapseWhitespace: true
35 | },
36 | inject: true
37 | }),
38 | new webpack.LoaderOptionsPlugin({
39 | minimize: false,
40 | debug: true,
41 | noInfo: true, // set to false to see a list of every file being bundled.
42 | options: {
43 | sassLoader: {
44 | includePaths: [path.resolve(__dirname, 'src', 'scss')]
45 | },
46 | context: '/',
47 | postcss: () => [autoprefixer],
48 | }
49 | })
50 | ],
51 | module: {
52 | rules: [
53 | {test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel-loader']},
54 | {test: /\.eot(\?v=\d+.\d+.\d+)?$/, loader: 'file-loader'},
55 | {test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
56 | {test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'},
57 | {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'},
58 | {test: /\.(jpe?g|png|gif)$/i, loader: 'file-loader?name=[name].[ext]'},
59 | {test: /\.ico$/, loader: 'file-loader?name=[name].[ext]'},
60 | {test: /(\.css|\.scss|\.sass)$/, loaders: ['style-loader', 'css-loader?sourceMap', 'postcss-loader', 'sass-loader?sourceMap']}
61 | ]
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | // For info about this file refer to webpack and webpack-hot-middleware documentation
2 | // For info on how we're generating bundles with hashed filenames for cache busting: https://medium.com/@okonetchnikov/long-term-caching-of-static-assets-with-webpack-1ecb139adb95#.w99i89nsz
3 | import webpack from 'webpack';
4 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
5 | import WebpackMd5Hash from 'webpack-md5-hash';
6 | import HtmlWebpackPlugin from 'html-webpack-plugin';
7 | import autoprefixer from 'autoprefixer';
8 | import path from 'path';
9 |
10 | const GLOBALS = {
11 | 'process.env.NODE_ENV': JSON.stringify('production'),
12 | __DEV__: false
13 | };
14 |
15 | export default {
16 | resolve: {
17 | extensions: ['*', '.js', '.jsx', '.json']
18 | },
19 | devtool: 'source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool
20 | entry: path.resolve(__dirname, 'src/index'),
21 | target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test
22 | output: {
23 | path: path.resolve(__dirname, 'dist'),
24 | publicPath: '/',
25 | filename: '[name].[chunkhash].js'
26 | },
27 | plugins: [
28 | // Hash the files using MD5 so that their names change when the content changes.
29 | new WebpackMd5Hash(),
30 |
31 | // Tells React to build in prod mode. https://facebook.github.io/react/downloads.html
32 | new webpack.DefinePlugin(GLOBALS),
33 |
34 | // Generate an external css file with a hash in the filename
35 | new ExtractTextPlugin('[name].[contenthash].css'),
36 |
37 | // Generate HTML file that contains references to generated bundles. See here for how this works: https://github.com/ampedandwired/html-webpack-plugin#basic-usage
38 | new HtmlWebpackPlugin({
39 | template: 'src/index.ejs',
40 | minify: {
41 | removeComments: true,
42 | collapseWhitespace: true,
43 | removeRedundantAttributes: true,
44 | useShortDoctype: true,
45 | removeEmptyAttributes: true,
46 | removeStyleLinkTypeAttributes: true,
47 | keepClosingSlash: true,
48 | minifyJS: true,
49 | minifyCSS: true,
50 | minifyURLs: true
51 | },
52 | inject: true,
53 | // Note that you can add custom options here if you need to handle other custom logic in index.html
54 | // To track JavaScript errors via TrackJS, sign up for a free trial at TrackJS.com and enter your token below.
55 | trackJSToken: ''
56 | }),
57 |
58 | // Minify JS
59 | new webpack.optimize.UglifyJsPlugin({ sourceMap: true }),
60 |
61 | new webpack.LoaderOptionsPlugin({
62 | minimize: true,
63 | debug: false,
64 | noInfo: true, // set to false to see a list of every file being bundled.
65 | options: {
66 | sassLoader: {
67 | includePaths: [path.resolve(__dirname, 'src', 'scss')]
68 | },
69 | context: '/',
70 | postcss: () => [autoprefixer],
71 | }
72 | })
73 | ],
74 | module: {
75 | rules: [
76 | {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'},
77 | {test: /\.eot(\?v=\d+.\d+.\d+)?$/, loader: 'url-loader?name=[name].[ext]'},
78 | {test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=[name].[ext]'},
79 | {test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream&name=[name].[ext]'},
80 | {test: /\.svg(\?v=\d+.\d+.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml&name=[name].[ext]'},
81 | {test: /\.(jpe?g|png|gif)$/i, loader: 'file-loader?name=[name].[ext]'},
82 | {test: /\.ico$/, loader: 'file-loader?name=[name].[ext]'},
83 | {test: /(\.css|\.scss|\.sass)$/, loader: ExtractTextPlugin.extract('css-loader?sourceMap!postcss-loader!sass-loader?sourceMap')}
84 | ]
85 | }
86 | };
87 |
--------------------------------------------------------------------------------