Compare commits

..

No commits in common. "99401308d0badd21b76c26351305dcbddd543c88" and "c0764fcc84ccba01b50d0e7b9dc9f2bbfaa8c977" have entirely different histories.

29 changed files with 85 additions and 1077 deletions

5
.env
View File

@ -1,5 +0,0 @@
SUPABASE_URL="https://qsssikzgwomudkwfmgad.supabase.co"
# DATABASE_URL="postgresql://postgres.qsssikzgwomudkwfmgad:etrTXNz3ZOwaLJmT@aws-0-eu-north-1.pooler.supabase.com:6543/postgres"
DATABASE_URL="postgresql://postgres.qsssikzgwomudkwfmgad:Wrongly1-Untimed0-Peculiar0-Unlikable7-Cubbyhole8@aws-0-eu-north-1.pooler.supabase.com:6543/postgre"
APIKEY_MAPS="AIzaSyAwfOShBqkBcS46WqmlsIVWQJ8gpdOPk_4"
SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFzc3Npa3pnd29tdWRrd2ZtZ2FkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQzMjY1NTQsImV4cCI6MjA2OTkwMjU1NH0.BTSscdTcPP1GVmMB-H5caLpWsfuAw1V6mXiqogF8TjU"

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ dist
dist-ssr dist-ssr
*.local *.local
count.txt count.txt
.env
.nitro .nitro
.tanstack .tanstack
.output .output

View File

