Skip to content

Commit

Permalink
Update to Next 13, rework play page (#35)
Browse files Browse the repository at this point in the history
* Update to Next.js 13

- Update all dependencies to latest version.

* Update images to use new Image component

* Refactor to Next.js 13 app directory

* Add react-query

* Move API logic to src

* Add VSCode workspace settings

* Update play page

- Hero to guess is the one with the highest net worth.
- Show 10 heroes from the match instead of every hero.

* Update CI workflow

- Add exitOnceUploaded for Chromatic.
- Add e2e build script for Cypress' build parsing:
  cypress-io/github-action#272

* Add basic 404 page

* Clean up Match component

- Remove console.log.
- Remove unnecessary div.
- Add no-console ESLint rule.

* Add unit test for Loading

- Remove unused utils file.
  • Loading branch information
dricholm committed Nov 13, 2022
1 parent 8cb87f2 commit c352849
Show file tree
Hide file tree
Showing 100 changed files with 6,880 additions and 4,475 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Expand Up @@ -26,7 +26,7 @@ module.exports = {
},
plugins: ['@typescript-eslint', 'typescript-sort-keys'],
rules: {
'@typescript-eslint/array-type': [2, { default: 'generic' }],
'@typescript-eslint/array-type': [2],
'@typescript-eslint/no-shadow': 2,
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 2,
'@typescript-eslint/no-unnecessary-condition': 2,
Expand All @@ -35,6 +35,7 @@ module.exports = {
'@typescript-eslint/prefer-optional-chain': 2,
'@typescript-eslint/prefer-string-starts-ends-with': 2,
'@typescript-eslint/restrict-plus-operands': 2,
'no-console': 2,
'react/prop-types': 0,
'react/react-in-jsx-scope': 0,
'sort-keys': [2, 'asc', { natural: true }],
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/master.yaml
Expand Up @@ -31,10 +31,10 @@ jobs:
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
- name: Create mock service worker
run: npm run msw
- name: Run unit tests
run: npm run coverage
env:
CI: true
- name: Build website
run: npm run build
- name: Report coverage
Expand All @@ -44,8 +44,9 @@ jobs:
with:
autoAcceptChanges: master
buildScriptName: build-storybook
token: ${{ secrets.GITHUB_TOKEN }}
exitOnceUploaded: true
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}

e2e:
name: Run E2E tests
Expand All @@ -58,5 +59,5 @@ jobs:
- name: Run Cypress
uses: cypress-io/github-action@v4
with:
build: npm run build
build: npm run e2e:build
start: npm start
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -33,7 +33,8 @@ yarn-error.log*
# vercel
.vercel

# Generated sitemap
# Generated files
public/mockServiceWorker.js
public/robots.txt
public/sitemap.xml
public/sitemap-0.xml
18 changes: 14 additions & 4 deletions .storybook/main.js
Expand Up @@ -2,9 +2,6 @@ const path = require('path');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

module.exports = {
core: {
builder: 'webpack5',
},
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
Expand All @@ -26,8 +23,21 @@ module.exports = {
},
'storybook-addon-next-router',
],
core: {
builder: 'webpack5',
},
env: {
// Until storybook-addon-next-router is not updated for Next.js 13
// See https://github.com/lifeiscontent/storybook-addon-next-router/issues/68
__NEXT_NEW_LINK_BEHAVIOR: true,
},
staticDirs: ['../public'],
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
stories: [
'../app/**/*.stories.mdx',
'../app/**/*.stories.@(js|jsx|ts|tsx)',
'../src/**/*.stories.mdx',
'../src/**/*.stories.@(js|jsx|ts|tsx)',
],
webpackFinal: (config) => {
config.resolve.plugins = [
...(config.resolve.plugins || []),
Expand Down
24 changes: 24 additions & 0 deletions .storybook/preview.js
@@ -1,6 +1,30 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import { RouterContext } from 'next/dist/shared/lib/router-context';
import * as NextImage from 'next/image';
import '../styles/globals.scss';

initialize();

const OriginalNextImage = NextImage.default;
Object.defineProperty(NextImage, 'default', {
configurable: true,
value: (props) => <OriginalNextImage {...props} unoptimized />,
});

const queryClient = new QueryClient();

export const decorators = [
mswDecorator,
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
<ReactQueryDevtools />
</QueryClientProvider>
),
];

export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
@@ -0,0 +1,4 @@
{
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.tsdk": "node_modules\\typescript\\lib"
}
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -15,6 +15,9 @@ The live version is available [here](https://www.guessthehero.com/).
# Install dependencies
$ npm install

# Create mock service worker
$ npm run msw

# Serve application in development at http://localhost:3000
$ npm run dev

Expand Down
16 changes: 16 additions & 0 deletions app/Index.stories.tsx
@@ -0,0 +1,16 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import Component from './page';

export default {
component: Component,
parameters: {
layout: 'fullscreen',
},
title: 'Pages/Index',
} as ComponentMeta<typeof Component>;

const Template: ComponentStory<typeof Component> = (args) => (
<Component {...args} />
);

export const Index = Template.bind({});
@@ -1,18 +1,8 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import Base from '../../molecules/Base';
import Component from './About';
import Component from './page';

export default {
component: Component,
decorators: [
(Story) => (
<div id="__next" style={{ minHeight: '100vh' }}>
<Base>
<Story />
</Base>
</div>
),
],
parameters: {
layout: 'fullscreen',
},
Expand Down
11 changes: 11 additions & 0 deletions app/about/head.tsx
@@ -0,0 +1,11 @@
const Head = () => (
<>
<title>About - Guess the Hero</title>
<meta
content="Read more information about the website, how it works and how you can contribute."
name="description"
/>
</>
);

export default Head;
4 changes: 2 additions & 2 deletions src/components/pages/About/About.tsx → app/about/page.tsx
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import { FC } from 'react';
import Card from 'src/components/atoms/Card';
import styles from './About.module.scss';
import clsx from 'clsx';
import styles from './styles.module.scss';

const About: FC = () => (
<div className={clsx('container', styles.container)}>
Expand Down
File renamed without changes.
18 changes: 18 additions & 0 deletions app/error.tsx
@@ -0,0 +1,18 @@
'use client';

interface Props {
error: Error;
reset: VoidFunction;
}

const ErrorPage: React.FC<Props> = ({ error, reset }) => (
<div className="container">
<h1>Error</h1>
<p>{error.message}</p>
<button className="btn" onClick={reset}>
Try again
</button>
</div>
);

export default ErrorPage;
11 changes: 11 additions & 0 deletions app/head.tsx
@@ -0,0 +1,11 @@
const Head = () => (
<>
<title>A Dota 2 quiz - Guess the Hero</title>
<meta
content="A Dota 2 quiz game. Guess the hero from items bought in a match. See the purchased items of a hero and other optional stats and use them to try to guess the hero."
name="description"
/>
</>
);

export default Head;
@@ -1,6 +1,6 @@
import heroesJson from 'dotaconstants/build/heroes.json';
import { render, screen } from '@testing-library/react';
import Heroes from '.';
import heroesJson from 'dotaconstants/build/heroes.json';
import Heroes from './page';

describe('Heroes', () => {
it('should render every hero', () => {
Expand Down
@@ -1,18 +1,8 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import Base from '../../molecules/Base';
import Component from './Heroes';
import Component from './page';

export default {
component: Component,
decorators: [
(Story) => (
<div id="__next" style={{ minHeight: '100vh' }}>
<Base>
<Story />
</Base>
</div>
),
],
parameters: {
layout: 'fullscreen',
},
Expand Down
8 changes: 8 additions & 0 deletions app/heroes/head.tsx
@@ -0,0 +1,8 @@
const Head = () => (
<>
<title>Dota 2 heroes - Guess the Hero</title>
<meta content="See all of Dota 2's heroes." name="description" />
</>
);

export default Head;
Expand Up @@ -3,7 +3,7 @@ import heroesJson from 'dotaconstants/build/heroes.json';
import { FC } from 'react';
import Card from 'src/components/atoms/Card';
import HeroIcon from 'src/components/atoms/HeroIcon';
import styles from './Heroes.module.scss';
import styles from './styles.module.scss';

const ATTRIBUTES = ['Strength', 'Agility', 'Intelligence'];

Expand Down
File renamed without changes.
@@ -1,18 +1,8 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import Base from '../../molecules/Base';
import Component from './Items';
import Component from './page';

export default {
component: Component,
decorators: [
(Story) => (
<div id="__next" style={{ minHeight: '100vh' }}>
<Base>
<Story />
</Base>
</div>
),
],
parameters: {
layout: 'fullscreen',
},
Expand Down
11 changes: 11 additions & 0 deletions app/items/head.tsx
@@ -0,0 +1,11 @@
const Head = () => (
<>
<title>Dota 2 items - Guess the Hero</title>
<meta
content="Get to know all the items Dota 2 has to offer and learn all about them. Check their abilities, costs, lore and even more."
name="description"
/>
</>
);

export default Head;
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import { FC } from 'react';
import ItemList from 'src/components/molecules/ItemList';
import styles from './Items.module.scss';
import styles from './styles.module.scss';

const ITEMS = [
{
Expand Down
File renamed without changes.
30 changes: 30 additions & 0 deletions app/layout.tsx
@@ -0,0 +1,30 @@
'use client';

import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { FC, ReactNode } from 'react';
import Footer from 'src/components/atoms/Footer';
import Header from 'src/components/atoms/Header';
import queryClient from 'src/data/query-client';
import 'styles/globals.scss';

const RootLayout: FC<{ children: ReactNode }> = ({ children }) => (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="shortcut icon" href="/favicon.png" />
</head>
<body>
<QueryClientProvider client={queryClient}>
<Header />
<main>{children}</main>
<Footer />
<div className="absolute" id="modal" />
<ReactQueryDevtools />
</QueryClientProvider>
</body>
</html>
);

export default RootLayout;
3 changes: 3 additions & 0 deletions app/loading.tsx
@@ -0,0 +1,3 @@
import Loading from 'src/components/molecules/Loading';

export default Loading;
10 changes: 10 additions & 0 deletions app/not-found.tsx
@@ -0,0 +1,10 @@
import Link from 'next/link';

const NotFound: React.FC = () => (
<div className="container">
<h1>Not found</h1>
<Link href="/">To home page</Link>
</div>
);

export default NotFound;
11 changes: 5 additions & 6 deletions src/components/pages/Home/Home.tsx → app/page.tsx
@@ -1,20 +1,19 @@
import clsx from 'clsx';
import Link from 'next/link';
import { FC } from 'react';
import Button from 'src/components/atoms/Button';
import Card from 'src/components/atoms/Card';
import styles from './Home.module.scss';
import styles from './styles.module.scss';

const Home: FC = () => (
const Index: FC = () => (
<div className={clsx('container', styles.container)}>
<h1>Test your Dota 2 knowledge</h1>
<Card>
<p>How well can you guess a hero from seeing their purchased items?</p>
<Link href="/game" passHref={true}>
<Button>Play now</Button>
<Link className="btn" href="/play">
Play now
</Link>
</Card>
</div>
);

export default Home;
export default Index;
11 changes: 11 additions & 0 deletions app/play/head.tsx
@@ -0,0 +1,11 @@
const Head = () => (
<>
<title>Dota 2 - Guess the Hero</title>
<meta
content="Play a quiz game of Dota 2. Guess the hero from a random match only by seeing their inventory."
name="description"
/>
</>
);

export default Head;

1 comment on commit c352849

@vercel
Copy link

@vercel vercel bot commented on c352849 Nov 13, 2022

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.