├── .eslintrc ├── .gitignore ├── README.md ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── src ├── backend │ ├── config.ts │ └── db │ │ └── ColecaoCliente.ts ├── components │ ├── Botao.tsx │ ├── Entrada.tsx │ ├── Formulario.tsx │ ├── Icones.tsx │ ├── Layout.tsx │ ├── Tabela.tsx │ └── Titulo.tsx ├── core │ ├── Cliente.ts │ └── ClienteRepositorio.ts ├── hooks │ ├── useClientes.ts │ └── useTabelaOuForm.ts ├── pages │ ├── _app.js │ ├── api │ │ └── hello.js │ └── index.tsx └── styles │ └── globals.css ├── tailwind.config.js └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-crud", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "firebase": "^8.8.0", 13 | "next": "11.0.1", 14 | "react": "17.0.2", 15 | "react-dom": "17.0.2" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^17.0.14", 19 | "autoprefixer": "^10.3.1", 20 | "eslint": "7.31.0", 21 | "eslint-config-next": "11.0.1", 22 | "postcss": "^8.3.6", 23 | "tailwindcss": "^2.2.6", 24 | "typescript": "^4.3.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cod3rcursos/next-crud/d6043ae1ba10f083679726189e8018743fd90331/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/backend/config.ts: -------------------------------------------------------------------------------- 1 | import firebase from "firebase"; 2 | import 'firebase/firestore' 3 | 4 | if (!firebase.apps.length) { 5 | firebase.initializeApp({ 6 | apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, 7 | authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, 8 | projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, 9 | }) 10 | } 11 | 12 | export default firebase -------------------------------------------------------------------------------- /src/backend/db/ColecaoCliente.ts: -------------------------------------------------------------------------------- 1 | import firebase from "../config"; 2 | import Cliente from "../../core/Cliente"; 3 | import ClienteRepositorio from "../../core/ClienteRepositorio"; 4 | 5 | export default class ColecaoCliente implements ClienteRepositorio { 6 | 7 | #conversor = { 8 | toFirestore(cliente: Cliente) { 9 | return { 10 | nome: cliente.nome, 11 | idade: cliente.idade, 12 | } 13 | }, 14 | fromFirestore(snapshot: firebase.firestore.QueryDocumentSnapshot, options: firebase.firestore.SnapshotOptions): Cliente { 15 | const dados = snapshot.data(options) 16 | return new Cliente(dados.nome, dados.idade, snapshot.id) 17 | } 18 | } 19 | 20 | async salvar(cliente: Cliente): Promise { 21 | if(cliente?.id) { 22 | await this.colecao().doc(cliente.id).set(cliente) 23 | return cliente 24 | } else { 25 | const docRef = await this.colecao().add(cliente) 26 | const doc = await docRef.get() 27 | return doc.data() 28 | } 29 | } 30 | 31 | async excluir(cliente: Cliente): Promise { 32 | return this.colecao().doc(cliente.id).delete() 33 | } 34 | 35 | async obterTodos(): Promise { 36 | const query = await this.colecao().get() 37 | return query.docs.map(doc => doc.data()) ?? [] 38 | } 39 | 40 | private colecao() { 41 | return firebase 42 | .firestore().collection('clientes') 43 | .withConverter(this.#conversor) 44 | } 45 | } -------------------------------------------------------------------------------- /src/components/Botao.tsx: -------------------------------------------------------------------------------- 1 | interface BotaoProps { 2 | cor?: 'green' | 'blue' | 'gray' 3 | className?: string 4 | children: any 5 | onClick?: () => void 6 | } 7 | 8 | export default function Botao(props: BotaoProps) { 9 | const cor = props.cor ?? 'gray' 10 | return ( 11 | 18 | ) 19 | } -------------------------------------------------------------------------------- /src/components/Entrada.tsx: -------------------------------------------------------------------------------- 1 | interface EntradaProps { 2 | tipo?: 'text' | 'number' 3 | texto: string 4 | valor: any 5 | somenteLeitura?: boolean 6 | className?: string 7 | valorMudou?: (valor: any) => void 8 | } 9 | 10 | export default function Entrada(props: EntradaProps) { 11 | return ( 12 |
13 | 16 | props.valorMudou?.(e.target.value)} 21 | className={` 22 | border border-purple-500 rounded-lg 23 | focus:outline-none bg-gray-100 px-4 py-2 24 | ${props.somenteLeitura ? '' : 'focus:bg-white'} 25 | `} 26 | /> 27 |
28 | ) 29 | } -------------------------------------------------------------------------------- /src/components/Formulario.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Cliente from "../core/Cliente"; 3 | import Botao from "./Botao"; 4 | import Entrada from "./Entrada"; 5 | 6 | interface FormularioProps { 7 | cliente: Cliente 8 | clienteMudou?: (cliente: Cliente) => void 9 | cancelado?: () => void 10 | } 11 | 12 | export default function Formulario(props: FormularioProps) { 13 | const id = props.cliente?.id 14 | const [nome, setNome] = useState(props.cliente?.nome ?? '') 15 | const [idade, setIdade] = useState(props.cliente?.idade ?? 0) 16 | 17 | return ( 18 |
19 | {id ? ( 20 | 26 | ) : false} 27 | 33 | 39 |
40 | props.clienteMudou?.(new Cliente(nome, +idade, id))}> 42 | {id ? 'Alterar' : 'Salvar'} 43 | 44 | 45 | Cancelar 46 | 47 |
48 |
49 | ) 50 | } -------------------------------------------------------------------------------- /src/components/Icones.tsx: -------------------------------------------------------------------------------- 1 | export const IconeEdicao = ( 2 | 3 | 4 | 5 | ) 6 | 7 | export const IconeLixo = ( 8 | 9 | 10 | 11 | ) -------------------------------------------------------------------------------- /src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import Titulo from "./Titulo"; 2 | 3 | interface LayoutProps { 4 | titulo: string 5 | children: any 6 | } 7 | 8 | export default function Layout(props: LayoutProps) { 9 | return ( 10 |
14 | {props.titulo} 15 |
16 | {props.children} 17 |
18 |
19 | ) 20 | } -------------------------------------------------------------------------------- /src/components/Tabela.tsx: -------------------------------------------------------------------------------- 1 | import Cliente from "../core/Cliente"; 2 | import { IconeEdicao, IconeLixo } from "./Icones"; 3 | 4 | interface TabelaProps { 5 | clientes: Cliente[] 6 | clienteSelecionado?: (cliente: Cliente) => void 7 | clienteExcluido?: (cliente: Cliente) => void 8 | } 9 | 10 | export default function Tabela(props: TabelaProps) { 11 | 12 | const exibirAcoes = props.clienteExcluido || props.clienteSelecionado 13 | 14 | function renderizarCabecalho() { 15 | return ( 16 | 17 | Código 18 | Nome 19 | Idade 20 | {exibirAcoes ? Ações : false} 21 | 22 | ) 23 | } 24 | 25 | function renderizarDados() { 26 | return props.clientes?.map((cliente, i) => { 27 | return ( 28 | 30 | {cliente.id} 31 | {cliente.nome} 32 | {cliente.idade} 33 | {exibirAcoes ? renderizarAcoes(cliente) : false} 34 | 35 | ) 36 | }) 37 | } 38 | 39 | function renderizarAcoes(cliente: Cliente) { 40 | return ( 41 | 42 | {props.clienteSelecionado ? ( 43 | 50 | ) : false} 51 | {props.clienteExcluido ? ( 52 | 59 | ) : false} 60 | 61 | ) 62 | } 63 | 64 | return ( 65 | 66 | 70 | {renderizarCabecalho()} 71 | 72 | 73 | {renderizarDados()} 74 | 75 |
76 | ) 77 | } -------------------------------------------------------------------------------- /src/components/Titulo.tsx: -------------------------------------------------------------------------------- 1 | export default function Titulo(props) { 2 | return ( 3 |
4 |

5 | {props.children} 6 |

7 |
8 |
9 | ) 10 | } -------------------------------------------------------------------------------- /src/core/Cliente.ts: -------------------------------------------------------------------------------- 1 | export default class Cliente { 2 | #id: string 3 | #nome: string 4 | #idade: number 5 | 6 | constructor(nome: string, idade: number, id: string = null) { 7 | this.#nome = nome 8 | this.#idade = idade 9 | this.#id = id 10 | } 11 | 12 | static vazio() { 13 | return new Cliente('', 0) 14 | } 15 | 16 | get id() { 17 | return this.#id 18 | } 19 | 20 | get nome() { 21 | return this.#nome 22 | } 23 | 24 | get idade() { 25 | return this.#idade 26 | } 27 | } -------------------------------------------------------------------------------- /src/core/ClienteRepositorio.ts: -------------------------------------------------------------------------------- 1 | import Cliente from "./Cliente"; 2 | 3 | export default interface ClienteRepositorio { 4 | salvar(cliente: Cliente): Promise 5 | excluir(cliente: Cliente): Promise 6 | obterTodos(): Promise 7 | } -------------------------------------------------------------------------------- /src/hooks/useClientes.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import ColecaoCliente from "../backend/db/ColecaoCliente" 3 | import Cliente from "../core/Cliente" 4 | import ClienteRepositorio from "../core/ClienteRepositorio" 5 | import useTabelaOuForm from "./useTabelaOuForm" 6 | 7 | export default function useClientes() { 8 | const repo: ClienteRepositorio = new ColecaoCliente() 9 | 10 | const { tabelaVisivel, exibirTabela, exibirFormulario } = useTabelaOuForm() 11 | 12 | const [cliente, setCliente] = useState(Cliente.vazio()) 13 | const [clientes, setClientes] = useState([]) 14 | 15 | useEffect(obterTodos, []) 16 | 17 | function obterTodos() { 18 | repo.obterTodos().then(clientes => { 19 | setClientes(clientes) 20 | exibirTabela() 21 | }) 22 | } 23 | 24 | function selecionarCliente(cliente: Cliente) { 25 | setCliente(cliente) 26 | exibirFormulario() 27 | } 28 | 29 | async function excluirCliente(cliente: Cliente) { 30 | await repo.excluir(cliente) 31 | obterTodos() 32 | } 33 | 34 | function novoCliente() { 35 | setCliente(Cliente.vazio()) 36 | exibirFormulario() 37 | } 38 | 39 | async function salvarCliente(cliente: Cliente) { 40 | await repo.salvar(cliente) 41 | obterTodos() 42 | } 43 | 44 | return { 45 | cliente, 46 | clientes, 47 | novoCliente, 48 | salvarCliente, 49 | excluirCliente, 50 | selecionarCliente, 51 | obterTodos, 52 | tabelaVisivel, 53 | exibirTabela 54 | } 55 | } -------------------------------------------------------------------------------- /src/hooks/useTabelaOuForm.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function useTabelaOuForm() { 4 | const [visivel, setVisivel] = useState<'tabela' | 'form'>('tabela') 5 | 6 | const exibirTabela = () => setVisivel('tabela') 7 | const exibirFormulario = () => setVisivel('form') 8 | 9 | return { 10 | formularioVisivel: visivel === 'form', 11 | tabelaVisivel: visivel === 'tabela', 12 | exibirTabela, 13 | exibirFormulario, 14 | } 15 | } -------------------------------------------------------------------------------- /src/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import 'tailwindcss/tailwind.css' 3 | 4 | function MyApp({ Component, pageProps }) { 5 | return 6 | } 7 | 8 | export default MyApp 9 | -------------------------------------------------------------------------------- /src/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Botao from "../components/Botao"; 2 | import Formulario from "../components/Formulario"; 3 | import Layout from "../components/Layout"; 4 | import Tabela from "../components/Tabela"; 5 | 6 | import useClientes from "../hooks/useClientes"; 7 | 8 | export default function Home() { 9 | 10 | const { 11 | cliente, 12 | clientes, 13 | novoCliente, 14 | salvarCliente, 15 | selecionarCliente, 16 | excluirCliente, 17 | tabelaVisivel, 18 | exibirTabela 19 | } = useClientes() 20 | 21 | return ( 22 |
27 | 28 | {tabelaVisivel ? ( 29 | <> 30 |
31 | 33 | Novo Cliente 34 | 35 |
36 | 40 | 41 | ) : ( 42 | 47 | )} 48 |
49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx}', 5 | './src/components/**/*.{js,ts,jsx,tsx}' 6 | ], 7 | safelist: [ 8 | /^bg-/, 9 | /^to-/, 10 | /^from-/, 11 | ] 12 | }, 13 | darkMode: false, // or 'media' or 'class' 14 | theme: { 15 | extend: {}, 16 | }, 17 | variants: { 18 | extend: {}, 19 | }, 20 | plugins: [], 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | --------------------------------------------------------------------------------