├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.css ├── App.test.js ├── assets └── logo.svg ├── components ├── Create.js ├── CreateEdit.js ├── Dashboard.js ├── FormImageUpload.js ├── FormReactSelect.js ├── Home.js ├── ImageUpload.js ├── ImageUpload.txt ├── Item.js ├── ItemTeaser.js ├── List.js ├── Login.js ├── Register.js └── index.js ├── config └── constants.js ├── helpers └── auth.js ├── index.css └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://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 15 | npm-debug.log 16 | 17 | # constants 18 | src/config/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jamie Graham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 2 | 3 | Thanks to SimonKib for [this helpful SO thread about React / Firebase CRUD](http://stackoverflow.com/questions/35493490/is-there-a-simple-crud-example-app-using-react-js-and-firebase#answer-39932105), and Tyler McGinnis for [this helpful example of Authentication and Routing](https://github.com/tylermcginnis/react-router-firebase-auth) 4 | 5 | ## Setup Instructions 6 | 7 | * If you don't have it already, get create-react-app: 8 | 9 | ```javascript 10 | npm install -g create-react-app 11 | ``` 12 | 13 | * And create a new project: 14 | 15 | ``` 16 | create-react-app my-app 17 | ``` 18 | 19 | * Replace the src directory of your new app with this src directory 20 | * In src directory, install node modules: 21 | ``` 22 | npm install 23 | ``` 24 | 25 | * Start the app on http://localhost:3000 26 | ``` 27 | npm start 28 | ``` 29 | 30 | * If you don't have a Firebase account already, create one for free at https://firebase.google.com 31 | * Customize Firebase config details in src/config/constants.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixmeals", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.8.5" 7 | }, 8 | "dependencies": { 9 | "firebase": "^3.6.5", 10 | "react": "^15.4.2", 11 | "react-dom": "^15.4.2", 12 | "react-dropzone": "^3.9.2", 13 | "react-firebase": "^2.0.2", 14 | "react-firebase-file-uploader": "^2.1.1", 15 | "react-form": "^1.0.0-beta.1", 16 | "react-router": "^4.0.0-alpha.5", 17 | "react-select": "^1.0.0-rc.2" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test --env=jsdom", 23 | "eject": "react-scripts eject" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verticalgrain/react-firebase-auth-crud/343d0c0dd1bd5497d5581cf4ca6b91a35ff9c858/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | React App 17 | 18 | 19 |
20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /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-intro { 18 | font-size: large; 19 | } 20 | 21 | @keyframes App-logo-spin { 22 | from { transform: rotate(0deg); } 23 | to { transform: rotate(360deg); } 24 | } 25 | 26 | .App-header ul li { 27 | display: inline-block; 28 | color: white; 29 | padding: 0 20px; 30 | } 31 | 32 | .App-header a { 33 | color: white; 34 | } -------------------------------------------------------------------------------- /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 | }); 9 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/Create.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ref } from '../config/constants' 3 | import { firebaseAuth } from '../config/constants' 4 | 5 | export default class Create extends Component { 6 | constructor () { 7 | super(); 8 | this.state = { 9 | newitemtext : '' 10 | } 11 | this.dbItems = ref.child('items'); 12 | this.onNewItemChange = this.onNewItemChange.bind(this); 13 | this.handleNewItemSubmit = this.handleNewItemSubmit.bind(this); 14 | } 15 | 16 | handleNewItemSubmit(e) { 17 | e.preventDefault(); 18 | if (this.state.newitemtext && this.state.newitemtext.trim().length !== 0) { 19 | this.dbItems.push({ 20 | title: this.state.newitemtext 21 | }); 22 | this.setState({ 23 | newitemtext: '' 24 | }); 25 | } 26 | } 27 | 28 | onNewItemChange(e) { 29 | this.setState({newitemtext: e.target.value}); 30 | } 31 | 32 | render () { 33 | return ( 34 |
35 | 39 | 40 |
41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /src/components/CreateEdit.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ref } from '../config/constants' 3 | import { Form, Text, Textarea, Radio, FormError } from 'react-form'; 4 | import FormReactSelect from './FormReactSelect' 5 | import FormImageUpload from './FormImageUpload' 6 | 7 | export default class CreateEdit extends Component { 8 | constructor () { 9 | super(); 10 | this.state = { 11 | newitemtext : '' 12 | } 13 | this.dbItems = ref.child('items'); 14 | this.options = [ 15 | { value: 'one', label: 'One' }, 16 | { value: 'two', label: 'Two' } 17 | ] 18 | } 19 | 20 | 21 | render () { 22 | return ( 23 | 24 |
{ 26 | this.dbItems.push(values); 27 | }} 28 | defaultValues={{ 29 | ingredients: [], 30 | instructions: [] 31 | }} 32 | validate={values => { 33 | const { name, ingredients, instructions, tags, description, image, credit } = values 34 | return { 35 | name: !name ? 'A name is required' : undefined, 36 | ingredients: (!ingredients || !ingredients.length) ? 'You need at least one ingredient' : ingredients.map(ingredient => { 37 | const { name } = ingredient 38 | return { 39 | name: !name ? 'An ingredient name is required' : undefined, 40 | } 41 | }), 42 | instructions: (!instructions || !instructions.length) ? 'You must add at least 1 instruction' : false, 43 | } 44 | }} 45 | > 46 | 47 | {({values, submitForm, addValue, removeValue, getError}) => { 48 | return ( 49 | 50 | 51 | 52 | 53 |
54 | {!values.ingredients.length ? ( 55 | No ingredients have been added yet 56 | ) : values.ingredients.map((ingredients, i) => ( // Loop over the values however you'd like 57 |
58 | 59 |
60 |
Ingredient Name
61 | 65 |
66 |
67 |
Ingredient Quantity
68 | 72 |
73 |
74 |
Ingredient Unit
75 | 81 |
82 | 83 | 89 | 90 |
91 | ))} 92 |
93 | 94 |
95 | 101 |
102 | 103 |
104 | {!values.instructions.length ? ( 105 | No instructions have been added yet 106 | ) : values.instructions.map((instructions, i) => ( // Loop over the values however you'd like 107 |
108 | 109 |
110 |
Instruction
111 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | ) 154 | }} 155 | 156 | 157 | ) 158 | } 159 | } -------------------------------------------------------------------------------- /src/components/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class Dashboard extends Component { 4 | render() { 5 | return ( 6 |
7 | This is a dashboard 8 |
9 | ) 10 | } 11 | } -------------------------------------------------------------------------------- /src/components/FormImageUpload.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | var Dropzone = require('react-dropzone'); 3 | import { FormInput } from 'react-form' 4 | 5 | export default class FormImageUpload extends Component { 6 | constructor (props) { 7 | super(props); 8 | this.state = { 9 | field: props.field 10 | } 11 | } 12 | 13 | onDrop(acceptedFiles, rejectedFiles) { 14 | console.log('Accepted files: ', acceptedFiles); 15 | console.log('Rejected files: ', rejectedFiles); 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 | 22 | {({ onDrop }) => { 23 | return ( 24 | 25 |
Try dropping some files here, or click to select files to upload.
26 |
27 | ) 28 | }} 29 |
30 |
31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/FormReactSelect.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { FormInput } from 'react-form' 3 | import { Creatable } from 'react-select' 4 | import 'react-select/dist/react-select.css' 5 | 6 | export default class FormReactSelect extends Component { 7 | constructor (props) { 8 | super(props); 9 | this.state = { 10 | field: props.field, 11 | multi: props.multi, 12 | options: props.options, 13 | } 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 | 20 | {({ setValue, getValue, setTouched }) => { 21 | return ( 22 | setValue(val)} 28 | onBlur={() => setTouched()} 29 | /> 30 | ) 31 | }} 32 | 33 |
34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class Home extends Component { 4 | render () { 5 | return ( 6 |
7 | Home. Not Protected. Anyone can see this. 8 |
9 | ) 10 | } 11 | } -------------------------------------------------------------------------------- /src/components/ImageUpload.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | var Dropzone = require('react-dropzone'); 3 | 4 | export default class ImageUpload extends Component { 5 | 6 | onDrop(acceptedFiles, rejectedFiles) { 7 | console.log('Accepted files: ', acceptedFiles); 8 | console.log('Rejected files: ', rejectedFiles); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 | 15 |
Try dropping some files here, or click to select files to upload.
16 |
17 |
18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/ImageUpload.txt: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import firebase from 'firebase' 3 | import FileUploader from 'react-firebase-file-uploader' 4 | 5 | class ImageUpload extends Component { 6 | state = { 7 | image: '', 8 | isUploading: false, 9 | progress: 0, 10 | imageUrl: '' 11 | } 12 | 13 | handleUploadStart = () => this.setState({isUploading: true, progress: 0}); 14 | handleProgress = (progress) => this.setState({progress}); 15 | handleUploadError = (error) => { 16 | this.setState({isUploading: false}); 17 | console.error(error); 18 | } 19 | handleUploadSuccess = (filename) => { 20 | this.setState({image: filename, progress: 100, isUploading: false}); 21 | firebase.storage().ref('images').child(filename).getDownloadURL().then(url => this.setState({imageUrl: url})); 22 | }; 23 | 24 | render() { 25 | return ( 26 |
27 | 28 | {this.state.isUploading && 29 |

Progress: {this.state.progress}

30 | } 31 | {this.state.imageUrl && 32 | 33 | } 34 | 44 |
45 | ); 46 | } 47 | } 48 | 49 | export default ImageUpload; -------------------------------------------------------------------------------- /src/components/Item.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ref } from '../config/constants' 3 | import { firebaseAuth } from '../config/constants' 4 | import * as firebase from 'firebase'; 5 | 6 | class Item extends Component { 7 | constructor (props) { 8 | super(props); 9 | this.state = { 10 | id: props.params.itemid, 11 | title: 'loading', 12 | text: 'loading', 13 | }; 14 | this.theItem = ref.child('items/'+this.state.id); 15 | 16 | this.removeItem = this.removeItem.bind(this); 17 | this.itemChange = this.itemChange.bind(this); 18 | this.handleUpdateItem = this.handleUpdateItem.bind(this); 19 | 20 | } 21 | 22 | itemChange(e) { 23 | this.setState({ [e.target.name]: e.target.value }) 24 | } 25 | 26 | handleUpdateItem(e) { 27 | e.preventDefault(); 28 | console.log('attempting to update') 29 | // if (this.state.title && this.state.title.trim().length !== 0) { 30 | this.theItem.update(this.state); 31 | // } 32 | 33 | } 34 | 35 | removeItem(key){ 36 | this.theItem.remove(); 37 | } 38 | 39 | componentDidMount = () => { 40 | const item = {}; 41 | 42 | this.theItem.on('value', dataSnapshot => { 43 | 44 | dataSnapshot.forEach(function(childSnapshot) { 45 | item[childSnapshot.key] = childSnapshot.val() 46 | }); 47 | 48 | this.setState({ 49 | title: item['title'], 50 | text: item['text'] 51 | }) 52 | 53 | }) 54 | 55 | } 56 | 57 | render(){ 58 | var _this = this; 59 | 60 | return ( 61 |
62 |
63 | 64 | 71 |
72 | 73 |