@ -1,5 +1,5 @@
import { defineConfig } from 'drizzle-kit'; import { defineConfig } from 'drizzle-kit';
console.log(process.env.DATABASE_URL)
export default defineConfig({ export default defineConfig({
schema: './src/integrations/drizzle/db/schema.ts', schema: './src/integrations/drizzle/db/schema.ts',
out: './src/integrations/supabase/migrations', out: './src/integrations/supabase/migrations',

28
package-lock.json generated
View File

@ -17,7 +17,6 @@
"@tanstack/react-router-with-query": "^1.130.12", "@tanstack/react-router-with-query": "^1.130.12",
"@tanstack/react-start": "^1.130.15", "@tanstack/react-start": "^1.130.15",
"@tanstack/router-plugin": "^1.130.15", "@tanstack/router-plugin": "^1.130.15",
"@vis.gl/react-google-maps": "^1.5.5",
"drizzle-orm": "^0.44.4", "drizzle-orm": "^0.44.4",
"framer-motion": "^12.23.12", "framer-motion": "^12.23.12",
"postgres": "^3.4.7", "postgres": "^3.4.7",
@ -31,7 +30,6 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.1.3", "@biomejs/biome": "2.1.3",
"@tanstack/react-router-ssr-query": "^1.131.5", "@tanstack/react-router-ssr-query": "^1.131.5",
"@types/google.maps": "^3.58.1",
"@types/react": "^19.1.9", "@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7", "@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^4.7.0", "@vitejs/plugin-react": "^4.7.0",
@ -7652,12 +7650,6 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/google.maps": {
"version": "3.58.1",
"resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
"integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==",
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.2.0", "version": "24.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
@ -7875,20 +7867,6 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/@vis.gl/react-google-maps": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@vis.gl/react-google-maps/-/react-google-maps-1.5.5.tgz",
"integrity": "sha512-LgHtK1AtE2/BN4dPoK05oWu0jWmeDdyX0Ffqi+mZc+M4apaHn2sUxxKXAxhPF90O9vcsiou/ntm6/XBWX+gpqw==",
"license": "MIT",
"dependencies": {
"@types/google.maps": "^3.54.10",
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"react": ">=16.8.0 || ^19.0 || ^19.0.0-rc",
"react-dom": ">=16.8.0 || ^19.0 || ^19.0.0-rc"
}
},
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "4.7.0", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
@ -9951,12 +9929,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-fifo": { "node_modules/fast-fifo": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",

View File

@ -23,7 +23,6 @@
"@tanstack/react-router-with-query": "^1.130.12", "@tanstack/react-router-with-query": "^1.130.12",
"@tanstack/react-start": "^1.130.15", "@tanstack/react-start": "^1.130.15",
"@tanstack/router-plugin": "^1.130.15", "@tanstack/router-plugin": "^1.130.15",
"@vis.gl/react-google-maps": "^1.5.5",
"drizzle-orm": "^0.44.4", "drizzle-orm": "^0.44.4",
"framer-motion": "^12.23.12", "framer-motion": "^12.23.12",
"postgres": "^3.4.7", "postgres": "^3.4.7",
@ -37,7 +36,6 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.1.3", "@biomejs/biome": "2.1.3",
"@tanstack/react-router-ssr-query": "^1.131.5", "@tanstack/react-router-ssr-query": "^1.131.5",
"@types/google.maps": "^3.58.1",
"@types/react": "^19.1.9", "@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7", "@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^4.7.0", "@vitejs/plugin-react": "^4.7.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -1,4 +1,2 @@
## Supabase ## Supabase
Usuarios de prueba demo12 - juan.penalver@outlook.com Usuarios de prueba demo12 - juan.penalver@outlook.com
test

View File

@ -1,131 +1,6 @@
import { sql } from "drizzle-orm" import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
import { export const users = pgTable('demo', {
doublePrecision, id: serial('id').primaryKey(),
foreignKey, fullName: text('full_name'),
integer, phone: varchar('phone', { length: 256 }),
pgPolicy, });
pgTable,
serial,
text,
uuid,
varchar
} from "drizzle-orm/pg-core"
import { authenticatedRole, authUsers } from "drizzle-orm/supabase"
export const users = pgTable("demo", {
id: serial("id").primaryKey(),
fullName: text("full_name"),
phone: varchar("phone", { length: 256 })
})
export const profiles = pgTable(
"profiles",
{
id: uuid("id").notNull().primaryKey(),
firstName: text("first_name"),
lastName: text("last_name")
},
(table) => [
foreignKey({
columns: [table.id],
foreignColumns: [authUsers.id],
name: "profiles_id_fkey"
}).onDelete("cascade"),
pgPolicy("select-own-profile", {
for: "select",
to: authenticatedRole,
using: sql`${table.id} = auth.uid()`
}),
pgPolicy("update-own-profile", {
for: "update",
to: authenticatedRole,
using: sql`${table.id} = auth.uid()`,
withCheck: sql`${table.id} = auth.uid()`
}),
pgPolicy("insert-profile", {
for: "insert",
to: authenticatedRole,
withCheck: sql`${table.id} = auth.uid()`
})
]
).enableRLS()
// === Catálogo de certificaciones ===
export const certifications = pgTable(
"certifications",
{
id: serial("id").primaryKey(),
name: text("name").notNull()
},
() => [
// Política: todos los usuarios autenticados pueden leer, nadie puede escribir
pgPolicy("select-certifications", {
for: "select",
to: authenticatedRole,
using: sql`true`
})
]
).enableRLS()
// === Catálogo de modelos de drones ===
export const droneModels = pgTable(
"drone_models",
{
id: serial("id").primaryKey(),
name: text("name").notNull()
},
() => [
pgPolicy("select-drone-models", {
for: "select",
to: authenticatedRole,
using: sql`true`
})
]
).enableRLS()
// === Tabla principal de pilotos ===
export const pilots = pgTable(
"pilots",
{
id: uuid("id").notNull().primaryKey(), // Igual que auth.uid()
name: text("name"),
location: text("location"),
latitude: doublePrecision("latitude"),
longitude: doublePrecision("longitude"),
company: text("company"),
position: text("position"),
description: text("description"),
differentiation: text("differentiation"),
coverageAreas: text("coverage_areas"),
specializationAreas: text("specialization_areas"),
certificationIds: integer("certification_ids").array(), // IDs de tabla certifications
droneModelIds: integer("drone_model_ids").array(), // IDs de tabla drone_models
email: text("email")
},
(table) => [
// Relación con la tabla auth.users
foreignKey({
columns: [table.id],
foreignColumns: [authUsers.id],
name: "pilots_id_fkey"
}).onDelete("cascade"),
// === RLS ===
pgPolicy("select-own-pilot", {
for: "select",
to: authenticatedRole,
using: sql`${table.id} = auth.uid()`
}),
pgPolicy("update-own-pilot", {
for: "update",
to: authenticatedRole,
using: sql`${table.id} = auth.uid()`,
withCheck: sql`${table.id} = auth.uid()`
}),
pgPolicy("insert-pilot", {
for: "insert",
to: authenticatedRole,
withCheck: sql`${table.id} = auth.uid()`
})
]
).enableRLS()

View File

@ -1,10 +1,15 @@
import { drizzle } from 'drizzle-orm/postgres-js' import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres' import postgres from 'postgres'
import { users } from './db/schema'
const connectionString = process.env.DATABASE_URL! const connectionString = process.env.DATABASE_URL!
// Disable prefetch as it is not supported for "Transaction" pool mode // Disable prefetch as it is not supported for "Transaction" pool mode
const client = postgres(connectionString, { prepare: false }) const client = postgres(connectionString, { prepare: false })
export const db = drizzle(client); export const db = drizzle(client);
const allUsers = await db.select().from(users);
console.log(allUsers);

View File

@ -1,4 +1,4 @@
IF NOT EXISTS CREATE TABLE "demo" ( CREATE TABLE "demo" (
"id" serial PRIMARY KEY NOT NULL, "id" serial PRIMARY KEY NOT NULL,
"full_name" text, "full_name" text,
"phone" varchar(256) "phone" varchar(256)

View File

@ -1,11 +0,0 @@
CREATE TABLE "profiles" (
"id" uuid PRIMARY KEY NOT NULL,
"first_name" text,
"last_name" text
);
--> statement-breakpoint
ALTER TABLE "profiles" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE POLICY "select-own-profile" ON "profiles" AS PERMISSIVE FOR SELECT TO "authenticated" USING ("profiles"."id" = auth.uid());--> statement-breakpoint
CREATE POLICY "update-own-profile" ON "profiles" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ("profiles"."id" = auth.uid()) WITH CHECK ("profiles"."id" = auth.uid());--> statement-breakpoint
CREATE POLICY "insert-profile" ON "profiles" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ("profiles"."id" = auth.uid());

View File

@ -1,36 +0,0 @@
CREATE TABLE "certifications" (
"id" serial PRIMARY KEY NOT NULL,
"name" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "certifications" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "drone_models" (
"id" serial PRIMARY KEY NOT NULL,
"name" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "drone_models" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "pilots" (
"id" uuid PRIMARY KEY NOT NULL,
"name" text,
"location" text,
"latitude" double precision,
"longitude" double precision,
"company" text,
"position" text,
"description" text,
"differentiation" text,
"coverage_areas" text,
"specialization_areas" text,
"certification_ids" integer[],
"drone_model_ids" integer[],
"email" text
);
--> statement-breakpoint
ALTER TABLE "pilots" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "pilots" ADD CONSTRAINT "pilots_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE POLICY "select-certifications" ON "certifications" AS PERMISSIVE FOR SELECT TO "authenticated" USING (true);--> statement-breakpoint
CREATE POLICY "select-drone-models" ON "drone_models" AS PERMISSIVE FOR SELECT TO "authenticated" USING (true);--> statement-breakpoint
CREATE POLICY "select-own-pilot" ON "pilots" AS PERMISSIVE FOR SELECT TO "authenticated" USING ("pilots"."id" = auth.uid());--> statement-breakpoint
CREATE POLICY "update-own-pilot" ON "pilots" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ("pilots"."id" = auth.uid()) WITH CHECK ("pilots"."id" = auth.uid());--> statement-breakpoint
CREATE POLICY "insert-pilot" ON "pilots" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ("pilots"."id" = auth.uid());

View File

@ -1,125 +0,0 @@
{
"id": "7d0d4272-65ba-45cf-9dd3-a5e2008d3744",
"prevId": "c266fe94-b863-4b6c-930c-44af8af68c1a",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.profiles": {
"name": "profiles",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"first_name": {
"name": "first_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_name": {
"name": "last_name",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"profiles_id_fkey": {
"name": "profiles_id_fkey",
"tableFrom": "profiles",
"tableTo": "users",
"schemaTo": "auth",
"columnsFrom": [
"id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {
"select-own-profile": {
"name": "select-own-profile",
"as": "PERMISSIVE",
"for": "SELECT",
"to": [
"authenticated"
],
"using": "\"profiles\".\"id\" = auth.uid()"
},
"update-own-profile": {
"name": "update-own-profile",
"as": "PERMISSIVE",
"for": "UPDATE",
"to": [
"authenticated"
],
"using": "\"profiles\".\"id\" = auth.uid()",
"withCheck": "\"profiles\".\"id\" = auth.uid()"
},
"insert-profile": {
"name": "insert-profile",
"as": "PERMISSIVE",
"for": "INSERT",
"to": [
"authenticated"
],
"withCheck": "\"profiles\".\"id\" = auth.uid()"
}
},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.demo": {
"name": "demo",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"full_name": {
"name": "full_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"phone": {
"name": "phone",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -1,336 +0,0 @@
{
"id": "462e99b2-ba6b-4c91-9f6b-891795695955",
"prevId": "7d0d4272-65ba-45cf-9dd3-a5e2008d3744",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.certifications": {
"name": "certifications",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {
"select-certifications": {
"name": "select-certifications",
"as": "PERMISSIVE",
"for": "SELECT",
"to": [
"authenticated"
],
"using": "true"
}
},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.drone_models": {
"name": "drone_models",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {
"select-drone-models": {
"name": "select-drone-models",
"as": "PERMISSIVE",
"for": "SELECT",
"to": [
"authenticated"
],
"using": "true"
}
},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.pilots": {
"name": "pilots",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"location": {
"name": "location",
"type": "text",
"primaryKey": false,
"notNull": false
},
"latitude": {
"name": "latitude",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"longitude": {
"name": "longitude",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"company": {
"name": "company",
"type": "text",
"primaryKey": false,
"notNull": false
},
"position": {
"name": "position",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"differentiation": {
"name": "differentiation",
"type": "text",
"primaryKey": false,
"notNull": false
},
"coverage_areas": {
"name": "coverage_areas",
"type": "text",
"primaryKey": false,
"notNull": false
},
"specialization_areas": {
"name": "specialization_areas",
"type": "text",
"primaryKey": false,
"notNull": false
},
"certification_ids": {
"name": "certification_ids",
"type": "integer[]",
"primaryKey": false,
"notNull": false
},
"drone_model_ids": {
"name": "drone_model_ids",
"type": "integer[]",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"pilots_id_fkey": {
"name": "pilots_id_fkey",
"tableFrom": "pilots",
"tableTo": "users",
"schemaTo": "auth",
"columnsFrom": [
"id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {
"select-own-pilot": {
"name": "select-own-pilot",
"as": "PERMISSIVE",
"for": "SELECT",
"to": [
"authenticated"
],
"using": "\"pilots\".\"id\" = auth.uid()"
},
"update-own-pilot": {
"name": "update-own-pilot",
"as": "PERMISSIVE",
"for": "UPDATE",
"to": [
"authenticated"
],
"using": "\"pilots\".\"id\" = auth.uid()",
"withCheck": "\"pilots\".\"id\" = auth.uid()"
},
"insert-pilot": {
"name": "insert-pilot",
"as": "PERMISSIVE",
"for": "INSERT",
"to": [
"authenticated"
],
"withCheck": "\"pilots\".\"id\" = auth.uid()"
}
},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.profiles": {
"name": "profiles",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"first_name": {
"name": "first_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_name": {
"name": "last_name",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"profiles_id_fkey": {
"name": "profiles_id_fkey",
"tableFrom": "profiles",
"tableTo": "users",
"schemaTo": "auth",
"columnsFrom": [
"id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {
"select-own-profile": {
"name": "select-own-profile",
"as": "PERMISSIVE",
"for": "SELECT",
"to": [
"authenticated"
],
"using": "\"profiles\".\"id\" = auth.uid()"
},
"update-own-profile": {
"name": "update-own-profile",
"as": "PERMISSIVE",
"for": "UPDATE",
"to": [
"authenticated"
],
"using": "\"profiles\".\"id\" = auth.uid()",
"withCheck": "\"profiles\".\"id\" = auth.uid()"
},
"insert-profile": {
"name": "insert-profile",
"as": "PERMISSIVE",
"for": "INSERT",
"to": [
"authenticated"
],
"withCheck": "\"profiles\".\"id\" = auth.uid()"
}
},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.demo": {
"name": "demo",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"full_name": {
"name": "full_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"phone": {
"name": "phone",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -8,20 +8,6 @@
"when": 1754932713212, "when": 1754932713212,
"tag": "0000_talented_doorman", "tag": "0000_talented_doorman",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1755013739316,
"tag": "0001_nice_gargoyle",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1755075449620,
"tag": "0002_bouncy_apocalypse",
"breakpoints": true
} }
] ]
} }

View File

@ -1,39 +0,0 @@
import { useMutation } from "@tanstack/react-query"
import { toast } from "sonner"
import type z from "zod"
import { createProfile } from "@/lib/server/user"
import { profileFormSchema } from "@/lib/validation/user"
import { useValidation } from "../useValidation"
type TProfileForm = z.infer<typeof profileFormSchema>
export const useProfile = () => {
const { validate, errors } = useValidation({
defaultSchema: profileFormSchema
})
const signup = useMutation({
mutationKey: ["create-profile"],
mutationFn: async (data: TProfileForm) => createProfile({ data }),
onSuccess: () => {
toast.success("Your profile is created..", {
id: "create-profile"
})
}
})
const validateSignup = (formData: TProfileForm) => {
const isValid = validate({ formData })
if (!isValid) {
toast.error("Don't create", {
id: "create-profile"
})
}
signup.mutate(formData)
}
return {
profile: validateSignup,
errors,
isPending: signup.isPending
}
}

View File

@ -30,7 +30,7 @@ export const useLogin = () => {
onSuccess: () => { onSuccess: () => {
toast.success("Login successful! Redirecting to posts..", { id: "login" }) toast.success("Login successful! Redirecting to posts..", { id: "login" })
navigate({ navigate({
to: "/dashboard" to: "/post"
}) })
}, },
onError: (error) => { onError: (error) => {

View File

@ -1,14 +1,9 @@
import { redirect } from "@tanstack/react-router" import { redirect } from "@tanstack/react-router"
import { createServerFn } from "@tanstack/react-start" import { createServerFn } from "@tanstack/react-start"
import { eq } from "drizzle-orm"
import { db } from "@/integrations/drizzle" import { db } from "@/integrations/drizzle"
import { profiles, users } from "@/integrations/drizzle/db/schema" import { users } from "@/integrations/drizzle/db/schema"
import { getSupabaseServerClient } from "@/integrations/supabase/supabase" import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
import { import { loginFormSchema, signupFormSchema } from "../validation/user"
loginFormSchema,
profileFormSchema,
signupFormSchema
} from "../validation/user"
export const getUser = createServerFn().handler(async () => { export const getUser = createServerFn().handler(async () => {
const supabase = getSupabaseServerClient() const supabase = getSupabaseServerClient()
@ -19,13 +14,12 @@ export const getUser = createServerFn().handler(async () => {
message: error?.message ?? "Unknown error" message: error?.message ?? "Unknown error"
} }
} }
console.log(data)
return { return {
user: { user: {
id: data.user.id, id: data.user.id,
email: data.user.email, email: data.user.email,
name: data.user.user_metadata.name || "", name: data.user.user_metadata.name || ""
location: data.user.user_metadata.location || ""
}, },
error: false error: false
} }
@ -77,13 +71,7 @@ export const signupUser = createServerFn({ method: "POST" })
const supabase = getSupabaseServerClient() const supabase = getSupabaseServerClient()
const { error } = await supabase.auth.signUp({ const { error } = await supabase.auth.signUp({
email: data.email, email: data.email,
password: data.password, password: data.password
options: {
data: {
name: data.name,
location: data.location
}
}
}) })
if (error) { if (error) {
return { return {
@ -97,26 +85,8 @@ export const signupUser = createServerFn({ method: "POST" })
}) })
}) })
export const getAllUsers = createServerFn().handler(async () => { export const getAllUsers = createServerFn().handler(async () => {
const response = await db.select().from(users) const response = await db.select().from(users)
return response return response
}) })
export const createProfile = createServerFn({ method: "POST" })
.validator(profileFormSchema)
.handler(async ({ data }) => {
await db.insert(profiles).values(data).returning()
})
export const getProfile = createServerFn({ method: "POST" })
.validator((data: { id: string }) => data)
.handler(async ({ data }) => {
const { id } = data
const response = await db
.select()
.from(profiles)
.where(eq(profiles.id, id))
.limit(1)
return response[0] ?? null
})

View File

@ -8,11 +8,5 @@ export const loginFormSchema = z.object({
export const signupFormSchema = z.object({ export const signupFormSchema = z.object({
email: z.email("Invalid email address"), email: z.email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters long"), password: z.string().min(6, "Password must be at least 6 characters long"),
name: z.string().min(1, "The field is required"),
location: z.string().min(1, "The field is required"),
redirectUrl: z.string().optional() redirectUrl: z.string().optional()
}) })
export const profileFormSchema= z.object({
id: z.uuid()
})

View File

@ -14,8 +14,7 @@ import { Route as LogoutRouteImport } from './routes/logout'
import { Route as LoginRouteImport } from './routes/login' import { Route as LoginRouteImport } from './routes/login'
import { Route as AuthedRouteImport } from './routes/_authed' import { Route as AuthedRouteImport } from './routes/_authed'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as AuthedRegisterRouteImport } from './routes/_authed/register' import { Route as AuthedPostRouteImport } from './routes/_authed/post'
import { Route as AuthedDashboardRouteImport } from './routes/_authed/dashboard'
const SignupRoute = SignupRouteImport.update({ const SignupRoute = SignupRouteImport.update({
id: '/signup', id: '/signup',
@ -41,14 +40,9 @@ const IndexRoute = IndexRouteImport.update({
path: '/', path: '/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const AuthedRegisterRoute = AuthedRegisterRouteImport.update({ const AuthedPostRoute = AuthedPostRouteImport.update({
id: '/register', id: '/post',
path: '/register', path: '/post',
getParentRoute: () => AuthedRoute,
} as any)
const AuthedDashboardRoute = AuthedDashboardRouteImport.update({
id: '/dashboard',
path: '/dashboard',
getParentRoute: () => AuthedRoute, getParentRoute: () => AuthedRoute,
} as any) } as any)
@ -57,16 +51,14 @@ export interface FileRoutesByFullPath {
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/logout': typeof LogoutRoute '/logout': typeof LogoutRoute
'/signup': typeof SignupRoute '/signup': typeof SignupRoute
'/dashboard': typeof AuthedDashboardRoute '/post': typeof AuthedPostRoute
'/register': typeof AuthedRegisterRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/logout': typeof LogoutRoute '/logout': typeof LogoutRoute
'/signup': typeof SignupRoute '/signup': typeof SignupRoute
'/dashboard': typeof AuthedDashboardRoute '/post': typeof AuthedPostRoute
'/register': typeof AuthedRegisterRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
@ -75,14 +67,13 @@ export interface FileRoutesById {
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/logout': typeof LogoutRoute '/logout': typeof LogoutRoute
'/signup': typeof SignupRoute '/signup': typeof SignupRoute
'/_authed/dashboard': typeof AuthedDashboardRoute '/_authed/post': typeof AuthedPostRoute
'/_authed/register': typeof AuthedRegisterRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/login' | '/logout' | '/signup' | '/dashboard' | '/register' fullPaths: '/' | '/login' | '/logout' | '/signup' | '/post'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/login' | '/logout' | '/signup' | '/dashboard' | '/register' to: '/' | '/login' | '/logout' | '/signup' | '/post'
id: id:
| '__root__' | '__root__'
| '/' | '/'
@ -90,8 +81,7 @@ export interface FileRouteTypes {
| '/login' | '/login'
| '/logout' | '/logout'
| '/signup' | '/signup'
| '/_authed/dashboard' | '/_authed/post'
| '/_authed/register'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
@ -139,31 +129,22 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/_authed/register': { '/_authed/post': {
id: '/_authed/register' id: '/_authed/post'
path: '/register' path: '/post'
fullPath: '/register' fullPath: '/post'
preLoaderRoute: typeof AuthedRegisterRouteImport preLoaderRoute: typeof AuthedPostRouteImport
parentRoute: typeof AuthedRoute
}
'/_authed/dashboard': {
id: '/_authed/dashboard'
path: '/dashboard'
fullPath: '/dashboard'
preLoaderRoute: typeof AuthedDashboardRouteImport
parentRoute: typeof AuthedRoute parentRoute: typeof AuthedRoute
} }
} }
} }
interface AuthedRouteChildren { interface AuthedRouteChildren {
AuthedDashboardRoute: typeof AuthedDashboardRoute AuthedPostRoute: typeof AuthedPostRoute
AuthedRegisterRoute: typeof AuthedRegisterRoute
} }
const AuthedRouteChildren: AuthedRouteChildren = { const AuthedRouteChildren: AuthedRouteChildren = {
AuthedDashboardRoute: AuthedDashboardRoute, AuthedPostRoute: AuthedPostRoute,
AuthedRegisterRoute: AuthedRegisterRoute,
} }
const AuthedRouteWithChildren = const AuthedRouteWithChildren =

View File

@ -53,19 +53,11 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<HeadContent /> <HeadContent />
</head> </head>
<body> <body>
<div <HeroUIProvider>{children}</HeroUIProvider>
className="min-h-screen bg-gradient-to-br from-green-200 to-emerald-400"
style={{
backgroundImage:
"radial-gradient(50% 50% at 95% 5%, #34d399 0%, #6ee7b7 70%, #f5f5f5 100%)"
}}
>
<HeroUIProvider>{children}</HeroUIProvider>
</div>
<SonnerProvider /> <SonnerProvider />
<TanStackRouterDevtools /> <TanStackRouterDevtools />
<Scripts /> <Scripts />
</body> </body>
</html> </html>
) )
} }

View File

@ -1,12 +1,4 @@
import { import { createFileRoute, redirect } from "@tanstack/react-router"
Avatar,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownTrigger
} from "@heroui/react"
import { createFileRoute, Outlet } from "@tanstack/react-router"
import { getProfile } from "@/lib/server/user"
export const Route = createFileRoute("/_authed")({ export const Route = createFileRoute("/_authed")({
beforeLoad: ({ context }) => { beforeLoad: ({ context }) => {
@ -15,12 +7,6 @@ export const Route = createFileRoute("/_authed")({
// TODO: Redirect to login page // TODO: Redirect to login page
} }
}, },
loader: ({ context: { queryClient, user } }) => {
queryClient.ensureQueryData({
queryKey: ["profile"],
queryFn: () => getProfile({ data: { id: user?.id as string } })
})
},
errorComponent: ({ error }) => { errorComponent: ({ error }) => {
if (error.message === "Not authenticated") { if (error.message === "Not authenticated") {
return ( return (
@ -30,38 +16,5 @@ export const Route = createFileRoute("/_authed")({
) )
} }
throw error throw error
}, }
component: RouteComponent
}) })
function RouteComponent() {
const navigate = Route.useNavigate()
return (
<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" />
</DropdownTrigger>
<DropdownMenu
onAction={(key) => {
if (key === "exit") {
navigate({ to: "/logout" })
}
}}
>
<DropdownItem color="danger" key="exit">
Logout
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
<div></div>
</nav>
<Outlet />
</div>
)
}

View File

@ -1,141 +0,0 @@
import {
Avatar,
Card,
CardBody,
CardFooter,
CardHeader,
Chip,
Divider
} from "@heroui/react"
import { useQuery } from "@tanstack/react-query"
import { createFileRoute } from "@tanstack/react-router"
import { getAllUsers, getProfile } from "@/lib/server/user"
export const Route = createFileRoute("/_authed/dashboard")({
component: RouteComponent
})
function RouteComponent() {
const { user } = Route.useRouteContext()
const { data } = useQuery({
queryKey: ["users"],
queryFn: async () => {
return getAllUsers()
}
})
const { data: profile } = useQuery({
queryKey: ["profile"],
queryFn: async () => {
return getProfile({ data: { id: user?.id as string } })
}
})
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 {profile?.firstName ?? "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">
Ofertas de vuelo
</h2>
<p className="text-default-500 text-sm text-start">
Explora y gestiona tus ofertas de vuelo activas.
</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>
)
}
export default RouteComponent

View File

@ -0,0 +1,39 @@
import { Button } from "@heroui/react"
import { useQuery } from "@tanstack/react-query"
import { createFileRoute } from "@tanstack/react-router"
import { getAllUsers } from "@/lib/server/user"
export const Route = createFileRoute("/_authed/post")({
component: RouteComponent
})
function RouteComponent() {
const navigate = Route.useNavigate()
const { data } = useQuery({
queryKey: ["users"],
queryFn: async () => {
return getAllUsers()
}
})
console.log(data)
return (
<div>
Hello "/_authed/post"!{" "}
<div>
{" "}
<Button
onPress={() =>
navigate({
to: "/logout"
})
}
>
Logout
</Button>
</div>
</div>
)
}

View File

@ -1,42 +0,0 @@
import { Button, Form, Input, Slider, Textarea } from "@heroui/react"
import { createFileRoute } from "@tanstack/react-router"
import type { FormEvent } from "react"
import { useProfile } from "@/lib/hooks/user/useCreateUser"
export const Route = createFileRoute("/_authed/register")({
component: RouteComponent
})
function RouteComponent() {
const { errors, isPending, profile } = useProfile()
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
profile({
id: "e6472b9d-01a9-4e2e-8bdc-0ddaa9baf5d8",
firstName: formData.get("firstName") as string,
lastName: formData.get("lastName") as string
})
}
return (
<div>
<Form
className="grid gap-2 max-w-sm w-full"
onSubmit={handleSubmit}
validationErrors={errors}
>
<Input name="name" label="Nombre completo" isRequired />
<Input name="company" label="Empresa" />
<Input name="role" label="Cargo" />
<Textarea name="description" label="Descripción" />
<Textarea name="differentiator" label="¿Qué te hace diferente?" />
<Slider name="coverage_areas" label="Areas de cobertura" />
<Input name="services" label="Servicios" />{" "}
{/* Crear varios checkboxes */}
<Button isLoading={isPending} type="submit">
Crear usuario
</Button>
</Form>
</div>
)
}

View File

@ -1,17 +1,9 @@
import { Button, Form, Input } from "@heroui/react" import { Button, Form, Input } from "@heroui/react"
import { createFileRoute, redirect } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import type { FormEvent } from "react" import type { FormEvent } from "react"
import { useLogin } from "@/lib/hooks/user/useLogin" import { useLogin } from "@/lib/hooks/user/useLogin"
export const Route = createFileRoute("/login")({ export const Route = createFileRoute("/login")({
beforeLoad: ({ context }) => {
if (!context?.error) {
throw redirect({
to: "/dashboard"
})
// TODO: Redirect to login page
}
},
component: LoginComp component: LoginComp
}) })

View File

@ -17,9 +17,7 @@ function SignupComp() {
signup({ signup({
email: formData.get("email") as string, email: formData.get("email") as string,
password: formData.get("password") as string, password: formData.get("password") as string
name: formData.get("nombre") as string,
location: formData.get("location") as string
}) })
} }
@ -33,8 +31,6 @@ function SignupComp() {
> >
<Input name="email" type="email" label="Email" /> <Input name="email" type="email" label="Email" />
<Input name="password" type="password" label="Password" /> <Input name="password" type="password" label="Password" />
<Input name="nombre" label="Name" />
<Input name="location" label="Location" />
<Button type="submit" isLoading={isPending}> <Button type="submit" isLoading={isPending}>
Enviar Enviar
</Button> </Button>

View File

@ -1,4 +1,3 @@
@import url('https://fonts.googleapis.com/css2?family=Luckiest+Guy&family=Outfit:wght@100..900&family=Rubik+Vinyl&display=swap');
@import "tailwindcss"; @import "tailwindcss";
@plugin '../integrations/heroui/heroui.ts'; @plugin '../integrations/heroui/heroui.ts';
@ -14,15 +13,7 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
@theme {
--font-outfit: "Outfit", sans-serif;
--font-rubik-vinyl: "Rubik Vinyl", cursive;
--font-luckiest-guy: "Luckiest Guy", cursive;
}
code { code {
font-family: font-family:
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
} }