places - feat: implement user place management with CRUD operations and validation
This commit is contained in:
parent
66c60829ab
commit
653a9b2dc5
@ -17,7 +17,6 @@ export const useLogin = () => {
|
||||
mutationKey: ["login"],
|
||||
mutationFn: async (data: TLoginForm) => {
|
||||
const response = await user.login({ data })
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.message)
|
||||
}
|
||||
|
||||
@ -1,17 +1,48 @@
|
||||
import { createServerFn } from "@tanstack/react-start"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { db } from "@/integrations/drizzle"
|
||||
import { places } from "@/integrations/drizzle/db/schema"
|
||||
import { places as placesSchema } from "@/integrations/drizzle/db/schema"
|
||||
import { paginatedPlacesSchema, placeSchema } from "../validation/places"
|
||||
|
||||
export const insertUserPlace = createServerFn({
|
||||
method: "POST"
|
||||
}).handler(async ({ data }) => {
|
||||
await db.insert(places).values(data).returning()
|
||||
})
|
||||
.inputValidator(placeSchema)
|
||||
.handler(async ({ data }) => {
|
||||
await db.insert(placesSchema).values(data).returning()
|
||||
})
|
||||
|
||||
export const getUserPlaces = createServerFn().handler(async () => {
|
||||
export const editUserPlace = createServerFn({
|
||||
method: "POST"
|
||||
})
|
||||
.inputValidator(
|
||||
placeSchema.pick({
|
||||
hidden_place: true,
|
||||
name: true,
|
||||
description: true,
|
||||
id_user: true
|
||||
})
|
||||
)
|
||||
.handler(async ({ data }) => {
|
||||
await db
|
||||
.update(placesSchema)
|
||||
.set(data)
|
||||
.where(eq(placesSchema.id_user, data.id_user))
|
||||
})
|
||||
|
||||
export const getUserPlacesById = createServerFn()
|
||||
.inputValidator(paginatedPlacesSchema)
|
||||
.handler(async ({ data }) => {
|
||||
return await db
|
||||
.select()
|
||||
.from(places)
|
||||
.where(eq(places.id_user, "e6472b9d-01a9-4e2e-8bdc-0ddaa9baf5d8")) // No haría falta el where puedo filtrar mediante RSL que solo pueda ver sus places solo los datos que el ha insertado
|
||||
})
|
||||
.from(placesSchema)
|
||||
.where(eq(placesSchema.id_user, data.id_user))
|
||||
.limit(data.limit)
|
||||
.offset((data.page - 1) * data.limit)
|
||||
})
|
||||
|
||||
export const places = {
|
||||
insertUserPlace,
|
||||
editUserPlace,
|
||||
getUserPlacesById
|
||||
}
|
||||
|
||||
@ -82,7 +82,6 @@ const userData = createServerFn().handler(async () => {
|
||||
message: error?.message ?? "Unknown error"
|
||||
}
|
||||
}
|
||||
console.log(data)
|
||||
return {
|
||||
user: {
|
||||
id: data.user.id,
|
||||
|
||||
18
src/lib/validation/places.ts
Normal file
18
src/lib/validation/places.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import z from "zod"
|
||||
|
||||
export const placeSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
coord_x: z.string(),
|
||||
coord_y: z.string(),
|
||||
id_user: z.string(),
|
||||
hidden_place: z.boolean()
|
||||
})
|
||||
|
||||
export const paginatedPlacesSchema = z.object({
|
||||
page: z.number(),
|
||||
limit: z.number(),
|
||||
id_user: z.string(),
|
||||
})
|
||||
|
||||
|
||||
@ -14,11 +14,6 @@ export const Route = createFileRoute("/_authed")({
|
||||
// TODO: Redirect to login page
|
||||
}
|
||||
},
|
||||
loader: ({ context }) => {
|
||||
context.queryClient.ensureQueryData({
|
||||
queryKey: ["profile"]
|
||||
})
|
||||
},
|
||||
errorComponent: ({ error }) => {
|
||||
if (error.message === "Not authenticated") {
|
||||
return (
|
||||
@ -38,8 +33,6 @@ function RouteComponent() {
|
||||
<div>
|
||||
<nav className="flex gap-2 p-2 mb-2 rounded-md bg-clip-padding backdrop-filter backdrop-blur-xl bg-opacity-10 sticky top-0 z-20">
|
||||
<div className="w-full justify-between flex items-center">
|
||||
<div />
|
||||
<h1 className="font-black text-2xl md:text-4xl">FindYourPilots</h1>
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Avatar isBordered src="/profile.png" />
|
||||
|
||||
@ -1,11 +1,3 @@
|
||||
import {
|
||||
Avatar,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
Chip
|
||||
} from "@heroui/react"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
|
||||
export const Route = createFileRoute("/_authed/dashboard")({
|
||||
@ -14,106 +6,6 @@ export const Route = createFileRoute("/_authed/dashboard")({
|
||||
|
||||
function RouteComponent() {
|
||||
const { user } = Route.useRouteContext()
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center flex-col gap-2">
|
||||
<Card className="max-w-6xl border-none" fullWidth shadow="none">
|
||||
<CardHeader className="mb-0 pb-0 justify-between flex-wrap gap-2">
|
||||
<Chip variant="light" size="lg">
|
||||
Inicio {user?.name ?? "demo"}
|
||||
</Chip>
|
||||
<div className="rounded-md bg-warning/30 border-2 border-warning/70 px-3 py-1">
|
||||
<p> 🏗️ La web está en mantenimiento</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
{/* === GRID PRINCIPAL === */}
|
||||
<div className="grid sm:grid-cols-3 grid-rows-[auto_1fr] gap-5 mt-2">
|
||||
{/* === Tarjeta 1: Mis drones === */}
|
||||
<Card
|
||||
shadow="none"
|
||||
className="rounded-md max-w-sm bg-default-50"
|
||||
isPressable
|
||||
>
|
||||
<CardHeader className="flex justify-between items-center gap-2">
|
||||
<div className="flex flex-col gap-1 justify-start items-start">
|
||||
<h2 className="text-start font-semibold text-lg">
|
||||
Mis drones
|
||||
</h2>
|
||||
<p className="text-default-500 text-sm text-start">
|
||||
Actualiza tus drones en uso o pilotados.
|
||||
</p>
|
||||
</div>
|
||||
<Avatar
|
||||
src="/drone.webp"
|
||||
alt="Drone"
|
||||
className="rounded-full"
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<p className="text-default-500 text-sm text-start">Ver más</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
{/* === Tarjeta 2: Datos personales === */}
|
||||
<Card
|
||||
shadow="none"
|
||||
className="rounded-md max-w-sm bg-default-50"
|
||||
isPressable
|
||||
>
|
||||
<CardHeader className="flex justify-between items-center gap-2">
|
||||
<div className="flex flex-col gap-1 justify-start items-start">
|
||||
<h2 className="text-start font-semibold text-lg">
|
||||
Datos personales
|
||||
</h2>
|
||||
<p className="text-default-500 text-sm text-start">
|
||||
Actualiza tu información, ubicación y detalles.
|
||||
</p>
|
||||
</div>
|
||||
<Avatar
|
||||
src="/profile.png"
|
||||
alt="User"
|
||||
className="rounded-full"
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<p className="text-default-500 text-sm">Ver más</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
{/* === Tarjeta 3: Ofertas de vuelo === */}
|
||||
<Card
|
||||
shadow="none"
|
||||
className="rounded-md max-w-sm bg-default-50 row-span-2 self-end h-full"
|
||||
isPressable
|
||||
>
|
||||
<CardHeader className="flex justify-between items-center gap-2">
|
||||
<div className="flex flex-col gap-1 justify-start items-start">
|
||||
<h2 className="text-start font-semibold text-lg">
|
||||
Certificados
|
||||
</h2>
|
||||
<p className="text-default-500 text-sm text-start"></p>
|
||||
</div>
|
||||
<Avatar
|
||||
src="/profile.png"
|
||||
alt="User"
|
||||
className="rounded-full"
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p className="text-default-500 text-sm">Próximamente...</p>
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
<p className="text-default-500 text-sm">Ver más</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
{/* === Mapa (ocupa 2 columnas) === */}
|
||||
<div className="w-full h-[400px] bg-default-100 col-span-2 rounded-md flex justify-center items-center">
|
||||
{/* Aquí irá el mapa */}
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
console.log(user)
|
||||
return <div>Dashboard</div>
|
||||
}
|
||||
|
||||
@ -1,29 +1,30 @@
|
||||
import { Button } from "@heroui/react"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { getUserPlaces, insertUserPlace } from "@/lib/server/places"
|
||||
import { places } from "@/lib/server/places"
|
||||
import { user } from "@/lib/server/user"
|
||||
|
||||
export const Route = createFileRoute("/places")({
|
||||
component: RouteComponent
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
|
||||
const submitUser = async () => {
|
||||
await insertUserPlace({
|
||||
const userId = await user.userData()
|
||||
await places.insertUserPlace({
|
||||
data: {
|
||||
name: "Place 1",
|
||||
description: "A nice place",
|
||||
coord_x: "40.7128",
|
||||
coord_y: "-74.0060",
|
||||
id_user: "e6472b9d-01a9-4e2e-8bdc-0ddaa9baf5d8", // Replace with actual user ID
|
||||
id_user: userId.user?.id as string, // Replace with actual user ID
|
||||
hidden_place: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const fetchPlaces = async () => {
|
||||
const places = await getUserPlaces();
|
||||
console.log(places);
|
||||
const allPlacesByUser = await places.getUserPlacesById()
|
||||
console.log(allPlacesByUser)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user