├── public ├── .gitkeep ├── cover-01.png ├── favicon.ico ├── apple-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── ms-icon-70x70.png ├── apple-icon-57x57.png ├── apple-icon-60x60.png ├── apple-icon-72x72.png ├── apple-icon-76x76.png ├── ms-icon-144x144.png ├── ms-icon-150x150.png ├── ms-icon-310x310.png ├── android-icon-36x36.png ├── android-icon-48x48.png ├── android-icon-72x72.png ├── android-icon-96x96.png ├── apple-icon-114x114.png ├── apple-icon-120x120.png ├── apple-icon-144x144.png ├── apple-icon-152x152.png ├── apple-icon-180x180.png ├── banner-spiderman.jpeg ├── banner-spiderman.webp ├── android-icon-144x144.png ├── android-icon-192x192.png ├── apple-icon-precomposed.png ├── browserconfig.xml ├── manifest.json ├── logo.svg ├── error.svg └── banner-spiderman.svg ├── .eslintignore ├── .huskyrc.json ├── .babelrc ├── .lintstagedrc.json ├── next.config.js ├── pages ├── 404.tsx ├── _app.tsx ├── comics │ └── [id].tsx ├── characters │ └── [id].tsx ├── index.tsx └── _document.tsx ├── src ├── domain │ ├── models │ │ ├── character.ts │ │ └── comic.ts │ ├── errors │ │ ├── entity-not-found.tsx │ │ └── unexpected-error.tsx │ └── usecases │ │ ├── load-characters-by-id.ts │ │ ├── load-comic-by-id.ts │ │ ├── load-characters.ts │ │ └── load-comics.ts ├── presentation │ ├── components │ │ ├── wrapper │ │ │ ├── styles.ts │ │ │ └── index.tsx │ │ ├── search-input │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── header │ │ │ ├── components │ │ │ │ ├── avatar │ │ │ │ │ ├── styles.ts │ │ │ │ │ └── index.tsx │ │ │ │ └── menu-link │ │ │ │ │ ├── styles.ts │ │ │ │ │ └── index.tsx │ │ │ ├── styles.ts │ │ │ └── index.tsx │ │ ├── footer │ │ │ ├── styles.ts │ │ │ └── index.tsx │ │ ├── share │ │ │ ├── styles.ts │ │ │ └── index.tsx │ │ ├── character-list │ │ │ ├── styles.ts │ │ │ ├── components │ │ │ │ └── character-list-item │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ └── index.tsx │ │ ├── comic-list │ │ │ ├── styles.ts │ │ │ ├── components │ │ │ │ └── comic-list-item │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ └── index.tsx │ │ └── banner │ │ │ ├── index.tsx │ │ │ └── styles.ts │ ├── layouts │ │ └── app-layouts │ │ │ ├── styles.ts │ │ │ └── index.tsx │ ├── utils │ │ └── withSSGErrorHandler.tsx │ ├── pages │ │ ├── error │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── comics │ │ │ ├── styles.ts │ │ │ └── index.tsx │ │ ├── character │ │ │ ├── styles.ts │ │ │ └── index.tsx │ │ └── main │ │ │ └── index.tsx │ └── styles │ │ └── global.ts ├── main │ └── factories │ │ ├── http │ │ ├── marvel-http-client.ts │ │ └── mock-http-client.ts │ │ └── usecases │ │ ├── remote-load-comics.ts │ │ ├── remote-load-comic-by-id.ts │ │ ├── remote-load-characters.ts │ │ └── remote-load-character-by-id.ts ├── data │ ├── protocols │ │ └── http │ │ │ ├── marvel-http-response.ts │ │ │ └── http-client.ts │ └── usecases │ │ ├── remote-load-characters.ts │ │ ├── remote-load-character-by-id.ts │ │ ├── remote-load-comics.ts │ │ ├── remote-load-comic-by-id.ts │ │ ├── remote-load-character-by-id.spec.ts │ │ ├── remote-load-characters.spec.ts │ │ ├── remote-load-comics.spec.ts │ │ └── remote-load-comic-by-id.spec.ts └── infra │ └── http │ └── marvel-http-client.ts ├── next-env.d.ts ├── jest.config.js ├── .eslintrc.json ├── .gitignore ├── tsconfig.json ├── README.md └── package.json /public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | coverage -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged" 4 | } 5 | } -------------------------------------------------------------------------------- /public/cover-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/cover-01.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [["styled-components", { "ssr": true }]] 4 | } -------------------------------------------------------------------------------- /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/favicon-96x96.png -------------------------------------------------------------------------------- /public/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/ms-icon-70x70.png -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts, tsx}": [ 3 | "npm run lint:fix", 4 | "npm run test:staged" 5 | ] 6 | } -------------------------------------------------------------------------------- /public/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/android-icon-36x36.png -------------------------------------------------------------------------------- /public/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/android-icon-48x48.png -------------------------------------------------------------------------------- /public/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/android-icon-72x72.png -------------------------------------------------------------------------------- /public/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/android-icon-96x96.png -------------------------------------------------------------------------------- /public/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/banner-spiderman.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/banner-spiderman.jpeg -------------------------------------------------------------------------------- /public/banner-spiderman.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/banner-spiderman.webp -------------------------------------------------------------------------------- /public/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/android-icon-144x144.png -------------------------------------------------------------------------------- /public/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/android-icon-192x192.png -------------------------------------------------------------------------------- /public/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipebarcelospro/marvel-hub/HEAD/public/apple-icon-precomposed.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | images: { 4 | domains: ['i.annihil.us', 'terrigen-cdn-dev.marvel.com'] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorPage } from '../src/presentation/pages/error' 2 | 3 | export default function Custom404 (): React.ReactElement { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/models/character.ts: -------------------------------------------------------------------------------- 1 | export interface CharacterModel { 2 | id: string 3 | cover: string 4 | name: string 5 | description: string 6 | comicsCount: number 7 | } 8 | -------------------------------------------------------------------------------- /src/presentation/components/wrapper/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Container = styled.div` 4 | width: 85%; 5 | margin: 0 auto; 6 | ` 7 | -------------------------------------------------------------------------------- /src/presentation/layouts/app-layouts/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const PageWrapper = styled.main` 4 | min-height: calc(100vh - 180px); 5 | ` 6 | -------------------------------------------------------------------------------- /src/domain/errors/entity-not-found.tsx: -------------------------------------------------------------------------------- 1 | export class EntityNotFound extends Error { 2 | constructor (entityName: string) { 3 | super(`${entityName} not found`) 4 | this.name = 'EntityNotFound' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /src/domain/errors/unexpected-error.tsx: -------------------------------------------------------------------------------- 1 | export class UnexpectedError extends Error { 2 | constructor (statusCode: number) { 3 | super(`Unexpected status code - ${statusCode}`) 4 | this.name = 'UnexpectedError' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/main/factories/http/marvel-http-client.ts: -------------------------------------------------------------------------------- 1 | import { MarvelHttpClient } from '../../../infra/http/marvel-http-client' 2 | 3 | export const makeMarvelHttpClient = (): MarvelHttpClient => { 4 | return new MarvelHttpClient() 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/models/comic.ts: -------------------------------------------------------------------------------- 1 | export interface ComicModel { 2 | id: number 3 | title: string 4 | cover: string 5 | publishedAt: string 6 | writer: string 7 | penciler: string 8 | coverArtist: string 9 | description: string 10 | comics?: this[] 11 | } 12 | -------------------------------------------------------------------------------- /src/data/protocols/http/marvel-http-response.ts: -------------------------------------------------------------------------------- 1 | export interface MarvelHttpResponse { 2 | code: number 3 | status: string 4 | data: { 5 | offset?: number 6 | limit?: number 7 | total?: number 8 | count?: number 9 | results: T 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | collectCoverageFrom: [ 4 | '/src/**/*.{ts,tsx}' 5 | ], 6 | coverageDirectory: 'coverage', 7 | testEnvironment: 'node', 8 | transform: { 9 | '.+\\.(ts|tsx)$': 'ts-jest' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /src/presentation/components/wrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from './styles' 2 | 3 | interface WrapperProps { 4 | children: React.ReactNode 5 | } 6 | 7 | export function Wrapper ({ children }: WrapperProps): React.ReactElement { 8 | return ( 9 | 10 | {children} 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/presentation/components/search-input/index.tsx: -------------------------------------------------------------------------------- 1 | import { FiSearch } from 'react-icons/fi' 2 | import { Container } from './styles' 3 | 4 | export function SearchInput (): React.ReactElement { 5 | return ( 6 | 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { ThemeProvider } from 'styled-components' 3 | import { GlobalStyle } from '../src/presentation/styles/global' 4 | 5 | function MyApp ({ Component, pageProps }): React.ReactElement { 6 | return ( 7 | 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default MyApp 15 | -------------------------------------------------------------------------------- /src/main/factories/usecases/remote-load-comics.ts: -------------------------------------------------------------------------------- 1 | import { RemoteLoadComicsList } from '../../../data/usecases/remote-load-comics' 2 | import { LoadComicsList } from '../../../domain/usecases/load-comics' 3 | import { makeMarvelHttpClient } from '../http/marvel-http-client' 4 | 5 | export const makeRemoteLoadComicsList = (): LoadComicsList => { 6 | return new RemoteLoadComicsList(makeMarvelHttpClient()) 7 | } 8 | -------------------------------------------------------------------------------- /src/main/factories/usecases/remote-load-comic-by-id.ts: -------------------------------------------------------------------------------- 1 | import { RemoteLoadComicById } from '../../../data/usecases/remote-load-comic-by-id' 2 | import { LoadComicById } from '../../../domain/usecases/load-comic-by-id' 3 | import { makeMarvelHttpClient } from '../http/marvel-http-client' 4 | 5 | export const makeRemoteLoadComicById = (): LoadComicById => { 6 | return new RemoteLoadComicById(makeMarvelHttpClient()) 7 | } 8 | -------------------------------------------------------------------------------- /src/presentation/components/header/components/avatar/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Container = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | 8 | background: var(--color-primary-500); 9 | height: var(--size-sm); 10 | width: var(--size-sm); 11 | 12 | border-radius: var(--radius-base); 13 | box-shadow: var(--shadow); 14 | ` 15 | -------------------------------------------------------------------------------- /src/presentation/components/footer/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Container = styled.header` 4 | border-top: 1px solid var(--color-border); 5 | margin-top: auto; 6 | 7 | > div { 8 | display: flex; 9 | justify-content: space-between; 10 | align-items: center; 11 | 12 | min-height: 90px; 13 | 14 | > span { 15 | opacity: 0.6; 16 | } 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /src/main/factories/usecases/remote-load-characters.ts: -------------------------------------------------------------------------------- 1 | import { RemoteLoadCharactersList } from '../../../data/usecases/remote-load-characters' 2 | import { LoadCharactersList } from '../../../domain/usecases/load-characters' 3 | import { makeMarvelHttpClient } from '../http/marvel-http-client' 4 | 5 | export const makeRemoteLoadCharactersList = (): LoadCharactersList => { 6 | return new RemoteLoadCharactersList(makeMarvelHttpClient()) 7 | } 8 | -------------------------------------------------------------------------------- /src/main/factories/usecases/remote-load-character-by-id.ts: -------------------------------------------------------------------------------- 1 | import { RemoteLoadCharacterById } from '../../../data/usecases/remote-load-character-by-id' 2 | import { LoadCharacterById } from '../../../domain/usecases/load-characters-by-id' 3 | import { makeMarvelHttpClient } from '../http/marvel-http-client' 4 | 5 | export const makeRemoteLoadCharacterById = (): LoadCharacterById => { 6 | return new RemoteLoadCharacterById(makeMarvelHttpClient()) 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/usecases/load-characters-by-id.ts: -------------------------------------------------------------------------------- 1 | export interface LoadCharacterByIdDTO { 2 | id: string 3 | } 4 | 5 | export interface LoadCharacterById { 6 | execute: (params?: LoadCharacterByIdDTO) => Promise 7 | } 8 | 9 | export namespace LoadCharacterById { 10 | export interface Model { 11 | id: string 12 | cover: string 13 | name: string 14 | description: string 15 | comicsCount: number 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/presentation/components/header/components/menu-link/styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components' 2 | 3 | export const Container = styled.a<{ active?: boolean }>` 4 | display: flex; 5 | align-items: center; 6 | cursor: pointer; 7 | opacity: 0.6; 8 | 9 | ${props => props.active && css` 10 | opacity: 1; 11 | `} 12 | 13 | :hover { 14 | opacity: 1; 15 | } 16 | 17 | > svg { 18 | margin-right: var(--space-xs); 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /src/presentation/utils/withSSGErrorHandler.tsx: -------------------------------------------------------------------------------- 1 | import { GetStaticProps, GetStaticPropsContext, GetStaticPropsResult } from 'next' 2 | 3 | export function withSSGErrorHandler

(fn: GetStaticProps

) { 4 | return async (ctx: GetStaticPropsContext): Promise> => { 5 | try { 6 | return await fn(ctx) 7 | } catch (err) { 8 | console.error(err) 9 | return { 10 | notFound: true 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/presentation/layouts/app-layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import { Footer } from '../../components/footer' 2 | import { Header } from '../../components/header' 3 | import { PageWrapper } from './styles' 4 | 5 | interface AppLayoutProps { 6 | children: React.ReactNode 7 | } 8 | 9 | export function AppLayout ({ children }: AppLayoutProps): React.ReactElement { 10 | return ( 11 | <> 12 |

13 | 14 | {children} 15 | 16 |