├── client ├── src │ ├── utils │ │ ├── data.jsx │ │ ├── restClient.js │ │ └── MapContainer.js │ ├── index.css │ ├── img │ │ ├── home.jpg │ │ ├── carousel.png │ │ ├── ceremony.jpg │ │ ├── greetings.jpg │ │ └── testimonial.jpg │ ├── App.test.js │ ├── css │ │ ├── navigationBar.css │ │ ├── main.css │ │ ├── greetings.css │ │ ├── RSVP.css │ │ ├── home.css │ │ └── ceremony.css │ ├── index.js │ ├── components │ │ ├── Location.jsx │ │ ├── Home.jsx │ │ ├── Ceremony.jsx │ │ ├── NavigationBar.jsx │ │ ├── Greetings.jsx │ │ └── RSVP.jsx │ ├── App.css │ ├── App.js │ ├── logo.svg │ ├── ReactScrollableAnchor.js │ └── registerServiceWorker.js ├── run_dev.sh ├── build │ ├── home.jpg │ ├── favicon.ico │ ├── static │ │ └── media │ │ │ ├── home.1f400a9e.jpg │ │ │ ├── greetings.bfd7a3df.jpg │ │ │ ├── glyphicons-halflings-regular.e18bbf61.ttf │ │ │ ├── glyphicons-halflings-regular.f4769f9b.eot │ │ │ ├── glyphicons-halflings-regular.fa277232.woff │ │ │ ├── glyphicons-halflings-regular.448c34a5.woff2 │ │ │ ├── logo.5d5d9eef.svg │ │ │ └── glyphicons-halflings-regular.89889688.svg │ ├── manifest.json │ ├── index.html │ ├── asset-manifest.json │ └── service-worker.js ├── public │ ├── home.jpg │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── .gitignore └── package.json ├── .gitignore ├── Procfile ├── app.db ├── app.test.db ├── requirements.txt ├── test.py ├── README.md └── app.py /client/src/utils/data.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | /__pycache__ 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | 2 | web: gunicorn app:app 3 | -------------------------------------------------------------------------------- /app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/app.db -------------------------------------------------------------------------------- /app.test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/app.test.db -------------------------------------------------------------------------------- /client/run_dev.sh: -------------------------------------------------------------------------------- 1 | export REACT_APP_USERS_SERVICE_URL=http://localhost:8000 2 | npm start 3 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /client/build/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/home.jpg -------------------------------------------------------------------------------- /client/public/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/public/home.jpg -------------------------------------------------------------------------------- /client/src/img/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/src/img/home.jpg -------------------------------------------------------------------------------- /client/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/favicon.ico -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/src/img/carousel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/src/img/carousel.png -------------------------------------------------------------------------------- /client/src/img/ceremony.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/src/img/ceremony.jpg -------------------------------------------------------------------------------- /client/src/img/greetings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/src/img/greetings.jpg -------------------------------------------------------------------------------- /client/src/img/testimonial.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/src/img/testimonial.jpg -------------------------------------------------------------------------------- /client/build/static/media/home.1f400a9e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/static/media/home.1f400a9e.jpg -------------------------------------------------------------------------------- /client/build/static/media/greetings.bfd7a3df.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/static/media/greetings.bfd7a3df.jpg -------------------------------------------------------------------------------- /client/build/static/media/glyphicons-halflings-regular.e18bbf61.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/static/media/glyphicons-halflings-regular.e18bbf61.ttf -------------------------------------------------------------------------------- /client/build/static/media/glyphicons-halflings-regular.f4769f9b.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/static/media/glyphicons-halflings-regular.f4769f9b.eot -------------------------------------------------------------------------------- /client/build/static/media/glyphicons-halflings-regular.fa277232.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/static/media/glyphicons-halflings-regular.fa277232.woff -------------------------------------------------------------------------------- /client/build/static/media/glyphicons-halflings-regular.448c34a5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liranfar/flask-react-on-heroku/HEAD/client/build/static/media/glyphicons-halflings-regular.448c34a5.woff2 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.7 2 | Flask==1.0.2 3 | Flask-Cors==3.0.4 4 | Flask-SQLAlchemy==2.3.2 5 | gunicorn==19.8.1 6 | itsdangerous==0.24 7 | Jinja2==2.10 8 | MarkupSafe==1.0 9 | psycopg2==2.7.4 10 | six==1.11.0 11 | SQLAlchemy==1.2.8 12 | Werkzeug==0.14.1 13 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /client/src/css/navigationBar.css: -------------------------------------------------------------------------------- 1 | #navigation-bar { 2 | font-family: 'EB Garamond',serif; 3 | margin-bottom: 0; 4 | border-radius: 0px; 5 | background-color: white; 6 | border-color: green; 7 | 8 | } 9 | 10 | #navigation-bar li a:hover, #navigation-bar li a:visited , #navigation-bar li a:focus { 11 | color:black; 12 | } 13 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | # /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /client/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | import ReactScrollableAnchor from './ReactScrollableAnchor'; 7 | // ReactDOM.render(, document.getElementById('root')); 8 | ReactDOM.render(, document.getElementById('root')); 9 | registerServiceWorker(); 10 | -------------------------------------------------------------------------------- /client/src/components/Location.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MapContainer from '../utils/MapContainer' 3 | import ScrollableAnchor from 'react-scrollable-anchor' 4 | export default props => { 5 | const { id } = props 6 | return 7 |
12 |
13 | } -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /client/build/index.html: -------------------------------------------------------------------------------- 1 | Gili & Matan - The Wedding
-------------------------------------------------------------------------------- /client/src/utils/restClient.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (logMessage) => { 4 | console.log(logMessage); 5 | axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/users/ping`, { 6 | // params :{ 7 | // dataType: 'json' 8 | // } 9 | }) 10 | .then(function (response) { 11 | console.log('SUCCESS', response) 12 | }) 13 | .catch(function (error) { 14 | console.log('ERROR', error) 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /client/src/components/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ScrollableAnchor from 'react-scrollable-anchor' 3 | import '../css/home.css' 4 | export default (props) => { 5 | const { id } = props; 6 | return 7 |
8 |
9 |
10 |
11 |

Gili & Matan

12 |

Are getting

13 |

MARRIED!

14 |
15 |
16 |
17 |
18 |
19 | } -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "bootstrap": "^3.3.7", 8 | "google-maps-react": "^2.0.2", 9 | "react": "^16.4.0", 10 | "react-bootstrap": "^0.32.1", 11 | "react-dom": "^16.4.0", 12 | "react-full-page": "^0.1.4", 13 | "react-scripts": "1.1.4", 14 | "react-scrollable-anchor": "^0.6.1" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/src/css/main.css: -------------------------------------------------------------------------------- 1 | div.line { 2 | position: absolute; 3 | z-index: 4; 4 | top: -26px; 5 | left: 0; 6 | width: 100%; 7 | height: 24px; 8 | background-color: #a4c956; 9 | } 10 | 11 | div.line:before { 12 | content: ''; 13 | position: absolute; 14 | top: -3px; 15 | left: 0; 16 | width: 100%; 17 | height: 3px; 18 | background-color: #b9d57d; 19 | } 20 | 21 | div.line:after { 22 | content: ''; 23 | position: absolute; 24 | bottom: -3px; 25 | left: 0; 26 | width: 100%; 27 | height: 3px; 28 | background-color: #b9d57d; 29 | } 30 | 31 | div.line-bottom { 32 | top: 97%; 33 | } -------------------------------------------------------------------------------- /client/src/components/Ceremony.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../css/ceremony.css' 3 | import ScrollableAnchor from 'react-scrollable-anchor' 4 | 5 | export default props => { 6 | const { id } = props; 7 | return 8 |
9 |
10 |
11 |
12 |
13 |

The Ceremony

14 |

Will Be Held On

15 |

July 6, 2018

16 |

11:00 - Reception | 12:00 - Ceremony

17 |
18 |
19 |
20 |
21 |
22 |
23 | } -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import handleClick from './utils/restClient'; 5 | 6 | class App extends Component { 7 | constructor(){ 8 | super(); 9 | 10 | this.state = {}; 11 | this.handleClick = handleClick.bind(this); 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 |
18 | logo 19 |

Welcome to React

20 |
21 |

22 | To get started, edit src/App.js and save to reload. 23 |

24 | 31 |
32 | ); 33 | } 34 | } 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /client/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "static/css/main.f84a3ebb.css", 3 | "main.css.map": "static/css/main.f84a3ebb.css.map", 4 | "main.js": "static/js/main.b6a0aaa4.js", 5 | "main.js.map": "static/js/main.b6a0aaa4.js.map", 6 | "static/media/glyphicons-halflings-regular.eot": "static/media/glyphicons-halflings-regular.f4769f9b.eot", 7 | "static/media/glyphicons-halflings-regular.svg": "static/media/glyphicons-halflings-regular.89889688.svg", 8 | "static/media/glyphicons-halflings-regular.ttf": "static/media/glyphicons-halflings-regular.e18bbf61.ttf", 9 | "static/media/glyphicons-halflings-regular.woff": "static/media/glyphicons-halflings-regular.fa277232.woff", 10 | "static/media/glyphicons-halflings-regular.woff2": "static/media/glyphicons-halflings-regular.448c34a5.woff2", 11 | "static/media/greetings.jpg": "static/media/greetings.bfd7a3df.jpg", 12 | "static/media/home.jpg": "static/media/home.1f400a9e.jpg", 13 | "static/media/logo.svg": "static/media/logo.5d5d9eef.svg" 14 | } -------------------------------------------------------------------------------- /client/src/components/NavigationBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Navbar, 4 | Nav, 5 | NavItem, 6 | NavDropdown, 7 | MenuItem, 8 | Button 9 | } from 'react-bootstrap' 10 | import '../css/navigationBar.css' 11 | 12 | export default (props) => { 13 | 14 | const { brand,id } = props; 15 | 16 | return 17 | 18 | 19 | {brand} 20 | 21 | 22 | 23 | 24 | 41 | 42 | 43 | 44 | 45 | 46 | } -------------------------------------------------------------------------------- /client/src/components/Greetings.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../css/greetings.css' 3 | import carousel_img from '../img/testimonial.jpg' 4 | import { Carousel } from 'react-bootstrap' 5 | import ScrollableAnchor from 'react-scrollable-anchor' 6 | 7 | export default (props) => { 8 | const greetings = props.greetings 9 | // const greetings = ['1', '2'] 10 | const greeetingsElements = greetings.map((greeting, index) => { 11 | return 12 |
13 | {/* 900x500 */} 14 | 15 |
16 |

{greeting.content} 17 | {/*
18 | {greeting.name} */} 19 |

20 |
21 |
22 |
23 |
24 | }) 25 | 26 | return ( 27 | 28 |
29 |

Greetings

30 | 31 | {greeetingsElements} 32 | 33 |
34 |
); 35 | 36 | 37 | } -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from flask_testing import TestCase 2 | import json 3 | import unittest 4 | from app import db, app 5 | 6 | 7 | class BaseTestCase(TestCase): 8 | def create_app(self): 9 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.test.db' 10 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 11 | return app 12 | 13 | def setUp(self): 14 | db.create_all() 15 | db.session.commit() 16 | 17 | def tearDown(self): 18 | db.session.remove() 19 | db.drop_all() 20 | 21 | 22 | class TestRsvp(BaseTestCase): 23 | def test_rsvp_added(self): 24 | """Ensure the Rsvp has beed added succefully""" 25 | with self.client: 26 | response = self.client.post( 27 | '/rsvp', 28 | data=json.dumps({ 29 | 'fullName': 'michael', 30 | 'email': 'michael@mherman.org', 31 | 'message': 'see you there!' 32 | }), 33 | content_type='application/json', 34 | ) 35 | data = json.loads(response.data.decode()) 36 | self.assertEqual(response.status_code, 201) 37 | self.assertIn('success', data['status']) 38 | self.assertIn('RSVP has been added', data['message']) 39 | 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() -------------------------------------------------------------------------------- /client/src/css/greetings.css: -------------------------------------------------------------------------------- 1 | div#greetings { 2 | text-align: center; 3 | background-image: url(../img/greetings.jpg); 4 | height: 70vh; 5 | width: 100%; 6 | padding-top: 15px; 7 | padding-bottom: 50px; 8 | /* margin-bottom: 15px; */ 9 | } 10 | 11 | .carousel { 12 | height: 75%; 13 | background-color: rgba(0,0,0,0.2); 14 | } 15 | 16 | div#greetings h1 { 17 | padding-bottom: 30px; 18 | font-size: 85px; 19 | line-height: 87px; 20 | font-weight: 700; 21 | color: #fff; 22 | text-shadow: 2px 1px 0 rgba(0,0,0,0.2); 23 | text-align: center; 24 | font-family: 'Dancing Script',cursive; 25 | } 26 | 27 | .testimonial { 28 | max-width: 850px; 29 | margin: 25px auto; 30 | padding: 0 20px; 31 | } 32 | 33 | .testimonial-avatar { 34 | /* width: 100px; */ 35 | border-radius: 50%; 36 | } 37 | 38 | .testimonial-quote { 39 | display: block; 40 | font-size: 24px; 41 | font-weight: 300; 42 | padding: 45px 0px 0px 0px; 43 | position: relative; 44 | left: 0; 45 | right: 0; 46 | color: #fff; 47 | } 48 | 49 | ol li { 50 | background-color: black !important; 51 | } 52 | ol.carousel-indicators { 53 | bottom: 0px; 54 | display: none; 55 | } 56 | 57 | .carousel-control.right, .carousel-control.left { 58 | background-image: none; 59 | } 60 | 61 | p.message { 62 | margin: 30px 0 0 0; 63 | white-space: pre-line; 64 | } -------------------------------------------------------------------------------- /client/src/utils/MapContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react'; 3 | 4 | export class MapContainer extends Component { 5 | state = { 6 | showingInfoWindow: false, 7 | activeMarker: {}, 8 | selectedPlace: {}, 9 | }; 10 | 11 | onMarkerClick = (props, marker, e) => 12 | this.setState({ 13 | selectedPlace: props, 14 | activeMarker: marker, 15 | showingInfoWindow: true 16 | }); 17 | 18 | onMapClicked = (props) => { 19 | if (this.state.showingInfoWindow) { 20 | this.setState({ 21 | showingInfoWindow: false, 22 | activeMarker: null 23 | }) 24 | } 25 | }; 26 | 27 | render() { 28 | return ( 29 | 36 | 41 | 42 | 45 |
46 |

{this.state.selectedPlace.name}

47 |
48 |
49 |
50 | ); 51 | } 52 | } 53 | 54 | export default GoogleApiWrapper({ 55 | apiKey: ('AIzaSyBtz1k5ufj6N8SEokFGuB3aCyZLD24n5P4') 56 | })(MapContainer) 57 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Gili & Matan - The Wedding 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flask and react deployed on heroku 2 | a onepage react app served by flask & sqlite/postgres db, deployed easily on heroku. 3 | 4 | [Production ready example](https://gili-matan.herokuapp.com/) 5 | 6 | # Usage 7 | 8 | ### Local Development 9 | ``` 10 | $ git clone... 11 | $ cd <> 12 | ``` 13 | 14 | ### DB 15 | ``` 16 | sudo -u postgres psql postgres 17 | ``` 18 | ### Server 19 | 20 | ``` 21 | $ virtualenv -p python3 venv 22 | $ pip install -r requirements.txt 23 | $ gunicorn app:app 24 | ``` 25 | 26 | ### Client 27 | ``` 28 | $ cd client 29 | $ npm install 30 | $ chmod +x run_dev.sh 31 | $ ./run_dev.sh 32 | ``` 33 | To build client app comment out `process.env.REACT_APP_USERS_SERVICE_URL` and run : `npm run build` 34 | 35 | To check the build directory on a static server : 36 | ``` 37 | $ cd build 38 | $ python3 -m http.server 39 | ``` 40 | 41 | ### Deployment on heroku 42 | Disable flask-CORS 43 | ``` 44 | $ heroku login ... 45 | $ heroku create 46 | $ heroku addons:add heroku-postgresql:hobby-dev 47 | $ heroku run python 48 | >>> import os 49 | >>> os.environ.get('DATABASE_URL') 50 | ``` 51 | copy db_url to your app_config, and then: 52 | ``` 53 | $ heroku git:remote 54 | $ git push heroku master 55 | $ heroku run python 56 | >>> from app import db 57 | >>> db.create_all() 58 | >>> exit() 59 | ``` 60 | 61 | ## Resources 62 | 63 | 1. [static files in flask](https://stackoverflow.com/questions/20646822/how-to-serve-static-files-in-flask) 64 | 65 | 1. [python3 virtualenv](https://stackoverflow.com/questions/23842713/using-python-3-in-virtualenv) 66 | 67 | 1. [react-bootstrap](https://react-bootstrap.github.io/) 68 | 69 | 1. [react-scrollable-anchor](https://github.com/gabergg/react-scrollable-anchor) 70 | 71 | 1. [google-maps-react](https://github.com/fullstackreact/google-maps-react) 72 | 73 | 1. [Sahil Diwan - flask and postgres on heroku](http://blog.sahildiwan.com/posts/flask-and-postgresql-app-deployed-on-heroku/) 74 | 75 | 1. [Setting up flask app in heroku with a database](https://gist.github.com/mayukh18/2223bc8fc152631205abd7cbf1efdd41/) 76 | 77 | 1. [Testeimonials Carousel](https://codepen.io/jamy/pen/gbdWGJ) -------------------------------------------------------------------------------- /client/src/css/RSVP.css: -------------------------------------------------------------------------------- 1 | 2 | section#rsvp { 3 | margin-bottom: 70px; 4 | } 5 | 6 | section#rsvp input[type=text].form-control, section#rsvp select.form-control, 7 | section#rsvp input[type=email].form-control, 8 | section#rsvp textarea.form-control { 9 | border-color: #ced4da!important; 10 | border-radius: 2px; 11 | color: #495057; 12 | font-family: Montserrat,sans-serif; 13 | /* font-size: .6875rem!important; 14 | font-weight: 600; 15 | letter-spacing: .0625rem!important; 16 | padding: .875rem 1rem; */ 17 | } 18 | 19 | section#rsvp h3 { 20 | position: relative; 21 | font-family: 'EB Garamond',serif; 22 | font-size: 48px; 23 | line-height: 20px; 24 | font-weight: 400; 25 | color: grey; 26 | text-align: center; 27 | /* text-transform: capitalize; */ 28 | letter-spacing: 5px; 29 | text-shadow: 2px 1px 0 rgba(0,0,0,0.25); 30 | margin-top: 50px; 31 | margin-bottom: 50px; 32 | } 33 | 34 | section#rsvp h4 { 35 | text-align: center; 36 | } 37 | 38 | section#rsvp button { 39 | margin-top: 3.5rem; 40 | background-color: #fff; 41 | background-image: none; 42 | -webkit-background-size: initial; 43 | -moz-background-size: initial; 44 | -ms-background-size: initial; 45 | -o-background-size: initial; 46 | background-size: initial; 47 | background-position: initial; 48 | background-repeat: no-repeat; 49 | background-attachment: initial; 50 | color: #999; 51 | border: 1px solid #bbb; 52 | -webkit-border-radius: 1px; 53 | -moz-border-radius: 1px; 54 | -ms-border-radius: 1px; 55 | -o-border-radius: 1px; 56 | border-radius: 1px; 57 | -webkit-transition: all .3s ease-in-out; 58 | -moz-transition: all .3s ease-in-out; 59 | -ms-transition: all .3s ease-in-out; 60 | -o-transition: all .3s ease-in-out; 61 | transition: all .3s ease-in-out; 62 | } 63 | 64 | section#rsvp button:hover { 65 | background-color: #aacd62; 66 | background-image: none; 67 | -webkit-background-size: initial; 68 | -moz-background-size: initial; 69 | -ms-background-size: initial; 70 | -o-background-size: initial; 71 | background-size: initial; 72 | background-position: initial; 73 | background-repeat: no-repeat; 74 | background-attachment: initial; 75 | border: 1px solid #8cb339; 76 | color: #fff; 77 | text-shadow: 0 -1px 0 #999; 78 | -webkit-transition: all .3s ease-in-out; 79 | -moz-transition: all .3s ease-in-out; 80 | -ms-transition: all .3s ease-in-out; 81 | -o-transition: all .3s ease-in-out; 82 | transition: all .3s ease-in-out; 83 | } 84 | -------------------------------------------------------------------------------- /client/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/css/home.css: -------------------------------------------------------------------------------- 1 | #home { 2 | position: relative; 3 | width: 100%; 4 | padding-top: 120px; 5 | padding-right: 0; 6 | padding-bottom: 140px; 7 | padding-left: 0; 8 | background-color: transparent; 9 | background-image: url(../img/home.jpg); 10 | -webkit-background-size: cover; 11 | -moz-background-size: cover; 12 | -ms-background-size: cover; 13 | -o-background-size: cover; 14 | background-size: cover; 15 | background-position: top center; 16 | background-repeat: no-repeat; 17 | background-attachment: initial; 18 | } 19 | 20 | #home h1 { 21 | padding-bottom: 30px; 22 | font-size: 85px; 23 | line-height: 87px; 24 | font-weight: 700; 25 | color: #fff; 26 | text-shadow: 2px 1px 0 rgba(0,0,0,0.2); 27 | text-align: center; 28 | font-family: 'Dancing Script',cursive; 29 | } 30 | 31 | #home h3{ 32 | position: relative; 33 | font-family: 'EB Garamond',serif; 34 | font-size: 18px; 35 | line-height: 20px; 36 | font-weight: 400; 37 | color: #fff; 38 | text-align: center; 39 | text-transform: capitalize; 40 | letter-spacing: 5px; 41 | text-shadow: 2px 1px 0 rgba(0,0,0,0.25); 42 | } 43 | 44 | #home h3:before { 45 | content: ""; 46 | position: absolute; 47 | z-index: 3px; 48 | left: 20%; 49 | top: 50%; 50 | height: 2px; 51 | width: 20%; 52 | margin-top: 0; 53 | background-color: #fff; 54 | background-image: none; 55 | -webkit-background-size: initial; 56 | -moz-background-size: initial; 57 | -ms-background-size: initial; 58 | -o-background-size: initial; 59 | background-size: initial; 60 | background-position: top center; 61 | background-repeat: no-repeat; 62 | background-attachment: initial; 63 | } 64 | 65 | #home h3:after { 66 | content: ""; 67 | position: absolute; 68 | z-index: 3px; 69 | right: 20%; 70 | top: 50%; 71 | height: 2px; 72 | width: 20%; 73 | margin-top: 0; 74 | background-color: #fff; 75 | background-image: none; 76 | -webkit-background-size: initial; 77 | -moz-background-size: initial; 78 | -ms-background-size: initial; 79 | -o-background-size: initial; 80 | background-size: initial; 81 | background-position: top center; 82 | background-repeat: no-repeat; 83 | background-attachment: initial; 84 | } 85 | 86 | #home h2 { 87 | padding-top: 20px; 88 | font-family: 'Raleway',sans-serif; 89 | font-size: 120px; 90 | line-height: 122px; 91 | font-weight: 900; 92 | color: #a4c956; 93 | text-shadow: 8px 7px 0 rgba(164,201,86,0.35); 94 | text-align: center; 95 | } -------------------------------------------------------------------------------- /client/build/static/media/logo.5d5d9eef.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/ReactScrollableAnchor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import 'bootstrap/dist/css/bootstrap.min.css' 3 | import './css/main.css' 4 | import { Jumbotron } from 'react-bootstrap' 5 | import HomeSection from './components/Home' 6 | import Location from './components/Location' 7 | import NavigationBar from './components/NavigationBar' 8 | import Ceremony from './components/Ceremony' 9 | import RSVP from './components/RSVP' 10 | import axios from 'axios' 11 | import { configureAnchors } from 'react-scrollable-anchor' 12 | import Greetings from './components/Greetings' 13 | export default class Page extends Component { 14 | 15 | constructor() { 16 | super(); 17 | this.state = { 18 | rsvpForm: { 19 | fullName: '', 20 | email: '', 21 | additionalInformation: '', 22 | guests: '', 23 | events: '', 24 | isRsvpSent: false 25 | }, 26 | greetings: [], 27 | }; 28 | this.handleRsvp = this.handleRsvp.bind(this); 29 | this.handleChange = this.handleChange.bind(this); 30 | } 31 | 32 | 33 | handleChange(event) { 34 | const obj = {}; 35 | obj['rsvpForm'] = {...this.state.rsvpForm} 36 | obj['rsvpForm'][event.target.name] = event.target.value; 37 | this.setState(obj); 38 | }; 39 | 40 | handleRsvp = (event) => { 41 | event.preventDefault(); 42 | const data = {...this.state.rsvpForm} 43 | 44 | axios.post(`rsvp`, data) 45 | // axios.post(`${process.env.REACT_APP_USERS_SERVICE_URL}/rsvp`, data) 46 | .then((res) => { 47 | this.setState({rsvpForm: {fullName: '', email: '', additionalInformation: '', greeting: '', guests: '', events: '', isRsvpSent: true}}); 48 | }) 49 | .catch((err) => { 50 | console.log(err); 51 | }); 52 | } 53 | 54 | getGreetings = () => { 55 | // axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/greetings`) 56 | axios.get(`greetings`) 57 | .then((res) => { 58 | this.setState({greetings: res.data.data.greetings}); 59 | }) 60 | .catch((err) => { 61 | console.log(err); 62 | }); 63 | } 64 | 65 | componentDidMount() { 66 | this.getGreetings(); 67 | } 68 | 69 | render() { 70 | configureAnchors({scrollDuration: 1200}) 71 | return ( 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /client/src/css/ceremony.css: -------------------------------------------------------------------------------- 1 | section#ceremony { 2 | position: relative; 3 | width: 100%; 4 | padding-top: 120px; 5 | padding-right: 0; 6 | padding-bottom: 140px; 7 | padding-left: 0; 8 | background-color: transparent; 9 | /* background-image: url(../img/ceremony.jpg); */ 10 | -webkit-background-size: cover; 11 | -moz-background-size: cover; 12 | -ms-background-size: cover; 13 | -o-background-size: cover; 14 | background-size: cover; 15 | background-position: top center; 16 | background-repeat: no-repeat; 17 | background-attachment: initial; 18 | } 19 | 20 | section#ceremony h1 { 21 | padding-bottom: 30px; 22 | font-family: 'Dancing Script',cursive; 23 | font-size: 85px; 24 | line-height: 87px; 25 | font-weight: 700; 26 | color: #9D9D9D; 27 | text-shadow: 2px 1px 0 rgba(0,0,0,0.2); 28 | text-align: center; 29 | } 30 | 31 | section#ceremony h3 { 32 | position: relative; 33 | font-family: 'EB Garamond',serif; 34 | font-size: 18px; 35 | line-height: 20px; 36 | font-weight: 400; 37 | color: grey; 38 | text-align: center; 39 | text-transform: capitalize; 40 | letter-spacing: 5px; 41 | text-shadow: 2px 1px 0 rgba(0,0,0,0.25); 42 | } 43 | 44 | section#ceremony h3:before { 45 | content: ""; 46 | position: absolute; 47 | z-index: 3px; 48 | left: 20%; 49 | top: 50%; 50 | height: 2px; 51 | width: 20%; 52 | margin-top: 0; 53 | background-color: #9D9D9D; 54 | background-image: none; 55 | -webkit-background-size: initial; 56 | -moz-background-size: initial; 57 | -ms-background-size: initial; 58 | -o-background-size: initial; 59 | background-size: initial; 60 | background-position: top center; 61 | background-repeat: no-repeat; 62 | background-attachment: initial; 63 | } 64 | 65 | section#ceremony h3:after { 66 | content: ""; 67 | position: absolute; 68 | z-index: 3px; 69 | right: 20%; 70 | top: 50%; 71 | height: 2px; 72 | width: 20%; 73 | margin-top: 0; 74 | background-color: #9D9D9D; 75 | background-image: none; 76 | -webkit-background-size: initial; 77 | -moz-background-size: initial; 78 | -ms-background-size: initial; 79 | -o-background-size: initial; 80 | background-size: initial; 81 | background-position: top center; 82 | background-repeat: no-repeat; 83 | background-attachment: initial; 84 | } 85 | 86 | section#ceremony h2 { 87 | padding-top: 20px; 88 | font-family: 'Raleway',sans-serif; 89 | font-size: 120px; 90 | line-height: 122px; 91 | font-weight: 900; 92 | color: #a4c956; 93 | text-shadow: 8px 7px 0 rgba(164,201,86,0.35); 94 | text-align: center; 95 | } 96 | 97 | section#ceremony h4 { 98 | font-family: 'EB Garamond',serif; 99 | font-size: 30px; 100 | line-height: 24px; 101 | font-weight: 400; 102 | color: #9d9d9d; 103 | text-align: center; 104 | text-transform: capitalize; 105 | letter-spacing: 8px; 106 | margin-top: 70px; 107 | /* text-shadow: 2px 1px 0 rgba(0,0,0,0.4); */ 108 | } -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | from flask_cors import CORS 3 | from flask_sqlalchemy import SQLAlchemy 4 | 5 | # set the project root directory as the static folder, you can set others. 6 | app = Flask(__name__, 7 | static_url_path='', 8 | static_folder='client/build') 9 | 10 | # CORS(app) 11 | 12 | app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://hlavaovemabkqo:fbbedd28e5a4edddd45977b9640344393f93c409c0425f687d43fd97c174cd5f@ec2-54-225-96-191.compute-1.amazonaws.com:5432/ddell78uvtts4r' 13 | #app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost/gili_matan_rsvp' 14 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 15 | db = SQLAlchemy(app) 16 | 17 | # Create our database model 18 | class Rsvp(db.Model): 19 | __tablename__ = "rsvps" 20 | id = db.Column(db.Integer, primary_key=True) 21 | full_name = db.Column(db.String(120), unique=True) 22 | email = db.Column(db.String(120), unique=True) 23 | additional_information = db.Column(db.String(255)) 24 | greeting = db.Column(db.String(255)) 25 | events = db.Column(db.String(255)) 26 | guests = db.Column(db.Integer) 27 | 28 | def __init__(self,full_name, email, additional_information, greeting, events, guests): 29 | self.full_name = full_name 30 | self.email = email 31 | self.additional_information = additional_information 32 | self.greeting = greeting 33 | self.events = events 34 | self.guests = guests 35 | 36 | # def __repr__(self): 37 | # return '' % self.email 38 | 39 | def to_json(self): 40 | return { 41 | 'fullName': self.full_name, 42 | 'email': self.email, 43 | 'additional_information': self.additional_information, 44 | 'greeting': self.greeting, 45 | 'events':self. events, 46 | 'guests': self.guests, 47 | } 48 | 49 | @app.route('/') 50 | def root(): 51 | return app.send_static_file('index.html') 52 | 53 | @app.route('/users/ping') 54 | def ping_pong(): 55 | return jsonify({ 56 | 'status': 'success', 57 | 'message': 'pong!' 58 | }) 59 | 60 | @app.route('/rsvp', methods=['POST']) 61 | def rsvp(): 62 | post_data = request.get_json() 63 | response_object = { 64 | 'status': 'fail', 65 | 'message': 'Invalid payload.' 66 | } 67 | 68 | full_name = post_data['fullName'] 69 | email = post_data['email'] 70 | additional_information = post_data['additionalInformation'] 71 | greeting = post_data['greeting'] 72 | events = post_data['events'] 73 | guests = post_data['guests'] 74 | 75 | if not db.session.query(Rsvp).filter(Rsvp.email == email).count(): 76 | rsvp = Rsvp(full_name, email, additional_information, greeting, events, guests) 77 | db.session.add(rsvp) 78 | db.session.commit() 79 | 80 | response_object = { 81 | 'status': 'success', 82 | 'message': 'RSVP has been added' 83 | } 84 | 85 | return jsonify(response_object), 201 86 | 87 | return jsonify(response_object), 400 88 | 89 | @app.route('/greetings', methods=['GET']) 90 | def greetings(): 91 | all_greetings = Rsvp.query.with_entities(Rsvp.full_name, Rsvp.greeting).all() 92 | response_object = { 93 | 'status': 'Success', 94 | 'data': { 95 | 'greetings': [{'name' : greeting[0], 'content':greeting[1]} for greeting in all_greetings] 96 | } 97 | } 98 | 99 | return jsonify(response_object), 200 100 | 101 | if __name__ == '__main__': 102 | app.run() 103 | -------------------------------------------------------------------------------- /client/build/service-worker.js: -------------------------------------------------------------------------------- 1 | "use strict";var precacheConfig=[["/index.html","97a772728188eb4438f9df1bce8ef9a2"],["/static/css/main.f84a3ebb.css","cadeed5e6bd514b47f841a0956d6068e"],["/static/js/main.b6a0aaa4.js","8e8eadfac469dc494b410c3cc36225b5"],["/static/media/glyphicons-halflings-regular.448c34a5.woff2","448c34a56d699c29117adc64c43affeb"],["/static/media/glyphicons-halflings-regular.89889688.svg","89889688147bd7575d6327160d64e760"],["/static/media/glyphicons-halflings-regular.e18bbf61.ttf","e18bbf611f2a2e43afc071aa2f4e1512"],["/static/media/glyphicons-halflings-regular.f4769f9b.eot","f4769f9bdb7466be65088239c12046d1"],["/static/media/glyphicons-halflings-regular.fa277232.woff","fa2772327f55d8198301fdb8bcfc8158"],["/static/media/greetings.bfd7a3df.jpg","bfd7a3df30dad2e76b00654cc477afa8"],["/static/media/home.1f400a9e.jpg","1f400a9e0c70a32b01025b272d9a3021"],["/static/media/logo.5d5d9eef.svg","5d5d9eefa31e5e13a6610d9fa7a283bb"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(t){return t.redirected?("body"in t?Promise.resolve(t.body):t.blob()).then(function(e){return new Response(e,{headers:t.headers,status:t.status,statusText:t.statusText})}):Promise.resolve(t)},createCacheKey=function(e,t,n,a){var r=new URL(e);return a&&r.pathname.match(a)||(r.search+=(r.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),r.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,n){var t=new URL(e);return t.hash="",t.search=t.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(t){return n.every(function(e){return!e.test(t[0])})}).map(function(e){return e.join("=")}).join("&"),t.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],a=new URL(t,self.location),r=createCacheKey(a,hashParamName,n,/\.\w{8}\./);return[a.toString(),r]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(a){return setOfCachedUrls(a).then(function(n){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(t){if(!n.has(t)){var e=new Request(t,{credentials:"same-origin"});return fetch(e).then(function(e){if(!e.ok)throw new Error("Request for "+t+" returned a response with status "+e.status);return cleanResponse(e).then(function(e){return a.put(t,e)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var n=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(t){return t.keys().then(function(e){return Promise.all(e.map(function(e){if(!n.has(e.url))return t.delete(e)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(t){if("GET"===t.request.method){var e,n=stripIgnoredUrlParameters(t.request.url,ignoreUrlParametersMatching),a="index.html";(e=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,a),e=urlsToCacheKeys.has(n));var r="/index.html";!e&&"navigate"===t.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],t.request.url)&&(n=new URL(r,self.location).toString(),e=urlsToCacheKeys.has(n)),e&&t.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(e){return console.warn('Couldn\'t serve response for "%s" from cache: %O',t.request.url,e),fetch(t.request)}))}}); -------------------------------------------------------------------------------- /client/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /client/src/components/RSVP.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../css/RSVP.css' 3 | import ScrollableAnchor from 'react-scrollable-anchor' 4 | 5 | export default props => { 6 | const { id } = props; 7 | // const options = map(timezone,(val,key) => ); 8 | return 9 |
10 |
11 |
12 |
13 | {!(props.isRsvpSent) &&
props.handleRsvp(event)} id="form-rsvp"> 14 |
15 |
16 |

RSVP

17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 | 35 |
36 |
37 | 38 | 39 |
40 |
41 | 42 | 52 |
53 | 54 |
55 | 56 |
57 | 58 | 59 |
60 |
61 |
62 | 63 | 70 |
71 |
72 | 73 |
74 |
75 | 76 | 83 |
84 |
85 |
86 | 87 |
88 |
89 |
90 | 91 |