Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to latest + refactoring. #204

Closed
wants to merge 15 commits into from
6 changes: 0 additions & 6 deletions dashboard/15-final/app/api/auth/[...nextauth]/route.ts

This file was deleted.

40 changes: 21 additions & 19 deletions dashboard/15-final/app/dashboard/(overview)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
import Card from '@/app/ui/dashboard/card';
import { Suspense } from 'react';
import {
CardCollected,
CardPending,
CardTotalInvoices,
CardCustomers,
} from '@/app/ui/dashboard/card';
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 '@/app/lib/data';
import { Suspense } from 'react';
import {
RevenueChartSkeleton,
LatestInvoicesSkeleton,
CardSkeleton,
} from '@/app/ui/dashboard/skeletons';

export default async function Page() {
const {
numberOfInvoices,
numberOfCustomers,
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData();

export default function Page() {
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">
<Card title="Collected" value={totalPaidInvoices} type="collected" />
<Card title="Pending" value={totalPendingInvoices} type="pending" />
<Card title="Total Invoices" value={numberOfInvoices} type="invoices" />
<Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/>
<Suspense fallback={<CardSkeleton />}>
<CardCollected />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<CardPending />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<CardTotalInvoices />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<CardCustomers />
</Suspense>
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
<Suspense fallback={<RevenueChartSkeleton />}>
Expand Down
1 change: 0 additions & 1 deletion dashboard/15-final/app/dashboard/customers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export default async function Page({
};
}) {
const query = searchParams?.query || '';

const customers = await fetchFilteredCustomers(query);

return (
Expand Down
1 change: 1 addition & 0 deletions dashboard/15-final/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const metadata: Metadata = {
},
description: 'The official Next.js Learn Dashboard built with App Router.',
};

export default function RootLayout({
children,
}: {
Expand Down
79 changes: 64 additions & 15 deletions dashboard/15-final/app/lib/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,56 @@ export async function fetchCardData() {
}
}

export async function fetchInvoices() {
try {
const data = await sql`SELECT COUNT(*) FROM invoices`;
const numberOfInvoices = Number(data.rows[0].count ?? '0');

return {
numberOfInvoices,
};
} catch (error) {
console.error('Database Error:', error);
throw new Error('Failed to card data.');
}
}

export async function fetchCustomersCount() {
try {
const data = await sql`SELECT COUNT(*) FROM customers`;
const numberOfCustomers = Number(data.rows[0].count ?? '0');

return {
numberOfCustomers,
};
} catch (error) {
console.error('Database Error:', error);
throw new Error('Failed to card data.');
}
}

export async function fetchInvoiceStatus() {
try {
const data = await sql`SELECT
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending"
FROM invoices`;

const totalPaidInvoices = formatCurrency(data.rows[0].paid ?? '0');
const totalPendingInvoices = formatCurrency(data.rows[0].pending ?? '0');

return {
totalPaidInvoices,
totalPendingInvoices,
};
} catch (error) {
console.error('Database Error:', error);
throw new Error('Failed to card data.');
}
}

const ITEMS_PER_PAGE = 6;

export async function fetchFilteredInvoices(
query: string,
currentPage: number,
Expand Down Expand Up @@ -213,21 +262,21 @@ export async function fetchCustomersTable() {
export async function fetchFilteredCustomers(query: string) {
try {
const data = await sql<CustomersTable>`
SELECT
customers.id,
customers.name,
customers.email,
customers.image_url,
COUNT(invoices.id) AS total_invoices,
SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending,
SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid
FROM customers
LEFT JOIN invoices ON customers.id = invoices.customer_id
WHERE
customers.name ILIKE ${`%${query}%`} OR
customers.email ILIKE ${`%${query}%`}
GROUP BY customers.id, customers.name, customers.email, customers.image_url
ORDER BY customers.name ASC
SELECT
customers.id,
customers.name,
customers.email,
customers.image_url,
COUNT(invoices.id) AS total_invoices,
SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending,
SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid
FROM customers
LEFT JOIN invoices ON customers.id = invoices.customer_id
WHERE
customers.name ILIKE ${`%${query}%`} OR
customers.email ILIKE ${`%${query}%`}
GROUP BY customers.id, customers.name, customers.email, customers.image_url
ORDER BY customers.name ASC
`;

const customers = data.rows.map((customer) => ({
Expand Down
5 changes: 0 additions & 5 deletions dashboard/15-final/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import LoginForm from '@/app/ui/login-form';
import { authOptions } from '@/auth';
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';

export default async function Page() {
const session = await getServerSession(authOptions);
if (session) redirect('/dashboard');
return (
<main>
<LoginForm />
Expand Down
1 change: 1 addition & 0 deletions dashboard/15-final/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ArrowRightIcon } from '@heroicons/react/24/outline';
import { lusitana } from '@/app/ui/fonts';
import Image from 'next/image';
import Link from 'next/link';

export default function Page() {
return (
<main className="flex min-h-screen flex-col p-6">
Expand Down
38 changes: 0 additions & 38 deletions dashboard/15-final/app/ui/dashboard/acme-logo.tsx

This file was deleted.

55 changes: 41 additions & 14 deletions dashboard/15-final/app/ui/dashboard/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,63 @@ import {
InboxIcon,
} from '@heroicons/react/24/outline';
import { lusitana } from '@/app/ui/fonts';
import {
fetchCustomersCount,
fetchInvoices,
fetchInvoiceStatus,
} from '@/app/lib/data';

export async function CardCollected() {
const { numberOfInvoices } = await fetchInvoices();

return <Card title="Collected">{numberOfInvoices}</Card>;
}

export async function CardPending() {
const { totalPendingInvoices } = await fetchInvoiceStatus();

return <Card title="Pending">{totalPendingInvoices}</Card>;
}

const iconMap = {
collected: BanknotesIcon,
customers: UserGroupIcon,
pending: ClockIcon,
invoices: InboxIcon,
};
export async function CardTotalInvoices() {
const { totalPaidInvoices } = await fetchInvoiceStatus();

export default function Card({
return <Card title="Invoices">{totalPaidInvoices}</Card>;
}

export async function CardCustomers() {
const { numberOfCustomers } = await fetchCustomersCount();

return <Card title="Customers">{numberOfCustomers}</Card>;
}

function Card({
title,
value,
type,
children,
}: {
title: string;
value: number | string;
type: 'invoices' | 'customers' | 'pending' | 'collected';
children: React.ReactNode;
}) {
const Icon = iconMap[type];
const icons = {
collected: BanknotesIcon,
customers: UserGroupIcon,
pending: ClockIcon,
invoices: InboxIcon,
};

const Icon = icons[title.toLowerCase()];

return (
<div className="rounded-xl bg-gray-50 p-2 shadow-sm">
<div className="flex p-4">
{Icon ? <Icon className="h-5 w-5 text-gray-700" /> : null}
<Icon className="h-5 w-5 text-gray-700" />
<h3 className="ml-2 text-sm font-medium">{title}</h3>
</div>
<p
className={`${lusitana.className}
truncate rounded-xl bg-white px-4 py-8 text-center text-2xl`}
>
{value}
{children}
</p>
</div>
);
Expand Down
15 changes: 0 additions & 15 deletions dashboard/15-final/app/ui/dashboard/log-out-button.tsx

This file was deleted.

17 changes: 14 additions & 3 deletions dashboard/15-final/app/ui/dashboard/sidenav.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import LogOutButton from './log-out-button';
import AcmeLogo from '../acme-logo';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';

export default function SideNav() {
return (
Expand All @@ -17,7 +18,17 @@ export default function SideNav() {
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<LogOutButton />
<form
action={async () => {
'use server';
await signOut();
}}
>
<button className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
</button>
</form>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion dashboard/15-final/app/ui/invoices/create-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
>
Cancel
</Link>
<Button type="submit">Create Invoice</Button>
<Button>Create Invoice</Button>
</div>
</form>
);
Expand Down
2 changes: 1 addition & 1 deletion dashboard/15-final/app/ui/invoices/edit-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export default function EditInvoiceForm({
>
Cancel
</Link>
<Button type="submit">Edit Invoice</Button>
<Button>Edit Invoice</Button>
</div>
</form>
);
Expand Down
Loading
Loading