mirror of
https://github.com/vercel/next-learn.git
synced 2026-06-11 09:51:47 +00:00
Update the Learn codebase (#764)
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "next/core-web-vitals"
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
18
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import SideNav from '@/app/ui/dashboard/sidenav';
|
import SideNav from '@/app/ui/dashboard/sidenav';
|
||||||
|
|
||||||
export const experimental_ppr = true;
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
|
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
|
||||||
|
|||||||
@@ -8,12 +8,8 @@ import {
|
|||||||
Revenue,
|
Revenue,
|
||||||
} from './definitions';
|
} from './definitions';
|
||||||
import { formatCurrency } from './utils';
|
import { formatCurrency } from './utils';
|
||||||
import { unstable_noStore as noStore } from 'next/cache';
|
|
||||||
|
|
||||||
export async function fetchRevenue() {
|
export async function fetchRevenue() {
|
||||||
// Add noStore() here to prevent the response from being cached.
|
|
||||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
// Artificially delay a response for demo purposes.
|
// Artificially delay a response for demo purposes.
|
||||||
// Don't do this in production :)
|
// Don't do this in production :)
|
||||||
@@ -33,7 +29,6 @@ export async function fetchRevenue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchLatestInvoices() {
|
export async function fetchLatestInvoices() {
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
const data = await sql<LatestInvoiceRaw>`
|
const data = await sql<LatestInvoiceRaw>`
|
||||||
SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
|
SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
|
||||||
@@ -54,7 +49,6 @@ export async function fetchLatestInvoices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCardData() {
|
export async function fetchCardData() {
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
// You can probably combine these into a single SQL query
|
// You can probably combine these into a single SQL query
|
||||||
// However, we are intentionally splitting them to demonstrate
|
// However, we are intentionally splitting them to demonstrate
|
||||||
@@ -94,7 +88,6 @@ export async function fetchFilteredInvoices(
|
|||||||
query: string,
|
query: string,
|
||||||
currentPage: number,
|
currentPage: number,
|
||||||
) {
|
) {
|
||||||
noStore();
|
|
||||||
const offset = (currentPage - 1) * ITEMS_PER_PAGE;
|
const offset = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -127,7 +120,6 @@ export async function fetchFilteredInvoices(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchInvoicesPages(query: string) {
|
export async function fetchInvoicesPages(query: string) {
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
const count = await sql`SELECT COUNT(*)
|
const count = await sql`SELECT COUNT(*)
|
||||||
FROM invoices
|
FROM invoices
|
||||||
@@ -149,7 +141,6 @@ export async function fetchInvoicesPages(query: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchInvoiceById(id: string) {
|
export async function fetchInvoiceById(id: string) {
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
const data = await sql<InvoiceForm>`
|
const data = await sql<InvoiceForm>`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -193,7 +184,6 @@ export async function fetchCustomers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchFilteredCustomers(query: string) {
|
export async function fetchFilteredCustomers(query: string) {
|
||||||
noStore();
|
|
||||||
try {
|
try {
|
||||||
const data = await sql<CustomersTableType>`
|
const data = await sql<CustomersTableType>`
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ const customers = [
|
|||||||
email: 'lee@robinson.com',
|
email: 'lee@robinson.com',
|
||||||
image_url: '/customers/lee-robinson.png',
|
image_url: '/customers/lee-robinson.png',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: '3958dc9e-787f-4377-85e9-fec4b6a6442a',
|
|
||||||
name: 'Steph Dietz',
|
|
||||||
email: 'steph@dietz.com',
|
|
||||||
image_url: '/customers/steph-dietz.png',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: '76d65c26-f784-44a2-ac19-586678f7c2f2',
|
id: '76d65c26-f784-44a2-ac19-586678f7c2f2',
|
||||||
name: 'Michael Novotny',
|
name: 'Michael Novotny',
|
||||||
@@ -86,7 +80,7 @@ const invoices = [
|
|||||||
date: '2023-08-05',
|
date: '2023-08-05',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
customer_id: customers[7].id,
|
customer_id: customers[2].id,
|
||||||
amount: 54246,
|
amount: 54246,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
date: '2023-07-16',
|
date: '2023-07-16',
|
||||||
@@ -150,9 +144,4 @@ const revenue = [
|
|||||||
{ month: 'Dec', revenue: 4800 },
|
{ month: 'Dec', revenue: 4800 },
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = {
|
export { users, customers, invoices, revenue };
|
||||||
users,
|
|
||||||
customers,
|
|
||||||
invoices,
|
|
||||||
revenue,
|
|
||||||
};
|
|
||||||
118
dashboard/final-example/app/seed/route.ts
Normal file
118
dashboard/final-example/app/seed/route.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { db } from '@vercel/postgres';
|
||||||
|
import { invoices, customers, revenue, users } from '../lib/placeholder-data';
|
||||||
|
|
||||||
|
const client = await db.connect();
|
||||||
|
|
||||||
|
async function seedUsers() {
|
||||||
|
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedUsers = await Promise.all(
|
||||||
|
users.map(async (user) => {
|
||||||
|
const hashedPassword = await bcrypt.hash(user.password, 10);
|
||||||
|
return client.sql`
|
||||||
|
INSERT INTO users (id, name, email, password)
|
||||||
|
VALUES (${user.id}, ${user.name}, ${user.email}, ${hashedPassword})
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
`;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedInvoices() {
|
||||||
|
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
||||||
|
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS invoices (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||||
|
customer_id UUID NOT NULL,
|
||||||
|
amount INT NOT NULL,
|
||||||
|
status VARCHAR(255) NOT NULL,
|
||||||
|
date DATE NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedInvoices = await Promise.all(
|
||||||
|
invoices.map(
|
||||||
|
(invoice) => client.sql`
|
||||||
|
INSERT INTO invoices (customer_id, amount, status, date)
|
||||||
|
VALUES (${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date})
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedInvoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedCustomers() {
|
||||||
|
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
||||||
|
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS customers (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL,
|
||||||
|
image_url VARCHAR(255) NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedCustomers = await Promise.all(
|
||||||
|
customers.map(
|
||||||
|
(customer) => client.sql`
|
||||||
|
INSERT INTO customers (id, name, email, image_url)
|
||||||
|
VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url})
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedCustomers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedRevenue() {
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS revenue (
|
||||||
|
month VARCHAR(4) NOT NULL UNIQUE,
|
||||||
|
revenue INT NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedRevenue = await Promise.all(
|
||||||
|
revenue.map(
|
||||||
|
(rev) => client.sql`
|
||||||
|
INSERT INTO revenue (month, revenue)
|
||||||
|
VALUES (${rev.month}, ${rev.revenue})
|
||||||
|
ON CONFLICT (month) DO NOTHING;
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedRevenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
await client.sql`BEGIN`;
|
||||||
|
await seedUsers();
|
||||||
|
await seedCustomers();
|
||||||
|
await seedInvoices();
|
||||||
|
await seedRevenue();
|
||||||
|
await client.sql`COMMIT`;
|
||||||
|
|
||||||
|
return Response.json({ message: 'Database seeded successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
await client.sql`ROLLBACK`;
|
||||||
|
return Response.json({ error }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
import { lusitana } from '@/app/ui/fonts';
|
||||||
import Search from '@/app/ui/search';
|
import Search from '@/app/ui/search';
|
||||||
import {
|
import { FormattedCustomersTable } from '@/app/lib/definitions';
|
||||||
CustomersTableType,
|
|
||||||
FormattedCustomersTable,
|
|
||||||
} from '@/app/lib/definitions';
|
|
||||||
|
|
||||||
export default async function CustomersTable({
|
export default async function CustomersTable({
|
||||||
customers,
|
customers,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import clsx from 'clsx';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
import { lusitana } from '@/app/ui/fonts';
|
||||||
import { fetchLatestInvoices } from '@/app/lib/data';
|
import { fetchLatestInvoices } from '@/app/lib/data';
|
||||||
|
|
||||||
export default async function LatestInvoices() {
|
export default async function LatestInvoices() {
|
||||||
const latestInvoices = await fetchLatestInvoices();
|
const latestInvoices = await fetchLatestInvoices();
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function DeleteInvoice({ id }: { id: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form action={deleteInvoiceWithId}>
|
<form action={deleteInvoiceWithId}>
|
||||||
<button className="rounded-md border p-2 hover:bg-gray-100">
|
<button type="submit" className="rounded-md border p-2 hover:bg-gray-100">
|
||||||
<span className="sr-only">Delete</span>
|
<span className="sr-only">Delete</span>
|
||||||
<TrashIcon className="w-5" />
|
<TrashIcon className="w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {};
|
||||||
experimental: {
|
|
||||||
ppr: 'incremental',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -3,43 +3,32 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"lint": "next lint",
|
|
||||||
"prettier": "prettier --write --ignore-unknown .",
|
|
||||||
"prettier:check": "prettier --check --ignore-unknown .",
|
|
||||||
"seed": "node -r dotenv/config ./scripts/seed.js",
|
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.1.4",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@types/node": "20.5.7",
|
"@vercel/postgres": "^0.8.0",
|
||||||
"@vercel/postgres": "^0.5.0",
|
"autoprefixer": "10.4.19",
|
||||||
"autoprefixer": "10.4.15",
|
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.1.1",
|
||||||
"next": "15.0.0-canary.28",
|
"next": "15.0.0-rc.0",
|
||||||
"next-auth": "^5.0.0-beta.4",
|
"next-auth": "5.0.0-beta.19",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.38",
|
||||||
"react": "19.0.0-rc-6230622a1a-20240610",
|
"react": "19.0.0-rc-f994737d14-20240522",
|
||||||
"react-dom": "19.0.0-rc-6230622a1a-20240610",
|
"react-dom": "19.0.0-rc-f994737d14-20240522",
|
||||||
"tailwindcss": "3.3.3",
|
"tailwindcss": "3.4.4",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.5.2",
|
||||||
"use-debounce": "^9.0.4",
|
"use-debounce": "^10.0.1",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.1",
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
"@types/node": "20.14.8",
|
||||||
"@types/react": "18.3.3",
|
"@types/react": "18.3.3",
|
||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "18.3.0"
|
||||||
"@vercel/style-guide": "^5.0.1",
|
|
||||||
"dotenv": "^16.3.1",
|
|
||||||
"eslint": "^8.52.0",
|
|
||||||
"eslint-config-next": "15.0.0-rc.0",
|
|
||||||
"eslint-config-prettier": "9.0.0",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"prettier-plugin-tailwindcss": "0.5.4"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17.0"
|
"node": ">=20.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3357
dashboard/final-example/pnpm-lock.yaml
generated
3357
dashboard/final-example/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
|||||||
const styleguide = require('@vercel/style-guide/prettier');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
...styleguide,
|
|
||||||
plugins: [...styleguide.plugins, 'prettier-plugin-tailwindcss'],
|
|
||||||
};
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,179 +0,0 @@
|
|||||||
const { db } = require('@vercel/postgres');
|
|
||||||
const {
|
|
||||||
invoices,
|
|
||||||
customers,
|
|
||||||
revenue,
|
|
||||||
users,
|
|
||||||
} = require('../app/lib/placeholder-data.js');
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
|
|
||||||
async function seedUsers(client) {
|
|
||||||
try {
|
|
||||||
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
|
||||||
// Create the "users" table if it doesn't exist
|
|
||||||
const createTable = await client.sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
email TEXT NOT NULL UNIQUE,
|
|
||||||
password TEXT NOT NULL
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
console.log(`Created "users" table`);
|
|
||||||
|
|
||||||
// Insert data into the "users" table
|
|
||||||
const insertedUsers = await Promise.all(
|
|
||||||
users.map(async (user) => {
|
|
||||||
const hashedPassword = await bcrypt.hash(user.password, 10);
|
|
||||||
return client.sql`
|
|
||||||
INSERT INTO users (id, name, email, password)
|
|
||||||
VALUES (${user.id}, ${user.name}, ${user.email}, ${hashedPassword})
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
|
||||||
`;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`Seeded ${insertedUsers.length} users`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
createTable,
|
|
||||||
users: insertedUsers,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error seeding users:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function seedInvoices(client) {
|
|
||||||
try {
|
|
||||||
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
|
||||||
|
|
||||||
// Create the "invoices" table if it doesn't exist
|
|
||||||
const createTable = await client.sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS invoices (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
customer_id UUID 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) => client.sql`
|
|
||||||
INSERT INTO invoices (customer_id, amount, status, date)
|
|
||||||
VALUES (${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(client) {
|
|
||||||
try {
|
|
||||||
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
|
||||||
|
|
||||||
// Create the "customers" table if it doesn't exist
|
|
||||||
const createTable = await client.sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS customers (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() 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) => client.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(client) {
|
|
||||||
try {
|
|
||||||
// Create the "revenue" table if it doesn't exist
|
|
||||||
const createTable = await client.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) => client.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 function main() {
|
|
||||||
const client = await db.connect();
|
|
||||||
|
|
||||||
await seedUsers(client);
|
|
||||||
await seedCustomers(client);
|
|
||||||
await seedInvoices(client);
|
|
||||||
await seedRevenue(client);
|
|
||||||
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((err) => {
|
|
||||||
console.error(
|
|
||||||
'An error occurred while attempting to seed the database:',
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
"app/lib/placeholder-data.js",
|
"app/lib/placeholder-data.ts",
|
||||||
"scripts/seed.js"
|
"scripts/seed.js"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "next/core-web-vitals"
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
18
|
|
||||||
@@ -10,9 +10,6 @@ import {
|
|||||||
import { formatCurrency } from './utils';
|
import { formatCurrency } from './utils';
|
||||||
|
|
||||||
export async function fetchRevenue() {
|
export async function fetchRevenue() {
|
||||||
// Add noStore() here to prevent the response from being cached.
|
|
||||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Artificially delay a response for demo purposes.
|
// Artificially delay a response for demo purposes.
|
||||||
// Don't do this in production :)
|
// Don't do this in production :)
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ const customers = [
|
|||||||
email: 'lee@robinson.com',
|
email: 'lee@robinson.com',
|
||||||
image_url: '/customers/lee-robinson.png',
|
image_url: '/customers/lee-robinson.png',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: '3958dc9e-787f-4377-85e9-fec4b6a6442a',
|
|
||||||
name: 'Steph Dietz',
|
|
||||||
email: 'steph@dietz.com',
|
|
||||||
image_url: '/customers/steph-dietz.png',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: '76d65c26-f784-44a2-ac19-586678f7c2f2',
|
id: '76d65c26-f784-44a2-ac19-586678f7c2f2',
|
||||||
name: 'Michael Novotny',
|
name: 'Michael Novotny',
|
||||||
@@ -86,7 +80,7 @@ const invoices = [
|
|||||||
date: '2023-08-05',
|
date: '2023-08-05',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
customer_id: customers[7].id,
|
customer_id: customers[2].id,
|
||||||
amount: 54246,
|
amount: 54246,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
date: '2023-07-16',
|
date: '2023-07-16',
|
||||||
@@ -150,9 +144,4 @@ const revenue = [
|
|||||||
{ month: 'Dec', revenue: 4800 },
|
{ month: 'Dec', revenue: 4800 },
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = {
|
export { users, customers, invoices, revenue };
|
||||||
users,
|
|
||||||
customers,
|
|
||||||
invoices,
|
|
||||||
revenue,
|
|
||||||
};
|
|
||||||
118
dashboard/starter-example/app/seed/route.ts
Normal file
118
dashboard/starter-example/app/seed/route.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { db } from '@vercel/postgres';
|
||||||
|
import { invoices, customers, revenue, users } from '../lib/placeholder-data';
|
||||||
|
|
||||||
|
const client = await db.connect();
|
||||||
|
|
||||||
|
async function seedUsers() {
|
||||||
|
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedUsers = await Promise.all(
|
||||||
|
users.map(async (user) => {
|
||||||
|
const hashedPassword = await bcrypt.hash(user.password, 10);
|
||||||
|
return client.sql`
|
||||||
|
INSERT INTO users (id, name, email, password)
|
||||||
|
VALUES (${user.id}, ${user.name}, ${user.email}, ${hashedPassword})
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
`;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedInvoices() {
|
||||||
|
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
||||||
|
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS invoices (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||||
|
customer_id UUID NOT NULL,
|
||||||
|
amount INT NOT NULL,
|
||||||
|
status VARCHAR(255) NOT NULL,
|
||||||
|
date DATE NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedInvoices = await Promise.all(
|
||||||
|
invoices.map(
|
||||||
|
(invoice) => client.sql`
|
||||||
|
INSERT INTO invoices (customer_id, amount, status, date)
|
||||||
|
VALUES (${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date})
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedInvoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedCustomers() {
|
||||||
|
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
||||||
|
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS customers (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL,
|
||||||
|
image_url VARCHAR(255) NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedCustomers = await Promise.all(
|
||||||
|
customers.map(
|
||||||
|
(customer) => client.sql`
|
||||||
|
INSERT INTO customers (id, name, email, image_url)
|
||||||
|
VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url})
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedCustomers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedRevenue() {
|
||||||
|
await client.sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS revenue (
|
||||||
|
month VARCHAR(4) NOT NULL UNIQUE,
|
||||||
|
revenue INT NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const insertedRevenue = await Promise.all(
|
||||||
|
revenue.map(
|
||||||
|
(rev) => client.sql`
|
||||||
|
INSERT INTO revenue (month, revenue)
|
||||||
|
VALUES (${rev.month}, ${rev.revenue})
|
||||||
|
ON CONFLICT (month) DO NOTHING;
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return insertedRevenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
await client.sql`BEGIN`;
|
||||||
|
await seedUsers();
|
||||||
|
await seedCustomers();
|
||||||
|
await seedInvoices();
|
||||||
|
await seedRevenue();
|
||||||
|
await client.sql`COMMIT`;
|
||||||
|
|
||||||
|
return Response.json({ message: 'Database seeded successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
await client.sql`ROLLBACK`;
|
||||||
|
return Response.json({ error }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,39 +3,32 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"prettier": "prettier --write --ignore-unknown .",
|
|
||||||
"prettier:check": "prettier --check --ignore-unknown .",
|
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.1.4",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@types/node": "20.5.7",
|
"@vercel/postgres": "^0.8.0",
|
||||||
"@vercel/postgres": "^0.5.0",
|
"autoprefixer": "10.4.19",
|
||||||
"autoprefixer": "10.4.15",
|
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.1.1",
|
||||||
"next": "15.0.0-canary.28",
|
"next": "15.0.0-rc.0",
|
||||||
"postcss": "8.4.31",
|
"next-auth": "5.0.0-beta.19",
|
||||||
"react": "19.0.0-rc-6230622a1a-20240610",
|
"postcss": "8.4.38",
|
||||||
"react-dom": "19.0.0-rc-6230622a1a-20240610",
|
"react": "19.0.0-rc-f994737d14-20240522",
|
||||||
"tailwindcss": "3.3.3",
|
"react-dom": "19.0.0-rc-f994737d14-20240522",
|
||||||
"typescript": "5.2.2",
|
"tailwindcss": "3.4.4",
|
||||||
"zod": "^3.22.2"
|
"typescript": "5.5.2",
|
||||||
|
"use-debounce": "^10.0.1",
|
||||||
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.1",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/react": "18.2.21",
|
"@types/node": "20.14.8",
|
||||||
"@types/react-dom": "18.2.14",
|
"@types/react": "18.3.3",
|
||||||
"@vercel/style-guide": "^5.0.1",
|
"@types/react-dom": "18.3.0"
|
||||||
"dotenv": "^16.3.1",
|
|
||||||
"eslint": "^8.52.0",
|
|
||||||
"eslint-config-next": "15.0.0-rc.0",
|
|
||||||
"eslint-config-prettier": "9.0.0",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"prettier-plugin-tailwindcss": "0.5.4"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17.0"
|
"node": ">=20.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3365
dashboard/starter-example/pnpm-lock.yaml
generated
3365
dashboard/starter-example/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -18,6 +18,7 @@
|
|||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
"app/lib/placeholder-data.js",
|
"app/lib/placeholder-data.ts",
|
||||||
"scripts/seed.js"
|
"scripts/seed.js"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2023 Vercel, Inc.
|
Copyright (c) 2024 Vercel, Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
Reference in New Issue
Block a user