feat: Implement a new authentication system with dedicated login and registration pages, an authenticated route layout, and updated routing.
This commit is contained in:
@@ -6,11 +6,12 @@ import {
|
||||
} from "@tanstack/react-router"
|
||||
import { Devtools } from "@/integrations/devtools"
|
||||
import { getLocale } from "@/integrations/paraglide/runtime"
|
||||
import { user } from "@/lib/server/user"
|
||||
import appCss from "@/styles/globals.css?url"
|
||||
import Header from "../components/Header"
|
||||
|
||||
interface MyRouterContext {
|
||||
queryClient: QueryClient
|
||||
user: null
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
||||
@@ -20,8 +21,8 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
|
||||
if (typeof document !== "undefined") {
|
||||
document.documentElement.setAttribute("lang", getLocale())
|
||||
}
|
||||
return await user.userData()
|
||||
},
|
||||
|
||||
head: () => ({
|
||||
meta: [
|
||||
{
|
||||
@@ -52,7 +53,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
{children}
|
||||
<Devtools />
|
||||
<Scripts />
|
||||
|
||||
31
src/routes/_authed.tsx
Normal file
31
src/routes/_authed.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createFileRoute, Link, Outlet } from "@tanstack/react-router"
|
||||
|
||||
export const Route = createFileRoute("/_authed")({
|
||||
beforeLoad: ({ context }) => {
|
||||
if (context.error) {
|
||||
throw new Error("Not authenticated")
|
||||
}
|
||||
},
|
||||
errorComponent: ({ error }) => {
|
||||
if (error.message === "Not authenticated") {
|
||||
return (
|
||||
<p>
|
||||
Not authenticated. Please <Link to="/access/login">login</Link>.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
throw error
|
||||
},
|
||||
component: RouteComponent
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="min-h-screen flex gap-2">
|
||||
<div className="p-2"></div>
|
||||
<div className="sm:pl-25 w-full">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
9
src/routes/_authed/dashboard.tsx
Normal file
9
src/routes/_authed/dashboard.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_authed/dashboard')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/_authed/dashboard"!</div>
|
||||
}
|
||||
117
src/routes/access.login.tsx
Normal file
117
src/routes/access.login.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { Button, Card, Form, Input, Label, Spinner } from "@heroui/react"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { LogIn } from "lucide-react"
|
||||
import { useLogin } from "@/lib/hooks/useLogin"
|
||||
|
||||
export const Route = createFileRoute("/access/login")({
|
||||
component: RouteComponent
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const { login, isPending } = useLogin()
|
||||
|
||||
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget)
|
||||
const email = formData.get("email") as string
|
||||
const password = formData.get("password") as string
|
||||
|
||||
login({ email, password })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card.Content>
|
||||
<Form onSubmit={handleFormSubmit} className="flex flex-col gap-4">
|
||||
<Label isRequired className="ml-4 text-lg">
|
||||
Correo
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Introduce tu correo"
|
||||
type="email"
|
||||
name="email"
|
||||
variant="secondary"
|
||||
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} />}
|
||||
Entrar
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Card.Content>
|
||||
<Card.Footer>
|
||||
<div className="flex justify-evenly w-full gap-4">
|
||||
<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"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
</Card.Footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
120
src/routes/access.register.tsx
Normal file
120
src/routes/access.register.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Button, Form, Input, Label, Spinner } from "@heroui/react"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { LogIn } from "lucide-react"
|
||||
import { useSignup } from "@/lib/hooks/useSignup"
|
||||
|
||||
export const Route = createFileRoute("/access/register")({
|
||||
component: RouteComponent
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const { signup, isPending } = useSignup()
|
||||
|
||||
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
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({
|
||||
email,
|
||||
password,
|
||||
location,
|
||||
name
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={handleFormSubmit} className="flex flex-col gap-4">
|
||||
<Label isRequired className="ml-4 text-lg">
|
||||
Correo
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Introduce tu correo"
|
||||
type="email"
|
||||
name="email"
|
||||
variant="secondary"
|
||||
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} />}
|
||||
Entrar
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
<div className="flex justify-evenly w-full gap-4">
|
||||
<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"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
src/routes/access.tsx
Normal file
61
src/routes/access.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Card, type Key, Tabs } from "@heroui/react"
|
||||
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
|
||||
|
||||
export const Route = createFileRoute("/access")({
|
||||
beforeLoad: ({ location }) => {
|
||||
if (location.pathname === "/access") {
|
||||
redirect({
|
||||
to: "/access/login",
|
||||
replace: true,
|
||||
throw: true
|
||||
})
|
||||
}
|
||||
},
|
||||
component: RouteComponent
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const navigate = Route.useNavigate()
|
||||
|
||||
const onSelectTab = (tabId: Key) => {
|
||||
navigate({
|
||||
to: `/access/${tabId}`,
|
||||
viewTransition: true
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid-cols-2 grid min-h-screen">
|
||||
<div className=" p-5 bg-default">
|
||||
<h1 className="text-4xl font-bold text-end ">
|
||||
Find<span className="text-accent">your</span>Pilot
|
||||
</h1>
|
||||
<div className="flex items-center justify-center min-h-[90vh]">
|
||||
<Card className="w-full max-w-md bg-white/90 backdrop-blur-2xl border-3 border-accent-soft">
|
||||
<Card.Header>
|
||||
<Tabs className="w-full max-w-md" onSelectionChange={onSelectTab}>
|
||||
<Tabs.ListContainer>
|
||||
<Tabs.List aria-label="Options">
|
||||
<Tabs.Tab id="login" className="text-lg">
|
||||
Acceso
|
||||
<Tabs.Indicator className="bg-accent" />
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab id="register" className="text-lg ">
|
||||
<Tabs.Separator />
|
||||
Registro
|
||||
<Tabs.Indicator className="bg-accent" />
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
</Tabs.ListContainer>
|
||||
</Tabs>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<Outlet />
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import { Button, Card, Input, Label, Tabs } from "@heroui/react"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { LogIn } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
|
||||
export const Route = createFileRoute("/login")({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const [values, setValues] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
})
|
||||
return (
|
||||
<div className="grid-cols-2 grid min-h-screen">
|
||||
<div className=" p-5 bg-default">
|
||||
<h1 className="text-4xl font-bold text-end ">
|
||||
Find<span className="text-accent">your</span>Pilot
|
||||
</h1>
|
||||
<div className="flex items-center justify-center min-h-[90vh]">
|
||||
<Card className="w-full max-w-md bg-white/90 backdrop-blur-2xl border-3 border-accent-soft">
|
||||
<Card.Header>
|
||||
<Tabs className="w-full max-w-md">
|
||||
<Tabs.ListContainer>
|
||||
<Tabs.List aria-label="Options">
|
||||
<Tabs.Tab id="login" className="text-lg">
|
||||
Acceso
|
||||
<Tabs.Indicator className="bg-accent" />
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab id="register" className="text-lg ">
|
||||
<Tabs.Separator />
|
||||
Registro
|
||||
<Tabs.Indicator className="bg-accent" />
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
</Tabs.ListContainer>
|
||||
</Tabs>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form className="flex flex-col gap-4">
|
||||
<Label isRequired className="ml-4 text-lg">
|
||||
Correo
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Introduce tu correo"
|
||||
type="email"
|
||||
value={values.email}
|
||||
onChange={(e) =>
|
||||
setValues({ ...values, email: e.target.value })
|
||||
}
|
||||
variant="secondary"
|
||||
className="py-4 text-lg "
|
||||
required
|
||||
/>
|
||||
<Label isRequired className="ml-4 text-lg">
|
||||
Contraseña
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Introduce tu contraseña"
|
||||
type="password"
|
||||
value={values.password}
|
||||
onChange={(e) =>
|
||||
setValues({ ...values, password: e.target.value })
|
||||
}
|
||||
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"
|
||||
>
|
||||
<LogIn size={18} />
|
||||
Entrar
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card.Content>
|
||||
<Card.Footer>
|
||||
<div className="flex justify-evenly w-full gap-4">
|
||||
<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"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd" clip-rule="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"
|
||||
>
|
||||
<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>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user