├── .npmrc ├── public ├── icon-app.png ├── robots.txt └── icon-app@2x.png ├── .github ├── images │ ├── ddd-layers.png │ └── project-home.png └── workflows │ ├── bohr.yml │ └── test.yaml ├── src ├── vite-env.d.ts ├── domain │ ├── models │ │ ├── RepositoryOwner.ts │ │ ├── RepositoryIssue.ts │ │ ├── Repository.ts │ │ └── RepositoryDetails.ts │ ├── use-cases │ │ ├── LoadRepositories.ts │ │ ├── SaveRepositories.ts │ │ ├── SearchRepository.ts │ │ ├── GetRepositoryIssues.ts │ │ └── GetRepositoryDetails.ts │ └── errors │ │ ├── InvalidRepositoryError.ts │ │ └── UnexpectedError.ts ├── presentation │ ├── protocols │ │ └── validation.ts │ ├── components │ │ ├── Footer │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── footer.test.tsx │ │ └── RepositoriesList │ │ │ ├── index.tsx │ │ │ ├── styles.tsx │ │ │ └── repositories-list.test.tsx │ ├── styles │ │ └── global.ts │ ├── assets │ │ ├── github-background.svg │ │ └── logo.svg │ └── pages │ │ ├── Home │ │ ├── styles.ts │ │ └── index.tsx │ │ └── RepositoryDetails │ │ ├── styles.ts │ │ └── index.tsx ├── data │ ├── protocols │ │ ├── cache │ │ │ ├── SaveCacheClientProtocol.ts │ │ │ └── GetCacheClientProtocol.ts │ │ └── http │ │ │ └── HttpClientProtocol.ts │ └── use-cases │ │ ├── CacheSaveRepositories.ts │ │ ├── CacheLoadRepositories.ts │ │ ├── RemoteSearchRepository.ts │ │ ├── RemoteGetRepositoryIssues.ts │ │ └── RemoteGetRepositoryDetails.ts ├── main │ ├── factories │ │ ├── cache │ │ │ ├── repositories-cache-key-factory.ts │ │ │ ├── local-storage-get-cache-client-adapter-factory.ts │ │ │ └── local-storage-save-cache-client-adapter-factory.ts │ │ ├── http │ │ │ ├── axios-http-client-adapter-factory.ts │ │ │ ├── fetch-http-client-adapter-factory.ts │ │ │ └── api-url-factory.ts │ │ ├── use-cases │ │ │ ├── remote-search-repository-factory.ts │ │ │ ├── remote-get-repository-issues-factory.ts │ │ │ ├── cache-save-repositories-factory.ts │ │ │ ├── cache-load-repositories-factory.ts │ │ │ └── remote-get-repository-details-factory.ts │ │ └── views │ │ │ ├── repository-details-factory.tsx │ │ │ └── home-factory.tsx │ ├── App.tsx │ └── routes │ │ └── index.tsx ├── validation │ ├── errors │ │ └── RequiredFieldError.ts │ └── RequiredFieldValidator.ts ├── main.tsx └── infra │ ├── cache │ ├── LocalStorageSaveCacheClienteAdapter.ts │ ├── LocalStorageGetCacheClienteAdapter.ts │ ├── LocalStorageSaveCacheClienteAdapter.test.ts │ └── LocalStorageGetCacheClienteAdapter.test.ts │ └── http │ ├── FetchHttpClientAdapter.ts │ └── AxiosHttpClientAdapter.ts ├── .editorconfig ├── tsconfig.node.json ├── vitest-setup.ts ├── vite.config.ts ├── .eslintrc.cjs ├── index.html ├── .gitignore ├── vitest.config.ts ├── tsconfig.json ├── package.json ├── README.md └── yarn.lock /.npmrc: -------------------------------------------------------------------------------- 1 | engines=node>=18 2 | -------------------------------------------------------------------------------- /public/icon-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunderhus/GithubExplorer/HEAD/public/icon-app.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/icon-app@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunderhus/GithubExplorer/HEAD/public/icon-app@2x.png -------------------------------------------------------------------------------- /.github/images/ddd-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunderhus/GithubExplorer/HEAD/.github/images/ddd-layers.png -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /.github/images/project-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunderhus/GithubExplorer/HEAD/.github/images/project-home.png -------------------------------------------------------------------------------- /src/domain/models/RepositoryOwner.ts: -------------------------------------------------------------------------------- 1 | export interface RepositoryOwner { 2 | login: string 3 | avatar: string 4 | } 5 | -------------------------------------------------------------------------------- /src/presentation/protocols/validation.ts: -------------------------------------------------------------------------------- 1 | export interface Validation { 2 | validate: (value: string) => null | Error 3 | } 4 | -------------------------------------------------------------------------------- /src/data/protocols/cache/SaveCacheClientProtocol.ts: -------------------------------------------------------------------------------- 1 | export interface SaveCacheProtocol { 2 | save(key: string, content: T): void 3 | } 4 | -------------------------------------------------------------------------------- /src/domain/models/RepositoryIssue.ts: -------------------------------------------------------------------------------- 1 | export interface RepositoryIssue { 2 | id: number 3 | title: string 4 | linkTo: string 5 | createdBy: string 6 | } 7 | -------------------------------------------------------------------------------- /src/main/factories/cache/repositories-cache-key-factory.ts: -------------------------------------------------------------------------------- 1 | export const makeRepositoriesCacheKey = (): string => { 2 | return '@GihubExplorer:repositories' 3 | } 4 | -------------------------------------------------------------------------------- /src/domain/use-cases/LoadRepositories.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '../models/Repository' 2 | 3 | export interface LoadRepositories { 4 | load(): Repository[] 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/use-cases/SaveRepositories.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '../models/Repository' 2 | 3 | export interface SaveRepositories { 4 | save: (repositories: Repository[]) => void 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/use-cases/SearchRepository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '../models/Repository' 2 | 3 | export interface SearchRepository { 4 | search: (searchText: string) => Promise 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | -------------------------------------------------------------------------------- /src/domain/models/Repository.ts: -------------------------------------------------------------------------------- 1 | import { RepositoryOwner } from './RepositoryOwner' 2 | 3 | export interface Repository { 4 | name: string 5 | description: string 6 | owner: RepositoryOwner 7 | } 8 | -------------------------------------------------------------------------------- /src/validation/errors/RequiredFieldError.ts: -------------------------------------------------------------------------------- 1 | export class RequiredFieldError extends Error { 2 | constructor() { 3 | super('Required Field') 4 | this.name = 'RequiredFieldError' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/data/protocols/cache/GetCacheClientProtocol.ts: -------------------------------------------------------------------------------- 1 | export interface CacheContent { 2 | content: string; 3 | } 4 | 5 | export interface GetCacheClientProtocol { 6 | getItem: (key: string) => CacheContent; 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/errors/InvalidRepositoryError.ts: -------------------------------------------------------------------------------- 1 | export class InvalidRepositoryError extends Error { 2 | constructor() { 3 | super('Invalid Repository') 4 | this.name = 'InvalidRepositoryError' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/domain/errors/UnexpectedError.ts: -------------------------------------------------------------------------------- 1 | export class UnexpectedError extends Error { 2 | constructor() { 3 | super('Something unexpected happened. Please try again later.') 4 | this.name = 'UnexpectedError' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/domain/use-cases/GetRepositoryIssues.ts: -------------------------------------------------------------------------------- 1 | import { RepositoryIssue } from '../models/RepositoryIssue' 2 | 3 | export interface GetRepositoryIssues { 4 | get(owner: string, repositoryName: string): Promise 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/use-cases/GetRepositoryDetails.ts: -------------------------------------------------------------------------------- 1 | import { RepositoryDetails } from '../models/RepositoryDetails' 2 | 3 | export interface GetRepositoryDetails { 4 | get(owner: string, repositoryName: string): Promise 5 | } 6 | -------------------------------------------------------------------------------- /src/main/factories/http/axios-http-client-adapter-factory.ts: -------------------------------------------------------------------------------- 1 | import { AxiosHttpClientAdapter } from "@/infra/http/AxiosHttpClientAdapter"; 2 | 3 | export const makeAxiosHttpClientAdapter = (): AxiosHttpClientAdapter => { 4 | return new AxiosHttpClientAdapter(); 5 | }; 6 | -------------------------------------------------------------------------------- /src/main/factories/http/fetch-http-client-adapter-factory.ts: -------------------------------------------------------------------------------- 1 | import { FetchHttpClientAdapter } from "@/infra/http/FetchHttpClientAdapter"; 2 | 3 | export const makeFetchHttpClientAdapter = (): FetchHttpClientAdapter => { 4 | return new FetchHttpClientAdapter(); 5 | }; 6 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "@/main/App.tsx"; 4 | 5 | ReactDOM.createRoot(document.getElementById("root")!).render( 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/main/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import GlobalStyle from '../presentation/styles/global' 4 | import Routes from './routes' 5 | 6 | const App: React.FC = () => ( 7 | <> 8 | 9 | 10 | 11 | ) 12 | export default App 13 | -------------------------------------------------------------------------------- /src/domain/models/RepositoryDetails.ts: -------------------------------------------------------------------------------- 1 | import { RepositoryOwner } from './RepositoryOwner' 2 | 3 | export interface RepositoryDetails { 4 | name: string 5 | description: string 6 | stars: number 7 | forks: number 8 | issues: number 9 | owner: RepositoryOwner 10 | } 11 | -------------------------------------------------------------------------------- /vitest-setup.ts: -------------------------------------------------------------------------------- 1 | import { expect, afterEach } from "vitest"; 2 | import { cleanup } from "@testing-library/react"; 3 | import * as matchers from "@testing-library/jest-dom/matchers"; 4 | import "@testing-library/jest-dom"; 5 | 6 | expect.extend(matchers); 7 | 8 | afterEach(() => { 9 | cleanup(); 10 | }); 11 | -------------------------------------------------------------------------------- /.github/workflows/bohr.yml: -------------------------------------------------------------------------------- 1 | name: bohr.io deploy 2 | on: 3 | push: 4 | repository_dispatch: 5 | types: [bohr-dispatch] 6 | permissions: write-all 7 | jobs: 8 | deploy: 9 | name: Deploy on bohr.io 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: bohr-io/action@main -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | /// 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@": "/src", 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /src/main/factories/cache/local-storage-get-cache-client-adapter-factory.ts: -------------------------------------------------------------------------------- 1 | import { LocalStorageGetCacheClienteAdapter } from "@/infra/cache/LocalStorageGetCacheClienteAdapter"; 2 | 3 | export const makeLocalStorageGetCacheClientAdapter = 4 | (): LocalStorageGetCacheClienteAdapter => { 5 | return new LocalStorageGetCacheClienteAdapter(); 6 | }; 7 | -------------------------------------------------------------------------------- /src/main/factories/cache/local-storage-save-cache-client-adapter-factory.ts: -------------------------------------------------------------------------------- 1 | import { LocalStorageSaveCacheClienteAdapter } from "@/infra/cache/LocalStorageSaveCacheClienteAdapter"; 2 | 3 | export const makeLocalStorageSaveCacheAdapter = 4 | (): LocalStorageSaveCacheClienteAdapter => { 5 | return new LocalStorageSaveCacheClienteAdapter(); 6 | }; 7 | -------------------------------------------------------------------------------- /src/main/factories/http/api-url-factory.ts: -------------------------------------------------------------------------------- 1 | export const makeApiUrl = (path: string): string => { 2 | return `https://api.github.com/${path}` 3 | 4 | /** Fica a dica 5 | * - Em projetos reais a base url será via environment. 6 | * - passar por parâmetro se forem APIs diferentes 7 | * ou criar diferentes factories para cada API. 8 | */ 9 | } 10 | -------------------------------------------------------------------------------- /src/validation/RequiredFieldValidator.ts: -------------------------------------------------------------------------------- 1 | import { Validation } from '../presentation/protocols/validation' 2 | import { RequiredFieldError } from './errors/RequiredFieldError' 3 | 4 | export class RequiredFieldValidator implements Validation { 5 | validate = (value: string): null | Error => { 6 | if (!value || /^\s*$/.test(value)) { 7 | return new RequiredFieldError() 8 | } 9 | return null 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/cache/LocalStorageSaveCacheClienteAdapter.ts: -------------------------------------------------------------------------------- 1 | import { SaveCacheProtocol } from "@/data/protocols/cache/SaveCacheClientProtocol"; 2 | 3 | export class LocalStorageSaveCacheClienteAdapter implements SaveCacheProtocol { 4 | save(key: string, content: unknown): void { 5 | localStorage.setItem(key, this.adapt(content)); 6 | } 7 | 8 | private adapt(content: unknown): string { 9 | return JSON.stringify(content); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/presentation/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FiLinkedin } from "react-icons/fi"; 3 | import { Container } from "./styles"; 4 | 5 | const Footer: React.FC = () => { 6 | return ( 7 | 8 | 9 | 10 |

Matheus Sunderhus

11 |
12 |
13 | ); 14 | }; 15 | 16 | export default Footer; 17 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/presentation/components/Footer/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Container = styled.div` 4 | flex: 1; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | height: 40px; 9 | a { 10 | font-size: 16px; 11 | color: #3d3d4d; 12 | text-decoration: none; 13 | display: flex; 14 | &:hover svg { 15 | color: #2867b2; 16 | } 17 | 18 | svg { 19 | transition: color 0.2s linear; 20 | margin-right: 5px; 21 | } 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /src/main/factories/use-cases/remote-search-repository-factory.ts: -------------------------------------------------------------------------------- 1 | import { RemoteSearchRepository } from "@/data/use-cases/RemoteSearchRepository"; 2 | import { SearchRepository } from "@/domain/use-cases/SearchRepository"; 3 | import { makeApiUrl } from "../http/api-url-factory"; 4 | import { makeAxiosHttpClientAdapter } from "../http/axios-http-client-adapter-factory"; 5 | 6 | export const makeRemoteSearchRepository = (): SearchRepository => { 7 | return new RemoteSearchRepository( 8 | makeApiUrl("repos"), 9 | makeAxiosHttpClientAdapter() 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/data/use-cases/CacheSaveRepositories.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from "@/domain/models/Repository"; 2 | import { SaveRepositories } from "@/domain/use-cases/SaveRepositories"; 3 | import { SaveCacheProtocol } from "../protocols/cache/SaveCacheClientProtocol"; 4 | 5 | export class CacheSaveRepositories implements SaveRepositories { 6 | constructor( 7 | private readonly key: string, 8 | private readonly cacheClient: SaveCacheProtocol 9 | ) {} 10 | 11 | save(repositories: Repository[]): void { 12 | this.cacheClient.save(this.key, repositories); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/factories/use-cases/remote-get-repository-issues-factory.ts: -------------------------------------------------------------------------------- 1 | import { RemoteGetRepositoryIssues } from "@/data/use-cases/RemoteGetRepositoryIssues"; 2 | import { GetRepositoryIssues } from "@/domain/use-cases/GetRepositoryIssues"; 3 | import { makeApiUrl } from "../http/api-url-factory"; 4 | import { makeAxiosHttpClientAdapter } from "../http/axios-http-client-adapter-factory"; 5 | 6 | export const makeRemoteGetRepositoryIssues = (): GetRepositoryIssues => { 7 | return new RemoteGetRepositoryIssues( 8 | makeApiUrl("repos"), 9 | makeAxiosHttpClientAdapter() 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/infra/cache/LocalStorageGetCacheClienteAdapter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetCacheClientProtocol, 3 | CacheContent, 4 | } from "@/data/protocols/cache/GetCacheClientProtocol"; 5 | 6 | export class LocalStorageGetCacheClienteAdapter 7 | implements GetCacheClientProtocol 8 | { 9 | getItem(key: string): CacheContent { 10 | const cacheResult = localStorage.getItem(key); 11 | 12 | return this.adapt(cacheResult); 13 | } 14 | 15 | private adapt(cacheResult?: string | null): CacheContent { 16 | return { 17 | content: cacheResult || JSON.stringify([]), 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/factories/use-cases/cache-save-repositories-factory.ts: -------------------------------------------------------------------------------- 1 | import { CacheSaveRepositories } from "@/data/use-cases/CacheSaveRepositories"; 2 | import { SaveRepositories } from "@/domain/use-cases/SaveRepositories"; 3 | import { makeLocalStorageSaveCacheAdapter } from "../cache/local-storage-save-cache-client-adapter-factory"; 4 | import { makeRepositoriesCacheKey } from "../cache/repositories-cache-key-factory"; 5 | 6 | export const makeCacheSaveRepositories = (): SaveRepositories => { 7 | return new CacheSaveRepositories( 8 | makeRepositoriesCacheKey(), 9 | makeLocalStorageSaveCacheAdapter() 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/main/factories/use-cases/cache-load-repositories-factory.ts: -------------------------------------------------------------------------------- 1 | import { CacheLoadRepositories } from "@/data/use-cases/CacheLoadRepositories"; 2 | import { LoadRepositories } from "@/domain/use-cases/LoadRepositories"; 3 | import { makeLocalStorageGetCacheClientAdapter } from "../cache/local-storage-get-cache-client-adapter-factory"; 4 | import { makeRepositoriesCacheKey } from "../cache/repositories-cache-key-factory"; 5 | 6 | export const makeCacheLoadRepositories = (): LoadRepositories => { 7 | return new CacheLoadRepositories( 8 | makeRepositoriesCacheKey(), 9 | makeLocalStorageGetCacheClientAdapter() 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/main/factories/views/repository-details-factory.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import RepositoryDetailsPage from "@/presentation/pages/RepositoryDetails"; 3 | import { makeRemoteGetRepositoryDetails } from "../use-cases/remote-get-repository-details-factory"; 4 | import { makeRemoteGetRepositoryIssues } from "../use-cases/remote-get-repository-issues-factory"; 5 | 6 | export const makeRepositoryDetails: React.FC = () => { 7 | return ( 8 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Github - Explorer 8 | 9 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: CI - Vitest - unit tests 2 | 3 | on: 4 | push: 5 | branches: [dev] 6 | pull_request: 7 | branches: [dev] 8 | workflow_dispatch: 9 | branches: [dev] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup Node.js environment 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: "18" 22 | 23 | - name: Install Yarn 24 | run: npm install -g yarn 25 | 26 | - name: Install dependencies and Run tests 27 | run: | 28 | yarn install 29 | yarn test:ci 30 | -------------------------------------------------------------------------------- /src/main/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | BrowserRouter, 4 | Routes as ReactRouterRoutes, 5 | Route, 6 | } from 'react-router-dom' 7 | 8 | import { makeHome } from '../factories/views/home-factory' 9 | import { makeRepositoryDetails } from '../factories/views/repository-details-factory' 10 | 11 | const Routes: React.FC = () => ( 12 | 13 | 14 | 15 | 19 | 20 | 21 | ) 22 | 23 | export default Routes 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | pnpm-debug.log* 28 | lerna-debug.log* 29 | 30 | dist 31 | dist-ssr 32 | *.local 33 | 34 | # Editor directories and files 35 | .vscode/* 36 | !.vscode/extensions.json 37 | .idea 38 | .DS_Store 39 | *.suo 40 | *.ntvs* 41 | *.njsproj 42 | *.sln 43 | *.sw? -------------------------------------------------------------------------------- /src/data/protocols/http/HttpClientProtocol.ts: -------------------------------------------------------------------------------- 1 | export enum HttpStatusCode { 2 | ok = 200, 3 | noContent = 204, 4 | badRequest = 400, 5 | unauthorized = 401, 6 | forbidden = 403, 7 | notFound = 404, 8 | serverError = 500, 9 | } 10 | export type HttpMethod = 'GET' // | 'POST' | 'PUT' | 'DELETE'; 11 | 12 | export type HttpRequest = { 13 | url: string 14 | method: HttpMethod 15 | headers?: Record 16 | body?: unknown // valor que é determinado em tempo de execução 17 | } 18 | export type HttpResponse = { 19 | statusCode: number 20 | body?: unknown 21 | } 22 | 23 | export interface HttpClientProtocol { 24 | request: (data: HttpRequest) => Promise 25 | } 26 | -------------------------------------------------------------------------------- /src/main/factories/use-cases/remote-get-repository-details-factory.ts: -------------------------------------------------------------------------------- 1 | import { RemoteGetRepositoryDetails } from "@/data/use-cases/RemoteGetRepositoryDetails"; 2 | import { GetRepositoryDetails } from "@/domain/use-cases/GetRepositoryDetails"; 3 | import { makeApiUrl } from "../http/api-url-factory"; 4 | import { makeFetchHttpClientAdapter } from "../http/fetch-http-client-adapter-factory"; 5 | // import { makeAxiosHttpClientAdapter } from '../http/axios-http-client-adapter-factory' 6 | 7 | // You can change to use Axios or Fetch, it is up to you. 8 | export const makeRemoteGetRepositoryDetails = (): GetRepositoryDetails => { 9 | return new RemoteGetRepositoryDetails( 10 | makeApiUrl("repos"), 11 | makeFetchHttpClientAdapter() 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from "vite"; 2 | import viteConfig from "./vite.config"; 3 | import { configDefaults } from "vitest/config"; 4 | 5 | export default mergeConfig( 6 | viteConfig, 7 | defineConfig({ 8 | test: { 9 | environment: "jsdom", 10 | setupFiles: "./vitest-setup", 11 | globals: true, 12 | exclude: [...configDefaults.exclude], 13 | coverage: { 14 | provider: "istanbul", 15 | enabled: true, 16 | 17 | include: [ 18 | "!**/styles.{ts,tsx}", 19 | "!**.d.ts", 20 | "!src/main", 21 | "!main.tsx", 22 | "src/{data,presentation,domain,infra,validation}", 23 | ], 24 | }, 25 | }, 26 | }) 27 | ); 28 | -------------------------------------------------------------------------------- /src/data/use-cases/CacheLoadRepositories.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from "@/domain/models/Repository"; 2 | import { LoadRepositories } from "@/domain/use-cases/LoadRepositories"; 3 | import { GetCacheClientProtocol } from "../protocols/cache/GetCacheClientProtocol"; 4 | 5 | export class CacheLoadRepositories implements LoadRepositories { 6 | constructor( 7 | private readonly cacheKey: string, 8 | private readonly cacheClient: GetCacheClientProtocol 9 | ) {} 10 | 11 | load(): Repository[] { 12 | const cacheResult = this.cacheClient.getItem(this.cacheKey); 13 | 14 | return this.parse(cacheResult.content); 15 | } 16 | 17 | private parse(cacheContent: string): Repository[] { 18 | return JSON.parse(cacheContent) as Repository[]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/presentation/styles/global.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components' 2 | 3 | import githubBackground from '../assets/github-background.svg' 4 | 5 | export default createGlobalStyle` 6 | *{ 7 | margin:0; 8 | padding:0; 9 | outline:0; 10 | box-sizing: border-box; 11 | } 12 | body{ 13 | background: url(${githubBackground}) no-repeat 70% top; 14 | 15 | background-color:#f0f0f5; 16 | -webkit-font-smoothing: antialiased; 17 | } 18 | 19 | body,input, button{ 20 | font:16px Roboto, sans-serif; 21 | } 22 | 23 | #root{ 24 | max-width:960px; 25 | margin:0 auto; 26 | padding: 40px 20px; 27 | } 28 | button{ 29 | cursor:pointer; 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /src/main/factories/views/home-factory.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Home from "@/presentation/pages/Home"; 3 | import { makeRemoteSearchRepository } from "../use-cases/remote-search-repository-factory"; 4 | import { RequiredFieldValidator } from "@/validation/RequiredFieldValidator"; 5 | import { makeCacheLoadRepositories } from "../use-cases/cache-load-repositories-factory"; 6 | import { makeCacheSaveRepositories } from "../use-cases/cache-save-repositories-factory"; 7 | 8 | export const makeHome: React.FC = () => { 9 | return ( 10 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/presentation/components/Footer/footer.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import { describe, it, expect } from "vitest"; 3 | 4 | import Footer from "."; 5 | 6 | const makeSut = (): void => { 7 | render(