login form created

This commit is contained in:
2026-03-18 19:14:08 -05:00
parent e025d0f5cc
commit 6b8d7b53d0
20 changed files with 800 additions and 73 deletions

View File

@@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/lst.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title> <title>Logistics Support Tool</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -1,37 +1,113 @@
import { Link } from "@tanstack/react-router"; import { Link, useNavigate, useRouterState } from "@tanstack/react-router";
import { SidebarIcon } from "lucide-react"; import { SidebarIcon } from "lucide-react";
import { authClient, useSession } from "@/lib/auth-client";
import { ModeToggle } from "./mode-toggle"; import { ModeToggle } from "./mode-toggle";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "./ui/dropdown-menu";
import { useSidebar } from "./ui/sidebar"; import { useSidebar } from "./ui/sidebar";
export default function Header() { export default function Header() {
const { toggleSidebar } = useSidebar(); const { toggleSidebar } = useSidebar();
const { data: session } = useSession();
const { signOut } = authClient;
const router = useRouterState();
const navigate = useNavigate();
const currentPath = router.location.href;
return ( return (
<header className="sticky top-0 z-50 flex w-full items-center border-b bg-background"> <header className="sticky top-0 z-50 flex w-full items-center border-b bg-background">
<div className="flex h-(--header-height) w-full items-center gap-2 px-4"> <div className="flex justify-between w-full">
<div className="flex flex-row"> <div className="flex items-center gap-2 px-4">
<Link to="/"> <div className="flex flex-row">
<img <Link to="/">
src="/lst/app/imgs/dkLst.png" <img
alt="Home" src="/lst/app/imgs/dkLst.png"
className="size-8 cursor-pointer" alt="Home"
/> className="size-8 cursor-pointer"
</Link> />
</Link>
</div>
<Button
className="h-8 w-8"
variant="ghost"
size="icon"
onClick={toggleSidebar}
>
<SidebarIcon />
</Button>
</div>
<div className="flex flex-col gap-0.5 leading-none p-2">
<span className="font-semibold text-2xl">Logistics Support Tool</span>
</div>
<div className="m-1 flex gap-1">
<div>
<ModeToggle />
</div>
<div>
{session ? (
<DropdownMenu>
<DropdownMenuTrigger>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>
Hello {session.user?.name}
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Link to="/">Profile</Link>
</DropdownMenuItem>
{/* <DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuItem>Subscription</DropdownMenuItem> */}
<hr className="solid"></hr>
<DropdownMenuItem>
<div className="m-auto">
<Button
size="xs"
variant="ghost"
onClick={() =>
signOut({
fetchOptions: {
onSuccess: () => {
navigate({ to: "/" });
},
},
})
}
>
Logout
</Button>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<div className="">
<Button>
<Link to="/login" search={{ redirect: currentPath }}>
Login
</Link>
</Button>
</div>
)}
</div>
</div> </div>
<Button
className="h-8 w-8"
variant="ghost"
size="icon"
onClick={toggleSidebar}
>
<SidebarIcon />
</Button>
</div>
<div className="flex flex-col gap-0.5 leading-none w-52">
<span className="font-semibold">Logistics Support Tool</span>
</div>
<div className="m-1">
<ModeToggle />
</div> </div>
</header> </header>
); );

View File

@@ -0,0 +1,110 @@
import * as React from "react"
import { Avatar as AvatarPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Avatar({
className,
size = "default",
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
size?: "default" | "sm" | "lg"
}) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
data-size={size}
className={cn(
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
className
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn(
"aspect-square size-full rounded-full object-cover",
className
)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
className
)}
{...props}
/>
)
}
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-badge"
className={cn(
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
className
)}
{...props}
/>
)
}
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group"
className={cn(
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className
)}
{...props}
/>
)
}
function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className
)}
{...props}
/>
)
}
export {
Avatar,
AvatarImage,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarBadge,
}

View File

@@ -0,0 +1,103 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn(
"text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
className
)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
className
)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@@ -0,0 +1,31 @@
import * as React from "react"
import { Checkbox as CheckboxPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { CheckIcon } from "lucide-react"
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
>
<CheckIcon
/>
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

View File

@@ -0,0 +1,22 @@
import * as React from "react"
import { Label as LabelPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props}
/>
)
}
export { Label }

