Add /register route, split up authentication page

This commit is contained in:
2024-11-10 22:51:54 -06:00
parent f18adf0f41
commit d9d2e04d94
7 changed files with 256 additions and 138 deletions

View File

@@ -37,7 +37,8 @@
"timestamper",
"tseslint",
"vitest",
"xdist"
"xdist",
"Zustand's"
],
"python.analysis.extraPaths": ["./backend/"],
"editor.formatOnSave": true,

View File

@@ -1,68 +0,0 @@
import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { HTMLAttributes, useState } from "react";
interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> {}
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
async function onSubmit(event: React.SyntheticEvent) {
event.preventDefault();
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 3000);
}
return (
<div className={cn("grid gap-6", className)} {...props}>
<form onSubmit={onSubmit}>
<div className="grid gap-2">
<div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Email
</Label>
<Input
id="email"
placeholder="name@example.com"
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading}
/>
</div>
<Button disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Sign In with Email
</Button>
</div>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<Button variant="outline" type="button" disabled={isLoading}>
{isLoading ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : (
<Icons.gitHub className="mr-2 h-4 w-4" />
)}{" "}
GitHub
</Button>
</div>
);
}

View File

@@ -0,0 +1,108 @@
import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { Link } from "@tanstack/react-router";
import { HTMLAttributes, useState } from "react";
interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> {}
export function RegisterForm({ className, ...props }: UserAuthFormProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
async function onSubmit(event: React.SyntheticEvent) {
event.preventDefault();
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 3000);
}
return (
<div className={cn("grid gap-6", className)} {...props}>
<form onSubmit={onSubmit}>
<div className="grid gap-2">
<div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Email
</Label>
<Input
id="email"
placeholder="name@example.com"
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading}
/>
</div>
<Button disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Sign In with Email
</Button>
</div>
</form>
</div>
);
}
export function LoginForm({ className, ...props }: UserAuthFormProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
async function onSubmit(event: React.SyntheticEvent) {
event.preventDefault();
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 3000);
}
return (
<div className={cn("grid gap-6", className)} {...props}>
<form onSubmit={onSubmit}>
<div className="grid gap-2">
<div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Email
</Label>
<Input
id="email"
placeholder="name@example.com"
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading}
/>
</div>
<Button disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Sign In
</Button>
</div>
</form>
<p className="text-center text-sm text-muted-foreground">
<Link
href="/terms"
className="underline underline-offset-4 hover:text-primary"
>
Register
</Link>{" "}
or{" "}
<Link
href="/forgot-password"
className="underline underline-offset-4 hover:text-primary"
>
Forgot Password
</Link>
</p>
</div>
);
}

View File

@@ -0,0 +1,67 @@
import { Icons } from "@/components/icons";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Link } from "@tanstack/react-router";
import { ReactNode } from "react";
export function AgreementText() {
return (
<p className="text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "}
<Link
href="/terms"
className="underline underline-offset-4 hover:text-primary"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="underline underline-offset-4 hover:text-primary"
>
Privacy Policy
</Link>
.
</p>
);
}
export default function AuthenticationPage({
children,
}: {
children: ReactNode;
}) {
return (
<div className="container relative h-[100vh] flex-col items-center grid w-screen lg:max-w-none lg:grid-cols-2 lg:px-0">
<Link
href="/"
className={cn(
buttonVariants({ variant: "ghost" }),
"absolute right-4 top-4 md:right-8 md:top-8",
)}
>
Linkpulse
</Link>
<div className="relative hidden h-full flex-col grow bg-muted p-10 text-white dark:border-r lg:flex">
<div className="absolute inset-0 bg-zinc-900" />
<div className="relative z-20 flex items-center text-lg font-medium">
<Icons.linkpulse className="mr-2 h-6 w-6 text-white" />
Linkpulse
</div>
<div className="z-20 mt-auto space-y-2">
{/* <blockquote className="space-y-2">
<p className="text-lg">
&ldquo;This library has saved me countless hours of work and
helped me deliver stunning designs to my clients faster than
ever before.&rdquo;
</p>
<footer className="text-sm">Sofia Davis</footer>
</blockquote> */}
{/* <p className="text-lg"></p>
<footer className="text-sm"></footer> */}
</div>
</div>
<div className="px-6">{children}</div>
</div>
);
}

View File

