refactor(create-user): tanstack mutation

This commit is contained in:
Nicolas Meienberger 2026-01-21 22:09:52 +01:00
parent 82e7382459
commit d0acfc74b6
2 changed files with 40 additions and 28 deletions

View file

@ -1,4 +1,5 @@
import { arktypeResolver } from "@hookform/resolvers/arktype"; import { arktypeResolver } from "@hookform/resolvers/arktype";
import { useMutation } from "@tanstack/react-query";
import { type } from "arktype"; import { type } from "arktype";
import { Plus, UserPlus } from "lucide-react"; import { Plus, UserPlus } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
@ -35,7 +36,6 @@ interface CreateUserDialogProps {
export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) { export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const form = useForm<CreateUserFormValues>({ const form = useForm<CreateUserFormValues>({
resolver: arktypeResolver(createUserSchema), resolver: arktypeResolver(createUserSchema),
@ -48,10 +48,8 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
}, },
}); });
const onSubmit = async (values: CreateUserFormValues) => { const createUserMutation = useMutation({
setIsLoading(true); mutationFn: async (values: CreateUserFormValues) => {
try {
const { error } = await authClient.admin.createUser({ const { error } = await authClient.admin.createUser({
email: values.email, email: values.email,
password: values.password, password: values.password,
@ -61,20 +59,19 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
}); });
if (error) { if (error) {
toast.error("Failed to create user", { description: error.message }); throw new Error(error.message);
} else {
toast.success("User created successfully");
setIsOpen(false);
form.reset();
onUserCreated?.();
} }
} catch (err) { },
console.error(err); onSuccess: () => {
toast.error("An unexpected error occurred"); toast.success("User created successfully");
} finally { setIsOpen(false);
setIsLoading(false); form.reset();
} onUserCreated?.();
}; },
onError: (error: Error) => {
toast.error("Failed to create user", { description: error.message });
},
});
return ( return (
<Dialog open={isOpen} onOpenChange={setIsOpen}> <Dialog open={isOpen} onOpenChange={setIsOpen}>
@ -86,7 +83,7 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-106.25"> <DialogContent className="sm:max-w-106.25">
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}> <form onSubmit={form.handleSubmit((values) => createUserMutation.mutate(values))} className="space-y-6">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
<UserPlus className="h-5 w-5" /> <UserPlus className="h-5 w-5" />
@ -102,7 +99,7 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
<FormItem> <FormItem>
<FormLabel>Full Name</FormLabel> <FormLabel>Full Name</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="John Doe" disabled={isLoading} /> <Input {...field} placeholder="John Doe" disabled={createUserMutation.isPending} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -115,7 +112,7 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="johndoe" disabled={isLoading} /> <Input {...field} placeholder="johndoe" disabled={createUserMutation.isPending} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -128,7 +125,12 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>Email</FormLabel>
<FormControl> <FormControl>
<Input {...field} type="email" placeholder="john@example.com" disabled={isLoading} /> <Input
{...field}
type="email"
placeholder="john@example.com"
disabled={createUserMutation.isPending}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -141,7 +143,12 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
<FormItem> <FormItem>
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<FormControl> <FormControl>
<Input {...field} type="password" placeholder="Min. 8 characters" disabled={isLoading} /> <Input
{...field}
type="password"
placeholder="Min. 8 characters"
disabled={createUserMutation.isPending}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -153,7 +160,7 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Role</FormLabel> <FormLabel>Role</FormLabel>
<Select onValueChange={field.onChange} value={field.value} disabled={isLoading}> <Select onValueChange={field.onChange} value={field.value} disabled={createUserMutation.isPending}>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a role" /> <SelectValue placeholder="Select a role" />
@ -170,10 +177,15 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
/> />
</div> </div>
<DialogFooter> <DialogFooter>
<Button type="button" variant="outline" onClick={() => setIsOpen(false)} disabled={isLoading}> <Button
type="button"
variant="outline"
onClick={() => setIsOpen(false)}
disabled={createUserMutation.isPending}
>
Cancel Cancel
</Button> </Button>
<Button type="submit" loading={isLoading}> <Button type="submit" loading={createUserMutation.isPending}>
<UserPlus className="mr-2 h-4 w-4" /> <UserPlus className="mr-2 h-4 w-4" />
Create User Create User
</Button> </Button>

View file

@ -7,7 +7,7 @@ import {
repositoriesTable, repositoriesTable,
backupSchedulesTable, backupSchedulesTable,
} from "../../db/schema"; } from "../../db/schema";
import { eq, sql, and, count, inArray } from "drizzle-orm"; import { eq, ne, and, count, inArray } from "drizzle-orm";
import type { UserDeletionImpactDto } from "./auth.dto"; import type { UserDeletionImpactDto } from "./auth.dto";
export class AuthService { export class AuthService {
@ -37,7 +37,7 @@ export class AuthService {
and( and(
eq(member.organizationId, membership.organizationId), eq(member.organizationId, membership.organizationId),
eq(member.role, "owner"), eq(member.role, "owner"),
sql`user_id != ${userId}`, ne(member.userId, userId),
), ),
); );