diff --git a/dashboard/15-final/app/dashboard/customers/page.tsx b/dashboard/15-final/app/dashboard/customers/page.tsx index dcd62af..caf9718 100644 --- a/dashboard/15-final/app/dashboard/customers/page.tsx +++ b/dashboard/15-final/app/dashboard/customers/page.tsx @@ -1,5 +1,10 @@ import { fetchFilteredCustomers } from '@/app/lib/data'; import CustomersTable from '@/app/ui/customers/table'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Customers', +}; export default async function Page({ searchParams, diff --git a/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx b/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx index d9a5750..7063f05 100644 --- a/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx +++ b/dashboard/15-final/app/dashboard/invoices/[id]/edit/page.tsx @@ -2,6 +2,11 @@ import Form from '@/app/ui/invoices/edit-form'; import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'; import { fetchInvoiceById, fetchCustomers } from '@/app/lib/data'; import { notFound } from 'next/navigation'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Edit Invoice', +}; export default async function Page({ params }: { params: { id: string } }) { const id = params.id; diff --git a/dashboard/15-final/app/dashboard/invoices/create/page.tsx b/dashboard/15-final/app/dashboard/invoices/create/page.tsx index 413db57..de8107c 100644 --- a/dashboard/15-final/app/dashboard/invoices/create/page.tsx +++ b/dashboard/15-final/app/dashboard/invoices/create/page.tsx @@ -1,6 +1,11 @@ import { fetchCustomers } from '@/app/lib/data'; import Form from '@/app/ui/invoices/create-form'; import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Create Invoice', +}; export default async function Page() { const customers = await fetchCustomers(); diff --git a/dashboard/15-final/app/dashboard/invoices/page.tsx b/dashboard/15-final/app/dashboard/invoices/page.tsx index 1f4dca1..452ece1 100644 --- a/dashboard/15-final/app/dashboard/invoices/page.tsx +++ b/dashboard/15-final/app/dashboard/invoices/page.tsx @@ -6,6 +6,11 @@ import { lusitana } from '@/app/ui/fonts'; import { InvoicesTableSkeleton } from '@/app/ui/dashboard/skeletons'; import { Suspense } from 'react'; import { fetchInvoicesPages } from '@/app/lib/data'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Invoices', +}; export default async function Page({ searchParams, diff --git a/dashboard/15-final/app/layout.tsx b/dashboard/15-final/app/layout.tsx index b56de11..a0884e8 100644 --- a/dashboard/15-final/app/layout.tsx +++ b/dashboard/15-final/app/layout.tsx @@ -1,12 +1,14 @@ import '@/app/ui/global.css'; import { inter } from '@/app/ui/fonts'; -import type { Metadata } from 'next'; +import { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', + title: { + template: '%s | Acme Dashboard', + default: 'Acme Dashboard', + }, + description: 'The official Next.js Learn Dashboard built with App Router.', }; - export default function RootLayout({ children, }: { diff --git a/dashboard/15-final/app/lib/actions.ts b/dashboard/15-final/app/lib/actions.ts index 4850ea3..1ffc9a8 100644 --- a/dashboard/15-final/app/lib/actions.ts +++ b/dashboard/15-final/app/lib/actions.ts @@ -67,7 +67,7 @@ export async function createInvoice(prevState: State, formData: FormData) { }; } - // Revalidate cache and redirect user to invoices page + // Revalidate the cache for the invoices page and redirect the user. revalidatePath('/dashboard/invoices'); redirect('/dashboard/invoices'); } diff --git a/dashboard/15-final/app/lib/data.ts b/dashboard/15-final/app/lib/data.ts index e0ec01d..63bef45 100644 --- a/dashboard/15-final/app/lib/data.ts +++ b/dashboard/15-final/app/lib/data.ts @@ -5,6 +5,7 @@ import { InvoiceForm, InvoicesTable, LatestInvoiceRaw, + User, Revenue, } from './definitions'; import { formatCurrency } from './utils'; @@ -241,3 +242,13 @@ export async function fetchFilteredCustomers(query: string) { throw new Error('Failed to fetch customer table.'); } } + +export async function getUser(email: string) { + try { + const user = await sql`SELECT * from USERS where email=${email}`; + return user.rows[0] as User; + } catch (error) { + console.error('Failed to fetch user:', error); + throw new Error('Failed to fetch user.'); + } +} diff --git a/dashboard/15-final/app/opengraph-image.png b/dashboard/15-final/app/opengraph-image.png index 9d16698..569707e 100644 Binary files a/dashboard/15-final/app/opengraph-image.png and b/dashboard/15-final/app/opengraph-image.png differ diff --git a/dashboard/15-final/app/ui/login-form.tsx b/dashboard/15-final/app/ui/login-form.tsx index 08c6b9d..4b07e26 100644 --- a/dashboard/15-final/app/ui/login-form.tsx +++ b/dashboard/15-final/app/ui/login-form.tsx @@ -1,13 +1,16 @@ 'use client'; -import { signIn } from 'next-auth/react'; -import { useRouter } from 'next/navigation'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { lusitana } from '@/app/ui/fonts'; -import { AtSymbolIcon, KeyIcon } from '@heroicons/react/24/outline'; +import { + AtSymbolIcon, + KeyIcon, + ArrowRightIcon, +} from '@heroicons/react/24/outline'; import { Button } from './button'; -import { ArrowRightIcon } from '@heroicons/react/20/solid'; import AcmeLogo from './acme-logo'; +import { useRouter } from 'next/navigation'; +import { signIn } from 'next-auth/react'; export default function LoginForm() { const [email, setEmail] = useState(''); diff --git a/dashboard/15-final/auth.ts b/dashboard/15-final/auth.ts index a48dd22..b00a4de 100644 --- a/dashboard/15-final/auth.ts +++ b/dashboard/15-final/auth.ts @@ -1,18 +1,7 @@ import { NextAuthOptions } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; +import { getUser } from '@/app/lib/data'; import bcrypt from 'bcrypt'; -import { User } from '@/app/lib/definitions'; -import { sql } from '@vercel/postgres'; - -async function getUser(email: string) { - try { - const user = await sql`SELECT * from USERS where email=${email}`; - return user.rows[0] as User; - } catch (error) { - console.error('Failed to fetch user:', error); - throw new Error('Failed to fetch user.'); - } -} export const authOptions: NextAuthOptions = { providers: [ diff --git a/dashboard/15-final/package-lock.json b/dashboard/15-final/package-lock.json index c6d219c..54c4b94 100644 --- a/dashboard/15-final/package-lock.json +++ b/dashboard/15-final/package-lock.json @@ -18,7 +18,7 @@ "bcrypt": "^5.1.1", "clsx": "^2.0.0", "next": "^13.5.3", - "next-auth": "^4.23.1", + "next-auth": "^4.23.2", "postcss": "8.4.31", "react": "18.2.0", "react-dom": "18.2.0", @@ -48,9 +48,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", - "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1073,9 +1073,9 @@ } }, "node_modules/jose": { - "version": "4.14.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz", - "integrity": "sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.3.tgz", + "integrity": "sha512-RZJdL9Qjd1sqNdyiVteRGV/bnWtik/+PJh1JP4kT6+x1QQMn+7ryueRys5BEueuayvSVY8CWGCisCDazeRLTuw==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -1302,9 +1302,9 @@ } }, "node_modules/next-auth": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.23.1.tgz", - "integrity": "sha512-mL083z8KgRtlrIV6CDca2H1kduWJuK/3pTS0Fe2og15KOm4v2kkLGdSDfc2g+019aEBrJUT0pPW2Xx42ImN1WA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.23.2.tgz", + "integrity": "sha512-VRmInu0r/yZNFQheDFeOKtiugu3bt90Po3owAQDnFQ3YLQFmUKgFjcE2+3L0ny5jsJpBXaKbm7j7W2QTc6Ye2A==", "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", @@ -1477,11 +1477,11 @@ } }, "node_modules/openid-client": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.5.0.tgz", - "integrity": "sha512-Y7Xl8BgsrkzWLHkVDYuroM67hi96xITyEDSkmWaGUiNX6CkcXC3XyQGdv5aWZ6dukVKBFVQCADi9gCavOmU14w==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", + "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", "dependencies": { - "jose": "^4.14.4", + "jose": "^4.15.1", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" @@ -1731,9 +1731,9 @@ } }, "node_modules/preact": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.18.0.tgz", - "integrity": "sha512-O4dGFmErPd3RNVDvXmCbOW6hetnve6vYtjx5qf51mCUmBS96s66MrNQkEII5UThDGoNF7953ptA+aNupiDxVeg==", + "version": "10.18.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.18.1.tgz", + "integrity": "sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" diff --git a/dashboard/15-final/package.json b/dashboard/15-final/package.json index ebb2c58..750c661 100644 --- a/dashboard/15-final/package.json +++ b/dashboard/15-final/package.json @@ -20,7 +20,7 @@ "bcrypt": "^5.1.1", "clsx": "^2.0.0", "next": "^13.5.3", - "next-auth": "^4.23.1", + "next-auth": "^4.23.2", "postcss": "8.4.31", "react": "18.2.0", "react-dom": "18.2.0",