Compare commits

...

2 Commits

16 changed files with 2428 additions and 2279 deletions

View File

@@ -6,4 +6,4 @@ APIKEY_MAPS="AIzaSyAwfOShBqkBcS46WqmlsIVWQJ8gpdOPk_4"
SUPABASE_URL="https://qsssikzgwomudkwfmgad.supabase.co" SUPABASE_URL="https://qsssikzgwomudkwfmgad.supabase.co"
SUPABASE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFzc3Npa3pnd29tdWRrd2ZtZ2FkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQzMjY1NTQsImV4cCI6MjA2OTkwMjU1NH0.BTSscdTcPP1GVmMB-H5caLpWsfuAw1V6mXiqogF8TjU" SUPABASE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFzc3Npa3pnd29tdWRrd2ZtZ2FkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQzMjY1NTQsImV4cCI6MjA2OTkwMjU1NH0.BTSscdTcPP1GVmMB-H5caLpWsfuAw1V6mXiqogF8TjU"
VITE_LOGIN_USER="test@test.com" VITE_LOGIN_USER="test@test.com"
VITE_PASSWORD_USER="" VITE_PASSWORD_USER="test"

41
AGENTS.md Normal file
View File

@@ -0,0 +1,41 @@
# AGENTS.md — findyourpilot
Project context for AI coding agents. This file defines which skill files to load
depending on the task at hand.
<!-- intent-skills:start -->
# Skill mappings - when working in these areas, load the linked skill file into context.
skills:
- task: "Building or modifying routes, loaders, search params, or navigation with TanStack Router"
load: "node_modules/.pnpm/@tanstack+router-core@1.167.5/node_modules/@tanstack/router-core/skills/router-core/SKILL.md"
- task: "Working with route search params, type-safe URL state, or Zod-validated search params"
load: "node_modules/.pnpm/@tanstack+router-core@1.167.5/node_modules/@tanstack/router-core/skills/router-core/search-params/SKILL.md"
- task: "Implementing route guards, authentication redirects, or protected routes"
load: "node_modules/.pnpm/@tanstack+router-core@1.167.5/node_modules/@tanstack/router-core/skills/router-core/auth-and-guards/SKILL.md"
- task: "Loading data in routes, using loaders, or integrating with TanStack Query"
load: "node_modules/.pnpm/@tanstack+router-core@1.167.5/node_modules/@tanstack/router-core/skills/router-core/data-loading/SKILL.md"
- task: "Creating server functions, SSR patterns, full-stack logic, or the TanStack Start execution model"
load: "node_modules/.pnpm/@tanstack+start-client-core@1.166.13/node_modules/@tanstack/start-client-core/skills/start-core/SKILL.md"
- task: "Creating or modifying createServerFn, input validation on server functions, or handling server errors"
load: "node_modules/.pnpm/@tanstack+start-client-core@1.166.13/node_modules/@tanstack/start-client-core/skills/start-core/server-functions/SKILL.md"
- task: "Creating middleware, request middleware, or passing context between server functions"
load: "node_modules/.pnpm/@tanstack+start-client-core@1.166.13/node_modules/@tanstack/start-client-core/skills/start-core/middleware/SKILL.md"
- task: "Creating API endpoints or server-only routes (server property on createFileRoute)"
load: "node_modules/.pnpm/@tanstack+start-client-core@1.166.13/node_modules/@tanstack/start-client-core/skills/start-core/server-routes/SKILL.md"
- task: "Deploying the app to Cloudflare, Vercel, Netlify, Node.js/Docker, or configuring SSR/SPA/prerendering"
load: "node_modules/.pnpm/@tanstack+start-client-core@1.166.13/node_modules/@tanstack/start-client-core/skills/start-core/deployment/SKILL.md"
- task: "Writing, reviewing, or optimising Supabase/Postgres queries, schema design, RLS policies, or migrations"
load: ".agents/skills/supabase-postgres-best-practices/SKILL.md"
- task: "Designing or improving UI layouts, dashboards, component composition, or interactive product interfaces with HeroUI"
load: ".agents/skills/interface-design/SKILL.md"
<!-- intent-skills:end -->

View File

