feat: add user invite registration (#477)

This commit is contained in:
Meier Lukas
2024-05-12 10:04:20 +02:00
committed by GitHub
parent 82d41abf46
commit 94af21abbf
7 changed files with 359 additions and 5 deletions

View File

@@ -0,0 +1,92 @@
"use client";
import { useRouter } from "next/navigation";
import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { useForm, zodResolver } from "@homarr/form";
import {
showErrorNotification,
showSuccessNotification,
} from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import type { z } from "@homarr/validation";
import { validation } from "@homarr/validation";
interface RegistrationFormProps {
invite: {
id: string;
token: string;
};
}
export const RegistrationForm = ({ invite }: RegistrationFormProps) => {
const t = useScopedI18n("user");
const router = useRouter();
const { mutate, isPending } = clientApi.user.register.useMutation();
const form = useForm<FormType>({
validate: zodResolver(validation.user.registration),
initialValues: {
username: "",
password: "",
confirmPassword: "",
},
validateInputOnBlur: true,
validateInputOnChange: true,
});
const handleSubmit = (values: FormType) => {
mutate(
{
...values,
inviteId: invite.id,
token: invite.token,
},
{
onSuccess() {
showSuccessNotification({
title: t("action.register.notification.success.title"),
message: t("action.register.notification.success.message"),
});
router.push("/auth/login");
},
onError() {
showErrorNotification({
title: t("action.register.notification.error.title"),
message: t("action.register.notification.error.message"),
});
},
},
);
};
return (
<Stack gap="xl">
<form onSubmit={form.onSubmit(handleSubmit)}>
<Stack gap="lg">
<TextInput
label={t("field.username.label")}
autoComplete="off"
{...form.getInputProps("username")}
/>
<PasswordInput
label={t("field.password.label")}
autoComplete="new-password"
{...form.getInputProps("password")}
/>
<PasswordInput
label={t("field.passwordConfirm.label")}
autoComplete="new-password"
{...form.getInputProps("confirmPassword")}
/>
<Button type="submit" fullWidth loading={isPending}>
{t("action.register.label")}
</Button>
</Stack>
</form>
</Stack>
);
};
type FormType = z.infer<typeof validation.user.registration>;

View File

@@ -0,0 +1,72 @@
import { notFound } from "next/navigation";
import { Card, Center, Stack, Text, Title } from "@mantine/core";
import { auth } from "@homarr/auth/next";
import { and, db, eq } from "@homarr/db";
import { invites } from "@homarr/db/schema/sqlite";
import { getScopedI18n } from "@homarr/translation/server";
import { HomarrLogoWithTitle } from "~/components/layout/logo/homarr-logo";
import { RegistrationForm } from "./_registration-form";
interface InviteUsagePageProps {
params: {
id: string;
};
searchParams: {
token: string;
};
}
export default async function InviteUsagePage({
params,
searchParams,
}: InviteUsagePageProps) {
const session = await auth();
if (session) notFound();
const invite = await db.query.invites.findFirst({
where: and(
eq(invites.id, params.id),
eq(invites.token, searchParams.token),
),
columns: {
id: true,
token: true,
expirationDate: true,
},
with: {
creator: {
columns: {
name: true,
},
},
},
});
if (!invite || invite.expirationDate < new Date()) notFound();
const t = await getScopedI18n("user.page.invite");
return (
<Center>
<Stack align="center" mt="xl">
<HomarrLogoWithTitle size="lg" />
<Stack gap={6} align="center">
<Title order={3} fw={400} ta="center">
{t("title")}
</Title>
<Text size="sm" c="gray.5" ta="center">
{t("subtitle")}
</Text>
</Stack>
<Card bg="dark.8" w={64 * 6} maw="90vw">
<RegistrationForm invite={invite} />
</Card>
<Text size="xs" c="gray.5" ta="center">
{t("description", { username: invite.creator.name })}
</Text>
</Stack>
</Center>
);
}

View File

@@ -14,6 +14,10 @@ import { IconAlertTriangle } from "@tabler/icons-react";
import { signIn } from "@homarr/auth/client";
import { useForm, zodResolver } from "@homarr/form";
import {
showErrorNotification,
showSuccessNotification,
} from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import type { z } from "@homarr/validation";
import { validation } from "@homarr/validation";
@@ -44,11 +48,19 @@ export const LoginForm = () => {
throw response?.error;
}
void router.push("/");
showSuccessNotification({
title: t("action.login.notification.success.title"),
message: t("action.login.notification.success.message"),
});
router.push("/");
})
.catch((error: Error | string) => {
setIsLoading(false);
setError(error.toString());
showErrorNotification({
title: t("action.login.notification.error.title"),
message: t("action.login.notification.error.message"),
});
});
};
@@ -65,7 +77,7 @@ export const LoginForm = () => {
{...form.getInputProps("password")}
/>
<Button type="submit" fullWidth loading={isLoading}>
{t("action.login")}
{t("action.login.label")}
</Button>
</Stack>
</form>