Skip to content

Commit

Permalink
Cleaning up
Browse files Browse the repository at this point in the history
  • Loading branch information
EtienneK committed May 28, 2020
1 parent b3359ae commit 894b173
Show file tree
Hide file tree
Showing 16 changed files with 135 additions and 84 deletions.
15 changes: 9 additions & 6 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import getConfig from 'next/config';
import Link from 'next/link';
import { useRouter } from 'next/router';
Expand All @@ -10,7 +10,7 @@ import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import Spinner from 'react-bootstrap/Spinner';
import useIsAuthenticated from '../hooks/useIsAuthenticated';
import useCurrentUser from '../hooks/useCurrentUser';

interface NavItemLinkProps {
children: React.ReactNode;
Expand All @@ -32,16 +32,19 @@ function NavItemLink({ children, href, exact = false }: NavItemLinkProps): JSX.E

export default function Header(): JSX.Element {
const { publicRuntimeConfig: { appName } } = getConfig();
const { data, mutate } = useIsAuthenticated();
const { wrappedUser, mutate } = useCurrentUser();
const [isLoggingOut, setIsLoggingOut] = useState(false);

const router = useRouter();

const logout = async (): Promise<void> => {
setIsLoggingOut(true);
await fetch('/api/account/login', {
method: 'DELETE',
});
mutate(false);
mutate(null);
router.replace('/');
setIsLoggingOut(false);
};

const IsAuthenticatedStillLoading = (
Expand All @@ -52,7 +55,7 @@ export default function Header(): JSX.Element {
</Nav>
);

const IsAuthenticatedLoaded = (data && data.isAuthenticated ? (
const IsAuthenticatedLoaded = (wrappedUser?.user ? (
<Nav className="ml-auto">
<NavItemLink href="/account">
<BsPersonSquare />
Expand Down Expand Up @@ -102,7 +105,7 @@ export default function Header(): JSX.Element {
&nbsp;Proxy Example
</NavItemLink>
</Nav>
{data ? IsAuthenticatedLoaded : IsAuthenticatedStillLoading}
{wrappedUser && !isLoggingOut ? IsAuthenticatedLoaded : IsAuthenticatedStillLoading}
</Navbar.Collapse>
</Container>
</Navbar>
Expand Down
29 changes: 29 additions & 0 deletions hooks/useCurrentUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import useSWR from 'swr';

interface User {
id: string;
email: string;
}

interface UserWrapper {
user?: User;
}

const fetcher = async (url: string): Promise<UserWrapper> => {
const response = await fetch(url);
if (response.status === 200) {
return { user: await response.json() };
}
return {};
};

export default function useIsAuthenticated(): {
wrappedUser: UserWrapper;
mutate: (user: User) => Promise<UserWrapper>;
} {
const { data, mutate: wrappedMutate } = useSWR('/api/account/me', fetcher);
return {
wrappedUser: data,
mutate: (user?: User): Promise<UserWrapper> => wrappedMutate({ user }),
};
}
20 changes: 0 additions & 20 deletions hooks/useIsAuthenticated.ts

This file was deleted.

7 changes: 7 additions & 0 deletions models/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ schema.methods
return bcrypt.compare(candidatePassword, this.password);
};

schema.methods.toJSON = function () {
return {
id: this.id,
email: this.email,
};
};

const AccountModel = (conn: Connection): Model<AccountInterface> => (
conn.models[modelName] ? conn.models[modelName] : conn.model(
modelName,
Expand Down
8 changes: 4 additions & 4 deletions pages/account/forgot-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useForm } from 'react-hook-form';
import isEmail from 'validator/lib/isEmail';

import LoadingButton from '../../components/LoadingButton';
import useIsAuthenticated from '../../hooks/useIsAuthenticated';
import useCurrentUser from '../../hooks/useCurrentUser';
import AccountContainer from '../../components/AccountContainer';

export default function ForgotPassword(): JSX.Element {
Expand All @@ -25,10 +25,10 @@ export default function ForgotPassword(): JSX.Element {
} = useForm();
const router = useRouter();

const { data: isAuthenticatedData } = useIsAuthenticated();
const { wrappedUser } = useCurrentUser();
useEffect(() => {
if (isAuthenticatedData && isAuthenticatedData.isAuthenticated) router.replace('/account');
}, [isAuthenticatedData]);
if (wrappedUser?.user) router.replace('/account');
}, [wrappedUser]);

const onSubmit = async (data: any): Promise<void> => {
if (loading) return;
Expand Down
63 changes: 59 additions & 4 deletions pages/account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ import EmailInput from '../../components/EmailInput';
export default function ForgotPasswordReset(): JSX.Element {
const [me, setMe] = useState(null);
const [gettingMe, setGettingMe] = useState(false);

// Profile
const [submittingProfile, setSubmittingProfile] = useState(false);

// Password
const [submittingPasswordReset, setSubmittingPasswordReset] = useState(false);
const [passwordResetSubmitted, setPasswordResetSubmitted] = useState(false);

// Delete account
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleteAccountChecked, setDeleteAccountChecked] = useState(false);

useEffect(() => {
if (gettingMe) return;
setGettingMe(true);
Expand Down Expand Up @@ -137,13 +144,18 @@ export default function ForgotPasswordReset(): JSX.Element {
OK
</Button>
</>
)
: (
) : (
<Form
noValidate
onSubmit={handlePasswordResetSubmit(passwordResetSubmit)}
>
<LoadingButton loading={submittingPasswordReset}>Reset password</LoadingButton>
<p>
To change your password, press the button below to send a password
reset email.
</p>
<LoadingButton loading={submittingPasswordReset}>
Send password reset email
</LoadingButton>
</Form>
)}
</Card.Body>
Expand All @@ -156,7 +168,50 @@ export default function ForgotPasswordReset(): JSX.Element {
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="2">
<Card.Body>TODO</Card.Body>
<Card.Body>
{showDeleteConfirm
? (
<>
<p>
Are you sure you want to delete your account and all data associated with it?
</p>
<p>
<span className="font-weight-bold">This process can&apos;t be reversed.</span>
</p>
<Form.Check
type="checkbox"
className="font-weight-bold text-danger mb-3"
label="I am sure that I want to delete my account and all of my data."
checked={deleteAccountChecked}
onChange={(): void => setDeleteAccountChecked(!deleteAccountChecked)}
/>
<Button disabled={!deleteAccountChecked} block size="lg" variant="danger">
Confirm account deletion
</Button>
<Button block size="lg" variant="primary" onClick={(): void => setShowDeleteConfirm(false)}>
Cancel
</Button>
</>
) : (
<>
<p>
Press the button below to start the deletion process for your account
and all its associated data.
</p>
<Button
block
size="lg"
variant="danger"
onClick={(): void => {
setDeleteAccountChecked(false);
setShowDeleteConfirm(true);
}}
>
Delete account
</Button>
</>
)}
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
Expand Down
8 changes: 4 additions & 4 deletions pages/account/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useForm } from 'react-hook-form';
import isEmail from 'validator/lib/isEmail';

import LoadingButton from '../../components/LoadingButton';
import useIsAuthenticated from '../../hooks/useIsAuthenticated';
import useCurrentUser from '../../hooks/useCurrentUser';
import AccountContainer from '../../components/AccountContainer';

export default function Login(): JSX.Element {
Expand All @@ -22,8 +22,8 @@ export default function Login(): JSX.Element {
} = useForm();
const router = useRouter();

const { data: isAuthenticatedData, mutate } = useIsAuthenticated();
useEffect(() => { if (isAuthenticatedData && isAuthenticatedData.isAuthenticated) router.replace('/'); }, [isAuthenticatedData]);
const { wrappedUser, mutate } = useCurrentUser();
useEffect(() => { if (wrappedUser?.user) router.replace('/'); }, [wrappedUser]);

const onSubmit = async (data: any): Promise<void> => {
if (loading) return;
Expand All @@ -39,7 +39,7 @@ export default function Login(): JSX.Element {

switch (res.status) {
case 200:
mutate(true);
mutate(await res.json());
router.replace('/');
break;
case 401: {
Expand Down
8 changes: 4 additions & 4 deletions pages/account/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useForm } from 'react-hook-form';
import isEmail from 'validator/lib/isEmail';

import LoadingButton from '../../components/LoadingButton';
import useIsAuthenticated from '../../hooks/useIsAuthenticated';
import useCurrentUser from '../../hooks/useCurrentUser';
import PasswordChange from '../../components/PasswordChange';
import AccountContainer from '../../components/AccountContainer';

Expand All @@ -23,8 +23,8 @@ export default function SignUp(): JSX.Element {
} = useForm();
const router = useRouter();

const { data: isAuthenticatedData, mutate } = useIsAuthenticated();
useEffect(() => { if (isAuthenticatedData && isAuthenticatedData.isAuthenticated) router.replace('/'); }, [isAuthenticatedData]);
const { wrappedUser, mutate } = useCurrentUser();
useEffect(() => { if (wrappedUser?.user) router.replace('/'); }, [wrappedUser]);

const onSubmit = async (data: any): Promise<void> => {
if (loading) return;
Expand All @@ -40,7 +40,7 @@ export default function SignUp(): JSX.Element {

switch (res.status) {
case 201:
mutate(true);
mutate(await res.json());
router.replace('/');
break;
case 400: {
Expand Down
6 changes: 3 additions & 3 deletions pages/account/reset-password/[token].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useForm } from 'react-hook-form';
import LoadingButton from '../../../components/LoadingButton';
import PasswordChange from '../../../components/PasswordChange';
import AccountContainer from '../../../components/AccountContainer';
import useIsAuthenticated from '../../../hooks/useIsAuthenticated';
import useCurrentUser from '../../../hooks/useCurrentUser';

enum CurrentState {
CheckingToken,
Expand All @@ -25,7 +25,7 @@ enum CurrentState {
export default function ForgotPasswordReset(): JSX.Element {
const router = useRouter();
const { token } = router.query;
const { data: isAuthenticatedData } = useIsAuthenticated();
const { wrappedUser } = useCurrentUser();

const [currentState, setCurrentState] = useState<CurrentState>(CurrentState.CheckingToken);

Expand Down Expand Up @@ -132,7 +132,7 @@ export default function ForgotPasswordReset(): JSX.Element {
<AccountContainer>
<Alert variant="success">
<p>Your password has successfully been changed.</p>
{isAuthenticatedData?.isAuthenticated
{wrappedUser?.user
? (<p className="text-center"><Link href="/account"><a>Proceed to account</a></Link></p>)
: (<p className="text-center"><Link href="/account/login"><a>Proceed to Login</a></Link></p>)}
</Alert>
Expand Down
2 changes: 1 addition & 1 deletion pages/api/account/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ handler.post(
passport.session() as any,
passport.authenticate('local') as any,
async (req, res) => {
res.status(200).end();
res.status(200).json((req as any).user);
},
);

Expand Down
8 changes: 1 addition & 7 deletions pages/api/account/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import mongooseConnection from '../../../middlewares/mongoose-connection';
import session from '../../../middlewares/session';
import passport from '../../../middlewares/passport';
import isAuthenticated from '../../../middlewares/is-authenticated';
import { AccountInterface } from '../../../models/Account';

const handler = nextConnect();

Expand All @@ -16,12 +15,7 @@ handler.get(
passport.session() as any,
isAuthenticated,
async (req, res) => {
const account = (req as any).user as AccountInterface;
const { id, email } = account;
res.status(200).json({
id,
email,
});
res.status(200).json((req as any).user);
},
);

Expand Down
2 changes: 1 addition & 1 deletion pages/api/account/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ handler.post(

return req.login(account, (err) => {
if (err) throw err;
return res.status(201).end();
return res.status(201).json(account);
});
},
);
Expand Down
20 changes: 0 additions & 20 deletions pages/api/account/session.ts

This file was deleted.

4 changes: 2 additions & 2 deletions test/integration/pages/api/account/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('Integration tests for: /api/account/login', () => {
test('POST Should log the user in successfully', async () => {
// Arrange
const account = { email: email.toLowerCase(), password };
await new Account(account).save();
const savedAccount = await new Account(account).save();
const body = JSON.stringify({ email, password });

// Act
Expand All @@ -45,7 +45,7 @@ describe('Integration tests for: /api/account/login', () => {

// Assert
expect(response.status).toBe(200);
expect(await response.text()).toEqual('');
expect(await response.json()).toEqual({ id: savedAccount.id, email: email.toLowerCase() });
expect(response.headers.get('set-cookie')).toContain('connect.sid=');
});

Expand Down
Loading

1 comment on commit 894b173

@vercel
Copy link

@vercel vercel bot commented on 894b173 May 28, 2020

Choose a reason for hiding this comment

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

Please sign in to comment.