├── src ├── index.css ├── index.js ├── App.test.js ├── components │ ├── Dashboard.js │ ├── Home.js │ ├── ImageUpload.js │ ├── Register.js │ ├── ItemTeaser.js │ ├── FormImageUpload.js │ ├── FormReactSelect.js │ ├── Login.js │ ├── Create.js │ ├── ImageUpload.txt │ ├── List.js │ ├── Item.js │ ├── index.js │ └── CreateEdit.js ├── config │ └── constants.js ├── App.css ├── helpers │ └── auth.js └── assets │ └── logo.svg ├── public ├── favicon.ico └── index.html ├── .gitignore ├── package.json ├── README.md └── LICENSE /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verticalgrain/react-firebase-auth-crud/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | -------------------------------------------------------------------------------- /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/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/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 | } -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /src/config/constants.js: -------------------------------------------------------------------------------- 1 | import * as firebase from 'firebase'; 2 | import FirebaseImageUploader from 'firebase-image-uploader' 3 | 4 | const config = { 5 | apiKey: "", 6 | authDomain: "", 7 | databaseURL: "", 8 | storageBucket: "", 9 | messagingSenderId: "" 10 | }; 11 | 12 | firebase.initializeApp(config) 13 | 14 | export const ref = firebase.database().ref() 15 | 16 | export const firebaseAuth = firebase.auth -------------------------------------------------------------------------------- /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/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/Register.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { auth } from '../helpers/auth' 3 | 4 | export default class Register extends Component { 5 | handleSubmit = (e) => { 6 | e.preventDefault() 7 | auth(this.email.value,this.pw.value) 8 | } 9 | render() { 10 | return ( 11 |
12 | this.email = email} placeholder="Email"/> 13 | this.pw = pw} /> 14 | 15 |
16 | ) 17 | } 18 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/ItemTeaser.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Match, BrowserRouter, Link, Miss, Redirect } from 'react-router' 3 | 4 | class Item extends Component { 5 | constructor (props) { 6 | super(props); 7 | this.state = { 8 | id: props.dbkey, 9 | title: props.title, 10 | text: props.text, 11 | pathname: props.pathname 12 | }; 13 | 14 | } 15 | 16 | render(){ 17 | return ( 18 |
19 |
{JSON.stringify(this.state.id, null, 2)}
20 | {this.state.title} 21 |
{ this.state.title }
22 |
23 |
{ this.state.text }
24 |
25 | ); 26 | } 27 | } 28 | 29 | 30 | export default Item; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { login, loginOauth } from '../helpers/auth' 3 | 4 | export default class Login extends Component { 5 | handleSubmit = (e) => { 6 | e.preventDefault(); 7 | login(this.email.value, this.pw.value) 8 | } 9 | handleClickOauth = (e) => { 10 | e.preventDefault(); 11 | loginOauth(e.target.id); 12 | } 13 | render() { 14 | return ( 15 |
16 |
17 | this.email = email} placeholder="Email"/> 18 | this.pw = pw} /> 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | React App 17 | 18 | 19 |
20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/helpers/auth.js: -------------------------------------------------------------------------------- 1 | import { ref, firebaseAuth } from '../config/constants' 2 | 3 | export function auth (email, pw) { 4 | return firebaseAuth().createUserWithEmailAndPassword(email, pw) 5 | .then(saveUser) 6 | .catch((error) => console.log('Oops', error)) 7 | } 8 | 9 | export function logout() { 10 | return firebaseAuth().signOut() 11 | } 12 | 13 | export function login (email,pw) { 14 | return firebaseAuth().signInWithEmailAndPassword(email, pw) 15 | } 16 | 17 | export function loginOauth(oauthId) { 18 | 19 | switch(oauthId) { 20 | case 'google': 21 | var provider = new firebaseAuth.GoogleAuthProvider(); 22 | loginPopup(provider); 23 | break; 24 | case 'facebook': 25 | provider = new firebaseAuth.FacebookAuthProvider(); 26 | loginPopup(provider); 27 | console.log('facebook') 28 | break; 29 | case 'twitter': 30 | provider = new firebaseAuth.TwitterAuthProvider(); 31 | loginPopup(provider); 32 | break; 33 | } 34 | } 35 | 36 | export function loginPopup(provider){ 37 | return firebaseAuth().signInWithPopup(provider).then(function(result) { 38 | console.log('logged in') 39 | }).catch(function(error) { 40 | console.log(error) 41 | }); 42 | } 43 | 44 | export function saveUser(user) { 45 | return ref.child('users/${user.uid}/info') 46 | .set({ 47 | email: user.email, 48 | uid: user.uid 49 | }) 50 | .then(() => user) 51 | } -------------------------------------------------------------------------------- /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/List.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { ref } from '../config/constants' 3 | import { firebaseAuth } from '../config/constants' 4 | import { Match, Link } from 'react-router' 5 | import Item from './Item' 6 | import ItemTeaser from './ItemTeaser' 7 | 8 | export default class List extends Component { 9 | constructor (props) { 10 | super(); 11 | this.state = { 12 | items: [], 13 | pathname: props.pathname 14 | } 15 | this.dbItems = ref.child('items'); 16 | this.removeItem = this.removeItem.bind(this); 17 | } 18 | 19 | componentDidMount() { 20 | this.dbItems.on('value', dataSnapshot => { 21 | var items = []; 22 | 23 | dataSnapshot.forEach(function(childSnapshot) { 24 | var item = childSnapshot.val(); 25 | item['.key'] = childSnapshot.key; 26 | items.push(item); 27 | }); 28 | 29 | this.setState({ 30 | items: items 31 | }); 32 | }); 33 | } 34 | 35 | componentWillUnmount() { 36 | this.dbItems.off(); 37 | } 38 | 39 | removeItem(key){ 40 | this.dbItems.child(key).remove(); 41 | } 42 | 43 | render () { 44 | var _this = this; 45 | 46 | return ( 47 |
48 |
    49 | {this.state.items.map(function(item) { 50 | return ( 51 |
  • 52 | 53 |
  • 54 | ); 55 | })} 56 |
57 | 58 | 59 |
60 | ) 61 | } 62 | } -------------------------------------------------------------------------------- /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 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | ) 154 | }} 155 | 156 | 157 | ) 158 | } 159 | } --------------------------------------------------------------------------------