Skip to content

Commit 3555183

Browse files
committed
first commit
Signed-off-by: xizheyin <[email protected]>
0 parents  commit 3555183

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+9289
-0
lines changed

Diff for: .env.example

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copy from .env.local on the Vercel dashboard
2+
# https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database
3+
POSTGRES_URL=
4+
POSTGRES_PRISMA_URL=
5+
POSTGRES_URL_NON_POOLING=
6+
POSTGRES_USER=
7+
POSTGRES_HOST=
8+
POSTGRES_PASSWORD=
9+
POSTGRES_DATABASE=
10+
11+
# `openssl rand -base64 32`
12+
AUTH_SECRET=
13+
AUTH_URL=http://localhost:3000/api/auth

Diff for: .gitignore

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env*.local
29+
.env
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Cratespro-frontend

Diff for: app/api/crates/[name]/route.ts

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// /home/rust/workspace/next-learn/dashboard/starter-example/app/api/crates/[name]/route.ts
2+
3+
import { NextRequest, NextResponse } from 'next/server';
4+
5+
interface CrateInfo {
6+
name: string;
7+
version: string;
8+
description: string;
9+
repository: string;
10+
downloads: number;
11+
maintainers: Maintainer[];
12+
documentation: string;
13+
publishedDate: string;
14+
licenses: string[];
15+
dependencyLicenses: Record<string, number>;
16+
links: Record<string, string>;
17+
}
18+
19+
interface Maintainer {
20+
name: string;
21+
email: string;
22+
}
23+
24+
interface Dependency {
25+
name: string;
26+
version: string;
27+
}
28+
29+
interface Vulnerability {
30+
id: string;
31+
title: string;
32+
description: string;
33+
severity: 'low' | 'medium' | 'high';
34+
}
35+
36+
const crateInfo: CrateInfo = {
37+
name: 'example-crate',
38+
version: '1.0.0',
39+
description: 'This is an example crate.',
40+
repository: 'https://github.com/example/example-crate',
41+
downloads: 12345,
42+
maintainers: [
43+
{ name: 'John Doe', email: '[email protected]' },
44+
{ name: 'Jane Smith', email: '[email protected]' },
45+
],
46+
documentation: 'https://docs.example.com/example-crate',
47+
publishedDate: '2023-01-01',
48+
licenses: ['MIT', 'Apache-2.0'],
49+
dependencyLicenses: {
50+
'MIT': 5,
51+
'Apache-2.0': 3,
52+
},
53+
links: {
54+
homepage: 'https://example.com',
55+
repository: 'https://github.com/example/example-crate',
56+
},
57+
};
58+
59+
const dependencies: Dependency[] = [
60+
{ name: 'dep1', version: '1.0.0' },
61+
{ name: 'dep2', version: '2.0.0' },
62+
];
63+
64+
const vulnerabilities: Vulnerability[] = [
65+
{
66+
id: 'VULN-001',
67+
title: 'Example Vulnerability',
68+
description: 'This is an example vulnerability.',
69+
severity: 'high',
70+
},
71+
];
72+
73+
export async function GET(req: NextRequest, { params }: { params: { name: string } }) {
74+
const { name } = params;
75+
76+
if (!name || typeof name !== 'string') {
77+
return NextResponse.json({ error: 'Invalid crate name' }, { status: 400 });
78+
}
79+
80+
// Here you can add logic to fetch the actual data based on the crate name
81+
// For now, we'll return the example data
82+
83+
return NextResponse.json({
84+
crateInfo,
85+
dependencies,
86+
vulnerabilities,
87+
});
88+
}
89+
90+
export async function POST(req: NextRequest) {
91+
return NextResponse.json({ error: 'Method POST Not Allowed' }, { status: 405 });
92+
}