@@ -14,42 +14,45 @@
"machine-translate": "inlang machine translate --project project.inlang" "machine-translate": "inlang machine translate --project project.inlang"
}, },
"dependencies": { "dependencies": {
"@heroui/react": "^3.0.0-beta.8", "@heroui/react": "^3.0.0-rc.1",
"@heroui/styles": "^3.0.0-beta.8", "@heroui/styles": "^3.0.0-rc.1",
"@sentry/tanstackstart-react": "^10.42.0", "@sentry/tanstackstart-react": "^10.45.0",
"@supabase/ssr": "^0.9.0", "@supabase/ssr": "^0.9.0",
"@supabase/supabase-js": "^2.99.1", "@supabase/supabase-js": "^2.99.3",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.90.21", "@tanstack/react-query": "^5.91.2",
"@tanstack/react-router": "^1.166.7", "@tanstack/react-router": "^1.167.5",
"@tanstack/react-router-ssr-query": "^1.166.7", "@tanstack/react-router-ssr-query": "^1.166.9",
"@tanstack/react-start": "^1.166.8", "@tanstack/react-start": "^1.166.17",
"@tanstack/router-plugin": "^1.166.7", "@tanstack/router-plugin": "^1.166.14",
"clsx": "^2.1.1",
"lucide-react": "^0.577.0", "lucide-react": "^0.577.0",
"maplibre-gl": "^5.19.0", "maplibre-gl": "^5.20.2",
"nitro": "^3.0.1-alpha.2", "nitro": "^3.0.1-alpha.2",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"tailwindcss": "^4.2.1", "tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0" "tailwindcss": "^4.2.2",
"tw-animate-css": "^1.4.0",
"zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.4.6", "@biomejs/biome": "^2.4.8",
"@inlang/paraglide-js": "^2.13.1", "@inlang/paraglide-js": "^2.15.0",
"@tanstack/devtools-vite": "^0.5.5", "@tanstack/devtools-vite": "^0.6.0",
"@tanstack/react-devtools": "^0.9.13", "@tanstack/react-devtools": "^0.10.0",
"@tanstack/react-router-devtools": "^1.166.7",
"@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-router-devtools": "^1.166.9",
"@testing-library/dom": "^10.4.1", "@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@types/node": "^22.10.2", "@types/node": "^22.19.15",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4", "@vitejs/plugin-react": "^6.0.1",
"jsdom": "^28.1.0", "jsdom": "^29.0.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^7.3.1", "vite": "^8.0.1",
"vite-tsconfig-paths": "^6.1.1", "vite-tsconfig-paths": "^6.1.1",
"vitest": "^3.2.4" "vitest": "^4.1.0"
} }
} }

4050
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,13 +14,14 @@ export const useLogin = () => {
mutationKey: ["login"], mutationKey: ["login"],
mutationFn: async (data: TLoginForm) => { mutationFn: async (data: TLoginForm) => {
const response = await user.login({ data }) const response = await user.login({ data })
if (response.error) { if (response.error) {
throw new Error(response.message) throw new Error(response.message)
} }
}, },
onSuccess: () => { onSuccess: () => {
navigate({ navigate({
to: "/" to: "/dashboard"
}) })
} }
}) })

View File

@@ -1,38 +1,44 @@
import { toast } from "@heroui/react" import { toast } from "@heroui/react"
import { useMutation } from "@tanstack/react-query" import { useMutation } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router" import type z from "zod"
import type z from "zod" import { user } from "@/lib/server/user"
import { user } from "@/lib/server/user" import type { signupClientFormSchema } from "@/lib/validation/user"
import { signupFormSchema } from "@/lib/validation/user"
type TSignupClientForm = z.infer<typeof signupClientFormSchema>
type TSignupForm = z.infer<typeof signupFormSchema>
export const useSignup = () => {
export const useSignup = () => { const signupMutation = useMutation({
const navigate = useNavigate() mutationKey: ["signup"],
const signup = useMutation({ mutationFn: async (data: TSignupClientForm) => {
mutationKey: ["signup"], const { confirmPassword: _, ...serverData } = data
mutationFn: async (data: TSignupForm) => user.signup({ data }), const response = await user.signup({ data: serverData })
onSuccess: () => {
navigate({ if (response && "error" in response && response.error) {
to: "/access/login" throw new Error(
}) "message" in response
} ? (response.message as string)
}) : "Error desconocido"
)
const validateSignup = (formData: TSignupForm) => { }
if (!signupFormSchema.safeParse(formData).success) { }
toast.danger("Signup failed. Please check your input.") })
}
const promise = signup.mutateAsync(formData) const validateSignup = (formData: TSignupClientForm) => {
toast.promise(promise, { if (formData.password !== formData.confirmPassword) {
loading: "Signing up...", toast.danger("Las contraseñas no coinciden")
success: "Signup successful! Redirecting to login...", return
error: "Signup failed!" }
})
} const promise = signupMutation.mutateAsync(formData)
toast.promise(promise, {
return { loading: "Creando tu cuenta...",
signup: validateSignup, success: "¡Cuenta creada! Revisa tu correo para confirmarla.",
isPending: signup.isPending error: (error: Error) => error.message
} })
} }
return {
signup: validateSignup,
isPending: signupMutation.isPending
}
}

View File

