chore: implement pr feedback

This commit is contained in:
pit
2023-10-11 12:32:33 +03:00
parent 9e0d281883
commit e02ab7d256
7 changed files with 154 additions and 130 deletions

View File

@ -3,21 +3,23 @@
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Loader } from 'lucide-react'; import { useForm } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Combobox } from '@documenso/ui/primitives/combobox'; import { Combobox } from '@documenso/ui/primitives/combobox';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { FormErrorMessage } from '../../../../../components/form/form-error-message'; import { TUserFormSchema, ZUserFormSchema } from '~/providers/admin-user-profile-update.types';
import {
TUserFormSchema,
ZUserFormSchema,
} from '../../../../../providers/admin-user-profile-update.types';
export default function UserPage({ params }: { params: { id: number } }) { export default function UserPage({ params }: { params: { id: number } }) {
const { toast } = useToast(); const { toast } = useToast();
@ -34,21 +36,11 @@ export default function UserPage({ params }: { params: { id: number } }) {
const user = result.data; const user = result.data;
const roles = user?.roles; const roles = user?.roles ?? [];
let rolesArr: string[] = [];
if (roles) {
rolesArr = Object.values(roles);
}
const { mutateAsync: updateUserMutation } = trpc.admin.updateUser.useMutation(); const { mutateAsync: updateUserMutation } = trpc.admin.updateUser.useMutation();
const { const form = useForm<TUserFormSchema>({
register,
control,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<TUserFormSchema>({
resolver: zodResolver(ZUserFormSchema), resolver: zodResolver(ZUserFormSchema),
values: { values: {
name: user?.name ?? '', name: user?.name ?? '',
@ -85,42 +77,69 @@ export default function UserPage({ params }: { params: { id: number } }) {
return ( return (
<div> <div>
<h2 className="text-4xl font-semibold">Manage {user?.name}'s profile</h2> <h2 className="text-4xl font-semibold">Manage {user?.name}'s profile</h2>
<form className="mt-6 flex w-full flex-col gap-y-4" onSubmit={handleSubmit(onSubmit)}> <Form {...form}>
<div> <form onSubmit={form.handleSubmit(onSubmit)}>
<Label htmlFor="name" className="text-muted-foreground"> <fieldset className="mt-6 flex w-full flex-col gap-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel required className="text-muted-foreground">
Name Name
</Label> </FormLabel>
<Input defaultValue={user?.name ?? ''} type="text" {...register('name')} /> <FormControl>
<FormErrorMessage className="mt-1.5" error={errors.name} /> <Input type="text" {...field} value={field.value ?? ''} />
</div> </FormControl>
<div> <FormMessage />
<Label htmlFor="email" className="text-muted-foreground"> </FormItem>
Email )}
</Label> />
<Input defaultValue={user?.email} type="email" {...register('email')} /> <FormField
<FormErrorMessage className="mt-1.5" error={errors.email} /> control={form.control}
</div> name="email"
<div className="flex flex-col gap-2"> render={({ field }) => (
<Label htmlFor="roles" className="text-muted-foreground"> <FormItem>
User roles <FormLabel required className="text-muted-foreground">
</Label> Email
<Controller </FormLabel>
control={control} <FormControl>
name="roles" <Input type="text" {...field} />
render={({ field: { onChange } }) => ( </FormControl>
<Combobox listValues={rolesArr} onChange={(values: string[]) => onChange(values)} /> <FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="roles"
render={({ field: { onChange } }) => (
<FormItem>
<fieldset className="flex flex-col gap-2">
<FormLabel required className="text-muted-foreground">
Roles
</FormLabel>
<FormControl>
<Combobox
listValues={roles}
onChange={(values: string[]) => onChange(values)}
/>
</FormControl>
<FormMessage />
</fieldset>
</FormItem>
)} )}
/> />
<FormErrorMessage className="mt-1.5" error={errors.roles} />
</div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" disabled={isSubmitting}> <Button type="submit" loading={form.formState.isSubmitting}>
{isSubmitting && <Loader className="mr-2 h-5 w-5 animate-spin" />}
Update user Update user
</Button> </Button>
</div> </div>
</fieldset>
</form> </form>
</Form>
</div> </div>
); );
} }

View File

@ -15,7 +15,7 @@ export const updateUser = async ({ id, name, email, roles }: UpdateUserOptions)
}, },
}); });
const updatedUser = await prisma.user.update({ return await prisma.user.update({
where: { where: {
id, id,
}, },
@ -25,5 +25,4 @@ export const updateUser = async ({ id, name, email, roles }: UpdateUserOptions)
roles, roles,
}, },
}); });
return updatedUser;
}; };

