-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
730 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import DashboardSkeleton from "../../ui/skeletons"; | ||
|
||
export default function Loading() { | ||
return <DashboardSkeleton/> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import CardWrapper from '@/app/ui/dashboard/cards'; | ||
import RevenueChart from '@/app/ui/dashboard/revenue-chart'; | ||
import LatestInvoices from '@/app/ui/dashboard/latest-invoices'; | ||
import { lusitana } from '@/app/ui/fonts'; | ||
import { fetchCardData } from '../../lib/data'; | ||
import { Suspense } from 'react'; | ||
import { CardsSkeleton, LatestInvoicesSkeleton, RevenueChartSkeleton } from '@/app/ui/skeletons'; | ||
|
||
export default async function Page() { | ||
|
||
const { totalPaidInvoices, numberOfCustomers, totalPendingInvoices, numberOfInvoices } = await fetchCardData() | ||
return ( | ||
<main> | ||
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}> | ||
Dashboard | ||
</h1> | ||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4"> | ||
<Suspense fallback={<CardsSkeleton />}> | ||
<CardWrapper /> | ||
</Suspense> | ||
</div> | ||
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8"> | ||
<Suspense fallback={<RevenueChartSkeleton />}> | ||
<RevenueChart /> | ||
</Suspense> | ||
<Suspense fallback={<LatestInvoicesSkeleton />}> | ||
<LatestInvoices /> | ||
</Suspense> | ||
</div> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import Link from 'next/link'; | ||
import { FaceFrownIcon } from '@heroicons/react/24/outline'; | ||
|
||
export default function NotFound() { | ||
return ( | ||
<main className="flex h-full flex-col items-center justify-center gap-2"> | ||
<FaceFrownIcon className="w-10 text-gray-400" /> | ||
<h2 className="text-xl font-semibold">404 Not Found</h2> | ||
<p>Recurso nao encontrado ou nao existe !</p> | ||
<Link | ||
href="/dashboard/invoices" | ||
className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400" | ||
> | ||
Voltar | ||
</Link> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import Form from '@/app/ui/invoices/edit-form'; | ||
import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'; | ||
import { fetchCustomers, fetchInvoiceById } from '@/app/lib/data'; | ||
import { notFound } from 'next/navigation'; | ||
|
||
export default async function Page({ params }: { params: { id: string } }) { | ||
const id = params.id; | ||
|
||
const [invoice, customers] = await Promise.all([ | ||
fetchInvoiceById(id), | ||
fetchCustomers(), | ||
]); | ||
|
||
if (!invoice) { //se nao existir fatura com o id existente na base de dados | ||
return notFound(); | ||
} | ||
|
||
return ( | ||
<main> | ||
<Breadcrumbs | ||
breadcrumbs={[ | ||
{ label: 'Invoices', href: '/dashboard/invoices' }, | ||
{ | ||
label: 'Edit Invoice', | ||
href: `/dashboard/invoices/${id}/edit`, | ||
active: true, | ||
}, | ||
]} | ||
/> | ||
<Form invoice={invoice} customers={customers} /> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import Form from '@/app/ui/invoices/create-form'; | ||
import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'; | ||
import { fetchCustomers } from '@/app/lib/data'; | ||
|
||
export default async function Page() { | ||
const customers = await fetchCustomers(); | ||
|
||
return ( | ||
<main> | ||
<Breadcrumbs | ||
breadcrumbs={[ | ||
{ label: 'Invoices', href: '/dashboard/invoices' }, | ||
{ | ||
label: 'Create Invoice', | ||
href: '/dashboard/invoices/create', | ||
active: true, | ||
}, | ||
]} | ||
/> | ||
<Form customers={customers} /> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
'use client'; //error.tsxprecisa ser um componente cliente. | ||
|
||
import { useEffect } from 'react'; | ||
|
||
export default function Error({ | ||
error, | ||
reset, | ||
}: { | ||
error: Error & { digest?: string }; | ||
reset: () => void; | ||
}) { | ||
useEffect(() => { | ||
// Optionally log the error to an error reporting service | ||
console.error(error); | ||
}, [error]); | ||
|
||
return ( | ||
<main className="flex h-full flex-col items-center justify-center"> | ||
<h2 className="text-center">Something went wrong!</h2> | ||
<button | ||
className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400" | ||
onClick={ | ||
() => reset() //reset: Esta é uma função para redefinir o limite de erro. Quando executada, a função tentará renderizar novamente o segmento da rota. | ||
} | ||
> | ||
Try again | ||
</button> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,38 @@ | ||
export default function Page() { | ||
return <p>Invoices Page</p>; | ||
import Pagination from '@/app/ui/invoices/pagination'; | ||
import Search from '@/app/ui/search'; | ||
import Table from '@/app/ui/invoices/table'; | ||
import { CreateInvoice } from '@/app/ui/invoices/buttons'; | ||
import { lusitana } from '@/app/ui/fonts'; | ||
import { InvoicesTableSkeleton } from '@/app/ui/skeletons'; | ||
import { Suspense } from 'react'; | ||
import { fetchInvoicesPages } from '@/app/lib/data'; | ||
|
||
export default async function Page({ searchParams, }: { | ||
searchParams?: { | ||
query?: string, | ||
page?: string | ||
} | ||
}) { | ||
const query = searchParams?.query || ''; | ||
const currentPage = Number(searchParams?.page) || 1; | ||
|
||
const totalPages = await fetchInvoicesPages(query); | ||
|
||
return ( | ||
<div className="w-full"> | ||
<div className="flex w-full items-center justify-between"> | ||
<h1 className={`${lusitana.className} text-2xl`}>Invoices</h1> | ||
</div> | ||
<div className="mt-4 flex items-center justify-between gap-2 md:mt-8"> | ||
<Search placeholder="Pesquisar Faturas" /> | ||
<CreateInvoice /> | ||
</div> | ||
<Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}> | ||
<Table query={query} currentPage={currentPage} /> | ||
</Suspense> | ||
<div className="mt-5 flex w-full justify-center"> | ||
<Pagination totalPages={totalPages} /> | ||
</div> | ||
</div> | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
'use server'; | ||
|
||
import { sql } from '@vercel/postgres'; | ||
import { z } from 'zod'; | ||
import { revalidatePath } from 'next/cache'; | ||
import { redirect } from 'next/navigation'; | ||
import { signIn } from '@/auth'; | ||
import { AuthError } from 'next-auth'; | ||
|
||
export async function authenticate( | ||
prevState: string | undefined, | ||
formData: FormData, | ||
) { | ||
try { | ||
await signIn('credentials', formData); | ||
} catch (error) { | ||
if (error instanceof AuthError) { | ||
switch (error.type) { | ||
case 'CredentialsSignin': | ||
return 'Invalid credentials.'; | ||
default: | ||
return 'Something went wrong.'; | ||
} | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
//criando schema object | ||
const FormSchema = z.object({ | ||
id: z.string(), | ||
customerId: z.string({ | ||
invalid_type_error : 'Porfavor selecione um cliente.' | ||
}), | ||
amount: z.coerce.number().gt(0, { message : 'Dgite um valor maior que 0'}), | ||
status: z.enum(['pending', 'paid'],{ | ||
invalid_type_error: 'Porfavor selecione um status.' | ||
|
||
}), | ||
date: z.string(), | ||
}); | ||
//Tipagem do State | ||
export type State = { | ||
errors?: { | ||
customerId?: string[]; | ||
amount?: string[]; | ||
status?: string[]; | ||
}; | ||
message?: string | null; | ||
}; | ||
const CreateInvoice = FormSchema.omit({ id: true, date: true }); | ||
|
||
export async function createInvoice(prevState: State, formData: FormData) { | ||
const validatedFields = CreateInvoice.safeParse({ //safeParse()retornará um objeto contendo um campo successou error. | ||
customerId: formData.get('customerId'), | ||
amount: formData.get('amount'), | ||
status: formData.get('status'), | ||
}); | ||
|
||
//verifcando se foram validados corretamente | ||
if (!validatedFields.success) { | ||
return { | ||
errors: validatedFields.error.flatten().fieldErrors, | ||
message: 'Missing Fields. Failed to Create Invoice.', | ||
}; | ||
} | ||
//preparando para mandar os dados para o banco de dados | ||
const { customerId, amount, status } = validatedFields.data; | ||
const amountInCents = amount * 100; | ||
const date = new Date().toISOString().split('T')[0]; | ||
|
||
try { | ||
//inserindo os novos dados no banco de dados | ||
await sql` | ||
INSERT INTO invoices (customer_id, amount, status, date) | ||
VALUES (${customerId}, ${amountInCents}, ${status}, ${date})`; | ||
} catch(error) { | ||
console.error('Database Error:', error); | ||
throw new Error('Failed to create invoice.'); | ||
|
||
} | ||
/* | ||
Assim que o banco de dados for atualizado, o /dashboard/invoices | ||
caminho será revalidado e novos dados serão obtidos do servidor. | ||
*/ | ||
revalidatePath('/dashboard/invoices'); | ||
redirect('/dashboard/invoices'); | ||
} | ||
|
||
|
||
// Use Zod to update the expected types | ||
const UpdateInvoice = FormSchema.omit({ id: true, date: true }); | ||
|
||
export async function updateInvoice(id: string, formData: FormData) { | ||
const { customerId, amount, status } = UpdateInvoice.parse({ | ||
customerId: formData.get('customerId'), | ||
amount: formData.get('amount'), | ||
status: formData.get('status'), | ||
}); | ||
|
||
const amountInCents = amount * 100; | ||
try { | ||
await sql` | ||
UPDATE invoices | ||
SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status} | ||
WHERE id = ${id} | ||
`; | ||
} catch (error) { | ||
console.error('Database Error:', error); | ||
throw new Error('Failed to update invoice.'); | ||
} | ||
revalidatePath('/dashboard/invoices'); | ||
redirect('/dashboard/invoices'); | ||
} | ||
export async function deleteInvoice(id: string) { | ||
try { | ||
await sql`DELETE FROM invoices WHERE id = ${id}`; | ||
revalidatePath('/dashboard/invoices'); | ||
return { message: 'Deleted Invoice.' }; | ||
} catch (error) { | ||
return { message: 'Database Error: Failed to Delete Invoice.' }; | ||
} | ||
} |
Oops, something went wrong.