├── .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 |
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 |
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 |
Image:
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 | Title
64 |
71 |
72 | Text
73 |
80 |
81 | Save
82 |
83 |
Delete
84 |
85 | );
86 | }
87 | }
88 |
89 | export default Item;
--------------------------------------------------------------------------------
/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/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/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 | Login
20 |
21 | Login with Google
22 | Login with facebook
23 |
24 |
25 |
26 | )
27 | }
28 | }
--------------------------------------------------------------------------------
/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 | Register
15 |
16 | )
17 | }
18 | }
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line
2 | import React, { Component } from 'react';
3 | import { Match, BrowserRouter, Link, Miss, Redirect } from 'react-router'
4 | import Login from './Login'
5 | import Register from './Register'
6 | import { logout } from '../helpers/auth'
7 | import { firebaseAuth } from '../config/constants'
8 | import logo from '../assets/logo.svg';
9 | import Home from './Home'
10 | import Dashboard from './Dashboard'
11 | import List from './List'
12 | import Create from './Create'
13 | import Item from './Item'
14 | import CreateEdit from './CreateEdit'
15 | import '../App.css';
16 |
17 | function MatchWhenAuthed ({component: Component, authed, ...rest}) {
18 | return (
19 | authed === true
22 | ?
23 | : }
24 | />
25 | )
26 | }
27 |
28 | function MatchWhenUnauthed ({component: Component, authed, ...rest}) {
29 | return (
30 | authed === false
33 | ?
34 | : }
35 | />
36 | )
37 | }
38 |
39 | export default class App extends Component {
40 | constructor () {
41 | super();
42 | this.state = {
43 | authed: false,
44 | loading: true,
45 | }
46 | }
47 |
48 | componentDidMount () {
49 | this.removeListener = firebaseAuth().onAuthStateChanged((user) => {
50 | if (user) {
51 | this.setState({
52 | authed: true,
53 | loading: false,
54 | })
55 | } else {
56 | this.setState({
57 | loading: false
58 | })
59 | }
60 | })
61 | }
62 | componentWillUnmount () {
63 | this.removeListener()
64 | }
65 |
66 | render() {
67 | const pathname = '';
68 | return (
69 |
70 | {({router}) => (
71 |
72 |
73 |
74 |
75 | App
76 |
77 |
78 |
79 | All Items
80 |
81 |
82 | Create Item
83 |
84 |
85 | Dashboard
86 |
87 |
88 | {this.state.authed
89 | ? {
91 | logout()
92 | this.setState({authed: false})
93 | router.transitionTo('/')
94 | }}
95 | className="nav__item">Logout
96 | :
97 | Login /
98 | Register
99 | }
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
Oops, there doesnt seem to be a page here } />
115 |
116 | )}
117 |
118 | );
119 | }
120 | }
--------------------------------------------------------------------------------
/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/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/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------