Skip to content

Commit

Permalink
feat: look up crew users by default
Browse files Browse the repository at this point in the history
  • Loading branch information
DenisVorop committed May 29, 2024
1 parent 914616c commit 8683f69
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/components/AppliedUsersFilter/AppliedUsersFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const AppliedUsersFilter = ({
mode="multiple"
placement="bottom"
value={data}
filter={data.map(({ user }) => user.email)}
readOnly={readOnly}
onChange={onChange}
onClose={onClose}
Expand Down
49 changes: 39 additions & 10 deletions src/components/UserDropdown/UserDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { nullable } from '@taskany/bricks';
import { safeUserData } from '../../utils/getUserName';
import { trpc } from '../../utils/trpcClient';
import { Dropdown, DropdownTrigger, DropdownPanel, DropdownGuardedProps } from '../Dropdown/Dropdown';
import { useUserResource } from '../../hooks/useUserResource';

import s from './UserDropdown.module.css';
import { tr } from './UserDropdown.i18n';

interface UserValue {
name?: string;
email?: string;
email: string;
image?: string;
}
export interface UserDropdownValue {
Expand Down Expand Up @@ -48,19 +49,17 @@ export const UserDropdown = ({
}: UserDropdownProps) => {
const [inputState, setInputState] = useState(query);

const { data: suggestions = [] } = trpc.user.suggestions.useQuery(
{
query: inputState,
filter: filter || [],
},
const { createUserByCrew } = useUserResource();

const { data: crewUsers } = trpc.crew.getUsers.useQuery(
{ query: inputState, filter },
{
enabled: inputState.length >= 2,
cacheTime: 0,
staleTime: 0,
select(data) {
return data.reduce<UserDropdownValue[]>((acc, cur) => {
const userData = safeUserData(cur);
if (userData) acc.push({ id: cur.id, user: userData });
acc.push({ id: cur.id, user: cur });
return acc;
}, []);
},
Expand All @@ -78,6 +77,36 @@ export const UserDropdown = ({
}, [onClose]);

const userGroup = useMemo(() => values.map(safeUserData).filter(Boolean), [values]);
const handleChange = useCallback(
async (crewUsers: UserDropdownValue | UserDropdownValue[]) => {
const lastAddedUser = Array.isArray(crewUsers) ? crewUsers.pop() : crewUsers;

if (!lastAddedUser && mode === 'multiple') {
onChange?.([]);
return;
}

if (!lastAddedUser?.user) return;

const goalsUser = await createUserByCrew(lastAddedUser.user);

const newUser = {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: goalsUser.activityId!,
user: goalsUser,
};

if (mode === 'single') {
onChange?.(newUser);
return;
}

if (!Array.isArray(crewUsers)) return;

onChange?.([...crewUsers, newUser]);
},
[createUserByCrew, mode, onChange],
);

return (
<Dropdown arrow onClose={handleClose}>
Expand All @@ -96,14 +125,14 @@ export const UserDropdown = ({
width={320}
title={tr('Suggestions')}
value={values}
items={suggestions}
items={crewUsers}
inputState={inputState}
selectable
placeholder={placeholder}
setInputState={setInputState}
mode={mode}
placement={placement}
onChange={onChange}
onChange={handleChange}
renderItem={({ item }) =>
nullable(item.user, ({ name, image, email }) => (
<User name={name} src={image} email={email} className={s.Owner} />
Expand Down
22 changes: 22 additions & 0 deletions src/hooks/useUserResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useCallback } from 'react';

import { trpc } from '../utils/trpcClient';

interface CrewUser {
email: string;
name?: string;
login?: string;
}

export const useUserResource = () => {
const createUserMutation = trpc.user.сreateUserByCrew.useMutation();

const createUserByCrew = useCallback(
async (user: CrewUser) => createUserMutation.mutateAsync(user),
[createUserMutation],
);

return {
createUserByCrew,
};
};
1 change: 1 addition & 0 deletions src/types/crew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export interface CrewUser {
email: string;
name?: string;
image?: string;
login?: string;
}
60 changes: 59 additions & 1 deletion trpc/router/crew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { TRPCError } from '@trpc/server';
import { z } from 'zod';

import { getGroupListSchema } from '../../src/schema/crew';
import { Team } from '../../src/types/crew';
import { CrewUser, Team } from '../../src/types/crew';
import { protectedProcedure, router } from '../trpcBackend';
import { prisma } from '../../src/utils/prisma';

const getToken = () => {
const authorization = process.env.CREW_API_TOKEN;
Expand Down Expand Up @@ -59,4 +60,61 @@ export const crew = router({
};
});
}),
getUsers: protectedProcedure
.input(
z.object({
query: z.string(),
filter: z.array(z.string()).optional(),
}),
)
.query(async ({ input }): Promise<CrewUser[]> => {
const { filter = [] } = input;

if (!process.env.NEXT_PUBLIC_CREW_URL) {
const data = await prisma.user.findMany({
where: {
OR: [
{
name: {
contains: input.query,
mode: 'insensitive',
},
},
{
nickname: {
contains: input.query,
mode: 'insensitive',
},
},
{
email: {
contains: input.query,
mode: 'insensitive',
},
},
],
},
});
return data.map((user) => ({ ...user, name: user.name || undefined, image: user.image || undefined }));
}

const response = await fetch(
`${process.env.NEXT_PUBLIC_CREW_URL}/api/rest/search/users?query=${input.query}`,
{
method: 'GET',
headers: {
authorization: getToken(),
'Content-Type': 'application/json',
},
},
);

if (!response.ok) {
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: response.statusText });
}

const data: CrewUser[] = await response.json();

return data.filter((user) => !filter.includes(user.email));
}),
});
43 changes: 42 additions & 1 deletion trpc/router/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ export const user = router({
getUserByNickname: protectedProcedure.input(z.string()).mutation(async ({ input }) => {
return prisma.user.findFirst({ where: { nickname: input } });
}),
getUserById: protectedProcedure.input(z.string().optional()).query(async ({ input }) => {
return prisma.user.findUnique({ where: { id: input } });
}),
getFilterUsersByIds: protectedProcedure.input(z.array(z.string()).optional()).query(async ({ input = [] }) => {
const users = await prisma.user.findMany({
where: { activityId: { in: input } },
Expand All @@ -164,10 +167,48 @@ export const user = router({
},
});

return users.reduce<{ id: string; user: ReturnType<typeof safeUserData> }[]>((acc, cur) => {
return users.reduce<{ id: string; user: NonNullable<ReturnType<typeof safeUserData>> }[]>((acc, cur) => {
const userData = safeUserData(cur.activity);
if (userData && cur.activity) acc.push({ id: cur.activity.id, user: userData });
return acc;
}, []);
}),
сreateUserByCrew: protectedProcedure
.input(
z.object({
email: z.string(),
name: z.string().optional(),
login: z.string().optional(),
}),
)
.mutation(async ({ input }) => {
const user = await prisma.user.findFirst({
where: {
OR: [{ nickname: input.login }, { email: input.email }],
},
});

if (user) return { ...user, name: user.name || undefined, image: user.image || undefined };

const newUser = await prisma.user.create({
data: {
email: input.email,
name: input.name,
nickname: input.login,
activity: {
create: {
settings: {
create: {},
},
},
},
},
});

return {
...newUser,
name: newUser.name || undefined,
image: newUser.image || undefined,
};
}),
});

0 comments on commit 8683f69

Please sign in to comment.