Skip to content

Commit

Permalink
[#53] [Frontend] As a guest I can view a newsletter content (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
tednguyendev committed Jun 23, 2023
1 parent 0bbf70d commit aa04905
Show file tree
Hide file tree
Showing 14 changed files with 332 additions and 10 deletions.
5 changes: 5 additions & 0 deletions nextjs-13/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { usePathname } from 'next/navigation';
import { ApiMeResponse } from './api/v1/me/route';
import AppLayout from '../components/AppLayout';
import AuthLayout from '../components/AuthLayout';
import PublicLayout from '../components/PublicLayout';
import '../stylesheets/application.scss';
import {
UserState,
Expand Down Expand Up @@ -45,6 +46,10 @@ export default function RootLayout({
return <AuthLayout>{children}</AuthLayout>;
}

if (pathname.includes('/newsletter')) {
return <PublicLayout>{children}</PublicLayout>;
}

return (
<UserContext.Provider value={userContextValue}>
<AppLayout>{children}</AppLayout>
Expand Down
67 changes: 67 additions & 0 deletions nextjs-13/src/app/newsletter/[id]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';

import { render, screen, waitFor } from '@testing-library/react';
import { useParams } from 'next/navigation';

import requestManager from 'lib/request/manager';

import ViewNewsletter from './page';

jest.mock('next-auth/react');
jest.mock('next/navigation');
jest.mock('lib/request/manager');

jest.mock('react-spinners', () => ({
ClipLoader: () => <div data-testid="clip-loader">Loading...</div>,
}));

describe('ViewNewsletter', () => {
beforeEach(() => {
requestManager.mockResolvedValue({ record: {} });
useParams.mockReturnValue({ id: 1 });
});

it('renders component', async () => {
render(<ViewNewsletter />);

await waitFor(() => {
expect(screen.getByTestId('view-newsletter')).toBeInTheDocument();
});
});

describe('given fetching data', () => {
beforeEach(() => {
requestManager.mockImplementation(() => new Promise(() => []));
});

it('renders ClipLoader', async () => {
render(<ViewNewsletter />);

expect(screen.getByTestId('clip-loader')).toBeInTheDocument();
});

it('does NOT render NewsletterDetail', async () => {
render(<ViewNewsletter />);

expect(screen.queryByTestId('newsletter')).not.toBeInTheDocument();
});
});

describe('given NOT fetching data', () => {
it('does NOT render ClipLoader', async () => {
render(<ViewNewsletter />);

await waitFor(() => {
expect(screen.queryByTestId('clip-loader')).not.toBeInTheDocument();
});
});

it('renders ListNewsletter', async () => {
render(<ViewNewsletter />);

await waitFor(() => {
expect(screen.getByTestId('newsletter')).toBeInTheDocument();
});
});
});
});
53 changes: 53 additions & 0 deletions nextjs-13/src/app/newsletter/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client';

import { useEffect, useState } from 'react';
import { ClipLoader } from 'react-spinners';

import { useParams, redirect } from 'next/navigation';

import NewsletterDetail from '@components/NewsletterDetail';
import requestManager from 'lib/request/manager';

const ViewNewsletter = () => {
const [record, setRecord] = useState([]);
const [hasError, setHasError] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const params = useParams();
const id = params.id;

const getNewletter = async () => {
setIsLoading(true);

try {
const response = await requestManager('GET', `v1/newsletter/${id}`);

setIsLoading(false);
setRecord(response.record);
} catch (error) {
setHasError(true);
setIsLoading(false);
}
};

useEffect(() => {
getNewletter();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (hasError) redirect('/auth/sign-in');
}, [hasError]);

return (
<div className="view-newsletter" data-testid="view-newsletter">
{isLoading ? (
<ClipLoader isLoading={isLoading} size={75} />
) : (
<NewsletterDetail newsletter={record} />
)}
</div>
);
};

export default ViewNewsletter;
2 changes: 1 addition & 1 deletion nextjs-13/src/app/send/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const SendNewsletter = () => {
<div>
<h3 data-testid="title">Your Newsletters</h3>
{isLoading ? (
<ClipLoader isLoading={isLoading} size={150} />
<ClipLoader isLoading={isLoading} size={75} />
) : (
<form className="send-newsletter__form" onSubmit={resetState}>
<label className="send-newsletter__label" htmlFor="email">
Expand Down
28 changes: 28 additions & 0 deletions nextjs-13/src/components/NewsletterDetail/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

import { render, waitFor, screen } from '@testing-library/react';

import { newsletterFactory } from '@test/factories/newsletter.factory';

import NewsletterDetail from './index';

describe('NewsletterDetail', () => {
it('renders the component', async () => {
render(<NewsletterDetail newsletter={{}} />);

await waitFor(() => expect(screen.getByTestId('newsletter')).toBeVisible());
});

it('renders a newsletter', async () => {
const record = {
id: newsletterFactory.id,
name: newsletterFactory.name,
content: newsletterFactory.content,
};

render(<NewsletterDetail newsletter={record} />);

expect(screen.getByText(record.name)).toBeInTheDocument();
expect(screen.getByText(record.content)).toBeInTheDocument();
});
});
23 changes: 23 additions & 0 deletions nextjs-13/src/components/NewsletterDetail/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

interface Record {
id: string;
name: string;
content: string;
}

interface Props {
newsletter: Record;
}

const NewsletterDetail = ({ newsletter }: Props) => {
return (
<div className="newsletter" data-testid="newsletter">
<h3 className="name">{newsletter.name}</h3>
<p className="content">{newsletter.content}</p>
<br />
</div>
);
};

export default NewsletterDetail;
48 changes: 48 additions & 0 deletions nextjs-13/src/components/PublicLayout/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { render, screen } from '@testing-library/react';
import { useSession } from 'next-auth/react';

import PublicLayout from './';

jest.mock('next-auth/react');

describe('PublicLayout', () => {
it('renders the component', () => {
useSession.mockReturnValue({ status: 'authenticated' });

render(
<PublicLayout>
<></>
</PublicLayout>
);

expect(screen.getByTestId('public-newsletter')).toBeInTheDocument();
});

describe('Session status is "unauthenticated', () => {
it('renders the banner', () => {
useSession.mockReturnValue({ status: 'unauthenticated' });

render(
<PublicLayout>
<></>
</PublicLayout>
);

expect(screen.getByTestId('banner')).toBeInTheDocument();
});
});

describe('Session status is "authenticated', () => {
it('does NOT render the banner', () => {
useSession.mockReturnValue({ status: 'authenticated' });

render(
<PublicLayout>
<></>
</PublicLayout>
);

expect(screen.queryByTestId('banner')).not.toBeInTheDocument();
});
});
});
24 changes: 24 additions & 0 deletions nextjs-13/src/components/PublicLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import Link from 'next/link';
import { useSession } from 'next-auth/react';

export default function PublicLayout({
children,
}: {
children: React.ReactNode;
}) {
const { status } = useSession();

return (
<div className="layout-public" data-testid="public-newsletter">
{status === 'unauthenticated' && (
<div className="banner" data-testid="banner">
<Link href="auth/sign-in">Sign up now</Link> to create your own
newsletters
</div>
)}
<main className="app-content">{children}</main>
</div>
);
}
6 changes: 5 additions & 1 deletion nextjs-13/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

const isPublicPath = (url: string) => {
return url.startsWith('/newsletter') || url.startsWith('/auth');
};

const middleware = async (req) => {
const user = await getToken({ req });
const url = req.nextUrl.pathname;
Expand All @@ -13,7 +17,7 @@ const middleware = async (req) => {
return NextResponse.redirect(new URL('/', req.url));
}

if (user === null && !url.startsWith('/auth')) {
if (!user && !isPublicPath(url)) {
return NextResponse.redirect(new URL('/auth/sign-in', req.url));
}
};
Expand Down
6 changes: 0 additions & 6 deletions nextjs-13/src/stylesheets/layouts/_app.scss

This file was deleted.

5 changes: 3 additions & 2 deletions nextjs-13/src/stylesheets/layouts/_index.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import '_auth';
@import '_default';
@import 'auth';
@import 'default';
@import 'public';
63 changes: 63 additions & 0 deletions nextjs-13/src/stylesheets/layouts/_public.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.layout-public {
.app-header,
.app-content {
padding: 0 var(--size-5);
}

.app-content {
padding-top: var(--size-5);
padding-bottom: var(--size-5);
width: 100%;
display: flex;
justify-content: center;
}

.dashboard {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 100vh;

&__nav {
width: 300px;
background: #001529;
padding: var(--size-3);

}

&__tab {
display: flex;
background: #ADD8E6;
border-radius: var(--radius-conditional-2);
padding: var(--size-2);
color: black;
cursor: pointer;
--_size: var(--font-size-1);
margin-bottom: var(--size-3);
// align-items: flex-end;
text-align: end;
text-decoration:none;

&--selected {
background: #1E90FF;
}
}
}

.app-header {
display: flex;
align-items: center;
height: var(--size-0);
}

.banner {
position: absolute;
width: 100%;
height: 30px;
background: rgb(38,194,160);
text-align: center;
color: var(--ifm-color-black);
line-height: 30px;
cursor: pointer;
}
}
1 change: 1 addition & 0 deletions nextjs-13/src/stylesheets/screens/_index.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import 'home';
@import 'send-newsletter';
@import 'view-newsletter';
11 changes: 11 additions & 0 deletions nextjs-13/src/stylesheets/screens/_view-newsletter.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.view-newsletter {
.newsletter {
margin-top: var(--size-8);
}

.content {
margin-top: var(--size-5);
white-space: pre-wrap;
word-break: keep-all;
}
}

0 comments on commit aa04905

Please sign in to comment.