mirror of
https://github.com/karakeep-app/karakeep.git
synced 2026-10-03 14:56:44 +02:00
290 lines
9.1 KiB
TypeScript
290 lines
9.1 KiB
TypeScript
"use client";
|
|
|
|
import type { TurnstileInstance } from "@marsidev/react-turnstile";
|
|
import { useRef, useState } from "react";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { ActionButton } from "@/components/ui/action-button";
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@/components/ui/form";
|
|
import { Input } from "@/components/ui/input";
|
|
import { useClientConfig } from "@/lib/clientConfig";
|
|
import { api } from "@/lib/trpc";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { Turnstile } from "@marsidev/react-turnstile";
|
|
import { TRPCClientError } from "@trpc/client";
|
|
import { AlertCircle, UserX } from "lucide-react";
|
|
import { signIn } from "next-auth/react";
|
|
import { useForm } from "react-hook-form";
|
|
import { z } from "zod";
|
|
|
|
import { zSignUpSchema } from "@karakeep/shared/types/users";
|
|
|
|
const VERIFY_EMAIL_ERROR = "Please verify your email address before signing in";
|
|
|
|
export default function SignUpForm() {
|
|
const form = useForm<z.infer<typeof zSignUpSchema>>({
|
|
resolver: zodResolver(zSignUpSchema),
|
|
defaultValues: {
|
|
email: "",
|
|
name: "",
|
|
password: "",
|
|
confirmPassword: "",
|
|
turnstileToken: "",
|
|
},
|
|
});
|
|
const [errorMessage, setErrorMessage] = useState("");
|
|
const router = useRouter();
|
|
const clientConfig = useClientConfig();
|
|
const turnstileSiteKey = clientConfig.turnstile?.siteKey;
|
|
const turnstileRef = useRef<TurnstileInstance>(null);
|
|
|
|
const createUserMutation = api.users.create.useMutation();
|
|
|
|
if (
|
|
clientConfig.auth.disableSignups ||
|
|
clientConfig.auth.disablePasswordAuth
|
|
) {
|
|
return (
|
|
<Card className="w-full">
|
|
<CardHeader className="text-center">
|
|
<CardTitle className="text-2xl font-bold">
|
|
Sign Up Unavailable
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Account registration is currently disabled
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="space-y-4">
|
|
<Alert>
|
|
<UserX className="h-4 w-4" />
|
|
<AlertDescription>
|
|
Signups are currently disabled. Please contact an administrator
|
|
for access.
|
|
</AlertDescription>
|
|
</Alert>
|
|
<Button asChild className="w-full">
|
|
<Link href="/signin">Back to Sign In</Link>
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card className="w-full">
|
|
<CardHeader className="text-center">
|
|
<CardTitle className="text-2xl font-bold">
|
|
Create Your Account
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Join Karakeep to start organizing your bookmarks
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<Form {...form}>
|
|
<form
|
|
onSubmit={form.handleSubmit(async (value) => {
|
|
if (turnstileSiteKey && !value.turnstileToken) {
|
|
form.setError("turnstileToken", {
|
|
type: "manual",
|
|
message: "Please complete the verification challenge",
|
|
});
|
|
return;
|
|
}
|
|
form.clearErrors("turnstileToken");
|
|
try {
|
|
await createUserMutation.mutateAsync(value);
|
|
} catch (e) {
|
|
if (e instanceof TRPCClientError) {
|
|
setErrorMessage(e.message);
|
|
}
|
|
// Reset turnstile widget on error to get a new token
|
|
if (turnstileSiteKey) {
|
|
turnstileRef.current?.reset();
|
|
form.setValue("turnstileToken", "");
|
|
}
|
|
return;
|
|
}
|
|
const resp = await signIn("credentials", {
|
|
redirect: false,
|
|
email: value.email.trim(),
|
|
password: value.password,
|
|
});
|
|
if (!resp || !resp.ok || resp.error) {
|
|
if (resp?.error === VERIFY_EMAIL_ERROR) {
|
|
router.replace(
|
|
`/check-email?email=${encodeURIComponent(value.email.trim())}`,
|
|
);
|
|
} else {
|
|
setErrorMessage(
|
|
resp?.error ?? "Hit an unexpected error while signing in",
|
|
);
|
|
}
|
|
// Reset turnstile widget on error to get a new token
|
|
if (turnstileSiteKey) {
|
|
turnstileRef.current?.reset();
|
|
form.setValue("turnstileToken", "");
|
|
}
|
|
return;
|
|
}
|
|
router.replace("/");
|
|
})}
|
|
className="space-y-4"
|
|
>
|
|
{errorMessage && (
|
|
<Alert variant="destructive">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<AlertDescription>{errorMessage}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Full Name</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="text"
|
|
placeholder="Enter your full name"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="email"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Email</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="email"
|
|
placeholder="Enter your email"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Password</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="password"
|
|
placeholder="Create a password"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="confirmPassword"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Confirm Password</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="password"
|
|
placeholder="Confirm your password"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
{turnstileSiteKey && (
|
|
<FormField
|
|
control={form.control}
|
|
name="turnstileToken"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Verification</FormLabel>
|
|
<FormControl>
|
|
<Turnstile
|
|
ref={turnstileRef}
|
|
siteKey={turnstileSiteKey}
|
|
onSuccess={(token) => {
|
|
field.onChange(token);
|
|
form.clearErrors("turnstileToken");
|
|
}}
|
|
onExpire={() => field.onChange("")}
|
|
onError={() => {
|
|
field.onChange("");
|
|
form.setError("turnstileToken", {
|
|
type: "manual",
|
|
message:
|
|
"Verification failed, please reload the challenge",
|
|
});
|
|
}}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
)}
|
|
|
|
<ActionButton
|
|
type="submit"
|
|
loading={
|
|
form.formState.isSubmitting || createUserMutation.isPending
|
|
}
|
|
className="w-full"
|
|
>
|
|
Create Account
|
|
</ActionButton>
|
|
</form>
|
|
</Form>
|
|
|
|
<div className="text-center">
|
|
<p className="text-sm text-gray-600">
|
|
Already have an account?{" "}
|
|
<Link
|
|
href="/signin"
|
|
className="font-medium text-blue-600 hover:text-blue-500"
|
|
>
|
|
Sign in
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|