Compare commits
2 Commits
e70ff89833
...
0bab29653a
Author | SHA1 | Date | |
---|---|---|---|
0bab29653a | |||
dbdb5356e2 |
@ -1,3 +1,4 @@
|
|||||||
|
import { deleteCountry } from "~/server/actions/deleteCountry";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
|
|
||||||
export default async function AllCountries() {
|
export default async function AllCountries() {
|
||||||
@ -6,7 +7,12 @@ export default async function AllCountries() {
|
|||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<h1 className="text-2xl">All Countries:</h1>
|
<h1 className="text-2xl">All Countries:</h1>
|
||||||
{countries.map((country) => (
|
{countries.map((country) => (
|
||||||
|
<div className="flex gap-1">
|
||||||
<p key={country.id}>{country.name}</p>
|
<p key={country.id}>{country.name}</p>
|
||||||
|
<form action={deleteCountry.bind(null, country.id)}>
|
||||||
|
<button>❌</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,14 +4,12 @@ import { Input } from "~/components/ui/input";
|
|||||||
import useFilterStore from "../store";
|
import useFilterStore from "../store";
|
||||||
|
|
||||||
export default function SearchBar() {
|
export default function SearchBar() {
|
||||||
const [query, setQuery] = useState("");
|
|
||||||
const setStoreSearchQuery = useFilterStore((state) => state.setSearchQuery);
|
const setStoreSearchQuery = useFilterStore((state) => state.setSearchQuery);
|
||||||
const { searchQuery } = useFilterStore((state) => state.filters);
|
const { searchQuery } = useFilterStore((state) => state.filters);
|
||||||
|
|
||||||
function handleInput(e: ChangeEvent<HTMLInputElement>) {
|
function handleInput(e: ChangeEvent<HTMLInputElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newValue = e.target.value;
|
const newValue = e.target.value;
|
||||||
setQuery(newValue);
|
|
||||||
setStoreSearchQuery(newValue);
|
setStoreSearchQuery(newValue);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
11
src/app/_components/SubmitButton.tsx
Normal file
11
src/app/_components/SubmitButton.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { useFormStatus } from "react-dom";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
|
||||||
|
export default function SubmitButton() {
|
||||||
|
const { pending } = useFormStatus();
|
||||||
|
return (
|
||||||
|
<Button disabled={pending}>
|
||||||
|
{pending ? "Adding country.." : "Add country"}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
import { deleteCountry } from "~/server/actions/deleteCountry";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import WineName from "./WineName";
|
|
||||||
|
|
||||||
export default async function WineList() {
|
export default async function WineList() {
|
||||||
const wines = await db.query.wines.findMany();
|
const wines = await db.query.wines.findMany();
|
||||||
return (
|
return (
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<h1 className="text-2xl">All wines:</h1>
|
<h1 className="text-2xl">All wines:</h1>
|
||||||
{wines && wines.length > 0 ? (
|
{wines ? (
|
||||||
<>
|
<>
|
||||||
<ul>
|
<ul>
|
||||||
{wines.map((wine) => (
|
{wines.map((wine) => (
|
||||||
<WineName key={wine.id} name={wine.name} />
|
<li key={wine.id}>{wine.name}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export default function WineName(props: { name: string }) {
|
|
||||||
const { name } = props;
|
|
||||||
return <p>{name}</p>;
|
|
||||||
}
|
|
@ -1,26 +1,23 @@
|
|||||||
import { revalidatePath } from "next/cache";
|
"use client";
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { db } from "~/server/db";
|
import { addCountry } from "~/server/actions/createCountry";
|
||||||
import { countries } from "~/server/db/schema";
|
import SubmitButton from "../SubmitButton";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
type NewCountry = {
|
const initialState = {
|
||||||
name: string;
|
name: "" as string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function CreateCountryForm() {
|
export default function CreateCountryForm() {
|
||||||
const addCountry = async (formData: FormData) => {
|
const ref = useRef<HTMLFormElement>(null);
|
||||||
"use server";
|
const handleSubmit = async (formData: FormData) => {
|
||||||
const newCountry: NewCountry = {
|
ref.current?.reset();
|
||||||
name: formData.get("name") as string,
|
await addCountry(formData);
|
||||||
};
|
|
||||||
await db.insert(countries).values(newCountry).returning();
|
|
||||||
revalidatePath("/");
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<form action={addCountry}>
|
<form ref={ref} action={handleSubmit}>
|
||||||
<Input name="name" required />
|
<Input name="name" required />
|
||||||
<Button>Add country</Button>
|
<SubmitButton />
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
59
src/components/ui/alert.tsx
Normal file
59
src/components/ui/alert.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border border-slate-200 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-slate-950 dark:border-slate-800 dark:[&>svg]:text-slate-50",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50",
|
||||||
|
destructive:
|
||||||
|
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
@ -1,67 +0,0 @@
|
|||||||
'use server'
|
|
||||||
import { ZodError, z } from 'zod';
|
|
||||||
import { db } from '../db/index'
|
|
||||||
import { wines } from '../db/schema'
|
|
||||||
import { QueryResult } from 'pg';
|
|
||||||
|
|
||||||
type NewWine = typeof wines.$inferInsert;
|
|
||||||
|
|
||||||
export type InsertResult = {
|
|
||||||
name: string;
|
|
||||||
producer: string;
|
|
||||||
id: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date | null;
|
|
||||||
|
|
||||||
}[];
|
|
||||||
export type Fields = {
|
|
||||||
name: FormDataEntryValue | null
|
|
||||||
producer: FormDataEntryValue | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FormState = {
|
|
||||||
message: string | QueryResult<never>;
|
|
||||||
errors: Record<keyof Fields, string> | undefined;
|
|
||||||
fieldValues: NewWine;
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
producer: z.string().uuid(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const addWine = async (
|
|
||||||
prevState: FormState,
|
|
||||||
formData: FormData): Promise<FormState> => {
|
|
||||||
const newWine: NewWine = {
|
|
||||||
name: formData.get('name') as string,
|
|
||||||
producer: formData.get('producer') as string
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
schema.parse(newWine)
|
|
||||||
|
|
||||||
await db.insert(wines).values(newWine);
|
|
||||||
return {
|
|
||||||
message: 'success',
|
|
||||||
errors: undefined,
|
|
||||||
fieldValues: {
|
|
||||||
name: "",
|
|
||||||
producer: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const zodError = error as ZodError;
|
|
||||||
const errorMap = zodError.flatten().fieldErrors;
|
|
||||||
return {
|
|
||||||
message: "error",
|
|
||||||
errors: {
|
|
||||||
name: errorMap["name"]?.[0] ?? "",
|
|
||||||
producer: errorMap["producer"]?.[0] ?? ""
|
|
||||||
},
|
|
||||||
fieldValues: {
|
|
||||||
name: newWine.name,
|
|
||||||
producer: newWine.producer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
'use server'
|
|
||||||
import { db } from "../db/index";
|
|
||||||
|
|
||||||
|
|
||||||
export async function getProducers(){
|
|
||||||
return db.query.producers.findMany();
|
|
||||||
}
|
|
17
src/server/actions/createCountry.ts
Normal file
17
src/server/actions/createCountry.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { db } from "../db";
|
||||||
|
import { countries } from "../db/schema";
|
||||||
|
|
||||||
|
type NewCountry = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addCountry = async (formData: FormData) => {
|
||||||
|
const newCountry: NewCountry = {
|
||||||
|
name: (formData.get("name") as string).toLowerCase(),
|
||||||
|
};
|
||||||
|
revalidatePath("/");
|
||||||
|
await db.insert(countries).values(newCountry).returning();
|
||||||
|
};
|
11
src/server/actions/deleteCountry.ts
Normal file
11
src/server/actions/deleteCountry.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../db";
|
||||||
|
import { countries } from "../db/schema";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
|
||||||
|
export async function deleteCountry(id: string) {
|
||||||
|
await db.delete(countries).where(eq(countries.id, id));
|
||||||
|
revalidatePath("/");
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
'use server'
|
|
||||||
import { db } from "../db/index";
|
|
||||||
|
|
||||||
export async function getAllWines(){
|
|
||||||
const wines = db.query.wines.findMany()
|
|
||||||
return wines;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
"use server";
|
|
||||||
import { producers } from "../db/schema";
|
|
||||||
import { db } from "../db";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
export default async function getProducer(id: string) {
|
|
||||||
return db.query.producers.findFirst({
|
|
||||||
where: eq(producers.id, id),
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
"use server";
|
|
||||||
import { sql } from "drizzle-orm";
|
|
||||||
import { db } from "../db";
|
|
||||||
import { wines, producers } from "../db/schema";
|
|
||||||
import { UUID } from "crypto";
|
|
||||||
|
|
||||||
export async function getWineDetails(id: string) {
|
|
||||||
const results = await db
|
|
||||||
.select()
|
|
||||||
.from(producers)
|
|
||||||
.where(sql`${producers.id} = ${id}`);
|
|
||||||
return results;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user