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

NextJS Example using cookieStorage [WIP] #804

Closed
wants to merge 15 commits into from
Closed
Binary file removed examples/graphql-server-typescript/.DS_Store
Binary file not shown.
27 changes: 27 additions & 0 deletions examples/next-graphql-typescript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/
next-env.d.ts

# production
/build

# misc
.DS_Store
.env*

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
18 changes: 18 additions & 0 deletions examples/next-graphql-typescript/components/FormError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { Typography, withStyles, WithStyles } from '@material-ui/core';

const styles = () => ({
formError: {
color: 'red',
},
});

interface Props {
error: string;
}

const FormError = ({ classes, error }: WithStyles<'formError'> & Props) => {
return <Typography className={classes.formError}>{error}</Typography>;
};

export default withStyles(styles)(FormError);
63 changes: 63 additions & 0 deletions examples/next-graphql-typescript/components/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import { Button, Typography } from '@material-ui/core';
import Link from 'next/link';
import { accountsClient, accountsGraphQL } from '../utils/accounts';
import Router from 'next/router';
interface State {
user?: any;
}

class Home extends React.Component<State> {
public state = {
user: null as any,
};
// use getInitialProps where LocalStorage is not available.
public async componentDidMount() {
// refresh the session to get a new accessToken if expired
const tokens = await accountsClient.refreshSession();
if (!tokens) {
Router.push('/login');
return;
}
const user = await accountsGraphQL.getUser();
await this.setState({ user });
}

public onResendEmail = async () => {
const { user } = this.state;
await accountsGraphQL.sendVerificationEmail(user.emails[0].address);
};

public onLogout = async () => {
await accountsClient.logout();
Router.push('/login');
};

public render() {
const { user } = this.state;
if (!user) {
return null;
}

return (
<div>
<Typography gutterBottom={true}>You are logged in</Typography>
<Typography gutterBottom={true}>Email: {user.emails[0].address}</Typography>
<Typography gutterBottom={true}>
You email is {user.emails[0].verified ? 'verified' : 'unverified'}
</Typography>
{!user.emails[0].verified && (
<Button onClick={this.onResendEmail}>Resend verification email</Button>
)}
<Link href="two-factor">
<a>Set up 2fa</a>
</Link>
<Button variant="outlined" color="primary" onClick={this.onLogout}>
<a>Logout</a>
</Button>
</div>
);
}
}

export default Home;
64 changes: 64 additions & 0 deletions examples/next-graphql-typescript/components/HomeSSR.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';

import { Button, Typography } from '@material-ui/core';
import Link from 'next/link';
import { accountsClient, accountsGraphQL } from '../utils/accounts';
import Router from 'next/router';
interface State {
user?: any;
}

class Home extends React.Component<State> {
public state = {
user: null as any,
};
// use getInitialProps where LocalStorage is not available.
// public async componentDidMount() {
// // refresh the session to get a new accessToken if expired
// const tokens = await accountsClient.refreshSession();
// if (!tokens) {
// Router.push('/login');
// return;
// }
// const user = await accountsGraphQL.getUser();
// await this.setState({ user });
// }

public onResendEmail = async () => {
const { user } = this.state;
await accountsGraphQL.sendVerificationEmail(user.emails[0].address);
};

public onLogout = async () => {
await accountsClient.logout();
Router.push('/login');
};

public render() {
const { user } = this.state;
if (!user) {
return null;
}

return (
<div>
<Typography gutterBottom={true}>You are logged in</Typography>
<Typography gutterBottom={true}>Email: {user.emails[0].address}</Typography>
<Typography gutterBottom={true}>
You email is {user.emails[0].verified ? 'verified' : 'unverified'}
</Typography>
{!user.emails[0].verified && (
<Button onClick={this.onResendEmail}>Resend verification email</Button>
)}
<Link href="two-factor">
<a>Set up 2fa</a>
</Link>
<Button variant="outlined" color="primary" onClick={this.onLogout}>
Logout
</Button>
</div>
);
}
}

