diff --git a/app/dashboard/(overview)/loading.tsx b/app/dashboard/(overview)/loading.tsx new file mode 100644 index 0000000..282320f --- /dev/null +++ b/app/dashboard/(overview)/loading.tsx @@ -0,0 +1,5 @@ +import DashboardSkeleton from "@/app/ui/skeletons"; + +export default function Loading() { + return ; +} \ No newline at end of file diff --git a/app/dashboard/(overview)/page.tsx b/app/dashboard/(overview)/page.tsx new file mode 100644 index 0000000..94cd8da --- /dev/null +++ b/app/dashboard/(overview)/page.tsx @@ -0,0 +1,27 @@ +import { Suspense } from "react"; +import Cards 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 { CardSkeleton, LatestInvoicesSkeleton, RevenueChartSkeleton } from "@/app/ui/skeletons"; + +export default async function Page() { + return ( +
+

Dashboard

+
+ }> + + +
+
+ }> + + + }> + + +
+
+ ); +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx deleted file mode 100644 index 38e4cad..0000000 --- a/app/dashboard/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

Dashboard Page

; -} \ No newline at end of file diff --git a/app/lib/data.ts b/app/lib/data.ts index c25648f..1567256 100644 --- a/app/lib/data.ts +++ b/app/lib/data.ts @@ -1,3 +1,4 @@ +import { unstable_noStore as noStore } from 'next/cache'; import { sql } from '@vercel/postgres'; import { CustomerField, @@ -13,17 +14,18 @@ import { formatCurrency } from './utils'; export async function fetchRevenue() { // Add noStore() here prevent the response from being cached. // This is equivalent to in fetch(..., {cache: 'no-store'}). + noStore(); try { // Artificially delay a reponse for demo purposes. // Don't do this in real life :) - // console.log('Fetching revenue data...'); - // await new Promise((resolve) => setTimeout(resolve, 3000)); + console.log('Fetching revenue data...'); + await new Promise((resolve) => setTimeout(resolve, 3000)); const data = await sql`SELECT * FROM revenue`; - // console.log('Data fetch complete after 3 seconds.'); + console.log('Data fetch complete after 3 seconds.'); return data.rows; } catch (error) { @@ -33,6 +35,8 @@ export async function fetchRevenue() { } export async function fetchLatestInvoices() { + noStore(); + try { const data = await sql` SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id @@ -53,6 +57,7 @@ export async function fetchLatestInvoices() { } export async function fetchCardData() { + noStore(); try { // You can probably combine these into a single SQL query // However, we are intentionally splitting them to demonstrate @@ -92,6 +97,7 @@ export async function fetchFilteredInvoices( query: string, currentPage: number, ) { + noStore(); const offset = (currentPage - 1) * ITEMS_PER_PAGE; try { @@ -124,6 +130,7 @@ export async function fetchFilteredInvoices( } export async function fetchInvoicesPages(query: string) { + noStore(); try { const count = await sql`SELECT COUNT(*) FROM invoices @@ -187,6 +194,7 @@ export async function fetchCustomers() { } export async function fetchFilteredCustomers(query: string) { + noStore(); try { const data = await sql` SELECT diff --git a/app/ui/dashboard/cards.tsx b/app/ui/dashboard/cards.tsx index bbd3a86..2960480 100644 --- a/app/ui/dashboard/cards.tsx +++ b/app/ui/dashboard/cards.tsx @@ -5,6 +5,7 @@ import { InboxIcon, } from '@heroicons/react/24/outline'; import { lusitana } from '@/app/ui/fonts'; +import { fetchCardData } from '@/app/lib/data'; const iconMap = { collected: BanknotesIcon, @@ -14,18 +15,25 @@ const iconMap = { }; export default async function Cards() { + const { + numberOfInvoices, + numberOfCustomers, + totalPaidInvoices, + totalPendingInvoices, + } = await fetchCardData(); + return ( <> {/* NOTE: comment in this code when you get to this point in the course */} - {/* + */} + /> ); } diff --git a/app/ui/dashboard/latest-invoices.tsx b/app/ui/dashboard/latest-invoices.tsx index caca7a3..a54fb85 100644 --- a/app/ui/dashboard/latest-invoices.tsx +++ b/app/ui/dashboard/latest-invoices.tsx @@ -2,12 +2,9 @@ import { ArrowPathIcon } from '@heroicons/react/24/outline'; import clsx from 'clsx'; import Image from 'next/image'; import { lusitana } from '@/app/ui/fonts'; -import { LatestInvoice } from '@/app/lib/definitions'; -export default async function LatestInvoices({ - latestInvoices, -}: { - latestInvoices: LatestInvoice[]; -}) { +import { fetchLatestInvoices } from '@/app/lib/data'; +export default async function LatestInvoices() { + const latestInvoices = await fetchLatestInvoices(); return (

@@ -16,7 +13,7 @@ export default async function LatestInvoices({
{/* NOTE: comment in this code when you get to this point in the course */} - {/*
+
{latestInvoices.map((invoice, i) => { return (
); })} -
*/} +

Updated just now

diff --git a/app/ui/dashboard/revenue-chart.tsx b/app/ui/dashboard/revenue-chart.tsx index 7ccc409..2ac876b 100644 --- a/app/ui/dashboard/revenue-chart.tsx +++ b/app/ui/dashboard/revenue-chart.tsx @@ -1,7 +1,7 @@ import { generateYAxis } from '@/app/lib/utils'; import { CalendarIcon } from '@heroicons/react/24/outline'; import { lusitana } from '@/app/ui/fonts'; -import { Revenue } from '@/app/lib/definitions'; +import { fetchRevenue } from '@/app/lib/data'; // This component is representational only. // For data visualization UI, check out: @@ -9,19 +9,16 @@ import { Revenue } from '@/app/lib/definitions'; // https://www.chartjs.org/ // https://airbnb.io/visx/ -export default async function RevenueChart({ - revenue, -}: { - revenue: Revenue[]; -}) { +export default async function RevenueChart() { + const revenue = await fetchRevenue(); const chartHeight = 350; // NOTE: comment in this code when you get to this point in the course - // const { yAxisLabels, topLabel } = generateYAxis(revenue); + const { yAxisLabels, topLabel } = generateYAxis(revenue); - // if (!revenue || revenue.length === 0) { - // return

No data available.

; - // } + if (!revenue || revenue.length === 0) { + return

No data available.

; + } return (
@@ -30,7 +27,7 @@ export default async function RevenueChart({

{/* NOTE: comment in this code when you get to this point in the course */} - {/*
+

Last 12 months

-
*/} +
); } diff --git a/package.json b/package.json index 89c9d2e..56d898f 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,35 @@ { - "private": true, - "scripts": { - "build": "next build", - "dev": "next dev", - "start": "next start" - }, - "dependencies": { - "@heroicons/react": "^2.0.18", - "@tailwindcss/forms": "^0.5.6", - "@types/node": "20.5.7", - "@vercel/postgres": "^0.5.0", - "autoprefixer": "10.4.15", - "bcrypt": "^5.1.1", - "clsx": "^2.0.0", - "next": "^14.0.0", - "postcss": "8.4.31", - "react": "18.2.0", - "react-dom": "18.2.0", - "tailwindcss": "3.3.3", - "typescript": "5.2.2", - "zod": "^3.22.2" - }, - "devDependencies": { - "@types/bcrypt": "^5.0.1", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.14", - "dotenv": "^16.3.1", - "prettier": "^3.0.3" - }, - "engines": { - "node": ">=18" - } -} + "private": true, + "scripts": { + "build": "next build", + "dev": "next dev", + "start": "next start", + "seed": "node -r dotenv/config ./scripts/seed.js" + }, + "dependencies": { + "@heroicons/react": "^2.0.18", + "@tailwindcss/forms": "^0.5.6", + "@types/node": "20.5.7", + "@vercel/postgres": "^0.5.0", + "autoprefixer": "10.4.15", + "bcrypt": "^5.1.1", + "clsx": "^2.0.0", + "next": "^14.0.0", + "postcss": "8.4.31", + "react": "18.2.0", + "react-dom": "18.2.0", + "tailwindcss": "3.3.3", + "typescript": "5.2.2", + "zod": "^3.22.2" + }, + "devDependencies": { + "@types/bcrypt": "^5.0.1", + "@types/react": "18.2.21", + "@types/react-dom": "18.2.14", + "dotenv": "^16.3.1", + "prettier": "^3.0.3" + }, + "engines": { + "node": ">=18" + } +} \ No newline at end of file