View File

@@ -0,0 +1,33 @@
import { Label } from "@radix-ui/react-label";
import { Checkbox } from "@/components/ui/checkbox";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type CheckboxFieldProps = {
label: string;
description?: string;
};
export const CheckboxField = ({ label }: CheckboxFieldProps) => {
const field = useFieldContext<boolean>();
return (
<div>
<div className="flex items-center gap-3">
<Label htmlFor={field.name}>
<span>{label}</span>
</Label>
<Checkbox
id={field.name}
checked={field.state.value}
onCheckedChange={(checked) => {
field.handleChange(checked === true);
}}
onBlur={field.handleBlur}
/>
</div>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -0,0 +1,18 @@
import type { AnyFieldMeta } from "@tanstack/react-form";
type FieldErrorsProps = {
meta: AnyFieldMeta;
};
export const FieldErrors = ({ meta }: FieldErrorsProps) => {
if (!meta.isTouched) return null;
return meta.errors.map((error) => (
<p
key={`${error.message}-${error.code ?? "err"}`}
className="text-sm font-medium text-destructive"
>
{error.message}
</p>
));
};

View File

@@ -0,0 +1,37 @@
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type InputFieldProps = {
label: string;
inputType: string;
required: boolean;
};
const autoCompleteMap: Record<string, string> = {
email: "email",
password: "current-password",
text: "off",
username: "username",
};
export const InputField = ({ label, inputType, required }: InputFieldProps) => {
const field = useFieldContext<any>();
return (
<div className="grid gap-3">
<Label htmlFor={field.name}>{label}</Label>
<Input
id={field.name}
autoComplete={autoCompleteMap[inputType] ?? "off"}
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
type={inputType}
required={required}
/>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -0,0 +1,49 @@
import { Eye, EyeOff } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type PasswordInputProps = {
label: string;
required?: boolean;
};
export const InputPasswordField = ({
label,
required = false,
}: PasswordInputProps) => {
const field = useFieldContext<any>();
const [show, setShow] = useState(false);
return (
<div className="grid gap-3 mt-2">
<Label htmlFor={field.name}>{label}</Label>
<div className="relative">
<Input
id={field.name}
type={show ? "text" : "password"}
autoComplete="current-password"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
required={required}
className="pr-10"
/>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => setShow((p) => !p)}
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7"
tabIndex={-1}
>
{show ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -0,0 +1,24 @@
import { useStore } from "@tanstack/react-form";
import { Button } from "@/components/ui/button";
import { useFormContext } from ".";
type SubmitButtonProps = {
children: React.ReactNode;
};
export const SubmitButton = ({ children }: SubmitButtonProps) => {
const form = useFormContext();
const [isSubmitting] = useStore(form.store, (state) => [
state.isSubmitting,
state.canSubmit,
]);
return (
<div className="">
<Button type="submit" disabled={isSubmitting}>
{children}
</Button>
</div>
);
};

View File

@@ -0,0 +1,23 @@
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
import { CheckboxField } from "./CheckBox.Field";
import { InputField } from "./Input.Field";
import { InputPasswordField } from "./InputPassword.Field";
import { SubmitButton } from "./SubmitButton";
export const { fieldContext, useFieldContext, formContext, useFormContext } =
createFormHookContexts();
export const { useAppForm } = createFormHook({
fieldComponents: {
InputField,
InputPasswordField,
//SelectField,
CheckboxField,
//DateField,
//TextArea,
//Searchable,
},
formComponents: { SubmitButton },
fieldContext,
formContext,
});

View File

@@ -13,7 +13,7 @@ const queryClient = new QueryClient({
queries: { queries: {
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
retry: 0, retry: 0,
refetchOnWindowFocus: false, refetchOnWindowFocus: true,
}, },
}, },
}); });

View File

@@ -12,6 +12,9 @@ import { Route as rootRouteImport } from './routes/__root'
import { Route as AboutRouteImport } from './routes/about' import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as AdminLogsRouteImport } from './routes/admin/logs' import { Route as AdminLogsRouteImport } from './routes/admin/logs'
import { Route as authLoginRouteImport } from './routes/(auth)/login'
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
const AboutRoute = AboutRouteImport.update({ const AboutRoute = AboutRouteImport.update({
id: '/about', id: '/about',
@@ -28,35 +31,81 @@ const AdminLogsRoute = AdminLogsRouteImport.update({
path: '/admin/logs', path: '/admin/logs',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const authLoginRoute = authLoginRouteImport.update({
id: '/(auth)/login',
path: '/login',
getParentRoute: () => rootRouteImport,
} as any)
const authUserSignupRoute = authUserSignupRouteImport.update({
id: '/(auth)/user/signup',
path: '/user/signup',
getParentRoute: () => rootRouteImport,
} as any)
const authUserResetpasswordRoute = authUserResetpasswordRouteImport.update({
id: '/(auth)/user/resetpassword',
path: '/user/resetpassword',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/login': typeof authLoginRoute
'/admin/logs': typeof AdminLogsRoute '/admin/logs': typeof AdminLogsRoute
'/user/resetpassword': typeof authUserResetpasswordRoute
'/user/signup': typeof authUserSignupRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/login': typeof authLoginRoute
'/admin/logs': typeof AdminLogsRoute '/admin/logs': typeof AdminLogsRoute
'/user/resetpassword': typeof authUserResetpasswordRoute
'/user/signup': typeof authUserSignupRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/': typeof IndexRoute
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/(auth)/login': typeof authLoginRoute
'/admin/logs': typeof AdminLogsRoute '/admin/logs': typeof AdminLogsRoute
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
'/(auth)/user/signup': typeof authUserSignupRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/about' | '/admin/logs' fullPaths:
| '/'
| '/about'
| '/login'
| '/admin/logs'
| '/user/resetpassword'
| '/user/signup'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/about' | '/admin/logs' to:
id: '__root__' | '/' | '/about' | '/admin/logs' | '/'
| '/about'
| '/login'
| '/admin/logs'
| '/user/resetpassword'
| '/user/signup'
id:
| '__root__'
| '/'
| '/about'
| '/(auth)/login'
| '/admin/logs'
| '/(auth)/user/resetpassword'
| '/(auth)/user/signup'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
AboutRoute: typeof AboutRoute AboutRoute: typeof AboutRoute
authLoginRoute: typeof authLoginRoute
AdminLogsRoute: typeof AdminLogsRoute AdminLogsRoute: typeof AdminLogsRoute
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
authUserSignupRoute: typeof authUserSignupRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@@ -82,13 +131,37 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AdminLogsRouteImport preLoaderRoute: typeof AdminLogsRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/(auth)/login': {
id: '/(auth)/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof authLoginRouteImport
parentRoute: typeof rootRouteImport
}
'/(auth)/user/signup': {
id: '/(auth)/user/signup'
path: '/user/signup'
fullPath: '/user/signup'
preLoaderRoute: typeof authUserSignupRouteImport
parentRoute: typeof rootRouteImport
}
'/(auth)/user/resetpassword': {
id: '/(auth)/user/resetpassword'
path: '/user/resetpassword'
fullPath: '/user/resetpassword'
preLoaderRoute: typeof authUserResetpasswordRouteImport
parentRoute: typeof rootRouteImport
}
} }
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
AboutRoute: AboutRoute, AboutRoute: AboutRoute,
authLoginRoute: authLoginRoute,
AdminLogsRoute: AdminLogsRoute, AdminLogsRoute: AdminLogsRoute,
authUserResetpasswordRoute: authUserResetpasswordRoute,
authUserSignupRoute: authUserSignupRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)

View File

@@ -0,0 +1,113 @@
import { Link, useNavigate } from "@tanstack/react-router";
import { toast } from "sonner";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
export default function LoginForm({ redirectPath }: { redirectPath: string }) {
const loginEmail = localStorage.getItem("loginEmail") || "";
const rememberMe = localStorage.getItem("rememberMe") === "true";
const navigate = useNavigate();
const form = useAppForm({
defaultValues: {
email: loginEmail,
password: "",
rememberMe: rememberMe,
},
onSubmit: async ({ value }) => {
// set remember me incase we want it later
if (value.rememberMe) {
localStorage.setItem("rememberMe", value.rememberMe.toString());
localStorage.setItem("loginEmail", value.email);
} else {
localStorage.removeItem("rememberMe");
localStorage.removeItem("loginEmail");
}
try {
const login = await authClient.signIn.email({
email: value.email,
password: value.password,
fetchOptions: {
onSuccess: () => {
navigate({ to: redirectPath ?? "/" });
},
},
});
if (login.error) {
toast.error(`${login.error?.message}`);
return;
}
toast.success(`Welcome back ${login.data?.user.name}`);
} catch (error) {
console.log(error);
}
},
});
return (
<div>
<Card className="p-3 w-96">
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your username and password below
</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.AppField name="email">
{(field) => (
<field.InputField label="Email" inputType="email" required />
)}
</form.AppField>
<form.AppField name="password">
{(field) => (
<field.InputPasswordField label="Password" required={true} />
)}
</form.AppField>
<div className="flex flex-row justify-between mt-3 mb-3">
<form.AppField name="rememberMe">
{(field) => <field.CheckboxField label="Remember me" />}
</form.AppField>
<Link
to="/user/resetpassword"
className="inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</Link>
</div>
<div className="flex justify-end mt-2">
<form.AppForm>
<form.SubmitButton>Login</form.SubmitButton>
</form.AppForm>
</div>
</form>
<div className="flex justify-center mt-2">
Don&apos;t have an account?{" "}
<Link to={"/user/signup"} className="underline underline-offset-4">
Sign up
</Link>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,32 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import z from "zod";
import { authClient } from "@/lib/auth-client";
import LoginForm from "./-components/LoginForm";
export const Route = createFileRoute("/(auth)/login")({
component: RouteComponent,
validateSearch: z.object({
redirect: z.string().optional(),
}),
beforeLoad: async () => {
const result = await authClient.getSession({
query: { disableCookieCache: true },
});
if (result.data) {
throw redirect({ to: "/" });
}
},
});
function RouteComponent() {
const search = Route.useSearch();
const redirectPath = search.redirect ?? "/";
return (
<div className="flex justify-center mt-10">
<LoginForm redirectPath={redirectPath} />
</div>
);
}

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import { createRootRoute, Outlet } from "@tanstack/react-router"; import { createRootRoute, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { Toaster } from "sonner";
import Header from "@/components/Header"; import Header from "@/components/Header";
import { AppSidebar } from "@/components/Sidebar/sidebar"; import { AppSidebar } from "@/components/Sidebar/sidebar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { Toaster } from "@/components/ui/sonner";
import { ThemeProvider } from "@/lib/theme-provider"; import { ThemeProvider } from "@/lib/theme-provider";
const RootLayout = () => ( const RootLayout = () => (

View File

@@ -1,12 +1,8 @@
import { import { createFileRoute } from "@tanstack/react-router";
createFileRoute,
useNavigate,
useRouter,
} from "@tanstack/react-router";
import { toast } from "sonner";
import z from "zod"; import z from "zod";
import { Button } from "../components/ui/button";
import { authClient, useSession } from "../lib/auth-client"; import { useSession } from "../lib/auth-client";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
validateSearch: z.object({ validateSearch: z.object({
@@ -18,44 +14,13 @@ export const Route = createFileRoute("/")({
function Index() { function Index() {
const { data: session, isPending } = useSession(); const { data: session, isPending } = useSession();
const router = useRouter();
const navigate = useNavigate();
const search = Route.useSearch();
const login = async () => { if (isPending)
try { return <div className="flex justify-center mt-10">Loading...</div>;
await authClient.signIn.email({
email: "blake.matthes@alpla.com",
password: "nova0511",
fetchOptions: {
onSuccess: () => {
navigate({ to: search.redirect ?? "/" });
},
},
});
toast.success("logged in");
} catch (error: any) {
console.error(error.response);
}
//console.log(session)
router.invalidate();
};
if (isPending) return <div>Loading...</div>;
// if (!session) return <button>Sign In</button> // if (!session) return <button>Sign In</button>
return ( return (
<div className="p-2"> <div className="flex justify-center mt-10">
<h3 className="w-2xl text-3xl">Welcome Home!</h3> <h3 className="w-2xl text-3xl">Welcome Home!</h3>
{!session ? (
<Button onClick={login}>Sign In</Button>
) : (
<div>
<span>welcome {session.user?.name}</span>
<Button onClick={() => authClient.signOut()}>Signout</Button>
</div>
)}
</div> </div>
); );
} }