export default Home;
33 changes: 33 additions & 0 deletions examples/next-graphql-typescript/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import Head from 'next/head';
import { makeStyles } from '@material-ui/styles';
import Navigation from '../components/Navigation';
const useStyles = makeStyles({
root: {
margin: 'auto',
maxWidth: 500,
marginTop: 50,
},
container: {
padding: 16,
},
});

interface Props {
titleKey: string;
}

const Layout: React.FC<Props> = ({ titleKey, children }) => {
const classes = useStyles({});
return (
<div className={classes.root}>
<Head>
<title>Title</title>
</Head>
<Navigation />
<div className={classes.container}>{children}</div>
</div>
);
};

export default Layout;
115 changes: 115 additions & 0 deletions examples/next-graphql-typescript/components/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react';
import {
Button,
FormControl,
Input,
InputLabel,
Typography,
withStyles,
WithStyles,
} from '@material-ui/core';
import Link from 'next/link';
import FormError from './FormError';
import { accountsPassword } from '../utils/accounts';
import Router from 'next/router';
const styles = () => ({
formContainer: {
display: 'flex',
flexDirection: 'column' as 'column',
},
});

interface State {
email: string;
password: string;
code: string;
error: string | null;
}

class Login extends React.Component<WithStyles<'formContainer'>, State> {
public state = {
code: '',
email: '',
error: null,
password: '',
};

public onChangeEmail = ({ target }: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ email: target.value });
};

public onChangePassword = ({ target }: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ password: target.value });
};

public onChangeCode = ({ target }: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ code: target.value });
};

public onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
this.setState({ error: null });
try {
await accountsPassword.login({
code: this.state.code,
password: this.state.password,
user: {
email: this.state.email,
},
});
Router.push({
pathname: '/',
// query: { name: "Zeit" }
});
} catch (err) {
this.setState({ error: err.message });
}
};

public render() {
const { classes } = this.props;
const { email, password, code, error } = this.state;
return (
<form onSubmit={this.onSubmit} className={classes.formContainer}>
<Typography variant="h3" gutterBottom={true}>
Login
</Typography>
<FormControl margin="normal">
<InputLabel htmlFor="email">Email</InputLabel>
<Input id="email" value={email} autoComplete="email" onChange={this.onChangeEmail} />
</FormControl>
<FormControl margin="normal">
<InputLabel htmlFor="password">Password</InputLabel>
<Input
id="password"
type="password"
value={password}
autoComplete="current-password"
onChange={this.onChangePassword}
/>
</FormControl>
<FormControl margin="normal">
<InputLabel htmlFor="password">2fa code if enabled</InputLabel>
<Input id="code" value={code} onChange={this.onChangeCode} />
</FormControl>
<Button variant="outlined" color="primary" type="submit">
Login
</Button>
{/* eslint-disable-next-line */}
{error && <FormError error={error!} />}
<Button>
<Link href="/signup" as={`/signup`}>
<a>Sign Up</a>
</Link>
</Button>
<Button>
<Link href="/reset-password" as={`/reset-password`}>
<a>Reset Password</a>
</Link>
</Button>
</form>
);
}
}

export default withStyles(styles)(Login);
22 changes: 22 additions & 0 deletions examples/next-graphql-typescript/components/MuiTheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createMuiTheme } from '@material-ui/core/styles';
import { red } from '@material-ui/core/colors';

// Create a theme instance.
const MuiTheme = createMuiTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
background: {
default: '#fff',
},
},
});

export default MuiTheme;
41 changes: 41 additions & 0 deletions examples/next-graphql-typescript/components/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import Link from 'next/link';

const Navigation: React.FC = () => {
return (
<ul className="root">
<li>
<Link href="" as={`/`}>
<a>Home</a>
</Link>
</li>
<li>
<Link href="/ssr" as={`/ssr`}>
<a>ssr</a>
</Link>
</li>
<style jsx>{`
.root {
margin: 1rem 0 1rem 0;
padding: 0;
display: flex;
list-style: none;
}
.root > li:not(:last-child) {
margin-right: 1rem;
}
a:link,
a:visited {
text-decoration: none;
color: navy;
text-transform: uppercase;
}
a:hover {
text-decoration: underline;
}
`}</style>
</ul>
);
};

export default Navigation;
Loading