user forms added

This commit is contained in:
2026-03-19 17:44:17 -05:00
parent 6b8d7b53d0
commit 85073c19d2
17 changed files with 576 additions and 70 deletions

View File

@@ -3,7 +3,7 @@ import type { Server as HttpServer } from "node:http";
//import { fileURLToPath } from "node:url"; //import { fileURLToPath } from "node:url";
import { instrument } from "@socket.io/admin-ui"; import { instrument } from "@socket.io/admin-ui";
import { Server } from "socket.io"; import { Server } from "socket.io";
import { auth } from "utils/auth.utils.js";
import { createLogger } from "../logger/logger.controller.js"; import { createLogger } from "../logger/logger.controller.js";
import { allowedOrigins } from "../utils/cors.utils.js"; import { allowedOrigins } from "../utils/cors.utils.js";
import { registerEmitter } from "./roomEmitter.socket.js"; import { registerEmitter } from "./roomEmitter.socket.js";
@@ -15,6 +15,7 @@ const log = createLogger({ module: "socket.io", subModule: "setup" });
//import type { Session, User } from "better-auth"; // adjust if needed //import type { Session, User } from "better-auth"; // adjust if needed
import { protectedRooms } from "./roomDefinitions.socket.js"; import { protectedRooms } from "./roomDefinitions.socket.js";
import { auth } from "../utils/auth.utils.js";
// declare module "socket.io" { // declare module "socket.io" {
// interface Socket { // interface Socket {

View File

@@ -77,7 +77,7 @@ export const auth = betterAuth({
minPasswordLength: 8, // optional config minPasswordLength: 8, // optional config
resetPasswordTokenExpirySeconds: process.env.RESET_EXPIRY_SECONDS, // time in seconds resetPasswordTokenExpirySeconds: process.env.RESET_EXPIRY_SECONDS, // time in seconds
sendResetPassword: async ({ user, token }) => { sendResetPassword: async ({ user, token }) => {
const frontendUrl = `${process.env.BETTER_AUTH_URL}/lst/app/user/resetpassword?token=${token}`; const frontendUrl = `${process.env.URL}/lst/app/user/resetpassword?token=${token}`;
const expiryMinutes = Math.floor( const expiryMinutes = Math.floor(
parseInt(process.env.RESET_EXPIRY_SECONDS ?? "3600", 10) / 60, parseInt(process.env.RESET_EXPIRY_SECONDS ?? "3600", 10) / 60,
); );
@@ -137,5 +137,3 @@ export const auth = betterAuth({
// }, // },
}, },
}); });
type Session = typeof auth.$Infer.Session;

View File

@@ -1,4 +1,3 @@
import os from "node:os";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { promisify } from "node:util"; import { promisify } from "node:util";
@@ -6,7 +5,6 @@ import type { Transporter } from "nodemailer";
import nodemailer from "nodemailer"; import nodemailer from "nodemailer";
import type Mail from "nodemailer/lib/mailer/index.js"; import type Mail from "nodemailer/lib/mailer/index.js";
import type { Address } from "nodemailer/lib/mailer/index.js"; import type { Address } from "nodemailer/lib/mailer/index.js";
import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
import hbs from "nodemailer-express-handlebars"; import hbs from "nodemailer-express-handlebars";
import { returnFunc } from "./returnHelper.utils.js"; import { returnFunc } from "./returnHelper.utils.js";
import { tryCatch } from "./trycatch.utils.js"; import { tryCatch } from "./trycatch.utils.js";
@@ -24,62 +22,36 @@ interface EmailData {
} }
export const sendEmail = async (data: EmailData) => { export const sendEmail = async (data: EmailData) => {
let transporter: Transporter; const fromEmail: string = `DoNotReply@mail.alpla.com`;
let fromEmail: string | Address; const transporter: Transporter = nodemailer.createTransport({
host: "smtp.azurecomm.net",
port: 587,
//rejectUnauthorized: false,
tls: {
minVersion: "TLSv1.2",
},
auth: {
user: "donotreply@mail.alpla.com",
pass: process.env.SMTP_PASSWORD,
},
debug: true,
});
if (os.hostname().includes("OLP")) { // creating the handlbar options
transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD, // The 16-character App Password
},
//debug: true,
});
// update the from email
fromEmail = process.env.EMAIL_USER as string;
// update the from email
fromEmail = `donotreply@alpla.com`;
} else {
//create the servers smtp config
let host = `${os.hostname().replace("VMS006", "")}-smtp.alpla.net`;
if (os.hostname().includes("VMS036")) {
host = "USMCD1-smtp.alpla.net";
}
transporter = nodemailer.createTransport({
host: host,
port: 25,
rejectUnauthorized: false,
//secure: false,
// auth: {
// user: "alplaprod",
// pass: "obelix",
// },
debug: true,
} as SMTPTransport.Options);
// update the from email
fromEmail = `donotreply@alpla.com`;
}
// create the handlebars view
const viewPath = path.resolve( const viewPath = path.resolve(
path.dirname(fileURLToPath(import.meta.url)), path.dirname(fileURLToPath(import.meta.url)),
"./mailViews", "../utils/mailViews/",
); );
const handlebarOptions = { const handlebarOptions = {
viewEngine: { viewEngine: {
extname: ".hbs", extname: ".hbs",
defaultLayout: "", //layoutsDir: path.resolve(viewPath, "layouts"), // Path to layouts directory
defaultLayout: "", // Specify the default layout
partialsDir: viewPath, partialsDir: viewPath,
}, },
viewPath: viewPath, viewPath: viewPath,
extName: ".hbs", extName: ".hbs", // File extension for Handlebars templates
}; };
transporter.use("compile", hbs(handlebarOptions)); transporter.use("compile", hbs(handlebarOptions));
@@ -89,7 +61,7 @@ export const sendEmail = async (data: EmailData) => {
to: data.email, to: data.email,
subject: data.subject, subject: data.subject,
//text: "You will have a reset token here and only have 30min to click the link before it expires.", //text: "You will have a reset token here and only have 30min to click the link before it expires.",
//html: emailTemplate("Blake's Test", "This is an example with css"), //html: emailTemplate("BlakesTest", "This is an example with css"),
template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs') template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs')
context: data.context, context: data.context,
}; };