@@ -13,6 +13,7 @@ import { createFileRoute } from '@tanstack/react-router'
// Import Routes
import { Route as rootRoute } from './routes/__root'
import { Route as RegisterImport } from './routes/register'
import { Route as LoginImport } from './routes/login'
import { Route as DashboardImport } from './routes/dashboard'
@@ -22,6 +23,12 @@ const IndexLazyImport = createFileRoute('/')()
// Create/Update Routes
const RegisterRoute = RegisterImport.update({
id: '/register',
path: '/register',
getParentRoute: () => rootRoute,
} as any)
const LoginRoute = LoginImport.update({
id: '/login',
path: '/login',
@@ -65,6 +72,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof LoginImport
parentRoute: typeof rootRoute
}
'/register': {
id: '/register'
path: '/register'
fullPath: '/register'
preLoaderRoute: typeof RegisterImport
parentRoute: typeof rootRoute
}
}
}
@@ -74,12 +88,14 @@ export interface FileRoutesByFullPath {
'/': typeof IndexLazyRoute
'/dashboard': typeof DashboardRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
}
export interface FileRoutesByTo {
'/': typeof IndexLazyRoute
'/dashboard': typeof DashboardRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
}
export interface FileRoutesById {
@@ -87,14 +103,15 @@ export interface FileRoutesById {
'/': typeof IndexLazyRoute
'/dashboard': typeof DashboardRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/dashboard' | '/login'
fullPaths: '/' | '/dashboard' | '/login' | '/register'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/dashboard' | '/login'
id: '__root__' | '/' | '/dashboard' | '/login'
to: '/' | '/dashboard' | '/login' | '/register'
id: '__root__' | '/' | '/dashboard' | '/login' | '/register'
fileRoutesById: FileRoutesById
}
@@ -102,12 +119,14 @@ export interface RootRouteChildren {
IndexLazyRoute: typeof IndexLazyRoute
DashboardRoute: typeof DashboardRoute
LoginRoute: typeof LoginRoute
RegisterRoute: typeof RegisterRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexLazyRoute: IndexLazyRoute,
DashboardRoute: DashboardRoute,
LoginRoute: LoginRoute,
RegisterRoute: RegisterRoute,
}
export const routeTree = rootRoute
@@ -122,7 +141,8 @@ export const routeTree = rootRoute
"children": [
"/",
"/dashboard",
"/login"
"/login",
"/register"
]
},
"/": {
@@ -133,6 +153,9 @@ export const routeTree = rootRoute
},
"/login": {
"filePath": "login.tsx"
},
"/register": {
"filePath": "register.tsx"
}
}
}

View File

@@ -1,10 +1,8 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import { useUserStore } from "@/lib/state";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { UserAuthForm } from "@/components/auth/UserAuthForm";
import { Icons } from "@/components/icons";
import { LoginForm } from "@/components/auth/form";
import AuthenticationPage from "@/components/pages/authentication";
export const Route = createFileRoute("/login")({
beforeLoad: async ({ location }) => {
@@ -22,67 +20,16 @@ export const Route = createFileRoute("/login")({
function Login() {
return (
<>
<div className="container relative h-[100vh] flex-col items-center grid w-screen lg:max-w-none lg:grid-cols-2 lg:px-0">
<a
href="/"
className={cn(
buttonVariants({ variant: "ghost" }),
"absolute right-4 top-4 md:right-8 md:top-8",
)}
>
Linkpulse
</a>
<div className="relative hidden h-full flex-col grow bg-muted p-10 text-white dark:border-r lg:flex">
<div className="absolute inset-0 bg-zinc-900" />
<div className="relative z-20 flex items-center text-lg font-medium">
<Icons.linkpulse className="mr-2 h-6 w-6 text-white" />
Linkpulse
</div>
<div className="z-20 mt-auto space-y-2">
{/* <blockquote className="space-y-2">
<p className="text-lg">
&ldquo;This library has saved me countless hours of work and
helped me deliver stunning designs to my clients faster than
ever before.&rdquo;
</p>
<footer className="text-sm">Sofia Davis</footer>
</blockquote> */}
{/* <p className="text-lg"></p>
<footer className="text-sm"></footer> */}
</div>
</div>
<div className="px-6">
<AuthenticationPage>
<div className="mx-auto flex w-full flex-col space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<h1 className="text-2xl font-semibold tracking-tight">
Create an account
</h1>
<h1 className="text-2xl font-semibold tracking-tight">Login</h1>
<p className="text-sm text-muted-foreground">
Enter your email below to create your account
Enter your email below to login
</p>
</div>
<UserAuthForm />
<p className="text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "}
<a
href="/terms"
className="underline underline-offset-4 hover:text-primary"
>
Terms of Service
</a>{" "}
and{" "}
<a
href="/privacy"
className="underline underline-offset-4 hover:text-primary"
>
Privacy Policy
</a>
.
</p>
<LoginForm />
</div>
</div>
</div>
</>
</AuthenticationPage>
);
}

View File

@@ -0,0 +1,40 @@
import { useUserStore } from "@/lib/state";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { RegisterForm } from "@/components/auth/form";
import AuthenticationPage, {
AgreementText,
} from "@/components/pages/authentication";
export const Route = createFileRoute("/register")({
beforeLoad: async ({ location }) => {
const isLoggedIn = useUserStore.getState().user !== null;
if (isLoggedIn) {
return redirect({
to: "/dashboard",
search: { redirect: location.href },
});
}
},
component: Register,
});
function Register() {
return (
<AuthenticationPage>
<div className="mx-auto flex w-full flex-col space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<h1 className="text-2xl font-semibold tracking-tight">
Create an account
</h1>
<p className="text-sm text-muted-foreground">
Enter your email below to create your account
</p>
</div>
<RegisterForm />
<AgreementText />
</div>
</AuthenticationPage>
);
}