mock index and testing rsl
This commit is contained in:
29
src/lib/server/instruments.ts
Normal file
29
src/lib/server/instruments.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { createServerFn } from "@tanstack/react-start"
|
||||||
|
import z from "zod"
|
||||||
|
import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
|
||||||
|
|
||||||
|
const getAllInstruments = createServerFn().handler(async () => {
|
||||||
|
const { data } = await getSupabaseServerClient().from("instruments").select()
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
|
||||||
|
const insertAInstrument = createServerFn()
|
||||||
|
.inputValidator(z.object({ name: z.string() }))
|
||||||
|
.handler(async ({ data }) => {
|
||||||
|
const { data: resp } = await getSupabaseServerClient()
|
||||||
|
.from("instruments")
|
||||||
|
.insert(data)
|
||||||
|
return resp
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteAInstrument = createServerFn()
|
||||||
|
.inputValidator(z.object({ id: z.number() }))
|
||||||
|
.handler(async ({ data }) => {
|
||||||
|
const { data: resp } = await getSupabaseServerClient()
|
||||||
|
.from("instruments")
|
||||||
|
.delete()
|
||||||
|
.eq("id", data.id)
|
||||||
|
return resp
|
||||||
|
})
|
||||||
|
|
||||||
|
export { getAllInstruments, insertAInstrument, deleteAInstrument }
|
||||||
@@ -4,7 +4,7 @@ import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
|
|||||||
import {
|
import {
|
||||||
loginFormSchema,
|
loginFormSchema,
|
||||||
signupFormSchema,
|
signupFormSchema,
|
||||||
userListParamsSchema,
|
userListParamsSchema
|
||||||
} from "../validation/user"
|
} from "../validation/user"
|
||||||
|
|
||||||
const login = createServerFn({ method: "POST" })
|
const login = createServerFn({ method: "POST" })
|
||||||
@@ -14,18 +14,18 @@ const login = createServerFn({ method: "POST" })
|
|||||||
|
|
||||||
const login = await supabase.auth.signInWithPassword({
|
const login = await supabase.auth.signInWithPassword({
|
||||||
email: data.email,
|
email: data.email,
|
||||||
password: data.password,
|
password: data.password
|
||||||
})
|
})
|
||||||
|
|
||||||
if (login.error) {
|
if (login.error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: login.error.message,
|
message: login.error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -36,14 +36,14 @@ const logout = createServerFn().handler(async () => {
|
|||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error.message,
|
message: error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/",
|
to: "/",
|
||||||
viewTransition: true,
|
viewTransition: true,
|
||||||
replace: true,
|
replace: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -58,18 +58,19 @@ const signup = createServerFn({ method: "POST" })
|
|||||||
data: {
|
data: {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
location: data.location,
|
location: data.location,
|
||||||
},
|
role: data.role
|
||||||
},
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error.message,
|
message: error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw redirect({
|
throw redirect({
|
||||||
href: data.redirectUrl || "/",
|
href: data.redirectUrl || "/"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ const userData = createServerFn().handler(async () => {
|
|||||||
if (error || !data.user) {
|
if (error || !data.user) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error?.message ?? "Unknown error",
|
message: error?.message ?? "Unknown error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -87,9 +88,9 @@ const userData = createServerFn().handler(async () => {
|
|||||||
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 || "",
|
location: data.user.user_metadata.location || ""
|
||||||
},
|
},
|
||||||
error: false,
|
error: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -102,12 +103,12 @@ const resendConfirmationEmail = createServerFn({ method: "POST" })
|
|||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error.message,
|
message: error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -117,18 +118,18 @@ const userList = createServerFn()
|
|||||||
const supabase = getSupabaseServerClient()
|
const supabase = getSupabaseServerClient()
|
||||||
const users = await supabase.auth.admin.listUsers({
|
const users = await supabase.auth.admin.listUsers({
|
||||||
page: data.page,
|
page: data.page,
|
||||||
perPage: data.limit,
|
perPage: data.limit
|
||||||
})
|
})
|
||||||
|
|
||||||
if (users.error) {
|
if (users.error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: users.error.message,
|
message: users.error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
users: users.data,
|
users: users.data,
|
||||||
error: false,
|
error: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -138,5 +139,5 @@ export const user = {
|
|||||||
signup,
|
signup,
|
||||||
userData,
|
userData,
|
||||||
resendConfirmationEmail,
|
resendConfirmationEmail,
|
||||||
userList,
|
userList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,24 @@ import { twMerge } from "tailwind-merge"
|
|||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function searchLocation(query: string) {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=1`
|
||||||
|
)
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
const place = data[0]
|
||||||
|
return {
|
||||||
|
coords: `(${place.lat}, ${place.lon})`, // Formato para el tipo POINT de Postgres
|
||||||
|
details: place,
|
||||||
|
city:
|
||||||
|
place.address?.city ||
|
||||||
|
place.address?.town ||
|
||||||
|
place.display_name.split(",")[0],
|
||||||
|
country: place.address?.country
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export const signupFormSchema = z.object({
|
|||||||
password: z.string().min(6, "La contraseña debe tener al menos 6 caracteres"),
|
password: z.string().min(6, "La contraseña debe tener al menos 6 caracteres"),
|
||||||
name: z.string().min(1, "El nombre es obligatorio"),
|
name: z.string().min(1, "El nombre es obligatorio"),
|
||||||
location: z.string().min(1, "La ubicación es obligatoria"),
|
location: z.string().min(1, "La ubicación es obligatoria"),
|
||||||
redirectUrl: z.string().optional()
|
redirectUrl: z.string().optional(),
|
||||||
|
role: z.enum(["pilot", "employer"])
|
||||||
})
|
})
|
||||||
|
|
||||||
// Schema extendido para el formulario cliente (incluye confirmación de contraseña)
|
// Schema extendido para el formulario cliente (incluye confirmación de contraseña)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|||||||
|
|
||||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<html lang={getLocale()}>
|
<html lang={getLocale()} className="dark">
|
||||||
<head>
|
<head>
|
||||||
<HeadContent />
|
<HeadContent />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
import { Card } from "@heroui/react"
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
Chip,
|
||||||
|
ScrollShadow,
|
||||||
|
Spinner
|
||||||
|
} from "@heroui/react"
|
||||||
|
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Drone } from "lucide-react"
|
import { Drone } from "lucide-react"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
@@ -10,6 +19,11 @@ import {
|
|||||||
MarkerPopup,
|
MarkerPopup,
|
||||||
MarkerTooltip
|
MarkerTooltip
|
||||||
} from "@/components/maps/map"
|
} from "@/components/maps/map"
|
||||||
|
import {
|
||||||
|
deleteAInstrument,
|
||||||
|
getAllInstruments,
|
||||||
|
insertAInstrument
|
||||||
|
} from "@/lib/server/instruments"
|
||||||
|
|
||||||
export const Route = createFileRoute("/_auth/dashboard")({
|
export const Route = createFileRoute("/_auth/dashboard")({
|
||||||
component: RouteComponent
|
component: RouteComponent
|
||||||
@@ -22,57 +36,134 @@ function RouteComponent() {
|
|||||||
name: "Location 1",
|
name: "Location 1",
|
||||||
lat: 40.76,
|
lat: 40.76,
|
||||||
lng: -73.98
|
lng: -73.98
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: "Location 2",
|
|
||||||
lat: 40.77,
|
|
||||||
lng: -73.99
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: "Location 3",
|
|
||||||
lat: 40.5874827,
|
|
||||||
lng: -1.7925343
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const { data: instruments, isPending } = useQuery({
|
||||||
|
queryKey: ["instruments"],
|
||||||
|
queryFn: async () => {
|
||||||
|
return await getAllInstruments()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const insertInstrument = useMutation({
|
||||||
|
mutationKey: ["instruments"],
|
||||||
|
mutationFn: async (data: { name: string }) => {
|
||||||
|
return await insertAInstrument({
|
||||||
|
data: {
|
||||||
|
name: data.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteInstrument = useMutation({
|
||||||
|
mutationKey: ["instruments"],
|
||||||
|
mutationFn: async (data: { id: number }) => {
|
||||||
|
return await deleteAInstrument({
|
||||||
|
data: {
|
||||||
|
id: data.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(instruments)
|
||||||
|
|
||||||
const [viewport, setViewport] = useState<MapViewport>({
|
const [viewport, setViewport] = useState<MapViewport>({
|
||||||
center: [40.5874827, -1.7925343],
|
center: [40.5874827, -1.7925343],
|
||||||
zoom: 8,
|
zoom: 8,
|
||||||
bearing: 0,
|
bearing: 0,
|
||||||
pitch: 0
|
pitch: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="w-full justify-center flex items-center min-h-screen">
|
||||||
<Card className="h-200 p-0 overflow-hidden">
|
<Card className="max-w-5xl w-full rounded-2xl" variant="secondary">
|
||||||
<MapComponent
|
<CardHeader>
|
||||||
center={[40.5874827, -1.7925343]}
|
<h1 className="text-2xl font-bold">Dashboard</h1>
|
||||||
zoom={10}
|
</CardHeader>
|
||||||
viewport={viewport}
|
<CardContent>
|
||||||
onViewportChange={setViewport}
|
<div className="flex border-2 gap-2 justify-between">
|
||||||
>
|
<div className="block gap-4">
|
||||||
{locations.map((location) => (
|
<Chip size="lg" variant="primary" color="accent">
|
||||||
<MapMarker
|
Instrumentos
|
||||||
key={location.id}
|
</Chip>
|
||||||
longitude={location.lng}
|
<form
|
||||||
latitude={location.lat}
|
onSubmit={(e) => {
|
||||||
>
|
e.preventDefault()
|
||||||
{/* Prueba para ssl */}
|
const formData = Object.fromEntries(
|
||||||
<MarkerContent>
|
new FormData(e.currentTarget)
|
||||||
<Drone size={24} color="green" className="text-green-200" />
|
)
|
||||||
</MarkerContent>
|
|
||||||
<MarkerTooltip>{location.name}</MarkerTooltip>
|
console.log(formData)
|
||||||
<MarkerPopup>
|
insertInstrument.mutate(formData as { name: string })
|
||||||
<div className="space-y-1">
|
}}
|
||||||
<p className="font-medium text-foreground">{location.name}</p>
|
>
|
||||||
<p className="text-xs text-muted-foreground">
|
<input name="name" />
|
||||||
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
|
<button type="submit">Agregar</button>
|
||||||
</p>
|
</form>
|
||||||
</div>
|
<div className="flex items-center">
|
||||||
</MarkerPopup>
|
{isPending && <Spinner size="sm" />}
|
||||||
</MapMarker>
|
</div>
|
||||||
))}
|
<ScrollShadow
|
||||||
</MapComponent>
|
hideScrollBar
|
||||||
|
className="max-w-xl h-[400px] min-w-[400px]"
|
||||||
|
>
|
||||||
|
{instruments?.map((instrument) => (
|
||||||
|
<div key={instrument.id}>
|
||||||
|
<p>{instrument.name}</p>
|
||||||
|
<Button
|
||||||
|
onPress={() =>
|
||||||
|
deleteInstrument.mutate({ id: instrument.id })
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
Eliminar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ScrollShadow>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<MapComponent
|
||||||
|
center={[40.5874827, -1.7925343]}
|
||||||
|
zoom={10}
|
||||||
|
className="min-w-xl w-full border-2"
|
||||||
|
viewport={viewport}
|
||||||
|
onViewportChange={setViewport}
|
||||||
|
>
|
||||||
|
{locations.map((location) => (
|
||||||
|
<MapMarker
|
||||||
|
key={location.id}
|
||||||
|
longitude={location.lng}
|
||||||
|
latitude={location.lat}
|
||||||
|
>
|
||||||
|
{/* Prueba para ssl */}
|
||||||
|
<MarkerContent>
|
||||||
|
<Drone
|
||||||
|
size={24}
|
||||||
|
color="green"
|
||||||
|
className="text-green-200"
|
||||||
|
/>
|
||||||
|
</MarkerContent>
|
||||||
|
<MarkerTooltip>{location.name}</MarkerTooltip>
|
||||||
|
<MarkerPopup>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-medium text-foreground">
|
||||||
|
{location.name}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</MarkerPopup>
|
||||||
|
</MapMarker>
|
||||||
|
))}
|
||||||
|
</MapComponent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
Spinner,
|
|
||||||
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 { ArrowRight, Drone } from "lucide-react"
|
||||||
import { useLogin } from "@/lib/hooks/useLogin"
|
import { useLogin } from "@/lib/hooks/useLogin"
|
||||||
|
|
||||||
export const Route = createFileRoute("/access/login")({
|
export const Route = createFileRoute("/access/login")({
|
||||||
@@ -37,13 +36,11 @@ function RouteComponent() {
|
|||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="py-4 text-lg"
|
className=" text-lg"
|
||||||
isRequired
|
isRequired
|
||||||
defaultValue={import.meta.env.VITE_LOGIN_USER}
|
defaultValue={import.meta.env.VITE_LOGIN_USER}
|
||||||
>
|
>
|
||||||
<Label isRequired className="ml-4 text-lg">
|
<Label isRequired>Correo</Label>
|
||||||
Correo
|
|
||||||
</Label>
|
|
||||||
<Input placeholder="Introduce tu correo" />
|
<Input placeholder="Introduce tu correo" />
|
||||||
<FieldError />
|
<FieldError />
|
||||||
</TextField>
|
</TextField>
|
||||||
@@ -51,13 +48,11 @@ function RouteComponent() {
|
|||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="py-4 text-lg"
|
className=" text-lg"
|
||||||
isRequired
|
isRequired
|
||||||
defaultValue={import.meta.env.VITE_PASSWORD_USER}
|
defaultValue={import.meta.env.VITE_PASSWORD_USER}
|
||||||
>
|
>
|
||||||
<Label isRequired className="ml-4 text-lg">
|
<Label isRequired>Contraseña</Label>
|
||||||
Contraseña
|
|
||||||
</Label>
|
|
||||||
<Input placeholder="Introduce tu contraseña" />
|
<Input placeholder="Introduce tu contraseña" />
|
||||||
<FieldError />
|
<FieldError />
|
||||||
</TextField>
|
</TextField>
|
||||||
@@ -70,12 +65,16 @@ function RouteComponent() {
|
|||||||
size="lg"
|
size="lg"
|
||||||
isPending={isPending}
|
isPending={isPending}
|
||||||
>
|
>
|
||||||
{isPending ? <Spinner /> : <LogIn size={18} />}
|
|
||||||
Entrar
|
Entrar
|
||||||
|
{isPending ? (
|
||||||
|
<Drone className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<ArrowRight size={18} />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
<div className="flex justify-evenly w-full gap-4 mt-2">
|
<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="tertiary">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16"
|
width="16"
|
||||||
@@ -108,7 +107,7 @@ function RouteComponent() {
|
|||||||
</svg>
|
</svg>
|
||||||
Google
|
Google
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="lg" className="w-full" variant="secondary">
|
<Button size="lg" className="w-full" variant="tertiary">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16"
|
width="16"
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
Spinner,
|
ListBox,
|
||||||
|
Select,
|
||||||
TextField
|
TextField
|
||||||
} from "@heroui/react"
|
} from "@heroui/react"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { UserPlus } from "lucide-react"
|
import { ArrowRight, Drone } from "lucide-react"
|
||||||
import { useSignup } from "@/lib/hooks/useSignup"
|
import { useSignup } from "@/lib/hooks/useSignup"
|
||||||
|
import { searchLocation } from "@/lib/utils"
|
||||||
|
|
||||||
export const Route = createFileRoute("/access/register")({
|
export const Route = createFileRoute("/access/register")({
|
||||||
component: RouteComponent
|
component: RouteComponent
|
||||||
@@ -19,15 +21,18 @@ export const Route = createFileRoute("/access/register")({
|
|||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { signup, isPending } = useSignup()
|
const { signup, isPending } = useSignup()
|
||||||
|
|
||||||
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const formData = new FormData(e.currentTarget)
|
const formData = new FormData(e.currentTarget)
|
||||||
|
const ubication = await searchLocation(formData.get("location") as string)
|
||||||
|
console.log(ubication)
|
||||||
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,
|
||||||
confirmPassword: formData.get("confirmPassword") as string,
|
confirmPassword: formData.get("confirmPassword") as string,
|
||||||
location: formData.get("location") as string,
|
location: formData.get("location") as string,
|
||||||
name: formData.get("name") as string
|
name: formData.get("name") as string,
|
||||||
|
role: (formData.get("role") as "pilot" | "employer") || "pilot"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,29 +41,45 @@ function RouteComponent() {
|
|||||||
<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
|
<div className="grid grid-cols-2 gap-4 items-baseline">
|
||||||
type="text"
|
<TextField
|
||||||
name="name"
|
type="text"
|
||||||
variant="secondary"
|
name="name"
|
||||||
className="py-2 text-lg"
|
variant="secondary"
|
||||||
isRequired
|
className=" text-lg"
|
||||||
>
|
isRequired
|
||||||
<Label isRequired className="ml-4 text-lg">
|
>
|
||||||
Nombre completo
|
<Label isRequired>Nombre completo</Label>
|
||||||
</Label>
|
<Input placeholder="Tu nombre y apellidos" />
|
||||||
<Input placeholder="Tu nombre y apellidos" />
|
<FieldError />
|
||||||
<FieldError />
|
</TextField>
|
||||||
</TextField>
|
<Select
|
||||||
<TextField
|
placeholder="Selecciona una opción"
|
||||||
type="email"
|
isRequired
|
||||||
name="email"
|
defaultValue="pilot"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="py-2 text-lg"
|
>
|
||||||
isRequired
|
<Label isRequired>Tipo de usuario</Label>
|
||||||
>
|
<Select.Trigger>
|
||||||
<Label isRequired className="ml-4 text-lg">
|
<Select.Value />
|
||||||
Correo electrónico
|
<Select.Indicator />
|
||||||
</Label>
|
</Select.Trigger>
|
||||||
|
<Select.Popover>
|
||||||
|
<ListBox>
|
||||||
|
<ListBox.Item id="pilot" textValue="Piloto">
|
||||||
|
Piloto
|
||||||
|
<ListBox.ItemIndicator />
|
||||||
|
</ListBox.Item>
|
||||||
|
<ListBox.Item id="employer" textValue="Reclutador">
|
||||||
|
Reclutador
|
||||||
|
<ListBox.ItemIndicator />
|
||||||
|
</ListBox.Item>
|
||||||
|
</ListBox>
|
||||||
|
</Select.Popover>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<TextField type="email" name="email" variant="secondary" isRequired>
|
||||||
|
<Label isRequired>Correo electrónico</Label>
|
||||||
<Input placeholder="tu@correo.com" />
|
<Input placeholder="tu@correo.com" />
|
||||||
<FieldError />
|
<FieldError />
|
||||||
</TextField>
|
</TextField>
|
||||||
@@ -66,41 +87,36 @@ function RouteComponent() {
|
|||||||
type="text"
|
type="text"
|
||||||
name="location"
|
name="location"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="py-2 text-lg"
|
|
||||||
isRequired
|
isRequired
|
||||||
>
|
>
|
||||||
<Label isRequired className="ml-4 text-lg">
|
<Label isRequired>Ubicación</Label>
|
||||||
Ubicación
|
|
||||||
</Label>
|
|
||||||
<Input placeholder="Ciudad, País" />
|
<Input placeholder="Ciudad, País" />
|
||||||
<FieldError />
|
<FieldError />
|
||||||
</TextField>
|
</TextField>
|
||||||
<TextField
|
<div className="grid grid-cols-2 gap-4 items-baseline">
|
||||||
type="password"
|
<TextField
|
||||||
name="password"
|
type="password"
|
||||||
variant="secondary"
|
name="password"
|
||||||
className="py-2 text-lg"
|
variant="secondary"
|
||||||
isRequired
|
className=" text-lg"
|
||||||
>
|
isRequired
|
||||||
<Label isRequired className="ml-4 text-lg">
|
>
|
||||||
Contraseña
|
<Label isRequired>Contraseña</Label>
|
||||||
</Label>
|
<Input placeholder="Mínimo 6 caracteres" />
|
||||||
<Input placeholder="Mínimo 6 caracteres" />
|
<FieldError />
|
||||||
<FieldError />
|
</TextField>
|
||||||
</TextField>
|
<TextField
|
||||||
<TextField
|
type="password"
|
||||||
type="password"
|
name="confirmPassword"
|
||||||
name="confirmPassword"
|
variant="secondary"
|
||||||
variant="secondary"
|
className=" text-lg"
|
||||||
className="py-2 text-lg"
|
isRequired
|
||||||
isRequired
|
>
|
||||||
>
|
<Label isRequired>Confirmar contraseña</Label>
|
||||||
<Label isRequired className="ml-4 text-lg">
|
<Input placeholder="Repite tu contraseña" />
|
||||||
Confirmar contraseña
|
<FieldError />
|
||||||
</Label>
|
</TextField>
|
||||||
<Input placeholder="Repite tu contraseña" />
|
</div>
|
||||||
<FieldError />
|
|
||||||
</TextField>
|
|
||||||
</Fieldset.Group>
|
</Fieldset.Group>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Button
|
<Button
|
||||||
@@ -109,12 +125,16 @@ function RouteComponent() {
|
|||||||
size="lg"
|
size="lg"
|
||||||
isPending={isPending}
|
isPending={isPending}
|
||||||
>
|
>
|
||||||
{isPending ? <Spinner /> : <UserPlus size={18} />}
|
|
||||||
Crear cuenta
|
Crear cuenta
|
||||||
|
{isPending ? (
|
||||||
|
<Drone className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<ArrowRight size={18} />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
<div className="flex justify-evenly w-full gap-4 mt-2">
|
<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="tertiary">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16"
|
width="16"
|
||||||
@@ -147,7 +167,7 @@ function RouteComponent() {
|
|||||||
</svg>
|
</svg>
|
||||||
Google
|
Google
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="lg" className="w-full" variant="secondary">
|
<Button size="lg" className="w-full" variant="tertiary">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16"
|
width="16"
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
|
|||||||
export const Route = createFileRoute("/access")({
|
export const Route = createFileRoute("/access")({
|
||||||
beforeLoad: ({ location }) => {
|
beforeLoad: ({ location }) => {
|
||||||
if (location.pathname === "/access") {
|
if (location.pathname === "/access") {
|
||||||
redirect({
|
throw redirect({
|
||||||
to: "/access/login",
|
to: "/access/login",
|
||||||
replace: true,
|
replace: true
|
||||||
throw: true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -25,37 +24,91 @@ function RouteComponent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid-cols-2 grid min-h-screen">
|
<div className="grid grid-cols-1 md:grid-cols-2 min-h-screen bg-background text-foreground font-sans">
|
||||||
<div className=" p-5 bg-default">
|
<div className="relative z-10 flex flex-col p-6 md:p-12 justify-between border-r border-border bg-surface/30 backdrop-blur-sm">
|
||||||
<h1 className="text-4xl font-bold text-end ">
|
<header className="flex items-center justify-between">
|
||||||
Find<span className="text-accent">your</span>Pilot
|
<div className="flex items-center gap-2">
|
||||||
</h1>
|
<div className="w-2 h-2 bg-accent animate-pulse" />
|
||||||
<div className="flex items-center justify-center min-h-[90vh]">
|
<span className="font-mono text-xs tracking-[0.3em] uppercase text-muted">
|
||||||
<Card className="w-full max-w-md bg-white/90 backdrop-blur-2xl border-3 border-accent-soft">
|
<h1 className="text-lg font-light tracking-tighter">
|
||||||
<Card.Header>
|
FIND<span className="font-bold text-accent">YOUR</span>PILOT
|
||||||
|
</h1>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-muted">
|
||||||
|
<p>
|
||||||
|
Connection: <span className="text-success">Secured</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Node: <span className="text-foreground">E-210_MAD</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Contenedor Central */}
|
||||||
|
<div className="flex flex-col items-center justify-center">
|
||||||
|
<Card className="w-full max-w-md shadow-none border border-border bg-surface/50 ">
|
||||||
|
<Card.Header className="p-0">
|
||||||
<Tabs className="w-full max-w-md" onSelectionChange={onSelectTab}>
|
<Tabs className="w-full max-w-md" onSelectionChange={onSelectTab}>
|
||||||
<Tabs.ListContainer>
|
<Tabs.ListContainer>
|
||||||
<Tabs.List aria-label="Options">
|
<Tabs.List aria-label="Options">
|
||||||
<Tabs.Tab id="login" className="text-lg">
|
<Tabs.Tab id="login">
|
||||||
Acceso
|
Entrar
|
||||||
<Tabs.Indicator className="bg-accent" />
|
<Tabs.Indicator />
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab id="register" className="text-lg ">
|
<Tabs.Tab id="register">
|
||||||
<Tabs.Separator />
|
<Tabs.Separator />
|
||||||
Registro
|
Registrarse
|
||||||
<Tabs.Indicator className="bg-accent" />
|
<Tabs.Indicator />
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs.ListContainer>
|
</Tabs.ListContainer>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Content>
|
<Card.Content className="p-4">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<div className="mt-8 flex gap-4 overflow-hidden">
|
||||||
|
<div className="h-[1px] w-12 bg-border self-center" />
|
||||||
|
<span className="text-[10px] font-mono text-muted uppercase">
|
||||||
|
Ready for Takeoff
|
||||||
|
</span>
|
||||||
|
<div className="h-[1px] w-12 bg-border self-center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer className="grid grid-cols-2 gap-4 border-t border-border pt-6 font-mono text-[9px] text-muted uppercase tracking-wider">
|
||||||
|
<div className="text-right">
|
||||||
|
<p>© 2026 / FYP_CORP</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<div className="relative hidden md:block overflow-hidden bg-accent">
|
||||||
|
<div className="absolute inset-0 bg-[url('https://cdn.pixabay.com/photo/2023/03/22/22/37/mavic-2-7870679_1280.jpg')] bg-cover bg-center opacity-90 " />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-background/50" />
|
||||||
|
<div className="absolute inset-0 z-20 flex flex-col justify-between p-10 pointer-events-none">
|
||||||
|
<div className="absolute top-10 left-10 w-20 h-20 border-l border-t" />
|
||||||
|
<div className="absolute top-10 right-10 w-20 h-20 border-r border-t" />
|
||||||
|
<div className="absolute bottom-10 left-10 w-20 h-20 border-l border-b" />
|
||||||
|
<div className="absolute bottom-10 right-10 w-20 h-20 border-r border-b" />
|
||||||
|
|
||||||
|
{/* Telemetría Dinámica */}
|
||||||
|
<div className="flex justify-between items-start font-mono ">
|
||||||
|
<div className="bg-black/40 backdrop-blur-md border border-white/10 p-4 rounded-sm invisible">
|
||||||
|
<div className="text-accent text-xs mb-1">TELEMETRY_STREAM</div>
|
||||||
|
<div className="text-white text-lg leading-none">40.4168° N</div>
|
||||||
|
<div className="text-white text-lg leading-none">3.7038° W</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-end gap-2 text-[10px] text-white/50">
|
||||||
|
<div className="bg-accent-soft-hover px-2 py-1 border text-accent">
|
||||||
|
REC ●
|
||||||
|
</div>
|
||||||
|
<span>4K / 60FPS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-accent bg-[url('https://cdn.pixabay.com/photo/2023/03/22/22/37/mavic-2-7870679_1280.jpg')] bg-cover"></div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +1,396 @@
|
|||||||
import { Button } from "@heroui/react"
|
import { Avatar, Button, Card, Chip, ScrollShadow } from "@heroui/react"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import {
|
import {
|
||||||
Route as RouteIcon,
|
ChevronRight,
|
||||||
Server,
|
Cpu,
|
||||||
|
Database,
|
||||||
|
Euro,
|
||||||
|
Globe,
|
||||||
|
Layers,
|
||||||
|
LogIn,
|
||||||
|
Map as MapIcon,
|
||||||
|
Radio,
|
||||||
|
Rocket,
|
||||||
Shield,
|
Shield,
|
||||||
Sparkles,
|
|
||||||
Waves,
|
|
||||||
Zap
|
Zap
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import {
|
||||||
|
Map as MapComponent,
|
||||||
|
MapMarker,
|
||||||
|
MarkerContent,
|
||||||
|
MarkerPopup,
|
||||||
|
MarkerTooltip
|
||||||
|
} from "@/components/maps/map"
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({ component: App })
|
export const Route = createFileRoute("/")({ component: App })
|
||||||
|
|
||||||
function App() {
|
const WebMockHeader = () => {
|
||||||
const navigate = Route.useNavigate()
|
const locations = [
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "EEUU",
|
||||||
|
lat: 40.76,
|
||||||
|
lng: -73.98
|
||||||
|
},
|
||||||
|
|
||||||
const features = [
|
|
||||||
{
|
{
|
||||||
icon: <Zap className="w-12 h-12 text-cyan-400" />,
|
id: 1,
|
||||||
title: "Powerful Server Functions",
|
name: "Madrid",
|
||||||
description:
|
lat: 40.4168,
|
||||||
"Write server-side code that seamlessly integrates with your client components. Type-safe, secure, and simple."
|
lng: -3.7038
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Server className="w-12 h-12 text-cyan-400" />,
|
id: 2,
|
||||||
title: "Flexible Server Side Rendering",
|
name: "Barcelona",
|
||||||
description:
|
lat: 41.3874,
|
||||||
"Full-document SSR, streaming, and progressive enhancement out of the box. Control exactly what renders where."
|
lng: 2.1686
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <RouteIcon className="w-12 h-12 text-cyan-400" />,
|
id: 3,
|
||||||
title: "API Routes",
|
name: "Sevilla",
|
||||||
description:
|
lat: 37.3891,
|
||||||
"Build type-safe API endpoints alongside your application. No separate backend needed."
|
lng: -5.9845
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Shield className="w-12 h-12 text-cyan-400" />,
|
id: 4,
|
||||||
title: "Strongly Typed Everything",
|
name: "Valencia",
|
||||||
description:
|
lat: 39.4699,
|
||||||
"End-to-end type safety from server to client. Catch errors before they reach production."
|
lng: -0.3763
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Waves className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "Full Streaming Support",
|
|
||||||
description:
|
|
||||||
"Stream data from server to client progressively. Perfect for AI applications and real-time updates."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Sparkles className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "Next Generation Ready",
|
|
||||||
description:
|
|
||||||
"Built from the ground up for modern web applications. Deploy anywhere JavaScript runs."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-linear-to-b from-slate-900 via-slate-800 to-slate-900">
|
<div className="space-y-6 animate-in fade-in zoom-in-95 duration-500">
|
||||||
<Button
|
<div className="flex justify-between items-center">
|
||||||
onPress={() => {
|
<div>
|
||||||
navigate({
|
<h3 className="text-xl font-black text-white tracking-tight">
|
||||||
to: "/login",
|
Panel de Control
|
||||||
viewTransition: true
|
</h3>
|
||||||
})
|
<p className="text-[10px] text-cyan-500 font-mono hidden">
|
||||||
}}
|
ID: 550e8400-e29b-41d4-a716-446655440000
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
Hola
|
|
||||||
</Button>
|
|
||||||
<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="relative max-w-5xl mx-auto">
|
|
||||||
<div className="flex items-center justify-center gap-6 mb-6">
|
|
||||||
<img
|
|
||||||
src="/tanstack-circle-logo.png"
|
|
||||||
alt="TanStack Logo"
|
|
||||||
className="w-24 h-24 md:w-32 md:h-32"
|
|
||||||
/>
|
|
||||||
<h1 className="text-6xl md:text-7xl font-black text-white tracking-[-0.08em]">
|
|
||||||
<span className="text-gray-300">TANSTACK</span>{" "}
|
|
||||||
<span className="bg-linear-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">
|
|
||||||
START
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<p className="text-2xl md:text-3xl text-gray-300 mb-4 font-light">
|
|
||||||
The framework for next generation AI applications
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg text-gray-400 max-w-3xl mx-auto mb-8">
|
</div>
|
||||||
Full-stack framework powered by TanStack Router for React and Solid.
|
<div className="flex gap-2">
|
||||||
Build modern applications with server functions, streaming, and type
|
<Chip size="sm" className="px-2" variant="soft">
|
||||||
safety.
|
<div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse mr-2" />{" "}
|
||||||
</p>
|
ACTIVO
|
||||||
<div className="flex flex-col items-center gap-4">
|
</Chip>
|
||||||
<a
|
</div>
|
||||||
href="https://tanstack.com/start"
|
</div>
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
className="px-8 py-3 bg-cyan-500 hover:bg-cyan-600 text-white font-semibold rounded-lg transition-colors shadow-lg shadow-cyan-500/50"
|
{[
|
||||||
>
|
{ label: "OFERTAS", val: "+300", unit: "", color: "text-white" },
|
||||||
Documentation
|
{
|
||||||
</a>
|
label: "PILOTOS",
|
||||||
<p className="text-gray-400 text-sm mt-2">
|
val: "+400",
|
||||||
Begin your TanStack Start journey by editing{" "}
|
unit: "",
|
||||||
<code className="px-2 py-1 bg-slate-700 rounded text-cyan-400">
|
color: "text-gray-200/60"
|
||||||
/src/routes/index.tsx
|
},
|
||||||
</code>
|
{
|
||||||
|
label: "TRAYECTOS",
|
||||||
|
val: "+400",
|
||||||
|
unit: "",
|
||||||
|
color: "text-white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "PUESTOS",
|
||||||
|
val: "+300",
|
||||||
|
unit: "",
|
||||||
|
color: "text-gray-200/60"
|
||||||
|
}
|
||||||
|
].map((s, i) => (
|
||||||
|
<div key={i} className="glass p-4 rounded-2xl">
|
||||||
|
<p className="text-[9px] text-slate-500 font-bold mb-1 tracking-widest">
|
||||||
|
{s.label}
|
||||||
|
</p>
|
||||||
|
<p className={`text-xl font-black ${s.color}`}>
|
||||||
|
{s.val}
|
||||||
|
<span className="text-xs ml-1 opacity-50">{s.unit}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<section className="py-16 px-6 max-w-7xl mx-auto">
|
<div className="relative aspect-video rounded-3xl overflow-hidden group">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="absolute inset-0 bg-transparent opacity-60">
|
||||||
{features.map((feature, index) => (
|
<MapComponent
|
||||||
<div
|
zoom={1}
|
||||||
key={index}
|
className="min-w-xl w-full"
|
||||||
className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-6 hover:border-cyan-500/50 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-500/10"
|
center={[2.3522, 48.8566]}
|
||||||
>
|
>
|
||||||
<div className="mb-4">{feature.icon}</div>
|
{(locations || [])?.map((location) => (
|
||||||
<h3 className="text-xl font-semibold text-white mb-3">
|
<MapMarker
|
||||||
{feature.title}
|
key={location.id}
|
||||||
</h3>
|
longitude={location.lng}
|
||||||
<p className="text-gray-400 leading-relaxed">
|
latitude={location.lat}
|
||||||
{feature.description}
|
>
|
||||||
</p>
|
{/* Prueba para ssl */}
|
||||||
</div>
|
<MarkerContent>
|
||||||
))}
|
{/* <Drone size={24} color="green" className="text-green-200" /> */}
|
||||||
|
<div className="w-3 h-3 bg-accent animate-pulse rounded-full" />
|
||||||
|
</MarkerContent>
|
||||||
|
<MarkerTooltip>{location.name}</MarkerTooltip>
|
||||||
|
<MarkerPopup>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-medium text-foreground">
|
||||||
|
{location.name}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</MarkerPopup>
|
||||||
|
</MapMarker>
|
||||||
|
))}
|
||||||
|
</MapComponent>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div className="absolute bottom-6 left-6 z-10">
|
||||||
|
<Button>
|
||||||
|
<MapIcon className="size-6" />
|
||||||
|
¿Quieres ser parte de mapa?
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="absolute inset-0 bg-linear-to-t from-brand-dark via-transparent to-transparent"></div>
|
||||||
|
<div className="absolute top-6 left-6 flex flex-col gap-2">
|
||||||
|
<div className="glass p-3 rounded-xl"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const navigate = Route.useNavigate()
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-brand-dark text-slate-400 ">
|
||||||
|
{/* NAVBAR */}
|
||||||
|
<nav className="flex items-center justify-between px-10 py-6 border-b border-white/5 backdrop-blur-xl sticky top-0 z-50">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 bg-accent animate-pulse" />
|
||||||
|
<span className="font-mono text-xs tracking-[0.3em] uppercase text-muted">
|
||||||
|
<h1 className="text-lg font-light tracking-tighter">
|
||||||
|
FIND<span className="font-bold text-accent">YOUR</span>PILOT
|
||||||
|
</h1>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:flex gap-8 text-[11px] font-black uppercase tracking-[0.2em]"></div>
|
||||||
|
<Button
|
||||||
|
onPress={() => {
|
||||||
|
navigate({ to: "/access/login" })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Acceso <LogIn />
|
||||||
|
</Button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* HERO / LANDING */}
|
||||||
|
<header className="relative pt-20 px-6 text-center lg:flex gap-4 pb-10">
|
||||||
|
<div className="lg:w-2/5">
|
||||||
|
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-cyan-500/10 blur-[120px] rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10 max-w-5xl mx-auto">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10 text-[10px] font-black uppercase tracking-[0.3em] mb-8 text-accent">
|
||||||
|
<Radio size={12} className="animate-pulse" />
|
||||||
|
<span className="text-accent">v4.0.2</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-6xl md:text-6xl font-black text-white tracking-tighter mb-8 leading-[0.9]">
|
||||||
|
ENCUENTRA A <br />
|
||||||
|
<span className="text-transparent bg-clip-text bg-linear-to-r from-accent-400 via-accent to-stone-400 px-4">
|
||||||
|
TU PILOTO
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-lg md:text-lg text-slate-300 max-w-2xl mx-auto mb-12 font-medium">
|
||||||
|
La plataforma definitiva para la gestión de pilotos de drones.
|
||||||
|
Conecta con los mejores profesionales y lleva tus proyectos al
|
||||||
|
siguiente <span className="italic">nivel</span>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap justify-center gap-4">
|
||||||
|
<Button size="lg">
|
||||||
|
Iniciar despliegue <Rocket size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button size="lg">
|
||||||
|
Más información <ChevronRight size={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex overflow-hidden group mt-20">
|
||||||
|
<div className="flex animate-marquee whitespace-nowrap gap-16 items-center hover:paused py-2">
|
||||||
|
{[
|
||||||
|
{ name: "PostgreSQL", icon: <Database size={32} /> },
|
||||||
|
{ name: "React", icon: <Layers size={32} /> },
|
||||||
|
{ name: "Tailwind", icon: <Zap size={32} /> },
|
||||||
|
{ name: "TanStack", icon: <Cpu size={32} /> },
|
||||||
|
{ name: "TypeScript", icon: <Shield size={32} /> },
|
||||||
|
{ name: "Vite", icon: <Globe size={32} /> }
|
||||||
|
].map((logo, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-center gap-4 grayscale opacity-40 hover:grayscale-0 hover:opacity-100 transition-all duration-500 cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="text-white">{logo.icon}</div>
|
||||||
|
<span className="text-xl font-black italic tracking-tighter text-white uppercase">
|
||||||
|
{logo.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Gradientes laterales para suavizar la entrada/salida (Fading effect) */}
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 left-0 w-20 bg-linear-to-r from-transparent to-transparent z-10"></div>
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 right-0 w-20 bg-linear-to-l from-transparent to-transparent z-10"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* DASHBOARD DEMO SECTION */}
|
||||||
|
<section className="w-3/5 px-6 relative items-start flex ">
|
||||||
|
<div className="max-w-5xl mx-auto w-full">
|
||||||
|
{/* Cabecera del simulador */}
|
||||||
|
<div className="flex items-center justify-between mb-4 px-4 hidden">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-red-500/40" />
|
||||||
|
<div className="w-2 h-2 rounded-full bg-orange-500/40" />
|
||||||
|
<div className="w-2 h-2 rounded-full bg-emerald-500/40" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-[9px] font-mono tracking-widest text-slate-600">
|
||||||
|
<span>BUFFER: 0ms</span>
|
||||||
|
<span className="flex items-center gap-1 text-cyan-500/60">
|
||||||
|
<Database size={10} /> PG_CONNECTED
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border bg-surface border-white/10 rounded shadow-[0_0_100px_rgba(6,182,212,0.05)] overflow-hidden">
|
||||||
|
<div className="flex flex-col lg:flex-row min-h-[600px]">
|
||||||
|
<aside className="w-full lg:max-w-82 border-r border-white/5 bg-black/40 p-8 flex flex-col gap-3">
|
||||||
|
<Chip size="lg" variant="tertiary">
|
||||||
|
<h3 className="text-xl font-black text-white tracking-tight">
|
||||||
|
Ofertas
|
||||||
|
</h3>
|
||||||
|
</Chip>
|
||||||
|
<ScrollShadow className="max-h-[500px]" hideScrollBar>
|
||||||
|
<div className="flex gap-3 flex-col">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
id: "J-001",
|
||||||
|
name: "Inspección de Aerogeneradores",
|
||||||
|
price: 1200,
|
||||||
|
location: "Parque Eólico Tarifa",
|
||||||
|
description:
|
||||||
|
"Se requiere vuelo de proximidad para detección de microfisuras en palas. Obligatorio sensor térmico.",
|
||||||
|
employer: "IberEnergía S.A.",
|
||||||
|
tags: ["Térmico", "STS-01", "Alta Prioridad"],
|
||||||
|
avatar: "https://i.pravatar.cc/150?u=corp1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "J-002",
|
||||||
|
name: "Ortomosaico Agrícola 50Ha",
|
||||||
|
price: 450,
|
||||||
|
location: "Valladolid, ES",
|
||||||
|
description:
|
||||||
|
"Mapeo multiespectral para análisis de estrés hídrico en viñedos. Entrega en GeoJSON.",
|
||||||
|
employer: "AgroTech Solutions",
|
||||||
|
tags: ["Multiespectral", "Mapeo"],
|
||||||
|
avatar: "https://i.pravatar.cc/150?u=corp2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "J-003",
|
||||||
|
name: "Grabación FPVCinematic",
|
||||||
|
price: 800,
|
||||||
|
location: "Madrid (Circuito)",
|
||||||
|
description:
|
||||||
|
"Seguimiento de vehículos a alta velocidad para spot publicitario. Se requiere dron de 5 pulgadas.",
|
||||||
|
employer: "RedMedia",
|
||||||
|
tags: ["FPV", "ProRes"],
|
||||||
|
avatar: "https://i.pravatar.cc/150?u=corp3"
|
||||||
|
}
|
||||||
|
].map((offer) => (
|
||||||
|
<Card key={offer.id}>
|
||||||
|
<Card.Header className="flex flex-col gap-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Avatar size="sm">
|
||||||
|
<Avatar.Image
|
||||||
|
alt="John Doe"
|
||||||
|
src={offer.avatar}
|
||||||
|
/>
|
||||||
|
<Avatar.Fallback>JD</Avatar.Fallback>
|
||||||
|
</Avatar>
|
||||||
|
<h1 className="text-md font-semibold text-start">
|
||||||
|
{offer.name}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<ScrollShadow
|
||||||
|
orientation="horizontal"
|
||||||
|
className="w-[200px]"
|
||||||
|
hideScrollBar
|
||||||
|
>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{offer.tags.map((tag) => (
|
||||||
|
<Chip variant="soft" key={tag}>
|
||||||
|
{tag}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollShadow>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content className=" text-slate-50">
|
||||||
|
<div className="text-xs text-start">
|
||||||
|
{offer.description}
|
||||||
|
</div>
|
||||||
|
</Card.Content>
|
||||||
|
<Card.Footer className="items-center justify-between">
|
||||||
|
<Chip>
|
||||||
|
{offer.price} <Euro className="size-4" />
|
||||||
|
</Chip>
|
||||||
|
<Button size="sm" variant="secondary">
|
||||||
|
Detalles
|
||||||
|
</Button>
|
||||||
|
</Card.Footer>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
<div className="flex justify-end pb-10">
|
||||||
|
<Button>Ver más</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollShadow>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{/* Main Content Area */}
|
||||||
|
<main className="flex-1 p-10 bg-linear-to-br from-transparent to-cyan-950/5">
|
||||||
|
<WebMockHeader />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* FOOTER TÉCNICO */}
|
||||||
|
<footer className="border-t border-white/5 py-20 px-10">
|
||||||
|
<div className="max-w-6xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-12">
|
||||||
|
<div>
|
||||||
|
<p className="text-white font-black italic mb-4">FYP.OS</p>
|
||||||
|
<p className="text-xs leading-loose">
|
||||||
|
Gestiona tú perfil profesional, tus misiones y tus drones.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{["Schema", "Security", "Endpoints"].map((title) => (
|
||||||
|
<div key={title}>
|
||||||
|
<p className="text-[10px] font-black text-white uppercase tracking-widest mb-4">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<ul className="text-xs space-y-2 opacity-50 font-mono">
|
||||||
|
<li>{`fetch_${title.toLowerCase()}`}</li>
|
||||||
|
<li>{`push_logs_v4`}</li>
|
||||||
|
<li>{`auth_verify`}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,60 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
@import "@heroui/styles";
|
@import "@heroui/styles";
|
||||||
|
|
||||||
@import 'tw-animate-css';
|
@import "tw-animate-css";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* Animación de balanceo suave (vuelo) */
|
||||||
|
@keyframes float {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0) rotate(0deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-15px) rotate(1deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animación de zoom de cámara de dron */
|
||||||
|
@keyframes drone-zoom {
|
||||||
|
from {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes marquee {
|
||||||
|
from {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--animate-marquee: marquee 30s linear infinite;
|
||||||
|
|
||||||
|
/* Registro de las animaciones para usarlas como clases */
|
||||||
|
--animate-float: float 6s ease-in-out infinite;
|
||||||
|
--animate-drone-zoom: drone-zoom 20s linear infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply m-0;
|
@apply m-0;
|
||||||
font-family:
|
font-family:
|
||||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
|
||||||
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family:
|
font-family:
|
||||||
source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* HeroUI Theme Customization
|
* HeroUI Theme Customization
|
||||||
@@ -24,85 +62,91 @@ code {
|
|||||||
* Only includes base variables from variables.css
|
* Only includes base variables from variables.css
|
||||||
* @see https://v3.heroui.com/docs/react/getting-started/theming
|
* @see https://v3.heroui.com/docs/react/getting-started/theming
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
* HeroUI Theme Customization
|
||||||
|
* Add this to your global.css after importing @heroui/styles
|
||||||
|
* Only includes base variables from variables.css
|
||||||
|
* @see https://heroui.com/docs/react/getting-started/theming
|
||||||
|
*/
|
||||||
|
|
||||||
:root,
|
:root,
|
||||||
.light,
|
.light,
|
||||||
.default,
|
.default,
|
||||||
[data-theme="light"],
|
[data-theme="light"],
|
||||||
[data-theme="default"] {
|
[data-theme="default"] {
|
||||||
/* Theme Colors (Light Mode) */
|
/* Theme Colors (Light Mode) */
|
||||||
--accent: oklch(66.78% 0.2232 43.72);
|
--accent: oklch(76.97% 0.2124 148.67);
|
||||||
--accent-foreground: oklch(15% 0.0300 43.72);
|
--accent-foreground: oklch(15% 0.03 148.67);
|
||||||
--background: oklch(97.02% 0.0064 43.72);
|
--background: oklch(97.02% 0.0069 148.67);
|
||||||
--border: oklch(90.00% 0.0064 43.72);
|
--border: oklch(90.00% 0.0069 148.67);
|
||||||
--danger: oklch(65.32% 0.2358 0.53);
|
--danger: oklch(65.32% 0.236 13.12);
|
||||||
--danger-foreground: oklch(99.11% 0 0);
|
--danger-foreground: oklch(99.11% 0 0);
|
||||||
--default: oklch(94.00% 0.0064 43.72);
|
--default: oklch(94.00% 0.0069 148.67);
|
||||||
--default-foreground: oklch(21.03% 0.0059 43.72);
|
--default-foreground: oklch(21.03% 0.0059 148.67);
|
||||||
--field-background: oklch(100.00% 0.0032 43.72);
|
--field-background: oklch(100.00% 0.0034 148.67);
|
||||||
--field-foreground: oklch(21.03% 0.0064 43.72);
|
--field-foreground: oklch(21.03% 0.0069 148.67);
|
||||||
--field-placeholder: oklch(55.17% 0.0128 43.72);
|
--field-placeholder: oklch(55.17% 0.0138 148.67);
|
||||||
--focus: oklch(66.78% 0.2232 43.72);
|
--focus: oklch(76.97% 0.2124 148.67);
|
||||||
--foreground: oklch(21.03% 0.0064 43.72);
|
--foreground: oklch(21.03% 0.0069 148.67);
|
||||||
--muted: oklch(55.17% 0.0128 43.72);
|
--muted: oklch(55.17% 0.0138 148.67);
|
||||||
--overlay: oklch(100.00% 0.0019 43.72);
|
--overlay: oklch(100.00% 0.0021 148.67);
|
||||||
--overlay-foreground: oklch(21.03% 0.0064 43.72);
|
--overlay-foreground: oklch(21.03% 0.0069 148.67);
|
||||||
--scrollbar: oklch(87.10% 0.0064 43.72);
|
--scrollbar: oklch(87.10% 0.0069 148.67);
|
||||||
--segment: oklch(100.00% 0.0064 43.72);
|
--segment: oklch(100.00% 0.0069 148.67);
|
||||||
--segment-foreground: oklch(21.03% 0.0064 43.72);
|
--segment-foreground: oklch(21.03% 0.0069 148.67);
|
||||||
--separator: oklch(92.00% 0.0064 43.72);
|
--separator: oklch(92.00% 0.0069 148.67);
|
||||||
--success: oklch(73.29% 0.1960 125.60);
|
--success: oklch(73.29% 0.1962 138.19);
|
||||||
--success-foreground: oklch(21.03% 0.0059 125.60);
|
--success-foreground: oklch(21.03% 0.0059 138.19);
|
||||||
--surface: oklch(100.00% 0.0032 43.72);
|
--surface: oklch(100.00% 0.0034 148.67);
|
||||||
--surface-foreground: oklch(21.03% 0.0064 43.72);
|
--surface-foreground: oklch(21.03% 0.0069 148.67);
|
||||||
--surface-secondary: oklch(95.24% 0.0051 43.72);
|
--surface-secondary: oklch(95.24% 0.0055 148.67);
|
||||||
--surface-secondary-foreground: oklch(21.03% 0.0064 43.72);
|
--surface-secondary-foreground: oklch(21.03% 0.0069 148.67);
|
||||||
--surface-tertiary: oklch(93.73% 0.0051 43.72);
|
--surface-tertiary: oklch(93.73% 0.0055 148.67);
|
||||||
--surface-tertiary-foreground: oklch(21.03% 0.0064 43.72);
|
--surface-tertiary-foreground: oklch(21.03% 0.0069 148.67);
|
||||||
--warning: oklch(78.19% 0.1605 47.12);
|
--warning: oklch(78.19% 0.1607 59.71);
|
||||||
--warning-foreground: oklch(21.03% 0.0059 47.12);
|
--warning-foreground: oklch(21.03% 0.0059 59.71);
|
||||||
|
|
||||||
/* Border Radius */
|
/* Border Radius */
|
||||||
--radius: 0.125rem;
|
--radius: 0.5rem;
|
||||||
--field-radius: 0.125rem;
|
--field-radius: 0.125rem;
|
||||||
|
|
||||||
/* Font Family */
|
/* Font Family */
|
||||||
/* Make sure to load Inter font in your app */
|
/* Make sure to load Hanken Grotesk font in your app */
|
||||||
--font-sans: var(--font-inter);
|
--font-sans: var(--font-hanken-grotesk);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark,
|
.dark,
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
/* Theme Colors (Dark Mode) */
|
/* Theme Colors (Dark Mode) */
|
||||||
--accent: oklch(66.78% 0.2232 43.72);
|
--accent: oklch(76.97% 0.2124 148.67);
|
||||||
--accent-foreground: oklch(15% 0.0300 43.72);
|
--accent-foreground: oklch(15% 0.03 148.67);
|
||||||
--background: oklch(12.00% 0.0064 43.72);
|
--background: oklch(12.00% 0.0069 148.67);
|
||||||
--border: oklch(28.00% 0.0064 43.72);
|
--border: oklch(28.00% 0.0069 148.67);
|
||||||
--danger: oklch(59.40% 0.1992 359.42);
|
--danger: oklch(59.40% 0.1994 12.01);
|
||||||
--danger-foreground: oklch(99.11% 0 0);
|
--danger-foreground: oklch(99.11% 0 0);
|
||||||
--default: oklch(27.40% 0.0064 43.72);
|
--default: oklch(27.40% 0.0069 148.67);
|
||||||
--default-foreground: oklch(99.11% 0 0);
|
--default-foreground: oklch(99.11% 0 0);
|
||||||
--field-background: oklch(21.03% 0.0128 43.72);
|
--field-background: oklch(21.03% 0.0138 148.67);
|
||||||
--field-foreground: oklch(99.11% 0.0064 43.72);
|
--field-foreground: oklch(99.11% 0.0069 148.67);
|
||||||
--field-placeholder: oklch(70.50% 0.0128 43.72);
|
--field-placeholder: oklch(70.50% 0.0138 148.67);
|
||||||
--focus: oklch(66.78% 0.2232 43.72);
|
--focus: oklch(76.97% 0.2124 148.67);
|
||||||
--foreground: oklch(99.11% 0.0064 43.72);
|
--foreground: oklch(99.11% 0.0069 148.67);
|
||||||
--muted: oklch(70.50% 0.0128 43.72);
|
--muted: oklch(70.50% 0.0138 148.67);
|
||||||
--overlay: oklch(21.03% 0.0128 43.72);
|
--overlay: oklch(21.03% 0.0138 148.67);
|
||||||
--overlay-foreground: oklch(99.11% 0.0064 43.72);
|
--overlay-foreground: oklch(99.11% 0.0069 148.67);
|
||||||
--scrollbar: oklch(70.50% 0.0064 43.72);
|
--scrollbar: oklch(70.50% 0.0069 148.67);
|
||||||
--segment: oklch(39.64% 0.0064 43.72);
|
--segment: oklch(39.64% 0.0069 148.67);
|
||||||
--segment-foreground: oklch(99.11% 0.0064 43.72);
|
--segment-foreground: oklch(99.11% 0.0069 148.67);
|
||||||
--separator: oklch(25.00% 0.0064 43.72);
|
--separator: oklch(25.00% 0.0069 148.67);
|
||||||
--success: oklch(73.29% 0.1960 125.60);
|
--success: oklch(73.29% 0.1962 138.19);
|
||||||
--success-foreground: oklch(21.03% 0.0059 125.60);
|
--success-foreground: oklch(21.03% 0.0059 138.19);
|
||||||
--surface: oklch(21.03% 0.0128 43.72);
|
--surface: oklch(21.03% 0.0138 148.67);
|
||||||
--surface-foreground: oklch(99.11% 0.0064 43.72);
|
--surface-foreground: oklch(99.11% 0.0069 148.67);
|
||||||
--surface-secondary: oklch(25.70% 0.0096 43.72);
|
--surface-secondary: oklch(25.70% 0.0103 148.67);
|
||||||
--surface-secondary-foreground: oklch(99.11% 0.0064 43.72);
|
--surface-secondary-foreground: oklch(99.11% 0.0069 148.67);
|
||||||
--surface-tertiary: oklch(27.21% 0.0096 43.72);
|
--surface-tertiary: oklch(27.21% 0.0103 148.67);
|
||||||
--surface-tertiary-foreground: oklch(99.11% 0.0064 43.72);
|
--surface-tertiary-foreground: oklch(99.11% 0.0069 148.67);
|
||||||
--warning: oklch(82.03% 0.1406 51.13);
|
--warning: oklch(82.03% 0.1407 63.72);
|
||||||
--warning-foreground: oklch(21.03% 0.0059 51.13);
|
--warning-foreground: oklch(21.03% 0.0059 63.72);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user