@@ -1,18 +1,28 @@
import * as z from "zod" import * as z from "zod"
export const loginFormSchema = z.object({ export const loginFormSchema = z.object({
email: z.email("Invalid email address"), email: z.email("Introduce un correo válido"),
password: z.string().min(1, "Password must be at least 1 character long") password: z.string().min(1, "La contraseña es obligatoria")
}) })
export const signupFormSchema = z.object({ export const signupFormSchema = z.object({
email: z.email("Invalid email address"), email: z.email("Introduce un correo válido"),
password: z.string().min(6, "Password must be at least 6 characters long"), password: z.string().min(6, "La contraseña debe tener al menos 6 caracteres"),
name: z.string().min(1, "The field is required"), name: z.string().min(1, "El nombre es obligatorio"),
location: z.string().min(1, "The field is required"), location: z.string().min(1, "La ubicación es obligatoria"),
redirectUrl: z.string().optional() redirectUrl: z.string().optional()
}) })
// Schema extendido para el formulario cliente (incluye confirmación de contraseña)
export const signupClientFormSchema = signupFormSchema
.extend({
confirmPassword: z.string().min(6, "Confirma tu contraseña")
})
.refine((data) => data.password === data.confirmPassword, {
message: "Las contraseñas no coinciden",
path: ["confirmPassword"]
})
export const profileFormSchema = z.object({ export const profileFormSchema = z.object({
id: z.uuid(), id: z.uuid(),
firstName: z.string().min(1, "First name is required"), firstName: z.string().min(1, "First name is required"),

View File

@@ -12,17 +12,13 @@ import { Route as rootRouteImport } from './routes/__root'
import { Route as LogoutRouteImport } from './routes/logout' import { Route as LogoutRouteImport } from './routes/logout'
import { Route as LoginRouteImport } from './routes/login' import { Route as LoginRouteImport } from './routes/login'
import { Route as AccessRouteImport } from './routes/access' import { Route as AccessRouteImport } from './routes/access'
import { Route as AuthedRouteImport } from './routes/_authed' import { Route as AuthRouteImport } from './routes/_auth'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query' import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
import { Route as DemoI18nRouteImport } from './routes/demo.i18n' import { Route as DemoI18nRouteImport } from './routes/demo.i18n'
<<<<<<< HEAD
import { Route as AccessRegisterRouteImport } from './routes/access.register' import { Route as AccessRegisterRouteImport } from './routes/access.register'
import { Route as AccessLoginRouteImport } from './routes/access.login' import { Route as AccessLoginRouteImport } from './routes/access.login'
import { Route as AuthedDashboardRouteImport } from './routes/_authed/dashboard' import { Route as AuthDashboardRouteImport } from './routes/_auth/dashboard'
=======
import { Route as AuthDashboardRouteImport } from './routes/auth/dashboard'
>>>>>>> main
import { Route as DemoSentryTestingRouteImport } from './routes/demo/sentry.testing' import { Route as DemoSentryTestingRouteImport } from './routes/demo/sentry.testing'
const LogoutRoute = LogoutRouteImport.update({ const LogoutRoute = LogoutRouteImport.update({
@@ -40,8 +36,8 @@ const AccessRoute = AccessRouteImport.update({
path: '/access', path: '/access',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const AuthedRoute = AuthedRouteImport.update({ const AuthRoute = AuthRouteImport.update({
id: '/_authed', id: '/_auth',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const IndexRoute = IndexRouteImport.update({ const IndexRoute = IndexRouteImport.update({
@@ -59,7 +55,6 @@ const DemoI18nRoute = DemoI18nRouteImport.update({
path: '/demo/i18n', path: '/demo/i18n',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
<<<<<<< HEAD
const AccessRegisterRoute = AccessRegisterRouteImport.update({ const AccessRegisterRoute = AccessRegisterRouteImport.update({
id: '/register', id: '/register',
path: '/register', path: '/register',
@@ -70,16 +65,10 @@ const AccessLoginRoute = AccessLoginRouteImport.update({
path: '/login', path: '/login',
getParentRoute: () => AccessRoute, getParentRoute: () => AccessRoute,
} as any) } as any)
const AuthedDashboardRoute = AuthedDashboardRouteImport.update({ const AuthDashboardRoute = AuthDashboardRouteImport.update({
id: '/dashboard', id: '/dashboard',
path: '/dashboard', path: '/dashboard',
getParentRoute: () => AuthedRoute, getParentRoute: () => AuthRoute,
=======
const AuthDashboardRoute = AuthDashboardRouteImport.update({
id: '/auth/dashboard',
path: '/auth/dashboard',
getParentRoute: () => rootRouteImport,
>>>>>>> main
} as any) } as any)
const DemoSentryTestingRoute = DemoSentryTestingRouteImport.update({ const DemoSentryTestingRoute = DemoSentryTestingRouteImport.update({
id: '/demo/sentry/testing', id: '/demo/sentry/testing',
@@ -89,34 +78,24 @@ const DemoSentryTestingRoute = DemoSentryTestingRouteImport.update({
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
<<<<<<< HEAD
'/access': typeof AccessRouteWithChildren '/access': typeof AccessRouteWithChildren
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/logout': typeof LogoutRoute '/logout': typeof LogoutRoute
'/dashboard': typeof AuthedDashboardRoute '/dashboard': typeof AuthDashboardRoute
'/access/login': typeof AccessLoginRoute '/access/login': typeof AccessLoginRoute
'/access/register': typeof AccessRegisterRoute '/access/register': typeof AccessRegisterRoute
=======
'/login': typeof LoginRouteRoute
'/auth/dashboard': typeof AuthDashboardRoute
>>>>>>> main
'/demo/i18n': typeof DemoI18nRoute '/demo/i18n': typeof DemoI18nRoute
'/demo/tanstack-query': typeof DemoTanstackQueryRoute '/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/demo/sentry/testing': typeof DemoSentryTestingRoute '/demo/sentry/testing': typeof DemoSentryTestingRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
<<<<<<< HEAD
'/access': typeof AccessRouteWithChildren '/access': typeof AccessRouteWithChildren
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/logout': typeof LogoutRoute '/logout': typeof LogoutRoute
'/dashboard': typeof AuthedDashboardRoute '/dashboard': typeof AuthDashboardRoute
'/access/login': typeof AccessLoginRoute '/access/login': typeof AccessLoginRoute
'/access/register': typeof AccessRegisterRoute '/access/register': typeof AccessRegisterRoute
=======
'/login': typeof LoginRouteRoute
'/auth/dashboard': typeof AuthDashboardRoute
>>>>>>> main
'/demo/i18n': typeof DemoI18nRoute '/demo/i18n': typeof DemoI18nRoute
'/demo/tanstack-query': typeof DemoTanstackQueryRoute '/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/demo/sentry/testing': typeof DemoSentryTestingRoute '/demo/sentry/testing': typeof DemoSentryTestingRoute
@@ -124,18 +103,13 @@ export interface FileRoutesByTo {
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/': typeof IndexRoute
<<<<<<< HEAD '/_auth': typeof AuthRouteWithChildren
'/_authed': typeof AuthedRouteWithChildren
'/access': typeof AccessRouteWithChildren '/access': typeof AccessRouteWithChildren
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/logout': typeof LogoutRoute '/logout': typeof LogoutRoute
'/_authed/dashboard': typeof AuthedDashboardRoute '/_auth/dashboard': typeof AuthDashboardRoute
'/access/login': typeof AccessLoginRoute '/access/login': typeof AccessLoginRoute
'/access/register': typeof AccessRegisterRoute '/access/register': typeof AccessRegisterRoute
=======
'/login': typeof LoginRouteRoute
'/auth/dashboard': typeof AuthDashboardRoute
>>>>>>> main
'/demo/i18n': typeof DemoI18nRoute '/demo/i18n': typeof DemoI18nRoute
'/demo/tanstack-query': typeof DemoTanstackQueryRoute '/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/demo/sentry/testing': typeof DemoSentryTestingRoute '/demo/sentry/testing': typeof DemoSentryTestingRoute
@@ -146,14 +120,10 @@ export interface FileRouteTypes {
| '/' | '/'
| '/access' | '/access'
| '/login' | '/login'
<<<<<<< HEAD
| '/logout' | '/logout'
| '/dashboard' | '/dashboard'
| '/access/login' | '/access/login'
| '/access/register' | '/access/register'
=======
| '/auth/dashboard'
>>>>>>> main
| '/demo/i18n' | '/demo/i18n'
| '/demo/tanstack-query' | '/demo/tanstack-query'
| '/demo/sentry/testing' | '/demo/sentry/testing'
@@ -162,31 +132,23 @@ export interface FileRouteTypes {
| '/' | '/'
| '/access' | '/access'
| '/login' | '/login'
<<<<<<< HEAD
| '/logout' | '/logout'
| '/dashboard' | '/dashboard'
| '/access/login' | '/access/login'
| '/access/register' | '/access/register'
=======
| '/auth/dashboard'
>>>>>>> main
| '/demo/i18n' | '/demo/i18n'
| '/demo/tanstack-query' | '/demo/tanstack-query'
| '/demo/sentry/testing' | '/demo/sentry/testing'
id: id:
| '__root__' | '__root__'
| '/' | '/'
| '/_authed' | '/_auth'
| '/access' | '/access'
| '/login' | '/login'
<<<<<<< HEAD
| '/logout' | '/logout'
| '/_authed/dashboard' | '/_auth/dashboard'
| '/access/login' | '/access/login'
| '/access/register' | '/access/register'
=======
| '/auth/dashboard'
>>>>>>> main
| '/demo/i18n' | '/demo/i18n'
| '/demo/tanstack-query' | '/demo/tanstack-query'
| '/demo/sentry/testing' | '/demo/sentry/testing'
@@ -194,15 +156,10 @@ export interface FileRouteTypes {
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
<<<<<<< HEAD AuthRoute: typeof AuthRouteWithChildren
AuthedRoute: typeof AuthedRouteWithChildren
AccessRoute: typeof AccessRouteWithChildren AccessRoute: typeof AccessRouteWithChildren
LoginRoute: typeof LoginRoute LoginRoute: typeof LoginRoute
LogoutRoute: typeof LogoutRoute LogoutRoute: typeof LogoutRoute
=======
LoginRouteRoute: typeof LoginRouteRoute
AuthDashboardRoute: typeof AuthDashboardRoute
>>>>>>> main
DemoI18nRoute: typeof DemoI18nRoute DemoI18nRoute: typeof DemoI18nRoute
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
DemoSentryTestingRoute: typeof DemoSentryTestingRoute DemoSentryTestingRoute: typeof DemoSentryTestingRoute
@@ -231,11 +188,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AccessRouteImport preLoaderRoute: typeof AccessRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/_authed': { '/_auth': {
id: '/_authed' id: '/_auth'
path: '' path: ''
fullPath: '/' fullPath: '/'
preLoaderRoute: typeof AuthedRouteImport preLoaderRoute: typeof AuthRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/': { '/': {
@@ -259,7 +216,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DemoI18nRouteImport preLoaderRoute: typeof DemoI18nRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
<<<<<<< HEAD
'/access/register': { '/access/register': {
id: '/access/register' id: '/access/register'
path: '/register' path: '/register'
@@ -274,20 +230,12 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AccessLoginRouteImport preLoaderRoute: typeof AccessLoginRouteImport
parentRoute: typeof AccessRoute parentRoute: typeof AccessRoute
} }
'/_authed/dashboard': { '/_auth/dashboard': {
id: '/_authed/dashboard' id: '/_auth/dashboard'
path: '/dashboard' path: '/dashboard'
fullPath: '/dashboard' fullPath: '/dashboard'
preLoaderRoute: typeof AuthedDashboardRouteImport
parentRoute: typeof AuthedRoute
=======
'/auth/dashboard': {
id: '/auth/dashboard'
path: '/auth/dashboard'
fullPath: '/auth/dashboard'
preLoaderRoute: typeof AuthDashboardRouteImport preLoaderRoute: typeof AuthDashboardRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof AuthRoute
>>>>>>> main
} }
'/demo/sentry/testing': { '/demo/sentry/testing': {
id: '/demo/sentry/testing' id: '/demo/sentry/testing'
@@ -299,16 +247,15 @@ declare module '@tanstack/react-router' {
} }
} }
interface AuthedRouteChildren { interface AuthRouteChildren {
AuthedDashboardRoute: typeof AuthedDashboardRoute AuthDashboardRoute: typeof AuthDashboardRoute
} }
const AuthedRouteChildren: AuthedRouteChildren = { const AuthRouteChildren: AuthRouteChildren = {
AuthedDashboardRoute: AuthedDashboardRoute, AuthDashboardRoute: AuthDashboardRoute,
} }
const AuthedRouteWithChildren = const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren)
AuthedRoute._addFileChildren(AuthedRouteChildren)
interface AccessRouteChildren { interface AccessRouteChildren {
AccessLoginRoute: typeof AccessLoginRoute AccessLoginRoute: typeof AccessLoginRoute
@@ -325,15 +272,10 @@ const AccessRouteWithChildren =
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
<<<<<<< HEAD AuthRoute: AuthRouteWithChildren,
AuthedRoute: AuthedRouteWithChildren,
AccessRoute: AccessRouteWithChildren, AccessRoute: AccessRouteWithChildren,
LoginRoute: LoginRoute, LoginRoute: LoginRoute,
LogoutRoute: LogoutRoute, LogoutRoute: LogoutRoute,
=======
LoginRouteRoute: LoginRouteRoute,
AuthDashboardRoute: AuthDashboardRoute,
>>>>>>> main
DemoI18nRoute: DemoI18nRoute, DemoI18nRoute: DemoI18nRoute,
DemoTanstackQueryRoute: DemoTanstackQueryRoute, DemoTanstackQueryRoute: DemoTanstackQueryRoute,
DemoSentryTestingRoute: DemoSentryTestingRoute, DemoSentryTestingRoute: DemoSentryTestingRoute,

View File

@@ -1,3 +1,4 @@
import { ToastProvider } from "@heroui/react"
import type { QueryClient } from "@tanstack/react-query" import type { QueryClient } from "@tanstack/react-query"
import { import {
createRootRouteWithContext, createRootRouteWithContext,
@@ -55,6 +56,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<HeadContent /> <HeadContent />
</head> </head>
<body> <body>
<ToastProvider />
{children} {children}
<Devtools /> <Devtools />
<Scripts /> <Scripts />

View File

@@ -1,8 +1,8 @@
import { createFileRoute, Link, Outlet } from "@tanstack/react-router" import { createFileRoute, Link, Outlet } from "@tanstack/react-router"
export const Route = createFileRoute("/_authed")({ export const Route = createFileRoute("/_auth")({
beforeLoad: ({ context }) => { beforeLoad: ({ context }) => {
if (context.error) { if (context.user.error) {
throw new Error("Not authenticated") throw new Error("Not authenticated")
} }
}, },

View File

@@ -5,13 +5,13 @@ import { useState } from "react"
import { import {
Map as MapComponent, Map as MapComponent,
MapMarker, MapMarker,
MapViewport, type MapViewport,
MarkerContent, MarkerContent,
MarkerPopup, MarkerPopup,
MarkerTooltip MarkerTooltip
} from "@/components/maps/map" } from "@/components/maps/map"
export const Route = createFileRoute("/auth/dashboard")({ export const Route = createFileRoute("/_auth/dashboard")({
component: RouteComponent component: RouteComponent
}) })
@@ -44,7 +44,7 @@ function RouteComponent() {
}) })
return ( return (
<div> <div>
<Card className="h-[800px] p-0 overflow-hidden"> <Card className="h-200 p-0 overflow-hidden">
<MapComponent <MapComponent
center={[40.5874827, -1.7925343]} center={[40.5874827, -1.7925343]}
zoom={10} zoom={10}

View File

@@ -1,9 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/_authed/dashboard')({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/_authed/dashboard"!</div>
}

View File

@@ -1,4 +1,13 @@
import { Button, Card, Form, Input, Label, Spinner } from "@heroui/react" import {
Button,
FieldError,
Fieldset,
Form,
Input,
Label,
Spinner,
TextField
} from "@heroui/react"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { LogIn } from "lucide-react" import { LogIn } from "lucide-react"
import { useLogin } from "@/lib/hooks/useLogin" import { useLogin } from "@/lib/hooks/useLogin"
@@ -20,98 +29,105 @@ function RouteComponent() {
} }
return ( return (
<div> <>
<Card.Content> <Form onSubmit={handleFormSubmit} className="flex flex-col gap-4">
<Form onSubmit={handleFormSubmit} className="flex flex-col gap-4"> <Fieldset>
<Label isRequired className="ml-4 text-lg"> <Fieldset.Group>
Correo <TextField
</Label> type="email"
<Input name="email"
placeholder="Introduce tu correo" variant="secondary"
type="email" className="py-4 text-lg"
name="email" isRequired
variant="secondary" defaultValue={import.meta.env.VITE_LOGIN_USER}
className="py-4 text-lg "
required
/>
<Label isRequired className="ml-4 text-lg">
Contraseña
</Label>
<Input
placeholder="Introduce tu contraseña"
type="password"
name="password"
variant="secondary"
className="py-4 text-lg "
required
/>
<div className="flex justify-end">
<Button
type="submit"
className="py-6 px-8 w-full text-white text-lg"
size="lg"
isPending={isPending}
> >
{isPending ? <Spinner /> : <LogIn size={18} />} <Label isRequired className="ml-4 text-lg">
Entrar Correo
</Button> </Label>
</div> <Input placeholder="Introduce tu correo" />
</Form> <FieldError />
</Card.Content> </TextField>
<Card.Footer> <TextField
<div className="flex justify-evenly w-full gap-4"> type="password"
<Button size="lg" className="w-full" variant="secondary"> name="password"
<svg variant="secondary"
xmlns="http://www.w3.org/2000/svg" className="py-4 text-lg"
width="16" isRequired
height="16" defaultValue={import.meta.env.VITE_PASSWORD_USER}
viewBox="0 0 16 16"
>
<g fill="none" fillRule="evenodd" clipRule="evenodd">
<path
fill="#f44336"
d="M7.209 1.061c.725-.081 1.154-.081 1.933 0a6.57 6.57 0 0 1 3.65 1.82a100 100 0 0 0-1.986 1.93q-1.876-1.59-4.188-.734q-1.696.78-2.362 2.528a78 78 0 0 1-2.148-1.658a.26.26 0 0 0-.16-.027q1.683-3.245 5.26-3.86"
opacity="0.987"
/>
<path
fill="#ffc107"
d="M1.946 4.92q.085-.013.161.027a78 78 0 0 0 2.148 1.658A7.6 7.6 0 0 0 4.04 7.99q.037.678.215 1.331L2 11.116Q.527 8.038 1.946 4.92"
opacity="0.997"
/>
<path
fill="#448aff"
d="M12.685 13.29a26 26 0 0 0-2.202-1.74q1.15-.812 1.396-2.228H8.122V6.713q3.25-.027 6.497.055q.616 3.345-1.423 6.032a7 7 0 0 1-.51.49"
opacity="0.999"
/>
<path
fill="#43a047"
d="M4.255 9.322q1.23 3.057 4.51 2.854a3.94 3.94 0 0 0 1.718-.626q1.148.812 2.202 1.74a6.62 6.62 0 0 1-4.027 1.684a6.4 6.4 0 0 1-1.02 0Q3.82 14.524 2 11.116z"
opacity="0.993"
/>
</g>
</svg>
Google
</Button>
<Button size="lg" className="w-full" variant="secondary">
<svg
xmlns="http://www.w3.org/2000/svg"
width="256"
height="256"
viewBox="0 0 256 256"
> >
<Label isRequired className="ml-4 text-lg">
Contraseña
</Label>
<Input placeholder="Introduce tu contraseña" />
<FieldError />
</TextField>
</Fieldset.Group>
</Fieldset>
<Button
type="submit"
className="py-6 px-8 w-full text-white text-lg"
size="lg"
isPending={isPending}
>
{isPending ? <Spinner /> : <LogIn size={18} />}
Entrar
</Button>
</Form>
<div className="flex justify-evenly w-full gap-4 mt-2">
<Button size="lg" className="w-full" variant="secondary">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true"
>
<g fill="none" fillRule="evenodd" clipRule="evenodd">
<path <path
fill="#1877f2" fill="#f44336"
d="M256 128C256 57.308 198.692 0 128 0S0 57.308 0 128c0 63.888 46.808 116.843 108 126.445V165H75.5v-37H108V99.8c0-32.08 19.11-49.8 48.348-49.8C170.352 50 185 52.5 185 52.5V84h-16.14C152.959 84 148 93.867 148 103.99V128h35.5l-5.675 37H148v89.445c61.192-9.602 108-62.556 108-126.445" d="M7.209 1.061c.725-.081 1.154-.081 1.933 0a6.57 6.57 0 0 1 3.65 1.82a100 100 0 0 0-1.986 1.93q-1.876-1.59-4.188-.734q-1.696.78-2.362 2.528a78 78 0 0 1-2.148-1.658a.26.26 0 0 0-.16-.027q1.683-3.245 5.26-3.86"
opacity="0.987"
/> />
<path <path
fill="#fff" fill="#ffc107"
d="m177.825 165l5.675-37H148v-24.01C148 93.866 152.959 84 168.86 84H185V52.5S170.352 50 156.347 50C127.11 50 108 67.72 108 99.8V128H75.5v37H108v89.445A129 129 0 0 0 128 256a129 129 0 0 0 20-1.555V165z" d="M1.946 4.92q.085-.013.161.027a78 78 0 0 0 2.148 1.658A7.6 7.6 0 0 0 4.04 7.99q.037.678.215 1.331L2 11.116Q.527 8.038 1.946 4.92"
opacity="0.997"
/> />
</svg> <path
Facebook fill="#448aff"
</Button> d="M12.685 13.29a26 26 0 0 0-2.202-1.74q1.15-.812 1.396-2.228H8.122V6.713q3.25-.027 6.497.055q.616 3.345-1.423 6.032a7 7 0 0 1-.51.49"
</div> opacity="0.999"
</Card.Footer> />
</div> <path
fill="#43a047"
d="M4.255 9.322q1.23 3.057 4.51 2.854a3.94 3.94 0 0 0 1.718-.626q1.148.812 2.202 1.74a6.62 6.62 0 0 1-4.027 1.684a6.4 6.4 0 0 1-1.02 0Q3.82 14.524 2 11.116z"
opacity="0.993"
/>
</g>
</svg>
Google
</Button>
<Button size="lg" className="w-full" variant="secondary">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 256 256"
aria-hidden="true"
>
<path
fill="#1877f2"
d="M256 128C256 57.308 198.692 0 128 0S0 57.308 0 128c0 63.888 46.808 116.843 108 126.445V165H75.5v-37H108V99.8c0-32.08 19.11-49.8 48.348-49.8C170.352 50 185 52.5 185 52.5V84h-16.14C152.959 84 148 93.867 148 103.99V128h35.5l-5.675 37H148v89.445c61.192-9.602 108-62.556 108-126.445"
/>
<path
fill="#fff"
d="m177.825 165l5.675-37H148v-24.01C148 93.866 152.959 84 168.86 84H185V52.5S170.352 50 156.347 50C127.11 50 108 67.72 108 99.8V128H75.5v37H108v89.445A129 129 0 0 0 128 256a129 129 0 0 0 20-1.555V165z"
/>
</svg>
Facebook
</Button>
</div>
</>
) )
} }

View File

@@ -9,7 +9,7 @@ import {
TextField TextField
} from "@heroui/react" } from "@heroui/react"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { LogIn } from "lucide-react" import { UserPlus } from "lucide-react"
import { useSignup } from "@/lib/hooks/useSignup" import { useSignup } from "@/lib/hooks/useSignup"
export const Route = createFileRoute("/access/register")({ export const Route = createFileRoute("/access/register")({
@@ -22,67 +22,105 @@ function RouteComponent() {
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
const formData = new FormData(e.currentTarget) const formData = new FormData(e.currentTarget)
const email = formData.get("email") as string
const password = formData.get("password") as string
const location = formData.get("location") as string
const name = formData.get("name") as string
signup({ signup({
email, email: formData.get("email") as string,
password, password: formData.get("password") as string,
location, confirmPassword: formData.get("confirmPassword") as string,
name location: formData.get("location") as string,
name: formData.get("name") as string
}) })
} }
return ( return (
<div> <>
<Form onSubmit={handleFormSubmit} className="flex flex-col gap-4"> <Form onSubmit={handleFormSubmit} className="flex flex-col gap-4">
<Fieldset> <Fieldset>
<Fieldset.Group> <Fieldset.Group>
<TextField
type="text"
name="name"
variant="secondary"
className="py-2 text-lg"
isRequired
>
<Label isRequired className="ml-4 text-lg">
Nombre completo
</Label>
<Input placeholder="Tu nombre y apellidos" />
<FieldError />
</TextField>
<TextField <TextField
type="email" type="email"
name="email" name="email"
variant="secondary" variant="secondary"
className="py-4 text-lg" className="py-2 text-lg"
isRequired isRequired
> >
<Label>Correo</Label> <Label isRequired className="ml-4 text-lg">
<Input placeholder="Introduce tu correo" /> Correo electrónico
</Label>
<Input placeholder="tu@correo.com" />
<FieldError />
</TextField>
<TextField
type="text"
name="location"
variant="secondary"
className="py-2 text-lg"
isRequired
>
<Label isRequired className="ml-4 text-lg">
Ubicación
</Label>
<Input placeholder="Ciudad, País" />
<FieldError />
</TextField>
<TextField
type="password"
name="password"
variant="secondary"
className="py-2 text-lg"
isRequired
>
<Label isRequired className="ml-4 text-lg">
Contraseña
</Label>
<Input placeholder="Mínimo 6 caracteres" />
<FieldError />
</TextField>
<TextField
type="password"
name="confirmPassword"
variant="secondary"
className="py-2 text-lg"
isRequired
>
<Label isRequired className="ml-4 text-lg">
Confirmar contraseña
</Label>
<Input placeholder="Repite tu contraseña" />
<FieldError /> <FieldError />
</TextField> </TextField>
</Fieldset.Group> </Fieldset.Group>
</Fieldset> </Fieldset>
<TextField <Button
type="password" type="submit"
name="password" className="py-6 px-8 w-full text-white text-lg"
variant="secondary" size="lg"
className="py-4 text-lg" isPending={isPending}
isRequired
> >
<Label>Contraseña</Label> {isPending ? <Spinner /> : <UserPlus size={18} />}
<Input placeholder="Introduce tu contraseña" /> Crear cuenta
<FieldError /> </Button>
</TextField>
<div className="flex justify-end">
<Button
type="submit"
className="py-6 px-8 w-full text-white text-lg"
size="lg"
isPending={isPending}
>
{isPending ? <Spinner /> : <LogIn size={18} />}
Entrar
</Button>
</div>
</Form> </Form>
<div className="flex justify-evenly w-full gap-4"> <div className="flex justify-evenly w-full gap-4 mt-2">
<Button size="lg" className="w-full" variant="secondary"> <Button size="lg" className="w-full" variant="secondary">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
aria-hidden="true"
> >
<g fill="none" fillRule="evenodd" clipRule="evenodd"> <g fill="none" fillRule="evenodd" clipRule="evenodd">
<path <path
@@ -112,9 +150,10 @@ function RouteComponent() {
<Button size="lg" className="w-full" variant="secondary"> <Button size="lg" className="w-full" variant="secondary">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="256" width="16"
height="256" height="16"
viewBox="0 0 256 256" viewBox="0 0 256 256"
aria-hidden="true"
> >
<path <path
fill="#1877f2" fill="#1877f2"
@@ -128,6 +167,6 @@ function RouteComponent() {
Facebook Facebook
</Button> </Button>
</div> </div>
</div> </>
) )
} }

View File

@@ -12,6 +12,8 @@ import {
export const Route = createFileRoute("/")({ component: App }) export const Route = createFileRoute("/")({ component: App })
function App() { function App() {
const navigate = Route.useNavigate()
const features = [ const features = [
{ {
icon: <Zap className="w-12 h-12 text-cyan-400" />, icon: <Zap className="w-12 h-12 text-cyan-400" />,
@@ -53,7 +55,17 @@ function App() {
return ( return (
<div className="min-h-screen bg-linear-to-b from-slate-900 via-slate-800 to-slate-900"> <div className="min-h-screen bg-linear-to-b from-slate-900 via-slate-800 to-slate-900">
<Button> Hola</Button> <Button
onPress={() => {
navigate({
to: "/login",
viewTransition: true
})
}}
>
{" "}
Hola
</Button>
<section className="relative py-20 px-6 text-center overflow-hidden"> <section className="relative py-20 px-6 text-center overflow-hidden">
<div className="absolute inset-0 bg-linear-to-r from-cyan-500/10 via-blue-500/10 to-purple-500/10"></div> <div className="absolute inset-0 bg-linear-to-r from-cyan-500/10 via-blue-500/10 to-purple-500/10"></div>
<div className="relative max-w-5xl mx-auto"> <div className="relative max-w-5xl mx-auto">

View File

@@ -1,5 +1,9 @@
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { user } from "@/lib/server/user"
export const Route = createFileRoute("/logout")({ export const Route = createFileRoute("/logout")({
beforeLoad: async ({ context }) => {} beforeLoad: async () => {
await user.logout()
},
preload: false
}) })