Passo a passo - orivaldosantana/mvp_banco_talentos GitHub Wiki
Mostrando como programar cada parte do sistema.
Este componente recebe dois parâmetros, um título e um componente filho.
export default function ComponentX({ title, children }) {
return (
<div>
<h2> {title} </h2>
<div>
{children}
</div>
</div>
)
}Escolher o local na ramificação de pastas dentro da pasta app. As pastas neste diretório servem de rotas para o sistema. Por exemplo, a pasta app/admin/relatorio implementa a rota admin/relatorio. O nome do arquivo JavaScript deve ser page.tsx.
O código a seguir apresenta um exemplo de menu lateral com três botões laterais construídos com o componente Link do Next.js. O estilo e a animação de cada botão estão no arquivo CSS em /app/page.module.css.
import Link from "next/link"
import styles from "../app/page.module.css"
export default function Navbar() {
return (
<div className={styles.menu}>
<nav>
<Link href="/admin/colaborador">Colaborador</Link>
<Link href="/admin/projeto">Projeto</Link>
<Link href="/admin/relatorio">Relatório</Link>
</nav>
</div>
)
}O código a seguir está implementado em app/admin/layout.tsx e define layout com um menu lateral, um cabeçalho e uma página. Neste caso, as páginas que forem construídas no ramo da pasta app/admin/ terão a estrutura deste layout.
import Header from "@/ui/header"
import Navbar from "@/ui/navbar"
import { Box } from "@mui/material"
export default function PagesLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<div>
<div>
<Header />
</div>
<Box sx={{ flexDirection: "row", display: "flex" }}>
<Navbar />
{children}
</Box>
</div>
)
}Criar a estrutura inicial do Prisma:
npx prisma init
O comando acima cria o arquivo schema.prisma e outros. O código a seguir apresentar um exemplo de schema.prisma para configurar o banco de dados para sqlite e definir uma tabela no banco de dados (esquema) para usuários (User).
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model User {
id Int @default(autoincrement()) @id
name String
email String
}Cria a estrutura de arquivo SQLs para a construção ou atualização do banco de dados.
npx prisma migrate dev
Para acessar os banco de dados usando o Prisma Studio:
npx prisma studio
Mais detalhes acessar o link Manual Banco de Dados.
Esta seção apresenta um código para cadastrar uma informação no banco de dados utilizando o conceito de Server Action do Next.js 14. O código a seguir obtêm os dados de um formulário contendo os campos nome e e-mail para gravar no banco de dados:
'use server'
import { PrismaClient } from '@prisma/client'
export const addUser = async (formData: FormData) => {
let name = formData.get('nome')
let email = formData.get('email')
const prisma = new PrismaClient()
try {
await prisma.user.create({
data: {
name: name.toString(),
email: email.toString(),
}
})
} catch (error) {
console.error(error)
} finally {
await prisma.$disconnect()
}
}O código acima pode ser organizado em um arquivo action.ts na pasta app\lib\.
No formulário é necessário adicionar o atributo action na tag form como a seguir:
<form action={addUser}>A função addUser foi declarada no código acima. Porém, para recuperar alguma mensagem vinda do backend será necessário incrementar esse código com a utilização do Hook useFormState.
Exemplo do código completo do componente que utilizar o formulário:
import { addUser } from "@/app/lib/action"
import TextField from "@mui/material/TextField"
import Box from "@mui/material/Box"
import Button from "@mui/material/Button" // Add this line to import the Button component
import { Typography } from "@mui/material"
import { useFormState } from 'react-dom'
export default function FormColaborador({ title }) {
const [state, formAddUserAction] = useFormState(addUser, undefined)
return (
<Box
sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<Typography variant="h4" sx={{ marginBottom: 2 }}>
{title}
</Typography>
<form action={formAddUserAction}>
<TextField
name="nome"
label="Nome"
fullWidth
required
variant="filled"
sx={{ marginBottom: 2 }}
/>
<TextField
name="email"
label="Email"
fullWidth
required
variant="filled"
sx={{ marginBottom: 2 }}
/>
<Button variant="contained" type="submit" fullWidth>
Cadastrar Colaborador
</Button>
</form>
</Box>
)
}A função de cadastro no banco de dados, addUser, deve ser adicionada como parâmetro na inicialização do Hook useFormState, assim como variável initialState que armazena uma mensagem inicial que pode ser enviada para addUser. O retorno do Hook useFormState é um vetor com a variável, mensagem de retorno depois da execução de addUser, e uma função ,action, usada para chamar uma ação do servidor por meio da formAddUserAction.
Para realizar o Hook useFormState é necessário importar:
import { useFormState } from 'react-dom'No código do formulário no atributo action usar o formAddUserAction definido na inicialização do useFormState:
<form action={formAddUserAction}>Criar um arquivo separado para organizar as funções relativas à recuperação dos dados no banco de dados. Sugestão para o caminho e nome do arquivo, src/app/lib/data.ts:
"use server";
import { PrismaClient } from "@prisma/client";
export const getUserData = async () => {
const prisma = new PrismaClient();
let users;
try {
users = await prisma.user.findMany();
console.log(users);
} catch (error) {
console.error(error);
} finally {
await prisma.$disconnect();
return users;
}
};Para mostrar os dados em uma página, podemos realizar uma chamada da função criada acima, getUserData, em uma página ou um componente do frontend, exemplo:
import { getUserData } from "@/app/lib/data";
export default async function RelatorioPage() {
const users = await getUserData();
return (
<>
{users !== undefined && (
<div>
{users.map((user) => (
<div key={user.name}>{user.name}</div>
))}
</div>
)}
</>
);
}Vamos ajustar o código inicial para ficar compatível com o Hook useFormState. Na função de que realiza o cadastro vamos adicionar mais um parâmetro na definição da função, o prevState do tipo {message: string}:
'use server'
import { PrismaClient } from '@prisma/client'
export const addUser = async (
prevState: {
message: string
},
formData: FormData
) => {
let name = formData.get('nome')
let email = formData.get('email')
let returnMessage
const prisma = new PrismaClient()
try {
await prisma.user.create({
data: {
name: name.toString(),
email: email.toString(),
}
})
returnMessage = { message: 'Cadastro realizado com sucesso!', type: 'success' }
} catch (error) {
returnMessage = { message: 'Erro ao realizar o cadastro!', type: 'error' }
console.error(error)
} finally {
await prisma.$disconnect()
return returnMessage
}
}No componente que realiza a chamada para o cadastro, vamos adicionar a inicialização do Hook useFormState:
const [state, formAddUserAction] = useFormState(addUser, initialState)A variável initialState deve ser definida no início do código do componente, logo depois das importações:
const initialState = {
message: '',
type: ''
}Mostrar um exemplo do código completo para o componente relacionado com o cadastro:
'use client'
import { addUser } from '@/app/lib/action'
import TextField from '@mui/material/TextField'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button' // Add this line to import the Button component
import { IconButton, Typography, Alert, Collapse } from '@mui/material'
import { Close } from '@mui/icons-material'
import { useFormState } from 'react-dom'
import { useEffect, useState } from 'react' // Add this line to import the useEffect hook
const initialState = {
message: ''
}
export default function FormColaborador({ title }) {
const [state, formAddUserAction] = useFormState(addUser, initialState)
const [openAlert, setOpenAlert] = useState(false)
useEffect(() => {
if (state?.message) setOpenAlert(true)
}, [state])
return (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }} >
<Typography variant="h4" sx={{ marginBottom: 2 }}>
{title}
</Typography>
<form action={formAddUserAction}>
<TextField name="nome" label="Nome" fullWidth required variant="filled" sx={{ marginBottom: 2 }} >
<TextField name="email" label="Email" fullWidth required variant="filled" sx={{ marginBottom: 2 }} />
<Button variant="contained" type="submit" fullWidth sx={{ marginTop: 2, marginBottom: 4 }}>
Cadastrar Colaborador
</Button>
<Collapse in={openAlert}>
<Alert
severity={state?.type}
action={
<IconButton onClick={() => setOpenAlert(false)}>
<Close />
</IconButton>
}
>
{state?.message}
</Alert>
</Collapse>
</form>
</Box>
)
}Depois de executar alguma ação e desejar redirecionar a aplicação para outra página, podemos usar o Next Router:
Inicialização:
import { useRouter } from 'next/navigation'
...
function NomeDoComponente() {
...
const router = useRouter()
...Utilização:
...
router.push('/') Importando a biblioteca:
import bcrypt from 'bcryptjs'
...Utilização:
...
const salt = await bcrypt.genSalt(10)
const hashedPassword = await bcrypt.hash(password, salt)
...- Use o hook
useFormStatecrie um componente para o formulário de login com os campos usuário e senha. - Adicione no arquivo
lib/action.tsa função para tratar os dados de login. Use a funçãosignIncom parâmetros'credentials', { email, password }, exemplo de código:
export async function handleCredentialLogin(
prevState: {
message: string
},
formData: FormData
) {
let email = formData.get('email')
let password = formData.get('password')
try {
const user = await signIn('credentials', { email, password })
return { message: 'Login realizado com sucesso!', type: 'success' }
} catch (error) {
if (error.type?.includes('CredentialsSignin')) {
return { message: 'E-mail ou senha inválidos!', type: 'error' }
}
throw error
}
}- No arquivo
lib/auth.ts, importe oproviderpara login com credenciais,credentials, e adicionar no vetor de configurações deprovidersdoNextAuth. A lógica aqui é simples, se encontrar o usuário do banco de dados e se a senha estiver correta, retorna este usuário, se ocorrer algum erro, retorna nulo:
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
...
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
try {
const user = await loginCredential(credentials)
return user
} catch (error) {
return null
}
},
}),
],
})- Implemente a lógica de login na função
loginCredential. Sugestão de lógica, buscar o usuário no banco de dados, se não encontrar, lançe um erro, compare a senha informada com a senha do banco criptografada, lançe um erro se forem diferentes, se ocorrer algum erro, lançe. Ao final, retorne o usuário:
const loginCredential = async (credentials) => {
const prisma = new PrismaClient()
let user = null
try {
user = await prisma.user.findFirst({
where: {
email: credentials.email
}
})
await prisma.$disconnect()
if (!user) {
throw new Error('Credenciais inválidas.')
}
const match = await bcrypt.compare(credentials.password, user.password)
if (!match) {
user = null
throw new Error('Credenciais inválidas..')
}
} catch (error) {
throw new Error('Falha ao realizar o login.')
} finally {
await prisma.$disconnect()
return user
}
}Acesse a documentação oficial aqui.
- Configure o Github para autorizar a autentição e obter
Client IDeClient Secret. - Guarde estes valores no arquivo de variáveis de ambiente,
.local.env:
AUTH_GITHUB_ID={CLIENT_ID}
AUTH_GITHUB_SECRET={CLIENT_SECRET}- No arquivo
lib/auth.ts, importe oproviderpara login com Github (OAuth),GitHub, e adicione no vetor de configurações deprovidersdoNextAuth:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
})- Adicione ao seu arquivo
api/auth/[...nextauth]/route.ts, os handlers que o NextAuth retorna, para que o Auth.js possa ser executado em qualquer solicitação recebida.
import { handlers } from "@/auth"
export const { GET, POST } = handlers- Use o hook
useFormStatee crie um componente para o formulário de login com os campos usuário e senha. - Adicione no arquivo
lib/action.tsa funçãohandleGitHubLoginpara tratar os dados de login, exemplo de código:
export const handleGitHubLogin = async () => {
'use server'
await signIn('github')
}- No arquivo
lib/auth.tsconfigurar oClientId,ClientSecret, ecallback:
callbacks: {
async signIn({ user, account, profile }) {
if (account.provider === 'github') {
// faz alguma coisa se for login via github
...
}
return true
}
}- Cadastrando o usuário quando realizar login via GitHub:
callbacks: {
async signIn({ user, account, profile }) {
if (account.provider === 'github') {
const prisma = new PrismaClient()
try {
let userBD = await prisma.user.findFirst({
where: {
email: user.email.toString()
}
})
if (userBD) {
console.log('E-mail já cadastrado!')
} else {
await prisma.user.create({
data: {
name: user.name.toString(),
email: user.email.toString(),
}
})
}
} catch (error) {
console.log('Erro ao realizar o cadastro!')
} finally {
await prisma.$disconnect()
return true
}
}
return true
}
}Acesse a documentação oficial aqui.