@@ -42,7 +46,7 @@ export default function CustomersTable() {
@@ -18,7 +18,7 @@ export default function LatestInvoices({
{lastFiveInvoices.map((invoice) => {
const customer = customers.find(
- (customer) => customer.id === invoice.customerId,
+ (customer) => customer.id === invoice.customer_id,
);
return (
customer.id === invoice?.customerId,
+ (customer) => customer.id === invoice?.customer_id,
);
const initialCustomer = customer ? customer.id : 0;
const initialAmount = invoice?.amount ? invoice.amount / 100 : 0;
@@ -31,7 +31,7 @@ export default function InvoiceForm({
if (selectedCustomer && amount) {
const newInvoice: Invoice = {
- customerId: selectedCustomer,
+ customer_id: selectedCustomer,
amount: amount * 100, // Convert to cents
// These would be generated on the server
diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index e0275b4..3ab7cac 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -1,5 +1,3 @@
-import { invoices, customers } from '@/app/lib/dummy-data';
-import { Customer } from '@/app/lib/definitions';
import Link from 'next/link';
import Image from 'next/image';
import {
@@ -10,6 +8,10 @@ import {
import DeleteInvoice from '@/app/ui/invoices/delete-button';
import TableSearch from './table-search';
import PaginationButtons from './pagination';
+import {
+ fetchFilteredInvoices,
+ fetchInvoiceCountBySearchTerm,
+} from '@/app/lib/data-fetches';
const ITEMS_PER_PAGE = 10;
@@ -42,7 +44,7 @@ function formatDateToLocal(dateStr: string, locale: string = 'en-US') {
return formatter.format(date);
}
-export default function InvoicesTable({
+export default async function InvoicesTable({
searchParams,
}: {
searchParams: {
@@ -53,35 +55,14 @@ export default function InvoicesTable({
const searchTerm = searchParams.query ?? '';
const currentPage = parseInt(searchParams.page ?? '1');
- const filteredInvoices = invoices.filter((invoice) => {
- const customer = getCustomerById(invoice.customerId);
-
- const invoiceMatches = Object.values(invoice).some(
- (value) =>
- value?.toString().toLowerCase().includes(searchTerm.toLowerCase()),
- );
-
- const customerMatches =
- customer &&
- Object.values(customer).some(
- (value) =>
- value?.toString().toLowerCase().includes(searchTerm.toLowerCase()),
- );
-
- return invoiceMatches || customerMatches;
- });
-
- const paginatedInvoices = filteredInvoices.slice(
- (currentPage - 1) * ITEMS_PER_PAGE,
- currentPage * ITEMS_PER_PAGE,
+ const invoices = await fetchFilteredInvoices(
+ searchTerm,
+ currentPage,
+ ITEMS_PER_PAGE,
);
- function getCustomerById(customerId: number): Customer | null {
- const customer = customers.find((customer) => customer.id === customerId);
- return customer ? customer : null;
- }
-
- const totalPages = Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE);
+ const totalCount = await fetchInvoiceCountBySearchTerm(searchTerm);
+ const totalPages = Math.ceil(totalCount / ITEMS_PER_PAGE);
return (
@@ -132,7 +113,7 @@ export default function InvoicesTable({
- {paginatedInvoices.map((invoice) => (
+ {invoices?.map((invoice) => (
|
{invoice.id}
@@ -140,18 +121,17 @@ export default function InvoicesTable({
|
- {getCustomerById(invoice.customerId)?.name}
+ {invoice.customer_name}
|
- {getCustomerById(invoice.customerId)?.email}
+ {invoice.customer_email}
|
{(invoice.amount / 100).toLocaleString('en-US', {
diff --git a/dashboard/15-final/package-lock.json b/dashboard/15-final/package-lock.json
index 6eb006b..0c0c073 100644
--- a/dashboard/15-final/package-lock.json
+++ b/dashboard/15-final/package-lock.json
@@ -13,6 +13,7 @@
"@types/node": "20.5.7",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
+ "@vercel/postgres": "^0.4.1",
"autoprefixer": "10.4.15",
"clsx": "^2.0.0",
"next": "13.4.19",
@@ -22,6 +23,9 @@
"tailwindcss": "3.3.3",
"typescript": "5.2.2"
},
+ "devDependencies": {
+ "dotenv": "^16.3.1"
+ },
"engines": {
"node": ">=18"
}
@@ -88,6 +92,14 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@neondatabase/serverless": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.5.6.tgz",
+ "integrity": "sha512-Ru0lG6W/nQtHRkDFVQFF+1PJYx8wd3jereln0Ep0YkiHey50hjTLVUycQoE4X977605pXMuFWORweuktzph+Xg==",
+ "dependencies": {
+ "@types/pg": "8.6.6"
+ }
+ },
"node_modules/@next/env": {
"version": "13.4.19",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz",
@@ -284,6 +296,16 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz",
"integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA=="
},
+ "node_modules/@types/pg": {
+ "version": "8.6.6",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz",
+ "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==",
+ "dependencies": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^2.2.0"
+ }
+ },
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -312,6 +334,20 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
+ "node_modules/@vercel/postgres": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.4.1.tgz",
+ "integrity": "sha512-rYlNnaXrr2/NWK/OodhAUyed0bomaizKKC8XXjNYv8I1K3m75oocP4IGTcBpZe76VCrHuaKW5d6jLQnuRRoNKg==",
+ "dependencies": {
+ "@neondatabase/serverless": "0.5.6",
+ "bufferutil": "4.0.7",
+ "utf-8-validate": "6.0.3",
+ "ws": "8.13.0"
+ },
+ "engines": {
+ "node": ">=14.6"
+ }
+ },
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -434,6 +470,18 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bufferutil": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
+ "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -561,6 +609,18 @@
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.4.503",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz",
@@ -921,6 +981,16 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-gyp-build": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
+ "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
@@ -979,6 +1049,34 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
+ "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -1135,6 +1233,41 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -1457,6 +1590,18 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/utf-8-validate": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz",
+ "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -1479,6 +1624,34 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
+ "node_modules/ws": {
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/yaml": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
@@ -1542,6 +1715,14 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "@neondatabase/serverless": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.5.6.tgz",
+ "integrity": "sha512-Ru0lG6W/nQtHRkDFVQFF+1PJYx8wd3jereln0Ep0YkiHey50hjTLVUycQoE4X977605pXMuFWORweuktzph+Xg==",
+ "requires": {
+ "@types/pg": "8.6.6"
+ }
+ },
"@next/env": {
"version": "13.4.19",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz",
@@ -1645,6 +1826,16 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz",
"integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA=="
},
+ "@types/pg": {
+ "version": "8.6.6",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz",
+ "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==",
+ "requires": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^2.2.0"
+ }
+ },
"@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -1673,6 +1864,17 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
+ "@vercel/postgres": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.4.1.tgz",
+ "integrity": "sha512-rYlNnaXrr2/NWK/OodhAUyed0bomaizKKC8XXjNYv8I1K3m75oocP4IGTcBpZe76VCrHuaKW5d6jLQnuRRoNKg==",
+ "requires": {
+ "@neondatabase/serverless": "0.5.6",
+ "bufferutil": "4.0.7",
+ "utf-8-validate": "6.0.3",
+ "ws": "8.13.0"
+ }
+ },
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -1743,6 +1945,14 @@
"update-browserslist-db": "^1.0.11"
}
},
+ "bufferutil": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
+ "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
+ "requires": {
+ "node-gyp-build": "^4.3.0"
+ }
+ },
"busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -1826,6 +2036,12 @@
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
+ "dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "dev": true
+ },
"electron-to-chromium": {
"version": "1.4.503",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz",
@@ -2075,6 +2291,11 @@
}
}
},
+ "node-gyp-build": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
+ "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ=="
+ },
"node-releases": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
@@ -2118,6 +2339,28 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
+ "pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
+ },
+ "pg-protocol": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
+ "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
+ },
+ "pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "requires": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ }
+ },
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -2197,6 +2440,29 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
+ },
+ "postgres-bytea": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
+ },
+ "postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
+ },
+ "postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "requires": {
+ "xtend": "^4.0.0"
+ }
+ },
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2395,6 +2661,14 @@
"picocolors": "^1.0.0"
}
},
+ "utf-8-validate": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz",
+ "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==",
+ "requires": {
+ "node-gyp-build": "^4.3.0"
+ }
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -2414,6 +2688,17 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
+ "ws": {
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "requires": {}
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+ },
"yaml": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
diff --git a/dashboard/15-final/package.json b/dashboard/15-final/package.json
index 7e6b047..1c19d7b 100644
--- a/dashboard/15-final/package.json
+++ b/dashboard/15-final/package.json
@@ -5,6 +5,7 @@
"scripts": {
"build": "next build",
"dev": "next dev",
+ "seed": "node -r dotenv/config ./scripts/seed.js",
"start": "next start"
},
"dependencies": {
@@ -13,6 +14,7 @@
"@types/node": "20.5.7",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
+ "@vercel/postgres": "^0.4.1",
"autoprefixer": "10.4.15",
"clsx": "^2.0.0",
"next": "13.4.19",
@@ -22,6 +24,9 @@
"tailwindcss": "3.3.3",
"typescript": "5.2.2"
},
+ "devDependencies": {
+ "dotenv": "^16.3.1"
+ },
"engines": {
"node": ">=18"
}
diff --git a/dashboard/15-final/scripts/seed.js b/dashboard/15-final/scripts/seed.js
new file mode 100644
index 0000000..9a3bfb6
--- /dev/null
+++ b/dashboard/15-final/scripts/seed.js
@@ -0,0 +1,118 @@
+const { sql } = require('@vercel/postgres');
+const { invoices, customers, revenue } = require('../app/lib/dummy-data.js');
+
+async function seedInvoices() {
+ try {
+ // Create the "invoices" table if it doesn't exist
+ const createTable = await sql`
+ CREATE TABLE IF NOT EXISTS invoices (
+ id SERIAL PRIMARY KEY,
+ customer_id INT NOT NULL,
+ amount INT NOT NULL,
+ status VARCHAR(255) NOT NULL,
+ date DATE NOT NULL
+ );
+ `;
+
+ console.log(`Created "invoices" table`);
+
+ // Insert data into the "invoices" table
+ const insertedInvoices = await Promise.all(
+ invoices.map(
+ (invoice) => sql`
+ INSERT INTO invoices (id, customer_id, amount, status, date)
+ VALUES (${invoice.id}, ${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date})
+ ON CONFLICT (id) DO NOTHING;
+ `,
+ ),
+ );
+
+ console.log(`Seeded ${insertedInvoices.length} invoices`);
+
+ return {
+ createTable,
+ invoices: insertedInvoices,
+ };
+ } catch (error) {
+ console.error('Error seeding invoices:', error);
+ throw error;
+ }
+}
+
+async function seedCustomers() {
+ try {
+ // Create the "invoices" table if it doesn't exist
+ const createTable = await sql`
+ CREATE TABLE IF NOT EXISTS customers (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ email VARCHAR(255) NOT NULL,
+ image_url VARCHAR(255) NOT NULL
+ );
+ `;
+
+ console.log(`Created "customers" table`);
+
+ // Insert data into the "customers" table
+ const insertedCustomers = await Promise.all(
+ customers.map(
+ (customer) => sql`
+ INSERT INTO customers (id, name, email, image_url)
+ VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url})
+ ON CONFLICT (id) DO NOTHING;
+ `,
+ ),
+ );
+
+ console.log(`Seeded ${insertedCustomers.length} customers`);
+
+ return {
+ createTable,
+ customers: insertedCustomers,
+ };
+ } catch (error) {
+ console.error('Error seeding customers:', error);
+ throw error;
+ }
+}
+
+async function seedRevenue() {
+ try {
+ // Create the "revenue" table if it doesn't exist
+ const createTable = await sql`
+ CREATE TABLE IF NOT EXISTS revenue (
+ month VARCHAR(4) NOT NULL UNIQUE,
+ revenue INT NOT NULL
+ );
+ `;
+
+ console.log(`Created "revenue" table`);
+
+ // Insert data into the "revenue" table
+ const insertedRevenue = await Promise.all(
+ revenue.map(
+ (rev) => sql`
+ INSERT INTO revenue (month, revenue)
+ VALUES (${rev.month}, ${rev.revenue})
+ ON CONFLICT (month) DO NOTHING;
+ `,
+ ),
+ );
+
+ console.log(`Seeded ${insertedRevenue.length} revenue`);
+
+ return {
+ createTable,
+ revenue: insertedRevenue,
+ };
+ } catch (error) {
+ console.error('Error seeding revenue:', error);
+ throw error;
+ }
+}
+
+(async () => {
+ await seedCustomers();
+ await seedInvoices();
+ await seedRevenue();
+})();
diff --git a/dashboard/15-final/tsconfig.json b/dashboard/15-final/tsconfig.json
index c714696..10ea0dc 100644
--- a/dashboard/15-final/tsconfig.json
+++ b/dashboard/15-final/tsconfig.json
@@ -22,6 +22,13 @@
"@/*": ["./*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ "app/lib/dummy-data.js",
+ "scripts/seed.js"
+ ],
"exclude": ["node_modules"]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2bb290d..656d270 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
importers:
.:
@@ -224,6 +228,9 @@ importers:
'@types/react-dom':
specifier: 18.2.7
version: 18.2.7
+ '@vercel/postgres':
+ specifier: ^0.4.1
+ version: 0.4.1
autoprefixer:
specifier: 10.4.15
version: 10.4.15(postcss@8.4.28)
@@ -248,6 +255,10 @@ importers:
typescript:
specifier: 5.2.2
version: 5.2.2
+ devDependencies:
+ dotenv:
+ specifier: ^16.3.1
+ version: 16.3.1
seo/demo:
dependencies:
@@ -585,6 +596,12 @@ packages:
resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
dev: true
+ /@neondatabase/serverless@0.5.6:
+ resolution: {integrity: sha512-Ru0lG6W/nQtHRkDFVQFF+1PJYx8wd3jereln0Ep0YkiHey50hjTLVUycQoE4X977605pXMuFWORweuktzph+Xg==}
+ dependencies:
+ '@types/pg': 8.6.6
+ dev: false
+
/@next/env@13.4.19:
resolution: {integrity: sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==}
dev: false
@@ -763,7 +780,6 @@ packages:
/@types/node@18.11.9:
resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==}
- dev: true
/@types/node@20.5.7:
resolution: {integrity: sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==}
@@ -777,6 +793,14 @@ packages:
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
dev: false
+ /@types/pg@8.6.6:
+ resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
+ dependencies:
+ '@types/node': 18.11.9
+ pg-protocol: 1.6.0
+ pg-types: 2.2.0
+ dev: false
+
/@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
@@ -997,6 +1021,16 @@ packages:
eslint-visitor-keys: 3.4.3
dev: true
+ /@vercel/postgres@0.4.1:
+ resolution: {integrity: sha512-rYlNnaXrr2/NWK/OodhAUyed0bomaizKKC8XXjNYv8I1K3m75oocP4IGTcBpZe76VCrHuaKW5d6jLQnuRRoNKg==}
+ engines: {node: '>=14.6'}
+ dependencies:
+ '@neondatabase/serverless': 0.5.6
+ bufferutil: 4.0.7
+ utf-8-validate: 6.0.3
+ ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+ dev: false
+
/@vercel/style-guide@5.0.1(eslint@8.48.0)(prettier@3.0.3)(typescript@4.8.4):
resolution: {integrity: sha512-3J/5xpwJ2Wk+cKB3EGY2KCdVQycaThLKhjBmgXPfIKb+E74lPpXVIDfaQE0D2JoAyIzGsqdH7Lbmr+DojwofxQ==}
engines: {node: '>=16'}
@@ -1281,6 +1315,14 @@ packages:
node-releases: 2.0.13
update-browserslist-db: 1.0.11(browserslist@4.21.10)
+ /bufferutil@4.0.7:
+ resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==}
+ engines: {node: '>=6.14.2'}
+ requiresBuild: true
+ dependencies:
+ node-gyp-build: 4.6.1
+ dev: false
+
/builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'}
@@ -1576,6 +1618,11 @@ packages:
esutils: 2.0.3
dev: true
+ /dotenv@16.3.1:
+ resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
+ engines: {node: '>=12'}
+ dev: true
+
/electron-to-chromium@1.4.508:
resolution: {integrity: sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==}
@@ -3408,6 +3455,11 @@ packages:
- babel-plugin-macros
dev: false
+ /node-gyp-build@4.6.1:
+ resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==}
+ hasBin: true
+ dev: false
+
/node-releases@2.0.13:
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
@@ -3648,6 +3700,26 @@ packages:
engines: {node: '>=8'}
dev: true
+ /pg-int8@1.0.1:
+ resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
+ engines: {node: '>=4.0.0'}
+ dev: false
+
+ /pg-protocol@1.6.0:
+ resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
+ dev: false
+
+ /pg-types@2.2.0:
+ resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
+ engines: {node: '>=4'}
+ dependencies:
+ pg-int8: 1.0.1
+ postgres-array: 2.0.0
+ postgres-bytea: 1.0.0
+ postgres-date: 1.0.7
+ postgres-interval: 1.2.0
+ dev: false
+
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
@@ -3749,6 +3821,28 @@ packages:
source-map-js: 1.0.2
dev: false
+ /postgres-array@2.0.0:
+ resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /postgres-bytea@1.0.0:
+ resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /postgres-date@1.0.7:
+ resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /postgres-interval@1.2.0:
+ resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ xtend: 4.0.2
+ dev: false
+
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -4644,6 +4738,14 @@ packages:
punycode: 2.3.0
dev: true
+ /utf-8-validate@6.0.3:
+ resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==}
+ engines: {node: '>=6.14.2'}
+ requiresBuild: true
+ dependencies:
+ node-gyp-build: 4.6.1
+ dev: false
+
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false
@@ -4766,6 +4868,22 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
+ resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dependencies:
+ bufferutil: 4.0.7
+ utf-8-validate: 6.0.3
+ dev: false
+
/xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
| |