mirror of
https://github.com/vercel/next-learn.git
synced 2026-06-11 09:51:47 +00:00
Add "create" invoice form (#139)
* Install @tailwindcss/forms * Add invoice form * Replace -zinc- with -gray- for consistency
This commit is contained in:
committed by
GitHub
parent
ab4d446e34
commit
340cd8dd05
@@ -1,3 +1,5 @@
|
||||
import AddInvoiceForm from "@/app/ui/invoice-form";
|
||||
|
||||
export default function Page() {
|
||||
return <div>Create new invoice page</div>
|
||||
return <AddInvoiceForm />;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ export default function Card({
|
||||
<div className="flex justify-between ">
|
||||
<h3 className="text-sm font-medium">{title}</h3>
|
||||
{Icon ? (
|
||||
<Icon className="h-5 w-5 text-zinc-700" aria-label={type} />
|
||||
<Icon className="h-5 w-5 text-gray-700" aria-label={type} />
|
||||
) : null}
|
||||
</div>
|
||||
<p className="mt-2 truncate text-2xl font-semibold tracking-wide md:text-3xl">
|
||||
{value}
|
||||
</p>
|
||||
<p className="mt-1.5 text-sm text-zinc-400">+00% since last month</p>
|
||||
<p className="mt-1.5 text-sm text-gray-400">+00% since last month</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ export default function Hero() {
|
||||
<>
|
||||
<div className="mx-auto mt-20 flex flex-col items-center space-y-6 p-2 md:w-1/3">
|
||||
<BackgroundBlur />
|
||||
<h1 className="text-center text-4xl font-bold tracking-tight text-zinc-900 sm:text-5xl">
|
||||
<h1 className="text-center text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
|
||||
Next.js Dashboard
|
||||
</h1>
|
||||
<p className="text-center leading-6 text-zinc-900">
|
||||
<p className="text-center leading-6 text-gray-900">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vulputate
|
||||
dapibus consectetur. Duis quis eros euismod.
|
||||
</p>
|
||||
<a href="/login">
|
||||
<button className="rounded-md bg-black px-4 py-2 text-sm font-semibold text-white hover:bg-zinc-700">
|
||||
<button className="rounded-md bg-black px-4 py-2 text-sm font-semibold text-white hover:bg-gray-700">
|
||||
Log in
|
||||
</button>
|
||||
</a>
|
||||
|
||||
78
dashboard/15-final/app/ui/invoice-form.tsx
Normal file
78
dashboard/15-final/app/ui/invoice-form.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
import { Invoice } from "@/app/lib/definitions";
|
||||
import { customers } from "@/app/lib/dummy-data";
|
||||
import { useState, FormEvent } from "react";
|
||||
|
||||
export default function AddInvoiceForm() {
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(0);
|
||||
const [amount, setAmount] = useState("");
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (selectedCustomer && amount) {
|
||||
const newInvoice: Invoice = {
|
||||
customerId: selectedCustomer,
|
||||
amount: parseInt(amount) * 100, // Convert to cents
|
||||
|
||||
// These would be generated on the server
|
||||
id: 1, // Record ID will be automatically incremented
|
||||
status: "pending", // Default status for a new invoice
|
||||
date: new Date().toISOString().split("T")[0],
|
||||
};
|
||||
|
||||
// TODO: Add this invoice to the database
|
||||
console.log("New Invoice:", newInvoice);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md p-4">
|
||||
<h2 className="mb-6 text-xl font-semibold text-gray-900">New Invoice</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-4">
|
||||
<label
|
||||
htmlFor="customer"
|
||||
className="mb-2 block text-sm font-semibold"
|
||||
>
|
||||
Customer
|
||||
</label>
|
||||
<select
|
||||
id="customer"
|
||||
onChange={(e) => setSelectedCustomer(Number(e.target.value))}
|
||||
className="block w-full rounded-md border-0 py-1.5 pl-3 text-sm ring-1 ring-inset ring-gray-200 placeholder:text-gray-200"
|
||||
>
|
||||
{customers.map((customer) => (
|
||||
<option key={customer.id} value={customer.id}>
|
||||
{customer.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="mb-2 block text-sm font-semibold">Amount</label>
|
||||
<div className="relative mt-2 rounded-md shadow-sm">
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<span className="text-gray-600 sm:text-sm">$</span>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
value={amount}
|
||||
placeholder="00.00"
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="block w-full rounded-md border-0 py-1.5 pl-7 text-sm leading-6 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-md bg-blue-500 px-4 py-2 text-center text-sm font-semibold text-white hover:bg-blue-600"
|
||||
>
|
||||
Create Invoice
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,35 +21,35 @@ export default function LoginForm() {
|
||||
<a href="/">
|
||||
<img className="h-6 w-auto" src="/logo.svg" alt="Next.js Logo" />
|
||||
</a>
|
||||
<p className="text-center font-semibold text-zinc-900">
|
||||
<p className="text-center font-semibold text-gray-900">
|
||||
Log in to your dashboard
|
||||
</p>
|
||||
<div className="w-full max-w-sm">
|
||||
<form onSubmit={handleSubmit} className="px-4">
|
||||
<div>
|
||||
<label
|
||||
className="block text-sm font-medium leading-8 text-zinc-900"
|
||||
className="block text-sm font-medium leading-8 text-gray-900"
|
||||
htmlFor="email"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
className="block w-full rounded-md border-0 p-1.5 text-zinc-900 shadow-sm ring-1 ring-inset ring-zinc-300 sm:text-sm"
|
||||
className="block w-full rounded-md border-0 p-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 sm:text-sm"
|
||||
id="email"
|
||||
type="text"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label
|
||||
className="block text-sm font-medium leading-8 text-zinc-900"
|
||||
className="block text-sm font-medium leading-8 text-gray-900"
|
||||
htmlFor="password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
className="block w-full rounded-md border-0 p-1.5 text-zinc-900 shadow-sm ring-1 ring-inset ring-zinc-300 sm:text-sm"
|
||||
className="block w-full rounded-md border-0 p-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 sm:text-sm"
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
@@ -58,7 +58,7 @@ export default function LoginForm() {
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
className="w-full rounded-md bg-black px-4 py-2 text-center text-sm font-semibold text-white hover:bg-zinc-700"
|
||||
className="w-full rounded-md bg-black px-4 py-2 text-center text-sm font-semibold text-white hover:bg-gray-700"
|
||||
type="submit"
|
||||
>
|
||||
Log in
|
||||
|
||||
@@ -11,16 +11,16 @@ export default function RevenueChart({ revenue }: { revenue: Revenue[] }) {
|
||||
const { yAxisLabels, topLabel } = generateYAxis(revenue);
|
||||
|
||||
if (!revenue || revenue.length === 0) {
|
||||
return <p className="mt-4 text-zinc-400">No data available.</p>;
|
||||
return <p className="mt-4 text-gray-400">No data available.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border p-6 shadow-sm md:col-span-5">
|
||||
<h2 className="font-semibold">Revenue</h2>
|
||||
<div className="sm:grid-cols-13 mt-4 grid grid-cols-12 items-end gap-2 md:gap-4">
|
||||
<div className="mt-4 grid grid-cols-12 items-end gap-2 sm:grid-cols-13 md:gap-4">
|
||||
{/* y-axis */}
|
||||
<div
|
||||
className="mb-6 hidden flex-col justify-between text-sm text-zinc-400 sm:flex"
|
||||
className="mb-6 hidden flex-col justify-between text-sm text-gray-400 sm:flex"
|
||||
style={{ height: `${chartHeight}px` }}
|
||||
>
|
||||
{yAxisLabels.map((label, index) => (
|
||||
@@ -38,7 +38,7 @@ export default function RevenueChart({ revenue }: { revenue: Revenue[] }) {
|
||||
}}
|
||||
></div>
|
||||
{/* x-axis */}
|
||||
<p className="-rotate-90 text-sm text-zinc-400 sm:rotate-0">
|
||||
<p className="-rotate-90 text-sm text-gray-400 sm:rotate-0">
|
||||
{month.month}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
import {
|
||||
MagnifyingGlassIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export default function Search() {
|
||||
async function submitForm(formData: FormData) {
|
||||
'use server'
|
||||
"use server";
|
||||
// TODO: Implement search
|
||||
// console.log(formData)
|
||||
}
|
||||
return (
|
||||
<div className="w-full h-full px-4 relative flex items-center">
|
||||
<MagnifyingGlassIcon className="h-5 text-gray-400"/>
|
||||
<div className="relative flex h-full w-full items-center px-4">
|
||||
<MagnifyingGlassIcon className="h-5 text-gray-400" />
|
||||
<form className="h-full w-full" action={submitForm}>
|
||||
<label htmlFor="search-field" className="sr-only">
|
||||
Search
|
||||
</label>
|
||||
<input
|
||||
id="search-field"
|
||||
className="focus:outline-none px-2 h-full w-full border-0 text-gray-900 placeholder:text-gray-400 focus:ring-0"
|
||||
className="h-full w-full border-0 px-2 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-0"
|
||||
placeholder="Search..."
|
||||
type="search"
|
||||
name="search"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
33
dashboard/15-final/package-lock.json
generated
33
dashboard/15-final/package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@tailwindcss/forms": "^0.5.6",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/react": "18.2.21",
|
||||
"@types/react-dom": "18.2.7",
|
||||
@@ -387,6 +388,17 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.6.tgz",
|
||||
"integrity": "sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==",
|
||||
"dependencies": {
|
||||
"mini-svg-data-uri": "^1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
@@ -2620,6 +2632,14 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||
"bin": {
|
||||
"mini-svg-data-uri": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -4331,6 +4351,14 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@tailwindcss/forms": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.6.tgz",
|
||||
"integrity": "sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==",
|
||||
"requires": {
|
||||
"mini-svg-data-uri": "^1.2.3"
|
||||
}
|
||||
},
|
||||
"@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
@@ -5910,6 +5938,11 @@
|
||||
"picomatch": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@tailwindcss/forms": "^0.5.6",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/react": "18.2.21",
|
||||
"@types/react-dom": "18.2.7",
|
||||
|
||||
@@ -18,6 +18,6 @@ const config: Config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [require("@tailwindcss/forms")],
|
||||
};
|
||||
export default config;
|
||||
|
||||
Reference in New Issue
Block a user