diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..52826782 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: rwieruch +patreon: # rwieruch +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with a single custom sponsorship URL diff --git a/.gitignore b/.gitignore index f5c26112..13c55066 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ # misc .DS_Store + +# env .env .env.development .env.production @@ -19,6 +21,12 @@ .env.test.local .env.production.local +# log npm-debug.log* yarn-debug.log* yarn-error.log* + +# firebase + +.firebase +.firebaserc diff --git a/.travis.yml b/.travis.yml index 1968f21a..1d8352ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ language: node_js node_js: - - stable + - '10' install: - npm install script: - - npm test \ No newline at end of file + - npm test diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a4c00ae1..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Robin Wieruch - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 85d8861b..179eeae2 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,135 @@ # react-firebase-authentication -[![Build Status](https://travis-ci.org/taming-the-state-in-react/react-firebase-authentication.svg?branch=master)](https://travis-ci.org/taming-the-state-in-react/react-firebase-authentication) [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) [![Greenkeeper badge](https://badges.greenkeeper.io/taming-the-state-in-react/react-firebase-authentication.svg)](https://greenkeeper.io/) +[![Build Status](https://travis-ci.org/the-road-to-react-with-firebase/react-firebase-authentication.svg?branch=master)](https://travis-ci.org/the-road-to-react-with-firebase/react-firebase-authentication) [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) [![Greenkeeper badge](https://badges.greenkeeper.io/the-road-to-react-with-firebase/react-firebase-authentication.svg)](https://greenkeeper.io/) -* Found in [Taming the State in React](https://roadtoreact.com/course-details?courseId=TAMING_THE_STATE) -* [Live](https://react-firebase-authentication.wieruch.com/) * [Tutorial](https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/) +## Variations + +* [Redux Version](https://github.com/the-road-to-react-with-firebase/react-redux-firebase-authentication) +* [MobX Version](https://github.com/the-road-to-react-with-firebase/react-mobx-firebase-authentication) +* [Gatsby Version](https://github.com/the-road-to-react-with-firebase/react-gatsby-firebase-authentication) +* [Firestore Version](https://github.com/the-road-to-react-with-firebase/react-firestore-authentication) +* [Semantic UI Version](https://github.com/the-road-to-react-with-firebase/react-semantic-ui-firebase-authentication) + ## Features * uses: * only React (create-react-app) - * firebase 5 - * react-router 4 - * no Redux/MobX - * [Redux Version](https://github.com/taming-the-state-in-react/react-redux-firebase-authentication) - * [MobX Version](https://github.com/taming-the-state-in-react/react-mobx-firebase-authentication) - * [React's 16.3 context API](https://reactjs.org/blog/2018/03/29/react-v-16-3.html) + * firebase + * react-router * features: * Sign In * Sign Up * Sign Out * Password Forget * Password Change + * Verification Email * Protected Routes with Authorization - * Database: Users + * Roles-based Authorization + * Social Logins with Google, Facebook and Twitter + * Linking of Social Logins on Account dashboard + * Auth Persistence with Local Storage + * Database with Users and Messages + +## License + +### Commercial license + +If you want to use this starter project to develop commercial sites, themes, projects, and applications, the Commercial license is the appropriate license. With this option, your source code is kept proprietary. Purchase an commercial license for different team sizes: + +* [1 Developer](https://gum.co/react-with-firebase-starter-pack-developer) +* [Team of up to 8 Developers](https://gum.co/react-with-firebase-starter-pack-team) +* [Unlimited Developers of an Organization](https://gum.co/react-with-firebase-starter-pack-organization) + +It grants you also access to the other starter projects in this GitHub organization. + +### Open source license + +If you are creating an open source application under a license compatible with the [GNU GPL license v3](https://www.gnu.org/licenses/gpl-3.0.html), you may use this starter project under the terms of the GPLv3. ## Installation -* `git clone git@github.com:taming-the-state-in-react/react-firebase-authentication.git` +* `git clone git@github.com:the-road-to-react-with-firebase/react-firebase-authentication.git` * `cd react-firebase-authentication` * `npm install` * `npm start` -* visit http://localhost:3000/ -* Use your own Firebase Credentials +* visit http://localhost:3000 + +Get an overview of Firebase, how to create a project, what kind of features Firebase offers, and how to navigate through the Firebase project dashboard in this [visual tutorial for Firebase](https://www.robinwieruch.de/firebase-tutorial/). + +### Firebase Configuration + +* copy/paste your configuration from your Firebase project's dashboard into one of these files + * *src/components/Firebase/firebase.js* file + * *.env* file + * *.env.development* and *.env.production* files + +The *.env* or *.env.development* and *.env.production* files could look like the following then: + +``` +REACT_APP_API_KEY=AIzaSyBtxZ3phPeXcsZsRTySIXa7n33NtQ +REACT_APP_AUTH_DOMAIN=react-firebase-s2233d64f8.firebaseapp.com +REACT_APP_DATABASE_URL=https://react-firebase-s2233d64f8.firebaseio.com +REACT_APP_PROJECT_ID=react-firebase-s2233d64f8 +REACT_APP_STORAGE_BUCKET=react-firebase-s2233d64f8.appspot.com +REACT_APP_MESSAGING_SENDER_ID=701928454501 +``` + +### Activate Sign-In Methods + +![firebase-enable-google-social-login_640](https://user-images.githubusercontent.com/2479967/49687774-e0a31e80-fb42-11e8-9d8a-4b4c794134e6.jpg) + +* Email/Password +* [Google](https://www.robinwieruch.de/react-firebase-social-login/) +* [Facebook](https://www.robinwieruch.de/firebase-facebook-login/) +* [Twitter](https://www.robinwieruch.de/firebase-twitter-login/) +* [Troubleshoot](https://www.robinwieruch.de/react-firebase-social-login/) + +### Activate Verification E-Mail + +* add a redirect URL for redirecting a user after an email verification into one of these files + * *src/components/Firebase/firebase.js* file + * *.env* file + * *.env.development* and *.env.production* files + +The *.env* or *.env.development* and *.env.production* files could look like the following then (excl. the Firebase configuration). + +**Development:** + +``` +REACT_APP_CONFIRMATION_EMAIL_REDIRECT=http://localhost:3000 +``` + +**Production:** + +``` +REACT_APP_CONFIRMATION_EMAIL_REDIRECT=https://mydomain.com +``` -### Use your own Firebase Credentials +### Security Rules -* visit https://firebase.google.com/ and create a Firebase App -* copy and paste your Credentials from your Firebase App into src/firebase/firebase.js -* activate Email/Password Sign-In Method in your Firebase App +``` +{ + "rules": { + ".read": false, + ".write": false, + "users": { + "$uid": { + ".read": "$uid === auth.uid || root.child('users/'+auth.uid).child('roles').hasChildren(['ADMIN'])", + ".write": "$uid === auth.uid || root.child('users/'+auth.uid).child('roles').hasChildren(['ADMIN'])" + }, + ".read": "root.child('users/'+auth.uid).child('roles').hasChildren(['ADMIN'])", + ".write": "root.child('users/'+auth.uid).child('roles').hasChildren(['ADMIN'])" + }, + "messages": { + ".indexOn": ["createdAt"], + "$uid": { + ".write": "data.exists() ? data.child('userId').val() === auth.uid : newData.child('userId').val() === auth.uid" + }, + ".read": "auth != null", + ".write": "auth != null", + }, + } +} +``` diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..340ed5b7 --- /dev/null +++ b/firebase.json @@ -0,0 +1,16 @@ +{ + "hosting": { + "public": "build", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} diff --git a/package.json b/package.json index c6cce3df..1eb98e4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-firebase-authentication", - "version": "0.1.0", + "version": "0.2.0", "private": true, "dependencies": { "firebase": "^5.6.0", diff --git a/src/components/Admin/index.js b/src/components/Admin/index.js index 79d7ea66..1038e5e7 100644 --- a/src/components/Admin/index.js +++ b/src/components/Admin/index.js @@ -1,9 +1,9 @@ -import React, { Component } from 'react'; -import { Switch, Route, Link } from 'react-router-dom'; +import React from 'react'; +import { Switch, Route } from 'react-router-dom'; import { compose } from 'recompose'; -import { withFirebase } from '../Firebase'; import { withAuthorization, withEmailVerification } from '../Session'; +import { UserList, UserItem } from '../Users'; import * as ROLES from '../../constants/roles'; import * as ROUTES from '../../constants/routes'; @@ -19,150 +19,8 @@ const AdminPage = () => ( ); -class UserListBase extends Component { - constructor(props) { - super(props); - - this.state = { - loading: false, - users: [], - }; - } - - componentDidMount() { - this.setState({ loading: true }); - - this.props.firebase.users().on('value', snapshot => { - const usersObject = snapshot.val(); - - const usersList = Object.keys(usersObject).map(key => ({ - ...usersObject[key], - uid: key, - })); - - this.setState({ - users: usersList, - loading: false, - }); - }); - } - - componentWillUnmount() { - this.props.firebase.users().off(); - } - - render() { - const { users, loading } = this.state; - - return ( -
-

Users

- {loading &&
Loading ...
} - -
- ); - } -} - -class UserItemBase extends Component { - constructor(props) { - super(props); - - this.state = { - loading: false, - user: null, - ...props.location.state, - }; - } - - componentDidMount() { - if (this.state.user) { - return; - } - - this.setState({ loading: true }); - - this.props.firebase - .user(this.props.match.params.id) - .on('value', snapshot => { - this.setState({ - user: snapshot.val(), - loading: false, - }); - }); - } - - componentWillUnmount() { - this.props.firebase.user(this.props.match.params.id).off(); - } - - onSendPasswordResetEmail = () => { - this.props.firebase.doPasswordReset(this.state.user.email); - }; - - render() { - const { user, loading } = this.state; - - return ( -
-

User ({this.props.match.params.id})

- {loading &&
Loading ...
} - - {user && ( -
- - ID: {user.uid} - - - E-Mail: {user.email} - - - Username: {user.username} - - - - -
- )} -
- ); - } -} - -const UserList = withFirebase(UserListBase); -const UserItem = withFirebase(UserItemBase); - const condition = authUser => - authUser && authUser.roles.includes(ROLES.ADMIN); + authUser && !!authUser.roles[ROLES.ADMIN]; export default compose( withEmailVerification, diff --git a/src/components/Firebase/firebase.js b/src/components/Firebase/firebase.js index b791945d..719e6dcd 100644 --- a/src/components/Firebase/firebase.js +++ b/src/components/Firebase/firebase.js @@ -73,7 +73,7 @@ class Firebase { // default empty roles if (!dbUser.roles) { - dbUser.roles = []; + dbUser.roles = {}; } // merge auth and db user diff --git a/src/components/Home/index.js b/src/components/Home/index.js index 0b7828f4..75510b2e 100644 --- a/src/components/Home/index.js +++ b/src/components/Home/index.js @@ -1,271 +1,21 @@ -import React, { Component } from 'react'; +import React from 'react'; import { compose } from 'recompose'; -import { - AuthUserContext, - withAuthorization, - withEmailVerification, -} from '../Session'; -import { withFirebase } from '../Firebase'; - -class HomePage extends Component { - constructor(props) { - super(props); - - this.state = { - loading: false, - users: null, - }; - } - - componentDidMount() { - this.setState({ loading: true }); - this.props.firebase.users().on('value', snapshot => { - this.setState({ - users: snapshot.val(), - loading: false, - }); - }); - } - - componentWillUnmount() { - this.props.firebase.users().off(); - } - - render() { - const { users, loading } = this.state; - - return ( -
-

Home Page

-

The Home Page is accessible by every signed in user.

- - -
- ); - } -} - -class MessagesBase extends Component { - constructor(props) { - super(props); - - this.state = { - text: '', - loading: false, - messages: [], - limit: 5, - }; - } - - componentDidMount() { - this.onListenForMessages(); - } - - onListenForMessages = () => { - this.setState({ loading: true }); - - this.props.firebase - .messages() - .orderByChild('createdAt') - .limitToLast(this.state.limit) - .on('value', snapshot => { - const messageObject = snapshot.val(); - - if (messageObject) { - const messageList = Object.keys(messageObject).map(key => ({ - ...messageObject[key], - uid: key, - })); - - this.setState({ - messages: messageList, - loading: false, - }); - } else { - this.setState({ messages: null, loading: false }); - } - }); - }; - - componentWillUnmount() { - this.props.firebase.messages().off(); - } - - onChangeText = event => { - this.setState({ text: event.target.value }); - }; - - onCreateMessage = (event, authUser) => { - this.props.firebase.messages().push({ - text: this.state.text, - userId: authUser.uid, - createdAt: this.props.firebase.serverValue.TIMESTAMP, - }); - - this.setState({ text: '' }); - - event.preventDefault(); - }; +import { withAuthorization, withEmailVerification } from '../Session'; +import Messages from '../Messages'; - onEditMessage = (message, text) => { - this.props.firebase.message(message.uid).set({ - ...message, - text, - editedAt: this.props.firebase.serverValue.TIMESTAMP, - }); - }; +const HomePage = () => ( +
+

Home Page

+

The Home Page is accessible by every signed in user.

- onRemoveMessage = uid => { - this.props.firebase.message(uid).remove(); - }; - - onNextPage = () => { - this.setState( - state => ({ limit: state.limit + 5 }), - this.onListenForMessages, - ); - }; - - render() { - const { users, usersLoading } = this.props; - const { text, messages, loading } = this.state; - - return ( - - {authUser => ( -
- {!loading && messages && ( - - )} - - {loading && usersLoading &&
Loading ...
} - - {users && messages && ( - ({ - ...message, - user: users[message.userId], - }))} - onEditMessage={this.onEditMessage} - onRemoveMessage={this.onRemoveMessage} - /> - )} - - {!messages &&
There are no messages ...
} - -
- this.onCreateMessage(event, authUser) - } -> - - -
-
- )} -
- ); - } -} - -const MessageList = ({ - messages, - onEditMessage, - onRemoveMessage, -}) => ( - + +
); -class MessageItem extends Component { - constructor(props) { - super(props); - - this.state = { - editMode: false, - editText: this.props.message.text, - }; - } - - onToggleEditMode = () => { - this.setState(state => ({ - editMode: !state.editMode, - editText: this.props.message.text, - })); - }; - - onChangeEditText = event => { - this.setState({ editText: event.target.value }); - }; - - onSaveEditText = () => { - this.props.onEditMessage(this.props.message, this.state.editText); - - this.setState({ editMode: false }); - }; - - render() { - const { message, onRemoveMessage } = this.props; - const { editMode, editText } = this.state; - - return ( -
  • - {editMode ? ( - - ) : ( - - {message.user.username} {message.text}{' '} - {message.editedAt && (Edited)} - - )} - - {editMode ? ( - - - - - ) : ( - - )} - - {!editMode && ( - - )} -
  • - ); - } -} - -const Messages = withFirebase(MessagesBase); - const condition = authUser => !!authUser; export default compose( - withFirebase, withEmailVerification, withAuthorization(condition), )(HomePage); diff --git a/src/components/Messages/MessageItem.js b/src/components/Messages/MessageItem.js new file mode 100644 index 00000000..b92f9545 --- /dev/null +++ b/src/components/Messages/MessageItem.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react'; + +class MessageItem extends Component { + constructor(props) { + super(props); + + this.state = { + editMode: false, + editText: this.props.message.text, + }; + } + + onToggleEditMode = () => { + this.setState(state => ({ + editMode: !state.editMode, + editText: this.props.message.text, + })); + }; + + onChangeEditText = event => { + this.setState({ editText: event.target.value }); + }; + + onSaveEditText = () => { + this.props.onEditMessage(this.props.message, this.state.editText); + + this.setState({ editMode: false }); + }; + + render() { + const { authUser, message, onRemoveMessage } = this.props; + const { editMode, editText } = this.state; + + return ( +
  • + {editMode ? ( + + ) : ( + + {message.userId} {message.text} + {message.editedAt && (Edited)} + + )} + + {authUser.uid === message.userId && ( + + {editMode ? ( + + + + + ) : ( + + )} + + {!editMode && ( + + )} + + )} +
  • + ); + } +} + +export default MessageItem; diff --git a/src/components/Messages/MessageList.js b/src/components/Messages/MessageList.js new file mode 100644 index 00000000..fb18585e --- /dev/null +++ b/src/components/Messages/MessageList.js @@ -0,0 +1,24 @@ +import React from 'react'; + +import MessageItem from './MessageItem'; + +const MessageList = ({ + authUser, + messages, + onEditMessage, + onRemoveMessage, +}) => ( + +); + +export default MessageList; diff --git a/src/components/Messages/Messages.js b/src/components/Messages/Messages.js new file mode 100644 index 00000000..29e52a3c --- /dev/null +++ b/src/components/Messages/Messages.js @@ -0,0 +1,135 @@ +import React, { Component } from 'react'; + +import { AuthUserContext } from '../Session'; +import { withFirebase } from '../Firebase'; +import MessageList from './MessageList'; + +class Messages extends Component { + constructor(props) { + super(props); + + this.state = { + text: '', + loading: false, + messages: [], + limit: 5, + }; + } + + componentDidMount() { + this.onListenForMessages(); + } + + onListenForMessages = () => { + this.setState({ loading: true }); + + this.props.firebase + .messages() + .orderByChild('createdAt') + .limitToLast(this.state.limit) + .on('value', snapshot => { + const messageObject = snapshot.val(); + + if (messageObject) { + const messageList = Object.keys(messageObject).map(key => ({ + ...messageObject[key], + uid: key, + })); + + this.setState({ + messages: messageList, + loading: false, + }); + } else { + this.setState({ messages: null, loading: false }); + } + }); + }; + + componentWillUnmount() { + this.props.firebase.messages().off(); + } + + onChangeText = event => { + this.setState({ text: event.target.value }); + }; + + onCreateMessage = (event, authUser) => { + this.props.firebase.messages().push({ + text: this.state.text, + userId: authUser.uid, + createdAt: this.props.firebase.serverValue.TIMESTAMP, + }); + + this.setState({ text: '' }); + + event.preventDefault(); + }; + + onEditMessage = (message, text) => { + const { uid, ...messageSnapshot } = message; + + this.props.firebase.message(message.uid).set({ + ...messageSnapshot, + text, + editedAt: this.props.firebase.serverValue.TIMESTAMP, + }); + }; + + onRemoveMessage = uid => { + this.props.firebase.message(uid).remove(); + }; + + onNextPage = () => { + this.setState( + state => ({ limit: state.limit + 5 }), + this.onListenForMessages, + ); + }; + + render() { + const { text, messages, loading } = this.state; + + return ( + + {authUser => ( +
    + {!loading && messages && ( + + )} + + {loading &&
    Loading ...
    } + + {messages && ( + + )} + + {!messages &&
    There are no messages ...
    } + +
    + this.onCreateMessage(event, authUser) + } +> + + +
    +
    + )} +
    + ); + } +} + +export default withFirebase(Messages); diff --git a/src/components/Messages/index.js b/src/components/Messages/index.js new file mode 100644 index 00000000..091383f2 --- /dev/null +++ b/src/components/Messages/index.js @@ -0,0 +1,3 @@ +import Messages from './Messages'; + +export default Messages; diff --git a/src/components/Navigation/index.js b/src/components/Navigation/index.js index 156e27ae..ba3571ea 100644 --- a/src/components/Navigation/index.js +++ b/src/components/Navigation/index.js @@ -29,7 +29,7 @@ const NavigationAuth = ({ authUser }) => (
  • Account
  • - {authUser.roles.includes(ROLES.ADMIN) && ( + {!!authUser.roles[ROLES.ADMIN] && (
  • Admin
  • diff --git a/src/components/Session/withEmailVerification.js b/src/components/Session/withEmailVerification.js index 9ce73340..1e6ff57f 100644 --- a/src/components/Session/withEmailVerification.js +++ b/src/components/Session/withEmailVerification.js @@ -32,13 +32,13 @@ const withEmailVerification = Component => {
    {this.state.isSent ? (

    - E-Mail confirmation sent: Check you E-Mails (Spam + E-Mail confirmation sent: Check your E-Mails (Spam folder included) for a confirmation E-Mail. Refresh this page once you confirmed your E-Mail.

    ) : (

    - Verify your E-Mail: Check you E-Mails (Spam folder + Verify your E-Mail: Check your E-Mails (Spam folder included) for a confirmation E-Mail or send another confirmation E-Mail.

    diff --git a/src/components/SignIn/index.js b/src/components/SignIn/index.js index 91e3f562..d32937c1 100644 --- a/src/components/SignIn/index.js +++ b/src/components/SignIn/index.js @@ -108,7 +108,7 @@ class SignInGoogleBase extends Component { return this.props.firebase.user(socialAuthUser.user.uid).set({ username: socialAuthUser.user.displayName, email: socialAuthUser.user.email, - roles: [], + roles: {}, }); }) .then(() => { @@ -154,7 +154,7 @@ class SignInFacebookBase extends Component { return this.props.firebase.user(socialAuthUser.user.uid).set({ username: socialAuthUser.additionalUserInfo.profile.name, email: socialAuthUser.additionalUserInfo.profile.email, - roles: [], + roles: {}, }); }) .then(() => { @@ -200,7 +200,7 @@ class SignInTwitterBase extends Component { return this.props.firebase.user(socialAuthUser.user.uid).set({ username: socialAuthUser.additionalUserInfo.profile.name, email: socialAuthUser.additionalUserInfo.profile.email, - roles: [], + roles: {}, }); }) .then(() => { diff --git a/src/components/SignUp/index.js b/src/components/SignUp/index.js index f10e50b8..a319cac2 100644 --- a/src/components/SignUp/index.js +++ b/src/components/SignUp/index.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import { Link, withRouter } from 'react-router-dom'; +import { compose } from 'recompose'; import { withFirebase } from '../Firebase'; import * as ROUTES from '../../constants/routes'; @@ -40,10 +41,10 @@ class SignUpFormBase extends Component { onSubmit = event => { const { username, email, passwordOne, isAdmin } = this.state; - const roles = []; + const roles = {}; if (isAdmin) { - roles.push(ROLES.ADMIN); + roles[ROLES.ADMIN] = ROLES.ADMIN; } this.props.firebase @@ -153,7 +154,10 @@ const SignUpLink = () => (

    ); -const SignUpForm = withRouter(withFirebase(SignUpFormBase)); +const SignUpForm = compose( + withRouter, + withFirebase, +)(SignUpFormBase); export default SignUpPage; diff --git a/src/components/Users/UserItem.js b/src/components/Users/UserItem.js new file mode 100644 index 00000000..05e43193 --- /dev/null +++ b/src/components/Users/UserItem.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react'; + +import { withFirebase } from '../Firebase'; + +class UserItem extends Component { + constructor(props) { + super(props); + + this.state = { + loading: false, + user: null, + ...props.location.state, + }; + } + + componentDidMount() { + if (this.state.user) { + return; + } + + this.setState({ loading: true }); + + this.props.firebase + .user(this.props.match.params.id) + .on('value', snapshot => { + this.setState({ + user: snapshot.val(), + loading: false, + }); + }); + } + + componentWillUnmount() { + this.props.firebase.user(this.props.match.params.id).off(); + } + + onSendPasswordResetEmail = () => { + this.props.firebase.doPasswordReset(this.state.user.email); + }; + + render() { + const { user, loading } = this.state; + + return ( +
    +

    User ({this.props.match.params.id})

    + {loading &&
    Loading ...
    } + + {user && ( +
    + + ID: {user.uid} + + + E-Mail: {user.email} + + + Username: {user.username} + + + + +
    + )} +
    + ); + } +} + +export default withFirebase(UserItem); diff --git a/src/components/Users/UserList.js b/src/components/Users/UserList.js new file mode 100644 index 00000000..85b49553 --- /dev/null +++ b/src/components/Users/UserList.js @@ -0,0 +1,76 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; + +import { withFirebase } from '../Firebase'; +import * as ROUTES from '../../constants/routes'; + +class UserList extends Component { + constructor(props) { + super(props); + + this.state = { + loading: false, + users: [], + }; + } + + componentDidMount() { + this.setState({ loading: true }); + + this.props.firebase.users().on('value', snapshot => { + const usersObject = snapshot.val(); + + const usersList = Object.keys(usersObject).map(key => ({ + ...usersObject[key], + uid: key, + })); + + this.setState({ + users: usersList, + loading: false, + }); + }); + } + + componentWillUnmount() { + this.props.firebase.users().off(); + } + + render() { + const { users, loading } = this.state; + + return ( +
    +

    Users

    + {loading &&
    Loading ...
    } + +
    + ); + } +} + +export default withFirebase(UserList); diff --git a/src/components/Users/index.js b/src/components/Users/index.js new file mode 100644 index 00000000..8ba9cffa --- /dev/null +++ b/src/components/Users/index.js @@ -0,0 +1,4 @@ +import UserList from './UserList'; +import UserItem from './UserItem'; + +export { UserList, UserItem };

    AltStyle によって変換されたページ (->オリジナル) /