Login start
This commit is contained in:
@@ -4,12 +4,34 @@
|
||||
import React, { Component } from 'react';
|
||||
import LinkList from './LinkList';
|
||||
import CreateLink from './CreateLink'
|
||||
import Header from './Header';
|
||||
import Login from './Login'
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return <CreateLink />;
|
||||
}
|
||||
}
|
||||
const App = () => {
|
||||
return (
|
||||
<div className="center w85">
|
||||
<Header />
|
||||
<div className="ph3 pv1 background-gray">
|
||||
<Switch>
|
||||
<Route exact path="/" component={LinkList} />
|
||||
<Route
|
||||
exact
|
||||
path="/create"
|
||||
component={CreateLink}
|
||||
/>
|
||||
<Route exact path="/login" component={Login} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// class App extends Component {
|
||||
// render() {
|
||||
// return <CreateLink />;
|
||||
// }
|
||||
// }
|
||||
|
||||
// function App() {
|
||||
// return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useHistory } from 'react-router';
|
||||
import { useMutation, gql } from '@apollo/client';
|
||||
|
||||
const CREATE_LINK_MUTATION = gql`
|
||||
@@ -17,6 +17,8 @@ const CREATE_LINK_MUTATION = gql`
|
||||
`;
|
||||
|
||||
const CreateLink = () => {
|
||||
const history = useHistory();
|
||||
|
||||
const [formState, setFormState] = useState({
|
||||
description: '',
|
||||
url: ''
|
||||
|
||||
90
client/src/components/Header.js
Normal file
90
client/src/components/Header.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AUTH_TOKEN } from '../constants';
|
||||
|
||||
const Header = () => {
|
||||
const history = useHistory();
|
||||
const authToken = localStorage.getItem(AUTH_TOKEN);
|
||||
return (
|
||||
<div className="flex pa1 justify-between nowrap orange">
|
||||
<div className="flex flex-fixed black">
|
||||
<div className="fw7 mr1">Hacker News</div>
|
||||
<Link to="/" className="ml1 no-underline black">
|
||||
new
|
||||
</Link>
|
||||
<div className="ml1">|</div>
|
||||
<Link to="/top" className="ml1 no-underline black">
|
||||
top
|
||||
</Link>
|
||||
<div className="ml1">|</div>
|
||||
<Link
|
||||
to="/search"
|
||||
className="ml1 no-underline black"
|
||||
>
|
||||
search
|
||||
</Link>
|
||||
{authToken && (
|
||||
<div className="flex">
|
||||
<div className="ml1">|</div>
|
||||
<Link
|
||||
to="/create"
|
||||
className="ml1 no-underline black"
|
||||
>
|
||||
submit
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-fixed">
|
||||
{authToken ? (
|
||||
<div
|
||||
className="ml1 pointer black"
|
||||
onClick={() => {
|
||||
localStorage.removeItem(AUTH_TOKEN);
|
||||
history.push(`/`);
|
||||
}}
|
||||
>
|
||||
logout
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
to="/login"
|
||||
className="ml1 no-underline black"
|
||||
>
|
||||
login
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
||||
// import React from 'react';
|
||||
// import { useHistory } from 'react-router';
|
||||
// import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
// const Header = () => {
|
||||
// const history = useHistory();
|
||||
// return (
|
||||
// <div className="flex pa1 justify-between nowrap orange">
|
||||
// <div className="flex flex-fixed black">
|
||||
// <div className="fw7 mr1">Hacker News</div>
|
||||
// <Link to="/" className="ml1 no-underline black">
|
||||
// new
|
||||
// </Link>
|
||||
// <div className="ml1">|</div>
|
||||
// <Link
|
||||
// to="/create"
|
||||
// className="ml1 no-underline black"
|
||||
// >
|
||||
// submit
|
||||
// </Link>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default Header;
|
||||
133
client/src/components/Login.js
Normal file
133
client/src/components/Login.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useMutation, gql } from '@apollo/client';
|
||||
import { AUTH_TOKEN } from '../constants';
|
||||
|
||||
const SIGNUP_MUTATION = gql`
|
||||
mutation SignupMutation(
|
||||
$email: String!
|
||||
$password: String!
|
||||
$name: String!
|
||||
) {
|
||||
signup(
|
||||
email: $email
|
||||
password: $password
|
||||
name: $name
|
||||
) {
|
||||
token
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const LOGIN_MUTATION = gql`
|
||||
mutation LoginMutation(
|
||||
$email: String!
|
||||
$password: String!
|
||||
) {
|
||||
login(email: $email, password: $password) {
|
||||
token
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Login = () => {
|
||||
const history = useHistory();
|
||||
|
||||
const [formState, setFormState] = useState({
|
||||
login: true,
|
||||
email: '',
|
||||
password: '',
|
||||
name: ''
|
||||
});
|
||||
|
||||
const [login] = useMutation(LOGIN_MUTATION, {
|
||||
variables: {
|
||||
email: formState.email,
|
||||
password: formState.password
|
||||
},
|
||||
onCompleted: ({ login }) => {
|
||||
localStorage.setItem(AUTH_TOKEN, login.token);
|
||||
history.push('/');
|
||||
}
|
||||
});
|
||||
|
||||
const [signup] = useMutation(SIGNUP_MUTATION, {
|
||||
variables: {
|
||||
name: formState.name,
|
||||
email: formState.email,
|
||||
password: formState.password
|
||||
},
|
||||
onCompleted: ({ signup }) => {
|
||||
localStorage.setItem(AUTH_TOKEN, signup.token);
|
||||
history.push('/');
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="mv3">
|
||||
{formState.login ? 'Login' : 'Sign Up'}
|
||||
</h4>
|
||||
<div className="flex flex-column">
|
||||
{!formState.login && (
|
||||
<input
|
||||
value={formState.name}
|
||||
onChange={(e) =>
|
||||
setFormState({
|
||||
...formState,
|
||||
name: e.target.value
|
||||
})
|
||||
}
|
||||
type="text"
|
||||
placeholder="Your name"
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
value={formState.email}
|
||||
onChange={(e) =>
|
||||
setFormState({
|
||||
...formState,
|
||||
email: e.target.value
|
||||
})
|
||||
}
|
||||
type="text"
|
||||
placeholder="Your email address"
|
||||
/>
|
||||
<input
|
||||
value={formState.password}
|
||||
onChange={(e) =>
|
||||
setFormState({
|
||||
...formState,
|
||||
password: e.target.value
|
||||
})
|
||||
}
|
||||
type="password"
|
||||
placeholder="Choose a safe password"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex mt3">
|
||||
<button
|
||||
className="pointer mr2 button"
|
||||
onClick={formState.login ? login : signup}
|
||||
>
|
||||
{formState.login ? 'login' : 'create account'}
|
||||
</button>
|
||||
<button
|
||||
className="pointer button"
|
||||
onClick={(e) =>
|
||||
setFormState({
|
||||
...formState,
|
||||
login: !formState.login
|
||||
})
|
||||
}
|
||||
>
|
||||
{formState.login
|
||||
? 'need to create an account?'
|
||||
: 'already have an account?'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
1
client/src/constants.js
Normal file
1
client/src/constants.js
Normal file
@@ -0,0 +1 @@
|
||||
export const AUTH_TOKEN = 'auth-token';
|
||||
@@ -1,4 +1,7 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { setContext } from '@apollo/client/link/context';
|
||||
import { AUTH_TOKEN } from './constants';
|
||||
// import LinkList from './components/LinkList';
|
||||
|
||||
// class App extends Component {
|
||||
@@ -11,7 +14,8 @@ import React from 'react';
|
||||
|
||||
// import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
// import './styles/index.css';
|
||||
import './styles/index.css';
|
||||
import './styles/tachyons.min.css'
|
||||
import App from './components/App';
|
||||
// // import * as serviceWorker from './serviceWorker';
|
||||
|
||||
@@ -28,17 +32,31 @@ const httpLink = createHttpLink({
|
||||
uri: 'http://localhost:4000'
|
||||
});
|
||||
|
||||
// attach the auth_token to all requests to GraphQL server
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
const token = localStorage.getItem(AUTH_TOKEN);
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : ''
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// 3
|
||||
const client = new ApolloClient({
|
||||
link: httpLink,
|
||||
link: authLink.concat(httpLink),
|
||||
// link: httpLink,
|
||||
cache: new InMemoryCache()
|
||||
});
|
||||
|
||||
// 4
|
||||
ReactDOM.render(
|
||||
<ApolloProvider client={client}>
|
||||
<App />
|
||||
</ApolloProvider>,
|
||||
<BrowserRouter>
|
||||
<ApolloProvider client={client}>
|
||||
<App />
|
||||
</ApolloProvider>
|
||||
</BrowserRouter>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
// serviceWorker.unregister();
|
||||
|
||||
@@ -1,4 +1,48 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.gray {
|
||||
color: #828282;
|
||||
}
|
||||
|
||||
.orange {
|
||||
background-color: #ff6600;
|
||||
}
|
||||
|
||||
.background-gray {
|
||||
background-color: rgb(246, 246, 239);
|
||||
}
|
||||
|
||||
.f11 {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.w85 {
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
color: black;
|
||||
background-color: buttonface;
|
||||
text-align: center;
|
||||
padding: 2px 6px 3px;
|
||||
border-width: 2px;
|
||||
border-style: outset;
|
||||
border-color: buttonface;
|
||||
cursor: pointer;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
/* body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
@@ -10,4 +54,4 @@ body {
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
} */
|
||||
|
||||
3
client/src/styles/tachyons.min.css
vendored
Normal file
3
client/src/styles/tachyons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user