mirror of
https://github.com/vercel/next-learn.git
synced 2026-06-11 09:51:47 +00:00
Add database (#153)
* add database to project. Seed data. Update customerId to customer_id * seed customers table data * use database everywhere * refactor * fix ts lint errors * add type to invoice edit page * remove fetch-data file and fetch data directly in components * update lates invoices to use sql * in invoice table, search the database here with SQL * rename tsx files to ts and add node script to seed data * address rest of PR comments * move all data fetches to own file * add types to filter invoices function * remove unused param * prettier * update function names
This commit is contained in:
7
dashboard/15-final/.env
Normal file
7
dashboard/15-final/.env
Normal file
@@ -0,0 +1,7 @@
|
||||
POSTGRES_URL="postgres://default:IQA2FonHJi9d@ep-square-union-29037633-pooler.us-east-1.postgres.vercel-storage.com:5432/verceldb"
|
||||
POSTGRES_PRISMA_URL="postgres://default:IQA2FonHJi9d@ep-square-union-29037633-pooler.us-east-1.postgres.vercel-storage.com:5432/verceldb?pgbouncer=true&connect_timeout=15"
|
||||
POSTGRES_URL_NON_POOLING="postgres://default:IQA2FonHJi9d@ep-square-union-29037633.us-east-1.postgres.vercel-storage.com:5432/verceldb"
|
||||
POSTGRES_USER="default"
|
||||
POSTGRES_HOST="ep-square-union-29037633-pooler.us-east-1.postgres.vercel-storage.com"
|
||||
POSTGRES_PASSWORD="IQA2FonHJi9d"
|
||||
POSTGRES_DATABASE="verceldb"
|
||||
@@ -1,10 +1,12 @@
|
||||
import InvoiceForm from '@/app/ui/invoices/form';
|
||||
import { invoices } from '@/app/lib/dummy-data';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Invoice } from '@/app/lib/definitions';
|
||||
import { fetchInvoiceById } from '@/app/lib/data-fetches';
|
||||
|
||||
export default function Page({ params }: { params: { id: string } }) {
|
||||
export default async function Page({ params }: { params: { id: string } }) {
|
||||
const id = params.id ? parseInt(params.id) : null;
|
||||
const invoice = invoices.find((invoice) => invoice.id === id);
|
||||
const invoiceData = await fetchInvoiceById(id);
|
||||
const invoice = invoiceData.rows[0] as Invoice;
|
||||
|
||||
if (!invoice) {
|
||||
notFound();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Invoice, Revenue } from './definitions';
|
||||
import { fetchLatestInvoices } from './data-fetches';
|
||||
|
||||
export const calculateAllInvoices = (
|
||||
invoices: Invoice[],
|
||||
@@ -19,7 +20,7 @@ export const calculateCustomerInvoices = (
|
||||
customerId: number,
|
||||
) => {
|
||||
return invoices
|
||||
.filter((invoice) => invoice.customerId === customerId)
|
||||
.filter((invoice) => invoice.customer_id === customerId)
|
||||
.filter((invoice) => !status || invoice.status === status)
|
||||
.reduce((total, invoice) => total + invoice.amount / 100, 0)
|
||||
.toLocaleString('en-US', {
|
||||
@@ -38,13 +39,12 @@ export const countCustomerInvoices = (
|
||||
invoices: Invoice[],
|
||||
customerId: number,
|
||||
) => {
|
||||
return invoices.filter((invoice) => invoice.customerId === customerId).length;
|
||||
return invoices.filter((invoice) => invoice.customer_id === customerId)
|
||||
.length;
|
||||
};
|
||||
|
||||
export const findLatestInvoices = (invoices: Invoice[]) => {
|
||||
return [...invoices]
|
||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
.slice(0, 5);
|
||||
export const findLatestInvoices = async () => {
|
||||
return await fetchLatestInvoices();
|
||||
};
|
||||
|
||||
export const generateYAxis = (revenue: Revenue[]) => {
|
||||
65
dashboard/15-final/app/lib/data-fetches.ts
Normal file
65
dashboard/15-final/app/lib/data-fetches.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { sql } from '@vercel/postgres';
|
||||
|
||||
export async function fetchAllInvoices() {
|
||||
const invoicesData = await sql`SELECT * FROM invoices`;
|
||||
return invoicesData.rows;
|
||||
}
|
||||
|
||||
export async function fetchAllCustomers() {
|
||||
const customersData = await sql`SELECT * FROM customers`;
|
||||
return customersData.rows;
|
||||
}
|
||||
|
||||
export async function fetchAllRevenue() {
|
||||
const revenueData = await sql`SELECT * FROM revenue`;
|
||||
return revenueData.rows;
|
||||
}
|
||||
|
||||
export async function fetchFilteredInvoices(
|
||||
searchTerm: string,
|
||||
currentPage: number,
|
||||
ITEMS_PER_PAGE: number,
|
||||
) {
|
||||
const invoicesData = await sql`
|
||||
SELECT
|
||||
invoices.*,
|
||||
customers.name AS customer_name,
|
||||
customers.email AS customer_email,
|
||||
customers.image_url AS customer_image
|
||||
FROM
|
||||
invoices
|
||||
JOIN
|
||||
customers ON invoices.customer_id = customers.id
|
||||
WHERE
|
||||
invoices.id::text ILIKE ${`%${searchTerm}%`} OR
|
||||
customers.name ILIKE ${`%${searchTerm}%`} OR
|
||||
customers.email ILIKE ${`%${searchTerm}%`} OR
|
||||
invoices.amount::text ILIKE ${`%${searchTerm}%`} OR
|
||||
invoices.date::text ILIKE ${`%${searchTerm}%`} OR
|
||||
invoices.status ILIKE ${`%${searchTerm}%`}
|
||||
LIMIT ${ITEMS_PER_PAGE}
|
||||
OFFSET ${(currentPage - 1) * ITEMS_PER_PAGE}
|
||||
`;
|
||||
return invoicesData.rows;
|
||||
}
|
||||
|
||||
export async function fetchInvoiceCountBySearchTerm(searchTerm: string) {
|
||||
const { rows: countRows } = await sql`
|
||||
SELECT COUNT(*)
|
||||
FROM invoices
|
||||
LEFT JOIN customers ON invoices.customer_id = customers.id
|
||||
WHERE (invoices.id::text ILIKE ${`%${searchTerm}%`} OR customers.name ILIKE ${`%${searchTerm}%`} OR customers.email ILIKE ${`%${searchTerm}%`})
|
||||
`;
|
||||
return countRows[0].count;
|
||||
}
|
||||
|
||||
export async function fetchInvoiceById(id: number | null) {
|
||||
return await sql`SELECT * from INVOICES where id=${id}`;
|
||||
}
|
||||
|
||||
export async function fetchLatestInvoices() {
|
||||
const latestInvoices = await sql`SELECT * FROM invoices
|
||||
ORDER BY date DESC
|
||||
LIMIT 5;`;
|
||||
return latestInvoices.rows;
|
||||
}
|
||||
@@ -12,12 +12,12 @@ export type Customer = {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
imageUrl: string;
|
||||
image_url: string;
|
||||
};
|
||||
|
||||
export type Invoice = {
|
||||
id: number;
|
||||
customerId: number;
|
||||
customer_id: number;
|
||||
amount: number;
|
||||
// In TypeScript, this is called a string union type.
|
||||
// It means that the "status" property can only be one of the two strings: 'pending' or 'paid'.
|
||||
@@ -1,7 +1,5 @@
|
||||
import { User, Customer, Invoice, Revenue } from './definitions';
|
||||
|
||||
// This file contains dummy data that you'll be replacing with real data in Chapter 7.
|
||||
export const users: User[] = [
|
||||
const users = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'User',
|
||||
@@ -10,142 +8,142 @@ export const users: User[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const customers: Customer[] = [
|
||||
const customers = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Ada Lovelace',
|
||||
email: 'ada@lovelace.com',
|
||||
imageUrl: '/customers/ada-lovelace.png',
|
||||
image_url: '/customers/ada-lovelace.png',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Grace Hopper',
|
||||
email: 'grace@hopper.com',
|
||||
imageUrl: '/customers/grace-hopper.png',
|
||||
image_url: '/customers/grace-hopper.png',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Hedy Lammar',
|
||||
email: 'hedy@lammar.com',
|
||||
imageUrl: '/customers/hedy-lammar.png',
|
||||
image_url: '/customers/hedy-lammar.png',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Margaret Hamilton',
|
||||
email: 'margaret@hamilton.com',
|
||||
imageUrl: '/customers/margaret-hamilton.png',
|
||||
image_url: '/customers/margaret-hamilton.png',
|
||||
},
|
||||
];
|
||||
|
||||
export const invoices: Invoice[] = [
|
||||
const invoices = [
|
||||
{
|
||||
id: 1,
|
||||
customerId: 1,
|
||||
customer_id: 1,
|
||||
amount: 15795,
|
||||
status: 'pending',
|
||||
date: '2023-12-06',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
customerId: 2,
|
||||
customer_id: 2,
|
||||
amount: 20348,
|
||||
status: 'pending',
|
||||
date: '2023-11-14',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
customerId: 3,
|
||||
customer_id: 3,
|
||||
amount: 3040,
|
||||
status: 'paid',
|
||||
date: '2023-10-29',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
customerId: 4,
|
||||
customer_id: 4,
|
||||
amount: 44800,
|
||||
status: 'paid',
|
||||
date: '2023-09-10',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
customerId: 1,
|
||||
customer_id: 1,
|
||||
amount: 34577,
|
||||
status: 'pending',
|
||||
date: '2023-08-05',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
customerId: 2,
|
||||
customer_id: 2,
|
||||
amount: 54246,
|
||||
status: 'pending',
|
||||
date: '2023-07-16',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
customerId: 3,
|
||||
customer_id: 3,
|
||||
amount: 8945,
|
||||
status: 'pending',
|
||||
date: '2023-06-27',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
customerId: 4,
|
||||
customer_id: 4,
|
||||
amount: 32545,
|
||||
status: 'paid',
|
||||
date: '2023-06-09',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
customerId: 3,
|
||||
customer_id: 3,
|
||||
amount: 1250,
|
||||
status: 'paid',
|
||||
date: '2023-06-17',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
customerId: 1,
|
||||
customer_id: 1,
|
||||
amount: 8945,
|
||||
status: 'paid',
|
||||
date: '2023-06-07',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
customerId: 2,
|
||||
customer_id: 2,
|
||||
amount: 500,
|
||||
status: 'paid',
|
||||
date: '2023-08-19',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
customerId: 3,
|
||||
customer_id: 3,
|
||||
amount: 8945,
|
||||
status: 'paid',
|
||||
date: '2023-06-03',
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
customerId: 3,
|
||||
customer_id: 3,
|
||||
amount: 8945,
|
||||
status: 'paid',
|
||||
date: '2023-06-18',
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
customerId: 4,
|
||||
customer_id: 4,
|
||||
amount: 8945,
|
||||
status: 'paid',
|
||||
date: '2023-10-04',
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
customerId: 3,
|
||||
customer_id: 3,
|
||||
amount: 1000,
|
||||
status: 'paid',
|
||||
date: '2022-06-05',
|
||||
},
|
||||
];
|
||||
|
||||
export const revenue: Revenue[] = [
|
||||
const revenue = [
|
||||
{ month: 'Jan', revenue: 2000 },
|
||||
{ month: 'Feb', revenue: 1800 },
|
||||
{ month: 'Mar', revenue: 2200 },
|
||||
@@ -159,3 +157,10 @@ export const revenue: Revenue[] = [
|
||||
{ month: 'Nov', revenue: 3000 },
|
||||
{ month: 'Dec', revenue: 4800 },
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
users,
|
||||
customers,
|
||||
invoices,
|
||||
revenue,
|
||||
};
|
||||
@@ -1,11 +1,15 @@
|
||||
import { customers, invoices } from '@/app/lib/dummy-data';
|
||||
import {
|
||||
countCustomerInvoices,
|
||||
calculateCustomerInvoices,
|
||||
} from '@/app/lib/calculations';
|
||||
import { Customer, Invoice } from '@/app/lib/definitions';
|
||||
import { fetchAllCustomers, fetchAllInvoices } from '@/app/lib/data-fetches';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function CustomersTable() {
|
||||
export default async function CustomersTable() {
|
||||
const invoices = (await fetchAllInvoices()) as Invoice[];
|
||||
const customers = (await fetchAllCustomers()) as Customer[];
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
@@ -42,7 +46,7 @@ export default function CustomersTable() {
|
||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-black sm:pl-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Image
|
||||
src={customer.imageUrl}
|
||||
src={customer.image_url}
|
||||
className="rounded-full"
|
||||
alt={customer.name}
|
||||
width={28}
|
||||
|
||||
@@ -3,14 +3,14 @@ import { Customer, Invoice } from '@/app/lib/definitions';
|
||||
import { findLatestInvoices } from '@/app/lib/calculations';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function LatestInvoices({
|
||||
export default async function LatestInvoices({
|
||||
invoices,
|
||||
customers,
|
||||
}: {
|
||||
invoices: Invoice[];
|
||||
customers: Customer[];
|
||||
}) {
|
||||
const lastFiveInvoices = findLatestInvoices(invoices);
|
||||
const lastFiveInvoices = await findLatestInvoices();
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-xl border p-6 shadow-sm md:col-span-4 lg:col-span-3">
|
||||
@@ -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 (
|
||||
<div
|
||||
@@ -27,7 +27,7 @@ export default function LatestInvoices({
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Image
|
||||
src={customer?.imageUrl || ''}
|
||||
src={customer?.image_url || ''}
|
||||
alt={customer?.name || ''}
|
||||
className="mr-4 rounded-full"
|
||||
width={32}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import Card from '@/app/ui/dashboard/card';
|
||||
import { invoices, customers, revenue } from '@/app/lib/dummy-data';
|
||||
import { calculateAllInvoices } from '@/app/lib/calculations';
|
||||
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
|
||||
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
|
||||
import { Customer, Invoice, Revenue } from '@/app/lib/definitions';
|
||||
import {
|
||||
fetchAllCustomers,
|
||||
fetchAllInvoices,
|
||||
fetchAllRevenue,
|
||||
} from '@/app/lib/data-fetches';
|
||||
|
||||
export default async function DashboardOverview() {
|
||||
const invoices = (await fetchAllInvoices()) as Invoice[];
|
||||
const customers = (await fetchAllCustomers()) as Customer[];
|
||||
const revenue = (await fetchAllRevenue()) as Revenue[];
|
||||
|
||||
export default function DashboardOverview() {
|
||||
const totalPaidInvoices = calculateAllInvoices(invoices, 'paid');
|
||||
const totalPendingInvoices = calculateAllInvoices(invoices, 'pending');
|
||||
const numberOfInvoices = invoices.length;
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function InvoiceForm({
|
||||
}) {
|
||||
// TO DO: Replace state and handleSubmit with a Server Action
|
||||
const customer = customers.find(
|
||||
(customer) => 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
|
||||
|
||||
@@ -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 (
|
||||
<div className="w-full">
|
||||
@@ -132,7 +113,7 @@ export default function InvoicesTable({
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 text-gray-500">
|
||||
{paginatedInvoices.map((invoice) => (
|
||||
{invoices?.map((invoice) => (
|
||||
<tr key={invoice.id}>
|
||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-black sm:pl-6">
|
||||
{invoice.id}
|
||||
@@ -140,18 +121,17 @@ export default function InvoicesTable({
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm">
|
||||
<div className="flex items-center gap-3">
|
||||
<Image
|
||||
src={`${getCustomerById(invoice.customerId)
|
||||
?.imageUrl}`}
|
||||
src={`${invoice.customer_image}`}
|
||||
className="rounded-full"
|
||||
alt="Customer Image"
|
||||
width={28}
|
||||
height={28}
|
||||
/>
|
||||
<p>{getCustomerById(invoice.customerId)?.name}</p>
|
||||
<p>{invoice.customer_name}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm">
|
||||
{getCustomerById(invoice.customerId)?.email}
|
||||
{invoice.customer_email}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm">
|
||||
{(invoice.amount / 100).toLocaleString('en-US', {
|
||||
|
||||
285
dashboard/15-final/package-lock.json
generated
285
dashboard/15-final/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
118
dashboard/15-final/scripts/seed.js
Normal file
118
dashboard/15-final/scripts/seed.js
Normal file
@@ -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();
|
||||
})();
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
120
pnpm-lock.yaml
generated
120
pnpm-lock.yaml
generated
@@ -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'}
|
||||
|
||||
Reference in New Issue
Block a user