mirror of
https://github.com/vercel/next-learn.git
synced 2026-07-09 06:55:12 +00:00
Chapters 12-13
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { deleteInvoice } from '@/app/lib/actions';
|
|
||||||
|
|
||||||
export function CreateInvoice() {
|
export function CreateInvoice() {
|
||||||
return (
|
return (
|
||||||
@@ -25,14 +24,11 @@ export function UpdateInvoice({ id }: { id: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DeleteInvoice({ id }: { id: string }) {
|
export function DeleteInvoice() {
|
||||||
return (
|
return (
|
||||||
<form action={deleteInvoice}>
|
<button className="rounded-md border p-2 hover:bg-gray-100">
|
||||||
<input type="hidden" name="id" value={id} />
|
<span className="sr-only">Delete</span>
|
||||||
<button className="rounded-md border p-2 hover:bg-gray-100">
|
<TrashIcon className="w-5" />
|
||||||
<span className="sr-only">Delete</span>
|
</button>
|
||||||
<TrashIcon className="w-5" />
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
UserCircleIcon,
|
UserCircleIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
import { createInvoice } from '@/app/lib/actions';
|
|
||||||
|
|
||||||
export default function Form({ customers }: { customers: CustomerField[] }) {
|
export default function Form({ customers }: { customers: CustomerField[] }) {
|
||||||
return (
|
return (
|
||||||
@@ -26,7 +25,6 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
|
|||||||
name="customerId"
|
name="customerId"
|
||||||
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
aria-describedby="customer-error"
|
|
||||||
>
|
>
|
||||||
<option value="" disabled>
|
<option value="" disabled>
|
||||||
Select a customer
|
Select a customer
|
||||||
@@ -39,18 +37,6 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
|
|||||||
</select>
|
</select>
|
||||||
<UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
|
<UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.errors?.customerId ? (
|
|
||||||
<div
|
|
||||||
id="customer-error"
|
|
||||||
aria-live="polite"
|
|
||||||
className="mt-2 text-sm text-red-500"
|
|
||||||
>
|
|
||||||
{state.errors.customerId.map((error: string) => (
|
|
||||||
<p key={error}>{error}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Invoice Amount */}
|
{/* Invoice Amount */}
|
||||||
@@ -72,18 +58,6 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
|
|||||||
<CurrencyDollarIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
<CurrencyDollarIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.errors?.amount ? (
|
|
||||||
<div
|
|
||||||
id="amount-error"
|
|
||||||
aria-live="polite"
|
|
||||||
className="mt-2 text-sm text-red-500"
|
|
||||||
>
|
|
||||||
{state.errors.amount.map((error: string) => (
|
|
||||||
<p key={error}>{error}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Invoice Status */}
|
{/* Invoice Status */}
|
||||||
@@ -125,24 +99,7 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{state.errors?.status ? (
|
|
||||||
<div
|
|
||||||
aria-describedby="status-error"
|
|
||||||
aria-live="polite"
|
|
||||||
className="mt-2 text-sm text-red-500"
|
|
||||||
>
|
|
||||||
{state.errors.status.map((error: string) => (
|
|
||||||
<p key={error}>{error}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.message ? (
|
|
||||||
<div aria-live="polite" className="my-2 text-sm text-red-500">
|
|
||||||
<p>{state.message}</p>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flex justify-end gap-4">
|
<div className="mt-6 flex justify-end gap-4">
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ import {
|
|||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
import { updateInvoice } from '@/app/lib/actions';
|
|
||||||
// @ts-ignore React types do not yet include useFormState
|
|
||||||
import { experimental_useFormState as useFormState } from 'react-dom';
|
|
||||||
|
|
||||||
export default function EditInvoiceForm({
|
export default function EditInvoiceForm({
|
||||||
invoice,
|
invoice,
|
||||||
@@ -20,11 +17,8 @@ export default function EditInvoiceForm({
|
|||||||
invoice: InvoiceForm;
|
invoice: InvoiceForm;
|
||||||
customers: CustomerField[];
|
customers: CustomerField[];
|
||||||
}) {
|
}) {
|
||||||
const initialState = { message: null, errors: [] };
|
|
||||||
const [state, dispatch] = useFormState(updateInvoice, initialState);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form action={dispatch}>
|
<form>
|
||||||
<div className="rounded-md bg-gray-50 p-4 md:p-6">
|
<div className="rounded-md bg-gray-50 p-4 md:p-6">
|
||||||
{/* Invoice ID */}
|
{/* Invoice ID */}
|
||||||
<input type="hidden" name="id" value={invoice.id} />
|
<input type="hidden" name="id" value={invoice.id} />
|
||||||
@@ -39,7 +33,6 @@ export default function EditInvoiceForm({
|
|||||||
name="customerId"
|
name="customerId"
|
||||||
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
||||||
defaultValue={invoice.customer_id}
|
defaultValue={invoice.customer_id}
|
||||||
aria-describedby="customer-error"
|
|
||||||
>
|
>
|
||||||
<option value="" disabled>
|
<option value="" disabled>
|
||||||
Select a customer
|
Select a customer
|
||||||
@@ -52,18 +45,6 @@ export default function EditInvoiceForm({
|
|||||||
</select>
|
</select>
|
||||||
<UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
|
<UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.errors?.customerId ? (
|
|
||||||
<div
|
|
||||||
id="customer-error"
|
|
||||||
aria-live="polite"
|
|
||||||
className="mt-2 text-sm text-red-500"
|
|
||||||
>
|
|
||||||
{state.errors.customerId.map((error: string) => (
|
|
||||||
<p key={error}>{error}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Invoice Amount */}
|
{/* Invoice Amount */}
|
||||||
@@ -80,23 +61,10 @@ export default function EditInvoiceForm({
|
|||||||
defaultValue={invoice.amount}
|
defaultValue={invoice.amount}
|
||||||
placeholder="Enter USD amount"
|
placeholder="Enter USD amount"
|
||||||
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
||||||
aria-describedby="amount-error"
|
|
||||||
/>
|
/>
|
||||||
<CurrencyDollarIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
<CurrencyDollarIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.errors?.amount ? (
|
|
||||||
<div
|
|
||||||
id="amount-error"
|
|
||||||
aria-live="polite"
|
|
||||||
className="mt-2 text-sm text-red-500"
|
|
||||||
>
|
|
||||||
{state.errors.amount.map((error: string) => (
|
|
||||||
<p key={error}>{error}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Invoice Status */}
|
{/* Invoice Status */}
|
||||||
@@ -140,24 +108,7 @@ export default function EditInvoiceForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{state.errors?.status ? (
|
|
||||||
<div
|
|
||||||
aria-describedby="status-error"
|
|
||||||
aria-live="polite"
|
|
||||||
className="mt-2 text-sm text-red-500"
|
|
||||||
>
|
|
||||||
{state.errors.status.map((error: string) => (
|
|
||||||
<p key={error}>{error}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.message ? (
|
|
||||||
<div aria-live="polite" className="my-2 text-sm text-red-500">
|
|
||||||
<p>{state.message}</p>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flex justify-end gap-4">
|
<div className="mt-6 flex justify-end gap-4">
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
Reference in New Issue
Block a user