Diff for: app/layout.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import '@/app/ui/global.css';
2+
import { inter } from '@/app/ui/fonts';
3+
4+
5+
export default function RootLayout({
6+
children,
7+
}: {
8+
children: React.ReactNode;
9+
}) {
10+
return (
11+
<html lang="en">
12+
<body className={`${inter.className} antialiased`}>{children}</body>
13+
</html >
14+
);
15+
}

Diff for: app/lib/data.ts

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { sql } from '@vercel/postgres';
2+
import {
3+
CustomerField,
4+
CustomersTableType,
5+
InvoiceForm,
6+
InvoicesTable,
7+
LatestInvoiceRaw,
8+
Revenue,
9+
} from './definitions';
10+
import { formatCurrency } from './utils';
11+
12+
export async function fetchRevenue() {
13+
try {
14+
// Artificially delay a response for demo purposes.
15+
// Don't do this in production :)
16+
17+
// console.log('Fetching revenue data...');
18+
// await new Promise((resolve) => setTimeout(resolve, 3000));
19+
20+
const data = await sql<Revenue>`SELECT * FROM revenue`;
21+
22+
// console.log('Data fetch completed after 3 seconds.');
23+
24+
return data.rows;
25+
} catch (error) {
26+
// 使用 Revenue 类型
27+
const f: Revenue = {
28+
month: "February",
29+
revenue: 12010,
30+
};
31+
const j: Revenue = {
32+
month: "January",
33+
revenue: 1500,
34+
};
35+
return [f, j];
36+
console.error('Database Error:', error);
37+
throw new Error('Failed to fetch revenue data.');
38+
}
39+
}
40+
41+
export async function fetchLatestInvoices() {
42+
try {
43+
const data = await sql<LatestInvoiceRaw>`
44+
SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
45+
FROM invoices
46+
JOIN customers ON invoices.customer_id = customers.id
47+
ORDER BY invoices.date DESC
48+
LIMIT 5`;
49+
50+
const latestInvoices = data.rows.map((invoice) => ({
51+
...invoice,
52+
amount: formatCurrency(invoice.amount),
53+
}));
54+
return latestInvoices;
55+
} catch (error) {
56+
console.error('Database Error:', error);
57+
throw new Error('Failed to fetch the latest invoices.');
58+
}
59+
}
60+
61+
export async function fetchCardData() {
62+
try {
63+
// You can probably combine these into a single SQL query
64+
// However, we are intentionally splitting them to demonstrate
65+
// how to initialize multiple queries in parallel with JS.
66+
const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
67+
const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
68+
const invoiceStatusPromise = sql`SELECT
69+
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",
70+
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending"
71+
FROM invoices`;
72+
73+
const data = await Promise.all([
74+
invoiceCountPromise,
75+
customerCountPromise,
76+
invoiceStatusPromise,
77+
]);
78+
79+
const numberOfInvoices = Number(data[0].rows[0].count ?? '0');
80+
const numberOfCustomers = Number(data[1].rows[0].count ?? '0');
81+
const totalPaidInvoices = formatCurrency(data[2].rows[0].paid ?? '0');
82+
const totalPendingInvoices = formatCurrency(data[2].rows[0].pending ?? '0');
83+
84+
return {
85+
numberOfCustomers,
86+
numberOfInvoices,
87+
totalPaidInvoices,
88+
totalPendingInvoices,
89+
};
90+
} catch (error) {
91+
console.error('Database Error:', error);
92+
throw new Error('Failed to fetch card data.');
93+
}
94+
}
95+
96+
const ITEMS_PER_PAGE = 6;
97+
export async function fetchFilteredInvoices(
98+
query: string,
99+
currentPage: number,
100+
) {
101+
const offset = (currentPage - 1) * ITEMS_PER_PAGE;
102+
103+
try {
104+
const invoices = await sql<InvoicesTable>`
105+
SELECT
106+
invoices.id,
107+
invoices.amount,
108+
invoices.date,
109+
invoices.status,
110+
customers.name,
111+
customers.email,
112+
customers.image_url
113+
FROM invoices
114+
JOIN customers ON invoices.customer_id = customers.id
115+
WHERE
116+
customers.name ILIKE ${`%${query}%`} OR
117+
customers.email ILIKE ${`%${query}%`} OR
118+
invoices.amount::text ILIKE ${`%${query}%`} OR
119+
invoices.date::text ILIKE ${`%${query}%`} OR
120+
invoices.status ILIKE ${`%${query}%`}
121+
ORDER BY invoices.date DESC
122+
LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset}
123+
`;
124+
125+
return invoices.rows;
126+
} catch (error) {
127+
console.error('Database Error:', error);
128+
throw new Error('Failed to fetch invoices.');
129+
}
130+
}
131+
132+
export async function fetchInvoicesPages(query: string) {
133+
try {
134+
const count = await sql`SELECT COUNT(*)
135+
FROM invoices
136+
JOIN customers ON invoices.customer_id = customers.id
137+
WHERE
138+
customers.name ILIKE ${`%${query}%`} OR
139+
customers.email ILIKE ${`%${query}%`} OR
140+
invoices.amount::text ILIKE ${`%${query}%`} OR
141+
invoices.date::text ILIKE ${`%${query}%`} OR
142+
invoices.status ILIKE ${`%${query}%`}
143+
`;
144+
145+
const totalPages = Math.ceil(Number(count.rows[0].count) / ITEMS_PER_PAGE);
146+
return totalPages;
147+
} catch (error) {
148+
console.error('Database Error:', error);
149+
throw new Error('Failed to fetch total number of invoices.');
150+
}
151+
}
152+
153+
export async function fetchInvoiceById(id: string) {
154+
try {
155+
const data = await sql<InvoiceForm>`
156+
SELECT
157+
invoices.id,
158+
invoices.customer_id,
159+
invoices.amount,
160+
invoices.status
161+
FROM invoices
162+
WHERE invoices.id = ${id};
163+
`;
164+
165+
const invoice = data.rows.map((invoice) => ({
166+
...invoice,
167+
// Convert amount from cents to dollars
168+
amount: invoice.amount / 100,
169+
}));
170+
171+
return invoice[0];
172+
} catch (error) {
173+
console.error('Database Error:', error);
174+
throw new Error('Failed to fetch invoice.');
175+
}
176+
}
177+
178+
export async function fetchCustomers() {
179+
try {
180+
const data = await sql<CustomerField>`
181+
SELECT
182+
id,
183+
name
184+
FROM customers
185+
ORDER BY name ASC
186+
`;
187+
188+
const customers = data.rows;
189+
return customers;
190+
} catch (err) {
191+
console.error('Database Error:', err);
192+
throw new Error('Failed to fetch all customers.');
193+
}
194+
}
195+
196+
export async function fetchFilteredCustomers(query: string) {
197+
try {
198+
const data = await sql<CustomersTableType>`
199+
SELECT
200+
customers.id,
201+
customers.name,
202+
customers.email,
203+
customers.image_url,
204+
COUNT(invoices.id) AS total_invoices,
205+
SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending,
206+
SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid
207+
FROM customers
208+
LEFT JOIN invoices ON customers.id = invoices.customer_id
209+
WHERE
210+
customers.name ILIKE ${`%${query}%`} OR
211+
customers.email ILIKE ${`%${query}%`}
212+
GROUP BY customers.id, customers.name, customers.email, customers.image_url
213+
ORDER BY customers.name ASC
214+
`;
215+
216+
const customers = data.rows.map((customer) => ({
217+
...customer,
218+
total_pending: formatCurrency(customer.total_pending),
219+
total_paid: formatCurrency(customer.total_paid),
220+
}));
221+
222+
return customers;
223+
} catch (err) {
224+
console.error('Database Error:', err);
225+
throw new Error('Failed to fetch customer table.');
226+
}
227+
}

0 commit comments

Comments
 (0)