View File

@ -2,7 +2,7 @@ import { Prisma } from '@prisma/client';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
type getAllUsersProps = { type GetAllUsersProps = {
username: string; username: string;
email: string; email: string;
page: number; page: number;
@ -14,7 +14,7 @@ export const findUsers = async ({
email = '', email = '',
page = 1, page = 1,
perPage = 10, perPage = 10,
}: getAllUsersProps) => { }: GetAllUsersProps) => {
const whereClause = Prisma.validator<Prisma.UserWhereInput>()({ const whereClause = Prisma.validator<Prisma.UserWhereInput>()({
OR: [ OR: [
{ {

View File

@ -1,24 +1,14 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
import { updateUser } from '@documenso/lib/server-only/admin/update-user'; import { updateUser } from '@documenso/lib/server-only/admin/update-user';
import { authenticatedProcedure, router } from '../trpc'; import { adminProcedure, router } from '../trpc';
import { ZUpdateProfileMutationByAdminSchema } from './schema'; import { ZUpdateProfileMutationByAdminSchema } from './schema';
export const adminRouter = router({ export const adminRouter = router({
updateUser: authenticatedProcedure updateUser: adminProcedure
.input(ZUpdateProfileMutationByAdminSchema) .input(ZUpdateProfileMutationByAdminSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input }) => {
const isUserAdmin = isAdmin(ctx.user);
if (!isUserAdmin) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Not authorized to perform this action.',
});
}
const { id, name, email, roles } = input; const { id, name, email, roles } = input;
try { try {

View File

@ -1,13 +1,12 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password'; import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
import { resetPassword } from '@documenso/lib/server-only/user/reset-password'; import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
import { updatePassword } from '@documenso/lib/server-only/user/update-password'; import { updatePassword } from '@documenso/lib/server-only/user/update-password';
import { updateProfile } from '@documenso/lib/server-only/user/update-profile'; import { updateProfile } from '@documenso/lib/server-only/user/update-profile';
import { authenticatedProcedure, procedure, router } from '../trpc'; import { adminProcedure, authenticatedProcedure, procedure, router } from '../trpc';
import { import {
ZForgotPasswordFormSchema, ZForgotPasswordFormSchema,
ZResetPasswordFormSchema, ZResetPasswordFormSchema,
@ -17,18 +16,7 @@ import {
} from './schema'; } from './schema';
export const profileRouter = router({ export const profileRouter = router({
getUser: authenticatedProcedure getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input }) => {
.input(ZRetrieveUserByIdQuerySchema)
.query(async ({ input, ctx }) => {
const isUserAdmin = isAdmin(ctx.user);
if (!isUserAdmin) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Not authorized to perform this action.',
});
}
try { try {
const { id } = input; const { id } = input;

View File

@ -1,6 +1,8 @@
import { TRPCError, initTRPC } from '@trpc/server'; import { TRPCError, initTRPC } from '@trpc/server';
import SuperJSON from 'superjson'; import SuperJSON from 'superjson';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
import { TrpcContext } from './context'; import { TrpcContext } from './context';
const t = initTRPC.context<TrpcContext>().create({ const t = initTRPC.context<TrpcContext>().create({
@ -28,9 +30,37 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
}); });
}); });
export const adminMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.session || !ctx.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'You must be logged in to perform this action.',
});
}
const isUserAdmin = isAdmin(ctx.user);
if (!isUserAdmin) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Not authorized to perform this action.',
});
}
return await next({
ctx: {
...ctx,
user: ctx.user,
session: ctx.session,
},
});
});
/** /**
* Routers and Procedures * Routers and Procedures
*/ */
export const router = t.router; export const router = t.router;
export const procedure = t.procedure; export const procedure = t.procedure;
export const authenticatedProcedure = t.procedure.use(authenticatedMiddleware); export const authenticatedProcedure = t.procedure.use(authenticatedMiddleware);
export const adminProcedure = t.procedure.use(adminMiddleware);

View File

@ -44,7 +44,6 @@ const Combobox = ({ listValues, onChange }: ComboboxProps) => {
}; };
return ( return (
<>
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
@ -77,7 +76,6 @@ const Combobox = ({ listValues, onChange }: ComboboxProps) => {
</Command> </Command>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</>
); );
}; };