View File

@@ -69,7 +69,7 @@ export default function Header() {
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem> <DropdownMenuItem>
<Link to="/">Profile</Link> <Link to="/user/profile">Profile</Link>
</DropdownMenuItem> </DropdownMenuItem>
{/* <DropdownMenuItem>Billing</DropdownMenuItem> {/* <DropdownMenuItem>Billing</DropdownMenuItem>

View File

@@ -0,0 +1,10 @@
import { cn } from "@/lib/utils"
import { Loader2Icon } from "lucide-react"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<Loader2Icon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
)
}
export { Spinner }

View File

@@ -20,7 +20,7 @@ export const InputField = ({ label, inputType, required }: InputFieldProps) => {
const field = useFieldContext<any>(); const field = useFieldContext<any>();
return ( return (
<div className="grid gap-3"> <div className="grid gap-3 mt-2">
<Label htmlFor={field.name}>{label}</Label> <Label htmlFor={field.name}>{label}</Label>
<Input <Input
id={field.name} id={field.name}

View File

@@ -1,6 +1,7 @@
import { useStore } from "@tanstack/react-form"; import { useStore } from "@tanstack/react-form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useFormContext } from "."; import { useFormContext } from ".";
import { Spinner } from "@/components/ui/spinner";
type SubmitButtonProps = { type SubmitButtonProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -17,7 +18,9 @@ export const SubmitButton = ({ children }: SubmitButtonProps) => {
return ( return (
<div className=""> <div className="">
<Button type="submit" disabled={isSubmitting}> <Button type="submit" disabled={isSubmitting}>
{children} {isSubmitting ? <><Spinner data-icon="inline-start" /> Submitting </> : <>{children}</>
}
</Button> </Button>
</div> </div>
); );

View File

@@ -15,6 +15,7 @@ import { Route as AdminLogsRouteImport } from './routes/admin/logs'
import { Route as authLoginRouteImport } from './routes/(auth)/login' import { Route as authLoginRouteImport } from './routes/(auth)/login'
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup' import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword' import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile'
const AboutRoute = AboutRouteImport.update({ const AboutRoute = AboutRouteImport.update({
id: '/about', id: '/about',
@@ -46,12 +47,18 @@ const authUserResetpasswordRoute = authUserResetpasswordRouteImport.update({
path: '/user/resetpassword', path: '/user/resetpassword',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const authUserProfileRoute = authUserProfileRouteImport.update({
id: '/(auth)/user/profile',
path: '/user/profile',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/login': typeof authLoginRoute '/login': typeof authLoginRoute
'/admin/logs': typeof AdminLogsRoute '/admin/logs': typeof AdminLogsRoute
'/user/profile': typeof authUserProfileRoute
'/user/resetpassword': typeof authUserResetpasswordRoute '/user/resetpassword': typeof authUserResetpasswordRoute
'/user/signup': typeof authUserSignupRoute '/user/signup': typeof authUserSignupRoute
} }
@@ -60,6 +67,7 @@ export interface FileRoutesByTo {
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/login': typeof authLoginRoute '/login': typeof authLoginRoute
'/admin/logs': typeof AdminLogsRoute '/admin/logs': typeof AdminLogsRoute
'/user/profile': typeof authUserProfileRoute
'/user/resetpassword': typeof authUserResetpasswordRoute '/user/resetpassword': typeof authUserResetpasswordRoute
'/user/signup': typeof authUserSignupRoute '/user/signup': typeof authUserSignupRoute
} }
@@ -69,6 +77,7 @@ export interface FileRoutesById {
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/(auth)/login': typeof authLoginRoute '/(auth)/login': typeof authLoginRoute
'/admin/logs': typeof AdminLogsRoute '/admin/logs': typeof AdminLogsRoute
'/(auth)/user/profile': typeof authUserProfileRoute
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute '/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
'/(auth)/user/signup': typeof authUserSignupRoute '/(auth)/user/signup': typeof authUserSignupRoute
} }
@@ -79,6 +88,7 @@ export interface FileRouteTypes {
| '/about' | '/about'
| '/login' | '/login'
| '/admin/logs' | '/admin/logs'
| '/user/profile'
| '/user/resetpassword' | '/user/resetpassword'
| '/user/signup' | '/user/signup'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
@@ -87,6 +97,7 @@ export interface FileRouteTypes {
| '/about' | '/about'
| '/login' | '/login'
| '/admin/logs' | '/admin/logs'
| '/user/profile'
| '/user/resetpassword' | '/user/resetpassword'
| '/user/signup' | '/user/signup'
id: id:
@@ -95,6 +106,7 @@ export interface FileRouteTypes {
| '/about' | '/about'
| '/(auth)/login' | '/(auth)/login'
| '/admin/logs' | '/admin/logs'
| '/(auth)/user/profile'
| '/(auth)/user/resetpassword' | '/(auth)/user/resetpassword'
| '/(auth)/user/signup' | '/(auth)/user/signup'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
@@ -104,6 +116,7 @@ export interface RootRouteChildren {
AboutRoute: typeof AboutRoute AboutRoute: typeof AboutRoute
authLoginRoute: typeof authLoginRoute authLoginRoute: typeof authLoginRoute
AdminLogsRoute: typeof AdminLogsRoute AdminLogsRoute: typeof AdminLogsRoute
authUserProfileRoute: typeof authUserProfileRoute
authUserResetpasswordRoute: typeof authUserResetpasswordRoute authUserResetpasswordRoute: typeof authUserResetpasswordRoute
authUserSignupRoute: typeof authUserSignupRoute authUserSignupRoute: typeof authUserSignupRoute
} }
@@ -152,6 +165,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof authUserResetpasswordRouteImport preLoaderRoute: typeof authUserResetpasswordRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/(auth)/user/profile': {
id: '/(auth)/user/profile'
path: '/user/profile'
fullPath: '/user/profile'
preLoaderRoute: typeof authUserProfileRouteImport
parentRoute: typeof rootRouteImport
}
} }
} }
@@ -160,6 +180,7 @@ const rootRouteChildren: RootRouteChildren = {
AboutRoute: AboutRoute, AboutRoute: AboutRoute,
authLoginRoute: authLoginRoute, authLoginRoute: authLoginRoute,
AdminLogsRoute: AdminLogsRoute, AdminLogsRoute: AdminLogsRoute,
authUserProfileRoute: authUserProfileRoute,
authUserResetpasswordRoute: authUserResetpasswordRoute, authUserResetpasswordRoute: authUserResetpasswordRoute,
authUserSignupRoute: authUserSignupRoute, authUserSignupRoute: authUserSignupRoute,
} }

View File

@@ -0,0 +1,90 @@
import { useRouter } from "@tanstack/react-router";
import { toast } from "sonner";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
export default function ChangePassword() {
const router = useRouter();
const form = useAppForm({
defaultValues: {
currentPassword: "",
newPassword: "",
confirmPassword: "",
},
onSubmit: async ({ value }) => {
if (value.newPassword !== value.confirmPassword) {
toast.error("Passwords do not match");
return;
}
const { data, error } = await authClient.changePassword({
newPassword: value.newPassword,
currentPassword: value.currentPassword,
revokeOtherSessions: true,
});
if (data) {
toast.success("Password has been updated");
form.reset();
router.invalidate();
//navigate({ to: "/login" });
}
if (error) {
toast.success(error.message);
}
},
});
return (
<div>
<Card className="p-6 w-96">
<CardHeader>
<CardTitle>Change password</CardTitle>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.AppField name="currentPassword">
{(field) => (
<field.InputPasswordField
label="Enter your current password"
required={true}
/>
)}
</form.AppField>
<form.AppField name="newPassword">
{(field) => (
<field.InputPasswordField
label="New password"
required={true}
/>
)}
</form.AppField>
<form.AppField name="confirmPassword">
{(field) => (
<field.InputPasswordField
label="Re-enter your password"
required={true}
/>
)}
</form.AppField>
<div className="flex justify-end mt-6">
<form.AppForm>
<form.SubmitButton>Update Profile</form.SubmitButton>
</form.AppForm>
</div>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@@ -4,7 +4,6 @@ import {
Card, Card,
CardContent, CardContent,
CardDescription, CardDescription,
CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";

View File

@@ -0,0 +1,76 @@
import { Link } from "@tanstack/react-router";
import { toast } from "sonner";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
export default function ResetForm() {
const form = useAppForm({
defaultValues: {
email: "",
},
onSubmit: async ({ value }) => {
const { data, error } = await authClient.requestPasswordReset({
email: value.email,
redirectTo: `${window.location.origin}`,
});
if (data) {
toast.success(data.message);
}
if (error) {
toast.error(error.message);
}
},
});
return (
<div className="flex justify-center mt-2">
<Card className="p-6 w-96">
<CardHeader>
<CardTitle>Reset your password</CardTitle>
<CardDescription>
Enter your email address and well send you a reset link
</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.AppField name="email">
{(field) => (
<field.InputField label="Email" inputType="email" required />
)}
</form.AppField>
<div className="flex justify-end mt-6">
<form.AppForm>
<form.SubmitButton>Send Reset Link</form.SubmitButton>
</form.AppForm>
</div>
</form>
<div className="mt-6 text-center text-sm text-gray-600">
Remembered your password?{" "}
<Link
to="/login"
className="text-primary underline underline-offset-4 hover:text-primary/80"
>
Back to login
</Link>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,94 @@
import { Link } from "@tanstack/react-router";
import React from "react";
import { toast } from "sonner";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
export default function ResetPassword({ token }: { token: string }) {
const form = useAppForm({
defaultValues: {
password: "",
confirmPassword: "",
},
onSubmit: async ({ value }) => {
if (!token) {
toast.error("No token was included");
return;
}
const { data, error } = await authClient.resetPassword({
newPassword: value.password,
token,
});
if (data) {
toast.success("Password has been reset");
}
if (error) {
toast.error(error.message);
}
},
});
return (
<div className="flex justify-center mt-2">
<Card className="p-6 w-96">
<CardHeader>
<CardTitle>Reset your password</CardTitle>
<CardDescription>
Enter your email address and well send you a reset link
</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.AppField name="password">
{(field) => (
<field.InputPasswordField
label="New Password"
required={true}
/>
)}
</form.AppField>
<form.AppField name="confirmPassword">
{(field) => (
<field.InputPasswordField
label="Confirm Password"
required={true}
/>
)}
</form.AppField>
<div className="flex justify-end mt-6">
<form.AppForm>
<form.SubmitButton>Send Reset Link</form.SubmitButton>
</form.AppForm>
</div>
</form>
<div className="mt-6 text-center text-sm text-gray-600">
Remembered your password?{" "}
<Link
to="/login"
className="text-primary underline underline-offset-4 hover:text-primary/80"
>
Back to login
</Link>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,99 @@
import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
import { toast } from "sonner";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { authClient, useSession } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
import ChangePassword from "./-components/ChangePassword";
export const Route = createFileRoute("/(auth)/user/profile")({
beforeLoad: async () => {
const result = await authClient.getSession({
query: { disableCookieCache: true }, // force DB/Server lookup
});
//console.log("session check:", result.data);
if (!result.data) {
throw redirect({
to: "/login",
search: {
redirect: location.pathname + location.search,
},
});
}
},
component: RouteComponent,
});
function RouteComponent() {
const { data: session } = useSession();
const router = useRouter();
const form = useAppForm({
defaultValues: {
name: session?.user.name,
},
onSubmit: async ({ value }) => {
const { data, error } = await authClient.updateUser({
name: value.name,
});
if (data) {
toast.success("Profile has been updated");
form.reset();
//navigate({ to: "/login" });
}
if (error) {
toast.success(error.message);
}
},
});
return (
<div className="flex justify-center mt-2 gap-2">
<div>
<Card className="p-6 w-96">
<CardHeader>
<CardTitle>Profile</CardTitle>
<CardDescription>
Change your profile and password below
</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.AppField name="name">
{(field) => (
<field.InputField
label="Name"
inputType="string"
required={true}
/>
)}
</form.AppField>
<div className="flex justify-end mt-6">
<form.AppForm>
<form.SubmitButton>Update Profile</form.SubmitButton>
</form.AppForm>
</div>
</form>
</CardContent>
</Card>
</div>
<div>
<ChangePassword />
</div>
</div>
);
}

View File

@@ -1,9 +1,31 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute, Link } from "@tanstack/react-router";
import { toast } from "sonner";
import z from "zod";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
import ResetForm from "./-components/ResetForm";
import ResetPassword from "./-components/ResetPassword";
export const Route = createFileRoute('/(auth)/user/resetpassword')({ export const Route = createFileRoute("/(auth)/user/resetpassword")({
component: RouteComponent, validateSearch: z.object({
}) token: z.string().optional(),
}),
component: RouteComponent,
});
function RouteComponent() { function RouteComponent() {
return <div>Hello "/(auth)/user/resetpassword"!</div> const { token } = Route.useSearch();
return (
<div className="">
{token ? <ResetPassword token={token} /> : <ResetForm />}
</div>
);
} }

View File

@@ -1,9 +1,128 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { toast } from "sonner";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
export const Route = createFileRoute('/(auth)/user/signup')({ export const Route = createFileRoute("/(auth)/user/signup")({
component: RouteComponent, component: RouteComponent,
}) });
function RouteComponent() { function RouteComponent() {
return <div>Hello "/(auth)/user/signup"!</div> const navigate = useNavigate();
const form = useAppForm({
defaultValues: {
name: "",
email: "",
password: "",
confirmPassword: "",
},
onSubmit: async ({ value }) => {
if (value.password !== value.confirmPassword) {
toast.error("Passwords do not match");
return;
}
const { data, error } = await authClient.signUp.email({
name: value.name,
email: value.email,
password: value.password,
callbackURL: `${window.location.origin}/lst/app`,
});
if (data) {
toast.success(`Welcome ${value.name}, to lst.`);
navigate({ to: "/" });
}
if (error) {
toast.error(error.message);
}
},
});
return (
<div className="flex justify-center mt-2">
<Card className="p-6 w-96">
<CardHeader>
<CardTitle>Create an account</CardTitle>
<CardDescription>Fill in your details to get started</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
{/* Username */}
<form.AppField name="name">
{(field) => (
<field.InputField
label="Name"
inputType="text"
required={true}
/>
)}
</form.AppField>
{/* Email */}
<form.AppField name="email">
{(field) => (
<field.InputField
label="Email address"
inputType="email"
required={true}
/>
)}
</form.AppField>
{/* Password */}
<form.AppField name="password">
{(field) => (
<field.InputField
label="Password"
inputType="password"
required={true}
/>
)}
</form.AppField>
{/* Confirm Password */}
<form.AppField name="confirmPassword">
{(field) => (
<field.InputField
label="Confirm Password"
inputType="password"
required={true}
/>
)}
</form.AppField>
<div className="flex justify-end mt-6">
<form.AppForm>
<form.SubmitButton>Sign Up</form.SubmitButton>
</form.AppForm>
</div>
</form>
<div className="mt-6 text-center text-sm text-gray-600">
Already have an account?{" "}
<Link
to={"/login"}
className="text-primary underline underline-offset-4 hover:text-primary/80"
>
Log in
</Link>
</div>
</CardContent>
</Card>
</div>
);
} }

View File

@@ -1,8 +1,8 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import z from "zod"; import z from "zod";
import { Button } from "@/components/ui/button";
import { useSession } from "../lib/auth-client"; import { authClient, useSession } from "../lib/auth-client";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
validateSearch: z.object({ validateSearch: z.object({
@@ -13,11 +13,12 @@ export const Route = createFileRoute("/")({
}); });
function Index() { function Index() {
const { data: session, isPending } = useSession(); const { isPending } = useSession();
if (isPending) if (isPending)
return <div className="flex justify-center mt-10">Loading...</div>; return <div className="flex justify-center mt-10">Loading...</div>;
// if (!session) return <button>Sign In</button> // if (!session) return <button>Sign In</button>
return ( return (
<div className="flex justify-center mt-10"> <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>

View File

@@ -27,7 +27,8 @@
//"allowImportingTsExtensions": true, //"allowImportingTsExtensions": true,
"noEmit": false "noEmit": false
}, },
"include": ["backend/**/*", "types"], "include": ["backend/**/*"
],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"frontend", "frontend",