Add "create" invoice form (#139)

* Install @tailwindcss/forms

* Add invoice form

* Replace -zinc- with -gray- for consistency
This commit is contained in:
Delba de Oliveira
2023-09-05 15:11:54 +01:00
committed by GitHub
parent ab4d446e34
commit 340cd8dd05
10 changed files with 139 additions and 28 deletions

View File

@@ -1,3 +1,5 @@
import AddInvoiceForm from "@/app/ui/invoice-form";
export default function Page() {
return <div>Create new invoice page</div>
return <AddInvoiceForm />;
}

View File

@@ -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>
);
}

View File

@@ -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>

View 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>
);
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>
)
}
);
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -18,6 +18,6 @@ const config: Config = {
},
},
},
plugins: [],
plugins: [require("@tailwindcss/forms")],
};
export default config;