### Install Dependencies and Run Development Server
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/README.md
Commands to install project dependencies using npm and start the development server. This is essential for running the application locally.
```shell
npm install
npm run dev
```
--------------------------------
### Install Next Themes for Theme Management
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
Installs the 'next-themes' package, which enables easy implementation of dark/light mode and system theme preference in Next.js applications. It's a common dependency for user interface customization.
```bash
pnpm add next-themes
```
--------------------------------
### Add Skeleton Component with shadcn-ui
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
This command uses pnpm to execute the shadcn-ui installer and add the skeleton component to the project. Skeleton loaders provide a visual placeholder while content is loading.
```bash
pnpm dlx shadcn-ui@latest add skeleton
```
--------------------------------
### Clone Repository and Navigate
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/README.md
Instructions for cloning the project repository using Git and navigating into the project directory. This is a standard setup step for most development projects.
```shell
git clone git@github.com:basir/next-pg-shadcn-ecommerce.git
cd next-pg-shadcn-ecommerce
```
--------------------------------
### Add Dropdown Menu Component with shadcn-ui
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
This command uses pnpm to execute the shadcn-ui installer and add the dropdown-menu component to the project. This component is often used for interactive menus in web applications.
```bash
pnpm dlx shadcn-ui@latest add dropdown-menu
```
--------------------------------
### Add Next-Auth and Bcryptjs for Authentication
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/02. create login page.md
Installs the beta version of next-auth for authentication and bcryptjs for secure password hashing. Also installs the corresponding TypeScript types for bcryptjs. These are essential for handling user credentials securely.
```bash
pnpm add next-auth@beta bcryptjs
pnpm add -D @types/bcryptjs
```
--------------------------------
### Loading Component for Dashboard (React/TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
A simple loading component that displays a dashboard skeleton while data is being fetched. It imports and renders the `DashboardSkeleton` component.
```tsx
import DashboardSkeleton from '@/components/shared/skeletons'
export default function Loading() {
return
}
```
--------------------------------
### Implement Theme Provider in app/layout.tsx
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
Sets up the ThemeProvider component from 'next-themes' in the root layout file. This enables the application to manage and switch between different themes (system, light, dark) by applying attributes to the HTML element.
```typescript
{children}
```
--------------------------------
### Define Root Layout and Metadata (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/01. create next app.md
Sets up the root HTML layout for the Next.js application, including metadata like title and description, and applies global styles and fonts. It uses environment variables for dynamic metadata and integrates custom fonts.
```typescript
import { Metadata } from 'next'
import { inter, lusitana } from './shared/fonts'
// Assuming APP_NAME, APP_DESCRIPTION, and SERVER_URL are defined elsewhere, e.g., in lib/constants.ts
// For demonstration, let's assume they are accessible here.
const APP_NAME = 'NextAdmin'; // Placeholder
const APP_DESCRIPTION = 'An modern dashboard'; // Placeholder
const SERVER_URL = 'http://localhost:3000'; // Placeholder
export const metadata: Metadata = {
title: {
template: `%s | ${APP_NAME}`,
default: APP_NAME,
},
description: APP_DESCRIPTION,
metadataBase: new URL(SERVER_URL),
}
export default function RootLayout({
children,
}: { children: React.ReactNode }) {
return (
{children}
)
}
```
--------------------------------
### Render Landing Page Content (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/01. create next app.md
The main landing page component for the admin dashboard. It features a header with the application logo, a welcome message, and navigation to the login page. It also displays responsive images for different screen sizes.
```typescript
import Link from 'next/link'
import Image from 'next/image'
import AppLogo from '@/components/shared/app-logo'
import { lusitana } from '@/shared/fonts'
import { ArrowRightIcon } from '@heroicons/react/24/outline'
export default function Page() {
return (
Welcome to Next 15 Admin Dashboard.
Log in
)
}
```
--------------------------------
### Dashboard Overview Page (React/TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
Renders the main dashboard overview page, displaying a title and a grid of data cards, revenue charts, and latest invoices using skeleton components while data is loading. It utilizes custom hooks or components like `CardsSkeleton`, `RevenueChartSkeleton`, and `LatestInvoicesSkeleton` for placeholder UI.
```tsx
import { lusitana } from '@/app/ui/fonts';
import { CardsSkeleton } from '@/components/shared/skeletons';
import { RevenueChartSkeleton } from '@/components/shared/skeletons';
import { LatestInvoicesSkeleton } from '@/components/shared/skeletons';
export default async function Page() {
return (
Dashboard
)
}
```
--------------------------------
### Create Various Skeleton Loaders for Dashboard Components
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
Provides React components that use the shadcn-ui Skeleton component to create placeholder UIs for different dashboard elements, including cards, charts, and invoice lists. These are essential for a good perceived performance while data is being fetched.
```typescript
export function CardSkeleton() {
return (
)
}
export function CardsSkeleton() {
return (
<>
>
)
}
export function RevenueChartSkeleton() {
return (
)
}
export function InvoiceSkeleton() {
return (
)
}
export function LatestInvoicesSkeleton() {
return (
```
--------------------------------
### Define Server and App Constants (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/01. create next app.md
Defines constants for server URL, application name, description, and items per page, often used for configuration and branding within the Next.js application. These values are typically sourced from environment variables.
```typescript
export const SERVER_URL =
process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000'
export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME || 'NextAdmin'
export const APP_DESCRIPTION =
process.env.NEXT_PUBLIC_APP_DESCRIPTION ||
'An modern dashboard built with Next.js 15, Postgres, Shadcn'
export const ITEMS_PER_PAGE = Number(process.env.ITEMS_PER_PAGE) || 5
```
--------------------------------
### Implement Sidebar Navigation Layout
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
Defines the structure for the main sidebar navigation of the dashboard. It includes placeholders for an application logo, navigation links, and integrates the ModeToggle component and a sign-out button. The layout adapts between mobile and desktop views.
```typescript
export default function SideNav() {
return (
)
}
```
--------------------------------
### Dashboard Skeleton Components (React/TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
Defines skeleton components for cards and invoice lists, used for placeholder UI. These components use a library like 'shadcn/ui' for their styling and structure, indicated by the `className` prop.
```tsx
import { Skeleton } from '@/components/ui/skeleton'
import { Card, CardContent, CardHeader } from '@/components/ui/card'
function CardSkeleton() {
return (
)
}
export default function DashboardSkeleton() {
return (
<>
>
)
}
```
--------------------------------
### Configure Google Fonts (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/01. create next app.md
Imports and configures Inter and Lusitana fonts from Google Fonts for use in the Next.js application. These font configurations are then exported for use in styling components.
```typescript
import { Inter, Lusitana } from 'next/font/google'
export const inter = Inter({ subsets: ['latin'] })
export const lusitana = Lusitana({
weight: ['400', '700'],
subsets: ['latin'],
})
```
--------------------------------
### Define Dashboard Page Layout
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
Establishes the primary layout for the dashboard pages. It creates a two-column layout: a fixed-width sidebar on the left and a scrollable content area on the right. This structure is responsive, adapting to different screen sizes.
```typescript
export default function Layout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
--------------------------------
### Create a Theme Mode Toggle Component
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/03. create dashboard page.md
A React component for toggling between 'light', 'dark', and 'system' themes using 'next-themes' and shadcn-ui components. It displays a sun/moon icon and the current theme name, ensuring it only renders after client-side mounting to avoid hydration mismatches.
```typescript
export default function ModeToggle() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
return (
Appearance setTheme('system')}
>
System
setTheme('light')}
>
Light
setTheme('dark')}
>
Dark
)
}
```
--------------------------------
### Create Application Logo Component (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/01. create next app.md
A React component that renders an application logo alongside its name. It uses Next.js Image and Link components, styled with Tailwind CSS, and incorporates custom fonts for the application name.
```typescript
import Link from 'next/link'
import Image from 'next/image'
import { lusitana } from './shared/fonts'
// Assuming APP_NAME is defined elsewhere, e.g., in lib/constants.ts
// For demonstration, let's assume it is accessible here.
const APP_NAME = 'NextAdmin'; // Placeholder
export default function AppLogo() {
return (
{APP_NAME}
)
}
```
--------------------------------
### Get User from Database (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/08. authenticate user from database.md
Fetches a user from the database based on their email address. It handles cases where the user is not found by throwing an error. This function is a dependency for the authentication process.
```typescript
export async function getUser(email: string) {
const user = await db.query.users.findFirst({
where: eq(users.email, email as string),
})
if (!user) throw new Error('User not found')
return user
}
```
--------------------------------
### Create Invoice Page in Next.js
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/10. create or update invoices.md
Sets up the metadata and renders the Create Invoice page, fetching customer data and displaying a breadcrumb navigation and a form.
```tsx
import {
Metadata,
} from 'next'
import Breadcrumbs from '@/components/breadcrumbs'
import Form from '@/components/shared/invoices/create-form'
import { fetchCustomers } from '@/lib/actions/customer.actions'
export const metadata: Metadata = {
title: 'Create Invoice',
}
export default async function Page() {
const customers = await fetchCustomers()
return (
)
}
```
--------------------------------
### Configure Next-Auth Settings
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/02. create login page.md
Sets up the core configuration for next-auth, including the sign-in page path. It defines the structure for authentication providers and callbacks, such as the authorized callback to manage user sessions and redirects based on login status and URL paths. Note that providers are added in `auth.ts` due to bcrypt compatibility.
```typescript
import type { NextAuthConfig } from 'next-auth'
export const authConfig = {
pages: {
signIn: '/login',
},
providers: [
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
// while this file is also used in non-Node.js environments
],
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard')
if (isOnDashboard) {
if (isLoggedIn) return true
return false // Redirect unauthenticated users to login page
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl))
}
return true
},
},
} satisfies NextAuthConfig
```
--------------------------------
### Set Up Next-Auth Middleware
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/02. create login page.md
Configures the middleware for next-auth to protect routes. It exports the authentication handler and defines a matcher configuration to apply the middleware to all routes except static assets and API calls, ensuring that only authenticated users can access protected sections of the application.
```typescript
import NextAuth from 'next-auth'
import { authConfig } from './auth.config'
export default NextAuth(authConfig).auth
export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: [
'/((?!api|_next/static|_next/image|.*\.svg$|.*\.png$|.*\.jpeg$).*)',
],
}
```
--------------------------------
### Fetch Card Data from Database (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/05. load data from database.md
Fetches counts of invoices and customers, and sums of paid and pending invoice amounts from the database. It uses `Promise.all` for concurrent fetching and `formatCurrency` for output formatting. Error handling is included for database operations.
```typescript
import { count, sql } from 'drizzle-orm'
import { db } from '@/lib/db'
import { invoices, customers } from '@/lib/schema'
import { formatCurrency } from '@/lib/utils'
export async function fetchCardData() {
try {
const invoiceCountPromise = db.select({ count: count() }).from(invoices)
const customerCountPromise = db
.select({ count: count() })
.from(customers)
const invoiceStatusPromise = db
.select({
paid: sql`SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END)`,
pending: sql`SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END)`,
})
.from(invoices)
const data = await Promise.all([
invoiceCountPromise,
customerCountPromise,
invoiceStatusPromise,
])
const numberOfInvoices = Number(data[0][0].count ?? '0')
const numberOfCustomers = Number(data[1][0].count ?? '0')
const totalPaidInvoices = formatCurrency(data[2][0].paid ?? '0')
const totalPendingInvoices = formatCurrency(data[2][0].pending ?? '0')
return {
numberOfCustomers,
numberOfInvoices,
totalPaidInvoices,
totalPendingInvoices,
}
} catch (error) {
console.error('Database Error:', error)
throw new Error('Failed to fetch card data.')
}
}
```
--------------------------------
### Seed Database
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/README.md
Command to execute the database seeding script using tsx. This populates the database with initial data required for the application.
```shell
npx tsx ./db/seed
```
--------------------------------
### Fetch Latest Invoices using TypeScript and Database Actions
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/07. create latest invoices table.md
Fetches the 5 most recent invoices from the database, joining with customer information. It formats the currency for each invoice. Dependencies include a database client, `eq` and `desc` functions, and a `formatCurrency` utility.
```typescript
export async function fetchLatestInvoices() {
try {
const data = await db
.select({
amount: invoices.amount,
name: customers.name,
image_url: customers.image_url,
email: customers.email,
id: invoices.id,
})
.from(invoices)
.innerJoin(customers, eq(invoices.customer_id, customers.id))
.orderBy(desc(invoices.date))
.limit(5)
const latestInvoices = data.map((invoice) => ({
...invoice,
amount: formatCurrency(invoice.amount),
}))
return latestInvoices
} catch (error) {
console.error('Database Error:', error)
throw new Error('Failed to fetch the latest invoices.')
}
}
```
--------------------------------
### Create and Update Invoice Server Actions (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/10. create or update invoices.md
Handles the creation and updating of invoices in the database. It uses Zod for validation, prepares data for insertion, and includes error handling for database operations. Redirects the user after successful operations.
```typescript
const FormSchema = z.object({
id: z.string(),
customerId: z.string({
invalid_type_error: 'Please select a customer.',
}),
amount: z.coerce
.number()
.gt(0, { message: 'Please enter an amount greater than $0.' }),
status: z.enum(['pending', 'paid'], {
invalid_type_error: 'Please select an invoice status.',
}),
date: z.string(),
})
const CreateInvoice = FormSchema.omit({ id: true, date: true })
const UpdateInvoice = FormSchema.omit({ date: true, id: true })
export type State = {
errors?: {
customerId?: string[]
amount?: string[]
status?: string[]
}
message?: string | null
}
export async function createInvoice(prevState: State, formData: FormData) {
// Validate form fields using Zod
const validatedFields = CreateInvoice.safeParse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
})
// If form validation fails, return errors early. Otherwise, continue.
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Create Invoice.',
}
}
// Prepare data for insertion into the database
const { customerId, amount, status } = validatedFields.data
const amountInCents = amount * 100
const date = new Date().toISOString().split('T')[0]
// Insert data into the database
try {
await db.insert(invoices).values({
customer_id: customerId,
amount: amountInCents,
status,
date,
})
} catch (error) {
// If a database error occurs, return a more specific error.
return {
message: 'Database Error: Failed to Create Invoice.',
}
}
// Revalidate the cache for the invoices page and redirect the user.
revalidatePath('/dashboard/invoices')
redirect('/dashboard/invoices')
}
export async function updateInvoice(
id: string,
prevState: State,
formData: FormData
) {
const validatedFields = UpdateInvoice.safeParse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
})
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Update Invoice.',
}
}
const { customerId, amount, status } = validatedFields.data
const amountInCents = amount * 100
try {
await db
.update(invoices)
.set({
customer_id: customerId,
amount: amountInCents,
status,
})
.where(eq(invoices.id, id))
} catch (error) {
return { message: 'Database Error: Failed to Update Invoice.' }
}
revalidatePath('/dashboard/invoices')
redirect('/dashboard/invoices')
}
```
--------------------------------
### Dashboard Overview Page Layout (Next.js)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/05. load data from database.md
The main dashboard page that displays a title 'Dashboard' and a grid of statistic cards. It uses `Suspense` to render the `StatCardsWrapper` component, providing a loading fallback while data is being fetched.
```jsx
import { lusitana } from '@/lib/fonts'
import { Suspense } from 'react'
import StatCardsWrapper from '@/components/shared/dashboard/stat-cards-wrapper'
import { CardsSkeleton } from '@/components/skeletons'
// ... other imports
// Inside the page component:
Dashboard
}>
```
--------------------------------
### Configure Drizzle Kit for Migrations
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/04. connect to database.md
Configures Drizzle Kit for database migrations. This file specifies the schema location, output directory for migrations, SQL dialect, and database credentials, typically read from environment variables.
```typescript
import '@/db/env-config'
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: './db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.POSTGRES_URL!,
},
})
```
--------------------------------
### Create Server Action for Authentication
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/02. create login page.md
Implements a server action named `authenticate` that handles form submissions for login. It uses `signIn` from next-auth to attempt authentication and catches potential `AuthError` exceptions, returning specific error messages for invalid credentials or other issues.
```typescript
'use server'
import { signIn } from '@/auth'
import { AuthError } from 'next-auth'
export async function authenticate(
prevState: string | undefined,
formData: FormData
) {
try {
await signIn('credentials', formData)
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return 'Invalid credentials.'
default:
return 'Something went wrong.'
}
}
throw error
}
}
```
--------------------------------
### Implement Credentials Provider for Login
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/02. create login page.md
Integrates the 'credentials' provider with next-auth, enabling custom login logic. The `authorize` function finds a user by email and verifies the provided password against the stored hash using bcrypt's `compare` function. Returns the user object on success or null on failure.
```typescript
import NextAuth from 'next-auth'
import { authConfig } from './auth.config'
import Credentials from 'next-auth/providers/credentials'
import { compare } from 'bcryptjs'
import { users } from '@/lib/placeholder-data'
export const {
auth,
signIn,
signOut,
} = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const user = users.find((x) => x.email === credentials.email)
if (!user) return null
const passwordsMatch = await compare(
credentials.password as string,
user.password
)
if (passwordsMatch) return user
console.log('Invalid credentials')
return null
},
}),
],
})
```
--------------------------------
### Define Placeholder User Data with Hashed Passwords
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/02. create login page.md
This TypeScript code defines a sample user array. Each user object includes an ID, name, email, and a hashed password generated using bcryptjs. This data is used for simulating user authentication during development.
```typescript
import { hashSync } from 'bcryptjs'
const users = [
{
id: '410544b2-4001-4271-9855-fec4b6a6442a',
name: 'User',
email: 'user@nextmail.com',
password: hashSync('123456', 10),
},
]
export { users }
```
--------------------------------
### Initialize Drizzle ORM with Vercel Postgres
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/04. connect to database.md
Initializes Drizzle ORM with the Vercel Postgres driver and the defined schema. This sets up the database connection object for use throughout the application.
```typescript
import * as schema from './schema'
import { drizzle } from 'drizzle-orm/vercel-postgres'
import { sql } from '@vercel/postgres'
const db = drizzle(sql, {
schema,
})
export default db
```
--------------------------------
### Fetch and Display Revenue Chart Data (TypeScript) - Next.js
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/06. display revenue chart.md
A wrapper component for the RevenueChart that asynchronously fetches revenue data and integrates it within a Card component. It utilizes a placeholder for the 'lusitana' font and assumes 'fetchRevenue' is an available async function.
```typescript
export default async function RevenueChartWrapper() {
const revenue = await fetchRevenue()
return (
Recent Revenue
)
}
```
--------------------------------
### Build Login Form Component
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/02. create login page.md
A React component that renders a login form using `useActionState` hook for managing form state and submission. It includes input fields for email and password, with associated labels and icons. The form utilizes the `authenticate` server action for submission.
```typescript
import { useActionState } from 'react-dom'
import { lusitana } from '@/app/ui/fonts' // Assuming lusitana font is imported
import { AtSign, LockKeyhole } from 'lucide-react' // Assuming these icons are imported
import { authenticate } from '@/lib/actions/user.actions'
export default function LoginForm() {
const [errorMessage, formAction, isPending] = useActionState(
authenticate,
undefined
)
return (
)
}
```
--------------------------------
### Generate Placeholder Customer and Invoice Data
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/04. connect to database.md
Provides placeholder data for customers and invoices, including IDs, names, emails, image URLs, amounts, statuses, and dates. This data can be used for seeding the database or for testing purposes.
```typescript
const customers = [
{
id: 'd6e15727-9fe1-4961-8c5b-ea44a9bd81aa',
name: 'Amari Hart',
email: 'amari@gmail.com',
image_url: '/customers/a1.jpeg',
},
{
id: '3958dc9e-712f-4377-85e9-fec4b6a6442a',
name: 'Alexandria Brown',
email: 'brown@gmail.com',
image_url: '/customers/a2.jpeg',
},
{
id: '3958dc9e-742f-4377-85e9-fec4b6a6442a',
name: 'Emery Cabrera',
email: 'emery@example.com',
image_url: '/customers/a3.jpeg',
},
{
id: '76d65c26-f784-44a2-ac19-586678f7c2f2',
name: 'Michael Novotny',
email: 'michael@novotny.com',
image_url: '/customers/a4.jpeg',
},
{
id: 'CC27C14A-0ACF-4F4A-A6C9-D45682C144B9',
name: 'Lily Conrad',
email: 'lily@yahoo.com',
image_url: '/customers/a5.jpeg',
},
{
id: '13D07535-C59E-4157-A011-F8D2EF4E0CBB',
name: 'Ricky Mata',
email: 'ricky@live.com',
image_url: '/customers/a6.jpeg',
},
]
const invoices = [
{
customer_id: customers[0].id,
amount: 15795,
status: 'pending',
date: '2022-12-06',
},
{
customer_id: customers[1].id,
amount: 20348,
status: 'pending',
date: '2022-11-14',
},
{
customer_id: customers[4].id,
amount: 3040,
status: 'paid',
date: '2022-10-29',
},
{
customer_id: customers[3].id,
amount: 44800,
status: 'paid',
date: '2023-09-10',
},
{
customer_id: customers[5].id,
amount: 34577,
status: 'pending',
date: '2023-08-05',
},
{
customer_id: customers[2].id,
amount: 54246,
status: 'pending',
date: '2023-07-16',
},
{
customer_id: customers[0].id,
amount: 666,
status: 'pending',
date: '2023-06-27',
},
{
customer_id: customers[3].id,
amount: 32545,
status: 'paid',
date: '2023-06-09',
},
{
customer_id: customers[4].id,
amount: 1250,
status: 'paid',
date: '2023-06-17',
},
{
customer_id: customers[5].id,
amount: 8546,
status: 'paid',
date: '2023-06-07',
},
{
customer_id: customers[1].id,
amount: 500,
status: 'paid',
date: '2023-08-19',
},
{
customer_id: customers[5].id,
amount: 8945,
```
--------------------------------
### Fetch Invoice by ID using Drizzle ORM (TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/10. create or update invoices.md
Fetches a single invoice from the database by its ID using Drizzle ORM. It handles data mapping, status conversion, and amount conversion from cents to dollars.
```ts
import {
eq,
} from 'drizzle-orm'
import { db } from '@/lib/db'
import { invoices } from '@/lib/db/schema'
import { InvoiceForm } from '@/lib/definitions'
export async function fetchInvoiceById(id: string) {
try {
const data = await db
.select({
id: invoices.id,
customer_id: invoices.customer_id,
amount: invoices.amount,
status: invoices.status,
date: invoices.date,
})
.from(invoices)
.where(eq(invoices.id, id))
const invoice = data.map((invoice) => ({
...invoice,
// Convert amount from cents to dollars
status: invoice.status === 'paid' ? 'paid' : 'pending',
amount: invoice.amount / 100,
}))
return invoice[0] as InvoiceForm
} catch (error) {
console.error('Database Error:', error)
throw new Error('Failed to fetch invoice.')
}
}
```
--------------------------------
### Fetch and Render Edit Invoice Page (Next.js/TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/10. create or update invoices.md
This Next.js page component fetches invoice and customer data for a given ID and renders the edit form. It handles cases where the invoice is not found by returning a 404 error.
```tsx
export const metadata: Metadata = {
title: 'Edit Invoice',
}
export default async function Page({ params }: { params: { id: string } }) {
const id = params.id
const [invoice, customers] = await Promise.all([
fetchInvoiceById(id),
fetchCustomers(),
])
if (!invoice) {
notFound()
}
return (
)
}
```
--------------------------------
### Create Revenue Chart Component (TypeScript) - Recharts
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/06. display revenue chart.md
Defines a reusable revenue chart component using Recharts and TypeScript for a Next.js application. It handles cases with no data and configures BarChart with XAxis, YAxis, and Bar for displaying monthly revenue.
```typescript
'use client'
export default function RevenueChart({
revenue,
}: {
revenue: { month: string; revenue: number }[]
}) {
if (!revenue || revenue.length === 0) {
return
No data available.
}
return (
`$${value}`}
/>
)
}
```
--------------------------------
### Display Stat Cards in Dashboard (React/TypeScript)
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/05. load data from database.md
A React component that fetches data using `fetchCardData` and renders individual `StatCard` components to display key metrics like collected amount, pending invoices, total invoices, and total customers. It utilizes `Suspense` for fallback UI.
```typescript
import { lusitana } from '@/lib/fonts'
import { Suspense } from 'react'
import { fetchCardData } from '@/lib/actions/invoice.actions'
import { BanknoteIcon, ClockIcon, InboxIcon, UsersIcon } from 'lucide-react'
import { Card, CardContent, CardHeader } from '@/components/ui/card'
const iconMap = {
collected: BanknoteIcon,
customers: UsersIcon,
pending: ClockIcon,
invoices: InboxIcon,
}
export default async function StatCardsWrapper() {
const {
numberOfInvoices,
numberOfCustomers,
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData()
return (
<>
>
)
}
export function StatCard({
title,
value,
type,
}: {
title: string
value: number | string
type: 'invoices' | 'customers' | 'pending' | 'collected'
}) {
const Icon = iconMap[type]
return (
{Icon ? : null}
{title}
{value}
)
}
```
--------------------------------
### Display Invoices Page in Next.js 15
Source: https://github.com/basir/next-15-admin-dashboard/blob/main/lessons/09. list or delete invoices.md
Renders the main invoices page, including search, create invoice button, invoice table, and pagination. Fetches total invoice pages based on query. Uses Suspense for fallback UI.
```typescript
export const metadata: Metadata = {
title: 'Invoices',
}
export default async function Page({
searchParams,
}: {
searchParams?: {
query?: string
page?: string
}
}) {
const query = searchParams?.query || ''
const currentPage = Number(searchParams?.page) || 1
const totalPages = await fetchInvoicesPages(query)
return (