Skip to content

Commit

Permalink
SignUp Page (#2148)
Browse files Browse the repository at this point in the history
SignUp Page

Co-authored-by: Meneguini <[email protected]>
Co-authored-by: DukeManh <[email protected]>
Co-authored-by: David Humphrey <[email protected]>
  • Loading branch information
4 people authored Apr 18, 2021
1 parent c6e7919 commit 0299b0d
Show file tree
Hide file tree
Showing 21 changed files with 1,600 additions and 43 deletions.
3 changes: 3 additions & 0 deletions docker/production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ services:
- AUTH_URL=${AUTH_URL}
- POSTS_URL=${POSTS_URL}
- SEARCH_URL=${SEARCH_URL}
- FEED_DISCOVERY_URL=${FEED_DISCOVERY_URL}
container_name: 'telescope'
restart: unless-stopped
environment:
Expand All @@ -47,8 +48,10 @@ services:
- PORT
- POSTS_URL
- API_URL
- AUTH_URL
- WEB_URL
- SEARCH_URL
- FEED_DISCOVERY_URL
- LOG_LEVEL
- FEED_URL
- FEED_URL_INTERVAL_MS
Expand Down
10 changes: 9 additions & 1 deletion src/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ const dotenv = require('dotenv');
const loadApiUrlFromEnv = (envFile) => dotenv.config({ path: envFile });

// ENV Variables we need to forward to next by prefix with NEXT_PUBLIC_*
const envVarsToForward = ['WEB_URL', 'API_URL', 'IMAGE_URL', 'POSTS_URL', 'AUTH_URL', 'SEARCH_URL'];
const envVarsToForward = [
'WEB_URL',
'API_URL',
'IMAGE_URL',
'POSTS_URL',
'AUTH_URL',
'SEARCH_URL',
'FEED_DISCOVERY_URL',
];

// Copy an existing ENV Var so it's visible to next: API_URL -> NEXT_PUBLIC_API_URL
const forwardToNext = (envVar) => {
Expand Down
5 changes: 4 additions & 1 deletion src/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"@mdx-js/loader": "^1.6.22",
"@next/mdx": "^10.1.3",
"@types/smoothscroll-polyfill": "^0.3.1",
"@types/yup": "^0.29.11",
"dotenv": "^8.2.0",
"formik": "^2.2.6",
"highlight.js": "10.7.2",
"jwt-decode": "^3.1.2",
"nanoid": "^3.1.22",
Expand All @@ -25,7 +27,8 @@
"react-use": "^17.2.1",
"smoothscroll-polyfill": "^0.4.4",
"swr": "^0.5.5",
"valid-url": "^1.0.9"
"valid-url": "^1.0.9",
"yup": "^0.32.9"
},
"devDependencies": {
"@testing-library/react": "^11.2.6",
Expand Down
53 changes: 36 additions & 17 deletions src/web/src/components/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, ReactNode } from 'react';
import { createContext, ReactNode, useState, useEffect } from 'react';
import { useLocalStorage } from 'react-use';
import { useRouter } from 'next/router';
import { nanoid } from 'nanoid';
Expand All @@ -9,6 +9,7 @@ import { loginUrl, logoutUrl, webUrl } from '../config';
export interface AuthContextInterface {
login: (returnTo?: string) => void;
logout: () => void;
register: (token: string) => void;
user?: User;
token?: string;
}
Expand All @@ -20,6 +21,9 @@ const AuthContext = createContext<AuthContextInterface>({
logout() {
throw new Error('You need to wrap your component in <AuthProvider>');
},
register() {
throw new Error('You need to wrap your component in <AuthProvider>');
},
});

type Props = {
Expand All @@ -29,6 +33,7 @@ type Props = {
const AuthProvider = ({ children }: Props) => {
const router = useRouter();
const { pathname, asPath } = router;
const [user, setUser] = useState<User | undefined>();
const [authState, setAuthState, removeAuthState] = useLocalStorage<string>(
'auth:state',
undefined,
Expand All @@ -43,12 +48,34 @@ const AuthProvider = ({ children }: Props) => {
// Server-side rendering.
if (typeof window === 'undefined') {
return (
<AuthContext.Provider value={{ login: () => null, logout: () => null }}>
<AuthContext.Provider value={{ login: () => null, logout: () => null, register: () => null }}>
{children}
</AuthContext.Provider>
);
}

// Mange the user state based on the presence and validity of the token
useEffect(() => {
const cleanup = () => {
removeToken();
removeAuthState();
setUser(undefined);
};

if (!token) {
cleanup();
return;
}

try {
setUser(User.fromToken(token));
} catch (err) {
// This token isn't parsable, remove all auth info from storage
console.error('Error parsing token for user', err);
cleanup();
}
}, [token]);

// Browser-side rendering
try {
// Try to extract access_token and state query params from the URL, which may not be there
Expand Down Expand Up @@ -78,20 +105,6 @@ const AuthProvider = ({ children }: Props) => {
console.error('Error parsing access_token from URL', err.message);
}

// If we have a token, see if we can extract user info from it
let user: User | undefined;
if (token) {
try {
user = User.fromToken(token);
} catch (err) {
// This token isn't ok, remove all auth info from storage
removeToken();
removeAuthState();

console.error('Error parsing token for user', err);
}
}

const login = (returnTo?: string) => {
// Create and store some random state that we'll send along with this login request
const loginState = nanoid();
Expand All @@ -111,8 +124,14 @@ const AuthProvider = ({ children }: Props) => {
window.location.href = `${logoutUrl}?redirect_uri=${webUrl}`;
};

const register = (token: string) => {
setToken(token);
};

return (
<AuthContext.Provider value={{ login, logout, user, token }}>{children}</AuthContext.Provider>
<AuthContext.Provider value={{ login, logout, register, user, token }}>
{children}
</AuthContext.Provider>
);
};

Expand Down
73 changes: 49 additions & 24 deletions src/web/src/components/BannerButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Link from 'next/link';
import { makeStyles } from '@material-ui/core';
import { useRouter } from 'next/router';
import { makeStyles, Tooltip, withStyles, Zoom } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import useAuth from '../hooks/use-auth';
import TelescopeAvatar from './TelescopeAvatar';
import PopUp from './PopUp';

const useStyles = makeStyles((theme) => ({
buttonsContainer: {
Expand All @@ -27,12 +29,35 @@ const useStyles = makeStyles((theme) => ({
},
}));

const ButtonTooltip = withStyles({
tooltip: {
fontSize: '1.5rem',
margin: 0,
},
})(Tooltip);

const LandingButtons = () => {
const classes = useStyles();
const { login, logout, user } = useAuth();

const classes = useStyles();

const router = useRouter();

return (
<div className={classes.buttonsContainer}>
<div
className={classes.buttonsContainer}
style={user?.isRegistered ? { width: '155px' } : { width: '250px' }}
>
{user && !user?.isRegistered && (
<PopUp
messageTitle="Telescope"
message={`Hello ${user?.name}, to sign in you need to create your Telescope account. Click SIGN UP to start.`}
agreeAction={() => router.push('/signup')}
agreeButtonText="SIGN UP"
disagreeAction={() => logout()}
disagreeButtonText="CANCEL"
/>
)}
<Link href="/about" passHref>
<Button
style={{
Expand All @@ -45,20 +70,18 @@ const LandingButtons = () => {
About us
</Button>
</Link>
{user ? (
{user?.isRegistered ? (
<>
<Button
onClick={() => logout()}
style={{
border: 'none',
padding: 0,
fontSize: '1.2rem',
}}
variant="outlined"
>
Sign out
</Button>
<TelescopeAvatar name={user.name} img={user.avatarUrl} size="40px" />
<ButtonTooltip title="Sign out" arrow placement="top" TransitionComponent={Zoom}>
<div>
<TelescopeAvatar
action={() => logout()}
name={user.name}
img={user.avatarUrl}
size="40px"
/>
</div>
</ButtonTooltip>
</>
) : (
<>
Expand All @@ -73,14 +96,16 @@ const LandingButtons = () => {
>
Sign in
</Button>
<Button
style={{
fontSize: '1.3rem',
}}
variant="outlined"
>
Sign up
</Button>
<Link href="/signup" passHref>
<Button
style={{
fontSize: '1.3rem',
}}
variant="outlined"
>
Sign up
</Button>
</Link>
</>
)}
</div>
Expand Down
73 changes: 73 additions & 0 deletions src/web/src/components/PopUp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { MouseEventHandler, useState } from 'react';

import { useRouter } from 'next/router';

import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';

type PopUpProps = {
messageTitle: string;
message: string;
buttonText?: string;
agreeAction?: MouseEventHandler;
disagreeAction?: MouseEventHandler;
agreeButtonText: string;
disagreeButtonText?: string;
simple?: boolean;
};

const PopUp = ({
messageTitle,
message,
agreeAction,
disagreeAction,
agreeButtonText,
disagreeButtonText,
simple,
buttonText,
}: PopUpProps) => {
const [open, setOpen] = useState(true);

const handleClose = () => {
setOpen(false);
};

const router = useRouter();

return (
<>
<Dialog
open={simple ? open : true}
onClose={() => router.push('/')}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{messageTitle}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">{message}</DialogContentText>
</DialogContent>
<DialogActions>
{disagreeAction && (
<Button onClick={disagreeAction} color="primary">
{disagreeButtonText}
</Button>
)}
{simple && (
<Button onClick={handleClose} color="primary">
{buttonText}
</Button>
)}
<Button onClick={agreeAction} color="primary" autoFocus>
{agreeButtonText}
</Button>
</DialogActions>
</Dialog>
</>
);
};

export default PopUp;
48 changes: 48 additions & 0 deletions src/web/src/components/SignUp/FormFields/CheckBoxInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { createStyles, makeStyles } from '@material-ui/core';
import { useField } from 'formik';
import FormControl from '@material-ui/core/FormControl';
import FormGroup from '@material-ui/core/FormGroup';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';

const useStyles = makeStyles(() =>
createStyles({
formInputLabel: {
fontSize: '1.4em',
color: 'black',
},
formControlLabel: {
fontSize: '1em',
color: '#474747',
},
})
);

type CheckboxProps = {
name: string;
label: string;
checked: boolean;
};

const CheckBoxInput = (props: CheckboxProps) => {
const classes = useStyles();

const { label, name, checked, ...rest } = props;
const [field, meta] = useField(props);

return (
<FormControl {...rest} error={!!meta.error && meta.touched}>
<FormGroup>
<FormControlLabel
control={<Checkbox {...field} checked={checked} />}
label={<span className={classes.formControlLabel}>{label}</span>}
name={name}
/>
</FormGroup>
<FormHelperText>{meta.error && meta.touched ? meta.error : ''}</FormHelperText>
</FormControl>
);
};

export default CheckBoxInput;
Loading

0 comments on commit 0299b0d

Please sign in to comment.