Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of Login and Registration components #1

Merged
merged 19 commits into from
Dec 7, 2016

Conversation

zacklitzsinger
Copy link
Contributor

This is a large batch of changes that provides a jumping off point for future development. Moving forward, features will be pulled in individually - however, early on there are so many things that are more experimental rather than specific features.

Changelog:

  • Linting rules were altered. I went ahead and set up a precommit hook that runs linting so all commits will have to pass linting.
  • Basic server to use when developing with hot module reloading. The server is run through babel-node, so it follows ES6 conventions.
  • GraphQL backend (currently to manage lists of users) along with a simple flat file database. This is not secure, so be careful about what information you put into it.
  • Simple Redux implementation with actions that call the API to login and create users.
  • Modified the file structure so that actions, reducers, etc. are all alongside the component they relate to.
  • We now support Stage 2 Babel features.

Zack Litzsinger added 15 commits November 22, 2016 19:43
fix npm version typo in DEVELOPMENT.md file
use babel-lint instead of eslint for stage2 features
add scaffolding for redux store
remove lib/index.js build file from repo
basic styling for login widget
actions can now talk to graphql and get responses back
serve webpack using middleware instead of dev server
registration action that talks to the server using graphql
webpack dev hot reloading
break schema up into smaller files
add password to register
fetch now passes the key along correctly

const app = express();

const PORT = 3000;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a node_env for port so that other people can change it easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to do this, I would prefer to add it as an optional argument. --port=3000 for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I thought about this a little more, I find it unlikely that developers will want to change this port, at least when developing this project. On their own server, they definitely would want that capability.

