### 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
Screenshots of the dashboard project showing desktop version Screenshot of the dashboard project showing mobile version
) } ``` -------------------------------- ### 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 (
{ 'use server' await signOut() }}>
) } ``` -------------------------------- ### 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} {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 (

Please log in to continue.

{/* Add submit button and error message display here */}
) } ``` -------------------------------- ### 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 (

Invoices

} >
) } ```