├── client
├── components
│ ├── style
│ │ ├── button.js
│ │ ├── colors.js
│ │ └── device.js
│ ├── NotFound.jsx
│ ├── Logo.jsx
│ ├── Contest.jsx
│ ├── Election.jsx
│ ├── Candidate.jsx
│ ├── Official.jsx
│ ├── Finances.jsx
│ └── OfficialDetails.jsx
├── assets
│ └── noImage.png
├── index.js
├── util
│ ├── ProtectedRoute.jsx
│ └── index.js
├── App.jsx
├── models
│ ├── election.js
│ ├── finances.js
│ └── officials.js
├── Session.js
└── routes
│ ├── Portal.jsx
│ ├── Elections.jsx
│ └── Officials.jsx
├── .travis.yml
├── .babelrc
├── .gitignore
├── README.md
├── jest-teardown.js
├── jest-setup.js
├── server
├── routes
│ └── api.js
├── index.js
└── controllers
│ └── apiController.js
├── index.html
├── webpack.config.js
├── __tests__
├── puppeteer.js
├── supertest.js
└── enzyme.js
└── package.json
/client/components/style/button.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | secret.js
4 | bundle.js
5 | TestComponent.jsx
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Congre$$ | Mobile-first, location based, political information data aggregator
2 |
--------------------------------------------------------------------------------
/jest-teardown.js:
--------------------------------------------------------------------------------
1 | module.exports = async (globalConfig) => {
2 | testServer.close();
3 | };
4 |
--------------------------------------------------------------------------------
/jest-setup.js:
--------------------------------------------------------------------------------
1 | module.exports = async () => {
2 | global.testServer = await require('./server');
3 | };
4 |
--------------------------------------------------------------------------------
/client/assets/noImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/followthemoeny/CongreSS/HEAD/client/assets/noImage.png
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './App.jsx';
4 |
5 | if (module && module.hot) {
6 | module.hot.accept();
7 | }
8 |
9 | render(, document.getElementById('root'));
10 |
--------------------------------------------------------------------------------
/client/components/style/colors.js:
--------------------------------------------------------------------------------
1 | const colors = {
2 | blue: '#0052a5',
3 | lightBlue: '#e5eef7',
4 | hoverBlue: '#0052bd',
5 | activeBlue: '#005276',
6 | red: '#e0162b',
7 | white: '#fffff0',
8 | black: '#1b1b1b'
9 | };
10 |
11 | export default colors;
--------------------------------------------------------------------------------
/client/util/ProtectedRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect, Route } from "react-router-dom";
3 |
4 | const ProtectedRoute = ({ validate, children, ...rest }) => {
5 | const render = ({ location }) => {
6 | const redirect = validate(location);
7 | if (redirect) {
8 | return ;
9 | }
10 | return children;
11 | }
12 |
13 | return ;
14 | };
15 |
16 | export default ProtectedRoute;
--------------------------------------------------------------------------------
/client/components/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFound = () => {
4 | return (
5 |
No candidate information available.
16 | );
17 |
18 | const ContestWrapper = styled.div`
19 | padding: 15px;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | `;
24 |
25 | const BallotTitle = styled(ContestWrapper)`
26 | font-size: 1.2em;
27 | padding: 0;
28 | `;
29 | ContestWrapper.displayName = 'ContestWrapper';
30 |
31 | const CandidatesWrapper = styled.div`
32 | font-weight: 1.4em;
33 | `;
34 |
35 | const Candidates = styled.div`
36 | font-size: 1.1em;
37 | `;
38 | return (
39 | No available contest information.
19 | );
20 |
21 | const ElectionWrapper = styled.div`
22 | display: flex;
23 | flex-direction: column;
24 | align-items: center;
25 | `;
26 | const ElectionHeader = styled.div`
27 | padding-top: 20px;
28 | display: flex;
29 | justify-content: center;
30 | font-size: 2em;
31 | font-weight: bold;
32 | `;
33 |
34 | ElectionHeader.displayName = 'ElectionHeader';
35 |
36 | const Date = styled.div`
37 | font-weight: bold;
38 | font-size: 1.4em;
39 | padding-bottom: 0.25em;
40 | color: ${colors.red};
41 | `;
42 |
43 | const Address = styled(Date)`
44 | font-size: 1em;
45 | color: black;
46 | `;
47 |
48 | return (
49 | ', () =>{
216 | // console.log(wrapper.type())
217 | // expect(wrapper.type()).toEqual('div')
218 | // })
219 |
220 | // })
221 | });
222 |
--------------------------------------------------------------------------------
/client/components/OfficialDetails.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import styled from 'styled-components';
4 | import { access } from '../util';
5 | import { device } from '../components/style/device';
6 | import colors from './style/colors';
7 | import Finances from '../components/Finances.jsx';
8 | import img from '../assets/noImage.png';
9 |
10 | const mediaIcons = {
11 | Facebook: (
12 |
20 | ),
21 | Twitter: (
22 |
30 | ),
31 | YouTube: (
32 |
40 | ),
41 | emails: (
42 |
50 | ),
51 | phones: (
52 |
60 | ),
61 | urls: (
62 |
70 | ),
71 | };
72 |
73 | const CardWrapper = styled.div`
74 | display: flex;
75 | flex-direction: column;
76 | align-items: center;
77 | margin: 0px 30px 40px 30px;
78 | padding-top: 20px;
79 | padding: 10px;
80 | padding-left: 0px;
81 | max-width: 80vw;
82 | min-width: 80vw;
83 | border-radius: 4px;
84 | @media ${device.laptop} {
85 | max-width: 100px;
86 | min-width: 25vw;
87 | align-items: center;
88 | }
89 | @media ${device.desktop} {
90 | min-width: 15vw;
91 | }
92 | `;
93 |
94 | const InfoWrapper = styled.div`
95 | display: flex;
96 | flex-direction: column;
97 | align-items: center;
98 | `;
99 |
100 | const Picture = styled.img`
101 | object-fit: cover;
102 | max-width: 300px;
103 | min-width: 300px;
104 | max-height: 300px;
105 | min-height: 300px;
106 | border-radius: 5%;
107 | margin-top: 10px;
108 | `;
109 |
110 | const Name = styled.h2`
111 | font-weight: bold;
112 | margin: 0;
113 | margin-bottom: 5px;
114 | padding: 0;
115 | `;
116 |
117 | const Position = styled.h3`
118 | padding: 0px 0px 5px 0px;
119 | margin: 0;
120 | `;
121 |
122 | const MoreInfoButton = styled.button`
123 | width: 300px;
124 | padding: 20px 18px 20px 18px;
125 | margin-top: 0px;
126 | font-size: 1.1em;
127 | font-weight: bold;
128 | border: none;
129 | border-radius: ${(props) => (props.rounded ? '0 0 10px 10px;' : '10px;')}
130 | background-color: ${colors.blue};
131 | color: white;
132 | `;
133 |
134 | const Party = styled.p`
135 | padding-top: 0px;
136 | padding-bottom: 10px;
137 | margin: 0px 15px;
138 | font-weight: bold;
139 | `;
140 |
141 | Party.displayName = 'Party';
142 |
143 | const ContactWrapper = styled.div`
144 | display: flex;
145 | justify-content: space-around;
146 | width: 100%;
147 | font-size: 1em;
148 | font-family: Arial;
149 | font-weight: bold;
150 | padding-top: 5px;
151 | `;
152 |
153 | const OfficialContact = styled.a`
154 | background-color: ${colors.red};
155 | border-radius: 10px;
156 | padding: 0.25em 1em 0.25em 1em;
157 | text-decoration: none;
158 | color: white;
159 | border: 1px solid red;
160 | width: 100px;
161 | `;
162 |
163 | const Media = styled.ul`
164 | list-style: none;
165 | display: flex;
166 | padding: 0px;
167 | margin: 0px;
168 | `;
169 | Media.displayName = 'Media';
170 |
171 | const ExternalAnchor = styled.a`
172 | &:hover {
173 | opacity: 80%;
174 | }
175 | text-decoration: none;
176 | display: flex;
177 | align-items: center;
178 | box-sizing: border-box;
179 | margin: 1em;
180 | `;
181 | ExternalAnchor.displayName = 'ExternalAnchor';
182 |
183 | const IdSpan = styled.span`
184 | margin-left: 1em;
185 | font-weight: bold;
186 | `;
187 |
188 | const OfficialDetails = (props) => {
189 | const websiteUrl = access(props).urls[0](null);
190 | const phoneNumber = access(props).phones[0](null);
191 | const address = access(props).address[0].line1(null);
192 | const {
193 | name,
194 | party,
195 | photoUrl,
196 | position,
197 | details,
198 | state,
199 | channels,
200 | urls,
201 | emails,
202 | phones,
203 | } = props;
204 |
205 | const outlets = [];
206 |
207 | if (channels) {
208 | const track = {};
209 | channels.forEach((el, index) => {
210 | if (!track[el.type]) {
211 | track[el.type] = true;
212 | outlets.push(
213 |
214 |
218 | {mediaIcons[el.type]}
219 |
220 | ,
221 | );
222 | }
223 | });
224 | }
225 | return (
226 |
227 |
228 | {name}
229 | {position}
230 | {party}
231 | {
234 | e.target.src = img;
235 | }}
236 | />
237 | {/*
238 |
239 | {emails ? (
240 |
241 | Email
242 |
243 | ) : null}
244 |
245 |
246 | {phones ? (
247 | Call
248 | ) : null}
249 |
250 |
251 | {urls ? (
252 | Website
253 | ) : null}
254 |
255 | {urls ? Website : null}
256 | */}
257 |
258 |
259 | {emails ? (
260 |
261 | {mediaIcons.emails}
262 |
263 | ) : null}
264 |
265 |
266 | {phones ? (
267 |
268 | {mediaIcons.phones}
269 |
270 | ) : null}
271 |
272 |
273 | {urls ? (
274 |
275 | {mediaIcons.urls}
276 |
277 | ) : null}
278 |
279 | {outlets}
280 |
281 |
282 |
283 |
284 | );
285 | };
286 | export default OfficialDetails;
287 |
--------------------------------------------------------------------------------
/client/models/officials.js:
--------------------------------------------------------------------------------
1 | const officals = {
2 | "normalizedInput": {
3 | "line1": "24 Starr Street",
4 | "city": "Brooklyn",
5 | "state": "NY",
6 | "zip": "11221"
7 | },
8 | "kind": "civicinfo#representativeInfoResponse",
9 | "divisions": {
10 | "ocd-division/country:us/state:ny/cd:7": {
11 | "name": "New York's 7th congressional district",
12 | "officeIndices": [
13 | 3
14 | ]
15 | },
16 | "ocd-division/country:us/state:ny/place:new_york": {
17 | "name": "New York city",
18 | "officeIndices": [
19 | 12,
20 | 13,
21 | 14
22 | ]
23 | },
24 | "ocd-division/country:us/state:ny/sldl:53": {
25 | "name": "New York Assembly district 53",
26 | "officeIndices": [
27 | 7
28 | ]
29 | },
30 | "ocd-division/country:us": {
31 | "name": "United States",
32 | "officeIndices": [
33 | 0,
34 | 1
35 | ]
36 | },
37 | "ocd-division/country:us/state:ny/supreme_court:2": {
38 | "name": "NY State Supreme Court - 2nd District"
39 | },
40 | "ocd-division/country:us/state:ny": {
41 | "name": "New York",
42 | "officeIndices": [
43 | 2,
44 | 4,
45 | 5,
46 | 8,
47 | 9
48 | ]
49 | },
50 | "ocd-division/country:us/state:ny/sldu:18": {
51 | "name": "New York State Senate district 18",
52 | "officeIndices": [
53 | 6
54 | ]
55 | },
56 | "ocd-division/country:us/state:ny/county:kings": {
57 | "name": "Kings County",
58 | "alsoKnownAs": [
59 | "ocd-division/country:us/state:ny/borough:brooklyn",
60 | "ocd-division/country:us/state:ny/place:new_york/county:kings"
61 | ],
62 | "officeIndices": [
63 | 10,
64 | 11
65 | ]
66 | }
67 | },
68 | "offices": [
69 | {
70 | "name": "President of the United States",
71 | "divisionId": "ocd-division/country:us",
72 | "levels": [
73 | "country"
74 | ],
75 | "roles": [
76 | "headOfState",
77 | "headOfGovernment"
78 | ],
79 | "officialIndices": [
80 | 0
81 | ]
82 | },
83 | {
84 | "name": "Vice President of the United States",
85 | "divisionId": "ocd-division/country:us",
86 | "levels": [
87 | "country"
88 | ],
89 | "roles": [
90 | "deputyHeadOfGovernment"
91 | ],
92 | "officialIndices": [
93 | 1
94 | ]
95 | },
96 | {
97 | "name": "U.S. Senator",
98 | "divisionId": "ocd-division/country:us/state:ny",
99 | "levels": [
100 | "country"
101 | ],
102 | "roles": [
103 | "legislatorUpperBody"
104 | ],
105 | "officialIndices": [
106 | 2,
107 | 3
108 | ]
109 | },
110 | {
111 | "name": "U.S. Representative",
112 | "divisionId": "ocd-division/country:us/state:ny/cd:7",
113 | "levels": [
114 | "country"
115 | ],
116 | "roles": [
117 | "legislatorLowerBody"
118 | ],
119 | "officialIndices": [
120 | 4
121 | ]
122 | },
123 | {
124 | "name": "Governor of New York",
125 | "divisionId": "ocd-division/country:us/state:ny",
126 | "levels": [
127 | "administrativeArea1"
128 | ],
129 | "roles": [
130 | "headOfGovernment"
131 | ],
132 | "officialIndices": [
133 | 5
134 | ]
135 | },
136 | {
137 | "name": "Lieutenant Governor of New York",
138 | "divisionId": "ocd-division/country:us/state:ny",
139 | "levels": [
140 | "administrativeArea1"
141 | ],
142 | "roles": [
143 | "deputyHeadOfGovernment"
144 | ],
145 | "officialIndices": [
146 | 6
147 | ]
148 | },
149 | {
150 | "name": "NY State Senator",
151 | "divisionId": "ocd-division/country:us/state:ny/sldu:18",
152 | "levels": [
153 | "administrativeArea1"
154 | ],
155 | "roles": [
156 | "legislatorUpperBody"
157 | ],
158 | "officialIndices": [
159 | 7
160 | ]
161 | },
162 | {
163 | "name": "NY State Assemblymember",
164 | "divisionId": "ocd-division/country:us/state:ny/sldl:53",
165 | "levels": [
166 | "administrativeArea1"
167 | ],
168 | "roles": [
169 | "legislatorLowerBody"
170 | ],
171 | "officialIndices": [
172 | 8
173 | ]
174 | },
175 | {
176 | "name": "NY Attorney General",
177 | "divisionId": "ocd-division/country:us/state:ny",
178 | "levels": [
179 | "administrativeArea1"
180 | ],
181 | "officialIndices": [
182 | 9
183 | ]
184 | },
185 | {
186 | "name": "NY State Comptroller",
187 | "divisionId": "ocd-division/country:us/state:ny",
188 | "levels": [
189 | "administrativeArea1"
190 | ],
191 | "officialIndices": [
192 | 10
193 | ]
194 | },
195 | {
196 | "name": "Brooklyn District Attorney",
197 | "divisionId": "ocd-division/country:us/state:ny/county:kings",
198 | "levels": [
199 | "administrativeArea2"
200 | ],
201 | "officialIndices": [
202 | 11
203 | ]
204 | },
205 | {
206 | "name": "Brooklyn Borough President",
207 | "divisionId": "ocd-division/country:us/state:ny/county:kings",
208 | "levels": [
209 | "administrativeArea2"
210 | ],
211 | "officialIndices": [
212 | 12
213 | ]
214 | },
215 | {
216 | "name": "New York Mayor",
217 | "divisionId": "ocd-division/country:us/state:ny/place:new_york",
218 | "levels": [
219 | "locality"
220 | ],
221 | "officialIndices": [
222 | 13
223 | ]
224 | },
225 | {
226 | "name": "New York City Comptroller",
227 | "divisionId": "ocd-division/country:us/state:ny/place:new_york",
228 | "levels": [
229 | "locality"
230 | ],
231 | "officialIndices": [
232 | 14
233 | ]
234 | },
235 | {
236 | "name": "New York Public Advocate",
237 | "divisionId": "ocd-division/country:us/state:ny/place:new_york",
238 | "levels": [
239 | "locality"
240 | ],
241 | "officialIndices": [
242 | 15
243 | ]
244 | }
245 | ],
246 | "officials": [
247 | {
248 | "name": "Donald J. Trump",
249 | "address": [
250 | {
251 | "line1": "1600 Pennsylvania Avenue Northwest",
252 | "city": "Washington",
253 | "state": "DC",
254 | "zip": "20500"
255 | }
256 | ],
257 | "party": "Republican Party",
258 | "phones": [
259 | "(202) 456-1111"
260 | ],
261 | "urls": [
262 | "https://www.whitehouse.gov/"
263 | ],
264 | "photoUrl": "https://www.whitehouse.gov/sites/whitehouse.gov/files/images/45/PE%20Color.jpg",
265 | "channels": [
266 | {
267 | "type": "Facebook",
268 | "id": "DonaldTrump"
269 | },
270 | {
271 | "type": "Twitter",
272 | "id": "potus"
273 | },
274 | {
275 | "type": "YouTube",
276 | "id": "whitehouse"
277 | }
278 | ]
279 | },
280 | {
281 | "name": "Mike Pence",
282 | "address": [
283 | {
284 | "line1": "1600 Pennsylvania Avenue Northwest",
285 | "city": "Washington",
286 | "state": "DC",
287 | "zip": "20500"
288 | }
289 | ],
290 | "party": "Republican Party",
291 | "phones": [
292 | "(202) 456-1111"
293 | ],
294 | "urls": [
295 | "https://www.whitehouse.gov/"
296 | ],
297 | "photoUrl": "https://www.whitehouse.gov/sites/whitehouse.gov/files/images/45/VPE%20Color.jpg",
298 | "channels": [
299 | {
300 | "type": "Facebook",
301 | "id": "mikepence"
302 | },
303 | {
304 | "type": "Twitter",
305 | "id": "VP"
306 | }
307 | ]
308 | },
309 | {
310 | "name": "Kirsten E. Gillibrand",
311 | "address": [
312 | {
313 | "line1": "478 Russell Senate Office Building",
314 | "city": "Washington",
315 | "state": "DC",
316 | "zip": "20510"
317 | }
318 | ],
319 | "party": "Democratic Party",
320 | "phones": [
321 | "(202) 224-4451"
322 | ],
323 | "urls": [
324 | "https://www.gillibrand.senate.gov/"
325 | ],
326 | "photoUrl": "http://bioguide.congress.gov/bioguide/photo/G/G000555.jpg",
327 | "channels": [
328 | {
329 | "type": "Facebook",
330 | "id": "KirstenGillibrand"
331 | },
332 | {
333 | "type": "Twitter",
334 | "id": "SenGillibrand"
335 | },
336 | {
337 | "type": "YouTube",
338 | "id": "KirstenEGillibrand"
339 | }
340 | ]
341 | },
342 | {
343 | "name": "Charles E. Schumer",
344 | "address": [
345 | {
346 | "line1": "322 Hart Senate Office Building",
347 | "city": "Washington",
348 | "state": "DC",
349 | "zip": "20510"
350 | }
351 | ],
352 | "party": "Democratic Party",
353 | "phones": [
354 | "(202) 224-6542"
355 | ],
356 | "urls": [
357 | "https://www.schumer.senate.gov/"
358 | ],
359 | "photoUrl": "http://bioguide.congress.gov/bioguide/photo/S/S000148.jpg",
360 | "channels": [
361 | {
362 | "type": "Facebook",
363 | "id": "senschumer"
364 | },
365 | {
366 | "type": "Twitter",
367 | "id": "SenSchumer"
368 | },
369 | {
370 | "type": "YouTube",
371 | "id": "SenatorSchumer"
372 | },
373 | {
374 | "type": "YouTube",
375 | "id": "ChuckSchumer"
376 | }
377 | ]
378 | },
379 | {
380 | "name": "Nydia M. Velázquez",
381 | "address": [
382 | {
383 | "line1": "2302 Rayburn House Office Building",
384 | "city": "Washington",
385 | "state": "DC",
386 | "zip": "20515"
387 | }
388 | ],
389 | "party": "Democratic Party",
390 | "phones": [
391 | "(202) 225-2361"
392 | ],
393 | "urls": [
394 | "https://velazquez.house.gov/"
395 | ],
396 | "photoUrl": "http://bioguide.congress.gov/bioguide/photo/V/V000081.jpg",
397 | "channels": [
398 | {
399 | "type": "Facebook",
400 | "id": "RepNydiaVelazquez"
401 | },
402 | {
403 | "type": "Twitter",
404 | "id": "NydiaVelazquez"
405 | },
406 | {
407 | "type": "YouTube",
408 | "id": "nydiavelazquez"
409 | }
410 | ]
411 | },
412 | {
413 | "name": "Andrew M. Cuomo",
414 | "address": [
415 | {
416 | "line1": "The Honorable Andrew M. Cuomo",
417 | "line2": "Governor of New York State",
418 | "line3": "NYS State Capitol Building",
419 | "city": "Albany",
420 | "state": "NY",
421 | "zip": "12224"
422 | }
423 | ],
424 | "party": "Democratic Party",
425 | "phones": [
426 | "(518) 474-8390"
427 | ],
428 | "urls": [
429 | "https://andrewcuomo.com/"
430 | ],
431 | "channels": [
432 | {
433 | "type": "Facebook",
434 | "id": "GovernorAndrewCuomo"
435 | },
436 | {
437 | "type": "Twitter",
438 | "id": "nygovcuomo"
439 | },
440 | {
441 | "type": "YouTube",
442 | "id": "nygovcuomo"
443 | }
444 | ]
445 | }
446 | ]
447 | };
448 |
449 | export default officals;
450 |
--------------------------------------------------------------------------------