app.use('/graphql', graphqlHTTP((req) => {
return {
schema: schema,
rootValue: req,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can do
app.use('/graphql', graphqlHTTP({
schema: MyGraphQLSchema,
graphiql: true
}));
The req will be passed in as context. I don't think we want to pass req as rootValue here. rootValue should be something totally customized, like root resolve functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I didn't realize the request was provided automatically. Thanks!


// Login endpoint. This authenticates the session. We could instead do
// this using GraphQL.
app.post('/login', function (req, res) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer using GraphQL for auth part as well, since we are going to use GraphQL. Not a fan of mixing both.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered doing this. However, authenticating the token in graphQL might get confusing. Personally, I'm okay with doing login using a normal endpoint and doing everything else in graphQL. I would also be okay with doing authentication in graphQL if it is relatively straightforward.

auth: {
type: GraphQLBoolean,
resolve (_, params, req) {
console.log('session: ', req.session);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

type: UserType,
args: {
uid: {
name: 'uid',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename the name to uid?

@@ -0,0 +1,10 @@
import {GraphQLInputObjectType, GraphQLNonNull, GraphQLString} from 'graphql';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest we change the file name to user_input.js, because this is the type for generic user, we can also use it for update_user


export default class App extends Component {
static propTypes = {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the props children needed to be declared here. prefer to remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do actually need to declare children propTypes: jsx-eslint/eslint-plugin-react#7. If you meant that App doesn't need children, it does because the whole point is to wrap other components with the provider and base styles.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, I didn't know the children is required for eslint. it's okay then.

@@ -0,0 +1,8 @@
import {combineReducers} from 'redux';
import login from '../Login/reducer';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can probably alias a root folder to src, so instead, we write code like import login from '../Login/reducer', we write import login from 'Login/reducer'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this, but it has downsides. Notably, it becomes more confusing which modules are internal vs external dependencies. Let's get other opinions on this. @dennismphil?

Copy link

@jessemao jessemao Dec 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't really need that I guess, easy way to do is to set a alias in webpack file. like

{
    resolve: {
          root: ... 
    }
}

and we can separate the external/internal packages when we import than by put the external package in front.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @jessemao it's not pretty to look. But good point about the downside of being confused. I prefer to keep it as is.

@@ -0,0 +1,6 @@
export default {
USERNAME: 'LOGIN_USERNAME',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally prefer to have action_types fields match up with its string value. Like LOGIN_USERNAME: LOGIN_USERNAME

Copy link
Contributor Author

@zacklitzsinger zacklitzsinger Dec 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason to do it this way is so that you can import all login actions together. I find importing individual action types to be a little cumbersome. It requires the imports to be changed every time you add a new action.

There's another method may be better.

export const USERNAME = 'LOGIN_USERNAME';
---
import * as LOGIN from './action_types';

I think this will still allow you to reference the action as LOGIN.USERNAME.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.

<div className={theme.property}>
<label className={theme.propertyLabel} htmlFor='uname'>{constants.USERNAME}</label>
<input type='text' id='uname' placeholder={constants.USERNAME} required={true} value={username} onChange={(e) => { onUsernameChange(e.target.value); }}/>
<input type='checkbox' id='remember' required={true} value={remember} onChange={(e) => { onRememberToggle(e.target.checked); }}/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be able to give our user the ability to change the id.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean developers right? I agree, but I think we should have sensible defaults and base the ids of subcomponents in a component off a base id. Like so:

const {id} = this.props;
<input type='text' id=`${id}-uname` />

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we should totally have a default id for it with the ability to change them as we want.

</div>
<div className={theme.property}>
<label className={theme.propertyLabel} htmlFor='pword'>{constants.PASSWORD}</label>
<input type='password' id='pword' placeholder={constants.PASSWORD} required={true} value={password} onChange={(e) => { onPasswordChange(e.target.value); }} />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here.

@@ -0,0 +1,7 @@
# Component library

Source files are bundled using Webpack and exported in `../lib`. Included components are defined here.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use abs path here.

@@ -0,0 +1,6 @@
export default {
USERNAME: 'REGISTER_USERNAME',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, match up field name and value.

const res = await query({query: `mutation {
createUser(data:{uid:'${name}', password:'${password}', fullName:'${fullName}'})
}`});
console.log('res: ', await res.json());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

export function attemptRegister(name, password, fullName) {
return async function (dispatch) {
const res = await query({query: `mutation {
createUser(data:{uid:'${name}', password:'${password}', fullName:'${fullName}'})
Copy link

@jessemao jessemao Dec 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use format like this if we need to pass in variables.

{
	query: `mutation ($userId: ID, $tag: [String]) {
            updateUser(data:{_id: $userId, tag: $tag}) {
                _id,
                tag
            }
        }`,
	variables: JSON.stringify({
		userId,
		tag
	})
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

<div className={theme.container}>
<div className={theme.property}>
<label className={theme.propertyLabel} htmlFor='uname'>{constants.USERNAME}</label>
<input type='text' id='uname' placeholder={constants.USERNAME} required={true} value={username} onChange={(e) => { onUsernameChange(e.target.value); }}/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as input section


export default {
// Localization
USERNAME: 'User name',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use Username or just User

@@ -1,17 +1,11 @@

const NODE_ENV = process.env.NODE_ENV;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can probably have a webpack.config.base to include the duplicate codes in both dev and production config files.

Copy link
Contributor Author

@zacklitzsinger zacklitzsinger Dec 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but at least for now, the development and production webpack files are different in a lot of subtle but significant ways. For example, development uses hot middleware + react-hot. At some point, production will probably use just a hash for CSS names rather than the BEM class name style. We could consider using webpack-merge.smart, but that is complicated enough that I would rather break it out.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if you have read this yet, it's really good.
survival webpack

Copy link

@jessemao jessemao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall looks good!

console.log('initial session: ', req.session);
const {uid, password} = req.body;
const user = db.get('users').get(uid).value();
if (!user || user.password != password) {
Copy link
Collaborator

@dennismphil dennismphil Dec 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use !== instead of !=

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should include this as an error in our eslint json.

import Login from './Login';
import Register from './Register';

if (document.getElementById('root') == null){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use strict equals

CONFIG = require("./webpack.config.development")
} else if (NODE_ENV == "production") {
CONFIG = require("./webpack.config.production")
if (NODE_ENV == 'development'){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strict equals

CONFIG = require("./webpack.config.production")
if (NODE_ENV == 'development'){
CONFIG = require('./webpack.config.development');
} else if (NODE_ENV == 'production') {
Copy link
Collaborator

@dennismphil dennismphil Dec 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strict equals

Copy link
Collaborator

@dennismphil dennismphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start!

@@ -15,6 +15,7 @@ import webpackHotMiddleware from 'webpack-hot-middleware';

const app = express();

// TODO: Provide as optional argument?
const PORT = 3000;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about let's just do const PORT = NODE_ENV.port || 3000;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding this, but I don't think anyone will use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants