mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add solid
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { createForm } from "@tanstack/solid-form";
|
||||
import { useNavigate } from "@tanstack/solid-router";
|
||||
import { z } from "zod";
|
||||
import { For } from "solid-js";
|
||||
|
||||
export default function SignInForm({
|
||||
onSwitchToSignUp,
|
||||
}: {
|
||||
onSwitchToSignUp: () => void;
|
||||
}) {
|
||||
const navigate = useNavigate({
|
||||
from: "/",
|
||||
});
|
||||
|
||||
const form = createForm(() => ({
|
||||
defaultValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
await authClient.signIn.email(
|
||||
{
|
||||
email: value.email,
|
||||
password: value.password,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
navigate({
|
||||
to: "/dashboard",
|
||||
});
|
||||
console.log("Sign in successful");
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error.error.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
validators: {
|
||||
onSubmit: z.object({
|
||||
email: z.string().email("Invalid email address"),
|
||||
password: z.string().min(8, "Password must be at least 8 characters"),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div class="mx-auto w-full mt-10 max-w-md p-6">
|
||||
<h1 class="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void form.handleSubmit();
|
||||
}}
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<form.Field name="email">
|
||||
{(field) => (
|
||||
<div class="space-y-2">
|
||||
<label for={field().name}>Email</label>
|
||||
<input
|
||||
id={field().name}
|
||||
name={field().name}
|
||||
type="email"
|
||||
value={field().state.value}
|
||||
onBlur={field().handleBlur}
|
||||
onInput={(e) => field().handleChange(e.currentTarget.value)} // Use onInput and currentTarget
|
||||
class="w-full rounded border p-2" // Example basic styling
|
||||
/>
|
||||
<For each={field().state.meta.errors}>
|
||||
{(error) => (
|
||||
<p class="text-sm text-red-600">{error?.message}</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form.Field name="password">
|
||||
{(field) => (
|
||||
<div class="space-y-2">
|
||||
<label for={field().name}>Password</label>
|
||||
<input
|
||||
id={field().name}
|
||||
name={field().name}
|
||||
type="password"
|
||||
value={field().state.value}
|
||||
onBlur={field().handleBlur}
|
||||
onInput={(e) => field().handleChange(e.currentTarget.value)}
|
||||
class="w-full rounded border p-2"
|
||||
/>
|
||||
<For each={field().state.meta.errors}>
|
||||
{(error) => (
|
||||
<p class="text-sm text-red-600">{error?.message}</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
<form.Subscribe>
|
||||
{(state) => (
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full rounded bg-indigo-600 p-2 text-white hover:bg-indigo-700 disabled:opacity-50"
|
||||
disabled={!state().canSubmit || state().isSubmitting}
|
||||
>
|
||||
{state().isSubmitting ? "Submitting..." : "Sign In"}
|
||||
</button>
|
||||
)}
|
||||
</form.Subscribe>
|
||||
</form>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSwitchToSignUp}
|
||||
class="text-sm text-indigo-600 hover:text-indigo-800 hover:underline" // Example basic styling
|
||||
>
|
||||
Need an account? Sign Up
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { createForm } from "@tanstack/solid-form";
|
||||
import { useNavigate } from "@tanstack/solid-router";
|
||||
import { z } from "zod";
|
||||
import { For } from "solid-js";
|
||||
|
||||
export default function SignUpForm({
|
||||
onSwitchToSignIn,
|
||||
}: {
|
||||
onSwitchToSignIn: () => void;
|
||||
}) {
|
||||
const navigate = useNavigate({
|
||||
from: "/",
|
||||
});
|
||||
|
||||
const form = createForm(() => ({
|
||||
defaultValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
name: "",
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
await authClient.signUp.email(
|
||||
{
|
||||
email: value.email,
|
||||
password: value.password,
|
||||
name: value.name,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
navigate({
|
||||
to: "/dashboard",
|
||||
});
|
||||
console.log("Sign up successful");
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error.error.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
validators: {
|
||||
onSubmit: z.object({
|
||||
name: z.string().min(2, "Name must be at least 2 characters"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
password: z.string().min(8, "Password must be at least 8 characters"),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div class="mx-auto w-full mt-10 max-w-md p-6">
|
||||
<h1 class="mb-6 text-center text-3xl font-bold">Create Account</h1>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void form.handleSubmit();
|
||||
}}
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<form.Field name="name">
|
||||
{(field) => (
|
||||
<div class="space-y-2">
|
||||
<label for={field().name}>Name</label>
|
||||
<input
|
||||
id={field().name}
|
||||
name={field().name}
|
||||
value={field().state.value}
|
||||
onBlur={field().handleBlur}
|
||||
onInput={(e) => field().handleChange(e.currentTarget.value)}
|
||||
class="w-full rounded border p-2"
|
||||
/>
|
||||
<For each={field().state.meta.errors}>
|
||||
{(error) => (
|
||||
<p class="text-sm text-red-600">{error?.message}</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form.Field name="email">
|
||||
{(field) => (
|
||||
<div class="space-y-2">
|
||||
<label for={field().name}>Email</label>
|
||||
<input
|
||||
id={field().name}
|
||||
name={field().name}
|
||||
type="email"
|
||||
value={field().state.value}
|
||||
onBlur={field().handleBlur}
|
||||
onInput={(e) => field().handleChange(e.currentTarget.value)}
|
||||
class="w-full rounded border p-2"
|
||||
/>
|
||||
<For each={field().state.meta.errors}>
|
||||
{(error) => (
|
||||
<p class="text-sm text-red-600">{error?.message}</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form.Field name="password">
|
||||
{(field) => (
|
||||
<div class="space-y-2">
|
||||
<label for={field().name}>Password</label>
|
||||
<input
|
||||
id={field().name}
|
||||
name={field().name}
|
||||
type="password"
|
||||
value={field().state.value}
|
||||
onBlur={field().handleBlur}
|
||||
onInput={(e) => field().handleChange(e.currentTarget.value)}
|
||||
class="w-full rounded border p-2"
|
||||
/>
|
||||
<For each={field().state.meta.errors}>
|
||||
{(error) => (
|
||||
<p class="text-sm text-red-600">{error?.message}</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
<form.Subscribe>
|
||||
{(state) => (
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full rounded bg-indigo-600 p-2 text-white hover:bg-indigo-700 disabled:opacity-50"
|
||||
disabled={!state().canSubmit || state().isSubmitting}
|
||||
>
|
||||
{state().isSubmitting ? "Submitting..." : "Sign Up"}
|
||||
</button>
|
||||
)}
|
||||
</form.Subscribe>
|
||||
</form>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSwitchToSignIn}
|
||||
class="text-sm text-indigo-600 hover:text-indigo-800 hover:underline"
|
||||
>
|
||||
Already have an account? Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useNavigate, Link } from "@tanstack/solid-router";
|
||||
import { createSignal, Show } from "solid-js";
|
||||
|
||||
export default function UserMenu() {
|
||||
const navigate = useNavigate();
|
||||
const session = authClient.useSession();
|
||||
const [isMenuOpen, setIsMenuOpen] = createSignal(false);
|
||||
|
||||
return (
|
||||
<div class="relative inline-block text-left">
|
||||
<Show when={session().isPending}>
|
||||
<div class="h-9 w-24 animate-pulse rounded" />
|
||||
</Show>
|
||||
|
||||
<Show when={!session().isPending && !session().data}>
|
||||
<Link to="/login" class="inline-block border rounded px-4 text-sm">
|
||||
Sign In
|
||||
</Link>
|
||||
</Show>
|
||||
|
||||
<Show when={!session().isPending && session().data}>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-block border rounded px-4 text-sm"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen())}
|
||||
>
|
||||
{session().data?.user.name}
|
||||
</button>
|
||||
|
||||
<Show when={isMenuOpen()}>
|
||||
<div class="absolute right-0 mt-2 w-56 rounded p-1 shadow-sm">
|
||||
<div class="px-4 text-sm">{session().data?.user.email}</div>
|
||||
<button
|
||||
class="mt-1 w-full border rounded px-4 text-center text-sm"
|
||||
onClick={() => {
|
||||
setIsMenuOpen(false);
|
||||
authClient.signOut({
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
navigate({ to: "/" });
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user