@greatapps/common 1.1.44 → 1.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/components/layouts/UsersSelectorPopover.mjs +92 -21
  2. package/dist/components/layouts/UsersSelectorPopover.mjs.map +1 -1
  3. package/dist/index.mjs +3 -0
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/modules/accounts/actions/account-management.action.mjs +80 -29
  6. package/dist/modules/accounts/actions/account-management.action.mjs.map +1 -1
  7. package/dist/modules/projects/actions/add-project-user.action.mjs +9 -0
  8. package/dist/modules/projects/actions/add-project-user.action.mjs.map +1 -0
  9. package/dist/modules/projects/actions/list-available-users.action.mjs +9 -0
  10. package/dist/modules/projects/actions/list-available-users.action.mjs.map +1 -0
  11. package/dist/modules/projects/actions/list-project-users.action.mjs +9 -0
  12. package/dist/modules/projects/actions/list-project-users.action.mjs.map +1 -0
  13. package/dist/modules/projects/actions/remove-project-user.action.mjs +9 -0
  14. package/dist/modules/projects/actions/remove-project-user.action.mjs.map +1 -0
  15. package/dist/modules/projects/hooks/project-users.hook.mjs +70 -0
  16. package/dist/modules/projects/hooks/project-users.hook.mjs.map +1 -0
  17. package/dist/modules/projects/services/project-users.service.mjs +67 -0
  18. package/dist/modules/projects/services/project-users.service.mjs.map +1 -0
  19. package/dist/modules/projects/types.mjs +22 -0
  20. package/dist/modules/projects/types.mjs.map +1 -0
  21. package/dist/server.mjs +10 -0
  22. package/dist/server.mjs.map +1 -1
  23. package/package.json +1 -1
  24. package/src/components/layouts/UsersSelectorPopover.tsx +163 -63
  25. package/src/index.ts +2 -0
  26. package/src/modules/accounts/actions/account-management.action.ts +112 -48
  27. package/src/modules/accounts/types.ts +1 -0
  28. package/src/modules/projects/actions/add-project-user.action.ts +8 -0
  29. package/src/modules/projects/actions/list-available-users.action.ts +8 -0
  30. package/src/modules/projects/actions/list-project-users.action.ts +8 -0
  31. package/src/modules/projects/actions/remove-project-user.action.ts +8 -0
  32. package/src/modules/projects/hooks/project-users.hook.ts +81 -0
  33. package/src/modules/projects/services/project-users.service.ts +94 -0
  34. package/src/modules/projects/types.ts +28 -0
  35. package/src/server.ts +7 -0
@@ -0,0 +1,9 @@
1
+ "use server";
2
+ import { projectUsersService } from "../services/project-users.service";
3
+ async function removeProjectUserAction(params) {
4
+ return projectUsersService.removeFromProject(params);
5
+ }
6
+ export {
7
+ removeProjectUserAction
8
+ };
9
+ //# sourceMappingURL=remove-project-user.action.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/projects/actions/remove-project-user.action.ts"],"sourcesContent":["'use server';\r\n\r\nimport { projectUsersService } from '../services/project-users.service';\r\nimport type { AddRemoveProjectUsersParams } from '../types';\r\n\r\nexport async function removeProjectUserAction(params: AddRemoveProjectUsersParams): Promise<void> {\r\n return projectUsersService.removeFromProject(params);\r\n}\r\n"],"mappings":";AAEA,SAAS,2BAA2B;AAGpC,eAAsB,wBAAwB,QAAoD;AAChG,SAAO,oBAAoB,kBAAkB,MAAM;AACrD;","names":[]}
@@ -0,0 +1,70 @@
1
+ "use client";
2
+ import { useMemo } from "react";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { addProjectUserAction } from "../actions/add-project-user.action";
5
+ import { listAvailableUsersAction } from "../actions/list-available-users.action";
6
+ import { listProjectUsersAction } from "../actions/list-project-users.action";
7
+ import { removeProjectUserAction } from "../actions/remove-project-user.action";
8
+ const DEFAULT_AVATAR = "/images/Logomark.png";
9
+ const projectUsersQueryKey = (id_project) => ["project-users", id_project];
10
+ function useProjectUsers(id_project) {
11
+ const queryClient = useQueryClient();
12
+ const { data: inProject = [], isLoading: isLoadingIn } = useQuery({
13
+ queryKey: [...projectUsersQueryKey(id_project), "in"],
14
+ queryFn: () => listProjectUsersAction(id_project),
15
+ enabled: !!id_project
16
+ });
17
+ const { data: notInProject = [], isLoading: isLoadingOut } = useQuery({
18
+ queryKey: [...projectUsersQueryKey(id_project), "out"],
19
+ queryFn: () => listAvailableUsersAction(id_project),
20
+ enabled: !!id_project
21
+ });
22
+ const invalidate = () => {
23
+ queryClient.invalidateQueries({ queryKey: projectUsersQueryKey(id_project) });
24
+ };
25
+ const addMutation = useMutation({
26
+ mutationFn: (user_id) => addProjectUserAction({ id_project, users: [user_id] }),
27
+ onSuccess: invalidate
28
+ });
29
+ const removeMutation = useMutation({
30
+ mutationFn: (user_id) => removeProjectUserAction({ id_project, users: [user_id] }),
31
+ onSuccess: invalidate
32
+ });
33
+ const users = useMemo(
34
+ () => [
35
+ ...inProject.map((u) => ({
36
+ id: u.id_user,
37
+ name: u.name,
38
+ avatar: u.photo ?? DEFAULT_AVATAR,
39
+ checked: true
40
+ })),
41
+ ...notInProject.map((u) => ({
42
+ id: u.id,
43
+ name: u.name,
44
+ avatar: u.photo ?? DEFAULT_AVATAR,
45
+ checked: false
46
+ }))
47
+ ],
48
+ [inProject, notInProject]
49
+ );
50
+ const toggleUser = (id) => {
51
+ const user = users.find((u) => u.id === id);
52
+ if (!user) return;
53
+ if (user.checked) {
54
+ removeMutation.mutate(id);
55
+ } else {
56
+ addMutation.mutate(id);
57
+ }
58
+ };
59
+ return {
60
+ users,
61
+ isLoading: isLoadingIn || isLoadingOut,
62
+ userCount: inProject.length,
63
+ toggleUser
64
+ };
65
+ }
66
+ export {
67
+ projectUsersQueryKey,
68
+ useProjectUsers
69
+ };
70
+ //# sourceMappingURL=project-users.hook.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/projects/hooks/project-users.hook.ts"],"sourcesContent":["'use client';\r\n\r\nimport { useMemo } from 'react';\r\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\r\n\r\nimport type { UserSelectorItem } from '../../../components/layouts/UsersSelectorPopover';\r\nimport { addProjectUserAction } from '../actions/add-project-user.action';\r\nimport { listAvailableUsersAction } from '../actions/list-available-users.action';\r\nimport { listProjectUsersAction } from '../actions/list-project-users.action';\r\nimport { removeProjectUserAction } from '../actions/remove-project-user.action';\r\n\r\nconst DEFAULT_AVATAR = '/images/Logomark.png';\r\n\r\nexport const projectUsersQueryKey = (id_project: number) => ['project-users', id_project];\r\n\r\nexport function useProjectUsers(id_project: number) {\r\n const queryClient = useQueryClient();\r\n\r\n const { data: inProject = [], isLoading: isLoadingIn } = useQuery({\r\n queryKey: [...projectUsersQueryKey(id_project), 'in'],\r\n queryFn: () => listProjectUsersAction(id_project),\r\n enabled: !!id_project,\r\n });\r\n\r\n const { data: notInProject = [], isLoading: isLoadingOut } = useQuery({\r\n queryKey: [...projectUsersQueryKey(id_project), 'out'],\r\n queryFn: () => listAvailableUsersAction(id_project),\r\n enabled: !!id_project,\r\n });\r\n\r\n const invalidate = () => {\r\n queryClient.invalidateQueries({ queryKey: projectUsersQueryKey(id_project) });\r\n };\r\n\r\n const addMutation = useMutation({\r\n mutationFn: (user_id: number) =>\r\n addProjectUserAction({ id_project, users: [user_id] }),\r\n onSuccess: invalidate,\r\n });\r\n\r\n const removeMutation = useMutation({\r\n mutationFn: (user_id: number) =>\r\n removeProjectUserAction({ id_project, users: [user_id] }),\r\n onSuccess: invalidate,\r\n });\r\n\r\n const users: UserSelectorItem[] = useMemo(\r\n () => [\r\n ...inProject.map((u) => ({\r\n id: u.id_user,\r\n name: u.name,\r\n avatar: u.photo ?? DEFAULT_AVATAR,\r\n checked: true,\r\n })),\r\n ...notInProject.map((u) => ({\r\n id: u.id,\r\n name: u.name,\r\n avatar: u.photo ?? DEFAULT_AVATAR,\r\n checked: false,\r\n })),\r\n ],\r\n [inProject, notInProject]\r\n );\r\n\r\n const toggleUser = (id: number) => {\r\n const user = users.find((u) => u.id === id);\r\n if (!user) return;\r\n if (user.checked) {\r\n removeMutation.mutate(id);\r\n } else {\r\n addMutation.mutate(id);\r\n }\r\n };\r\n\r\n return {\r\n users,\r\n isLoading: isLoadingIn || isLoadingOut,\r\n userCount: inProject.length,\r\n toggleUser,\r\n };\r\n}\r\n"],"mappings":";AAEA,SAAS,eAAe;AACxB,SAAS,aAAa,UAAU,sBAAsB;AAGtD,SAAS,4BAA4B;AACrC,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AAExC,MAAM,iBAAiB;AAEhB,MAAM,uBAAuB,CAAC,eAAuB,CAAC,iBAAiB,UAAU;AAEjF,SAAS,gBAAgB,YAAoB;AAClD,QAAM,cAAc,eAAe;AAEnC,QAAM,EAAE,MAAM,YAAY,CAAC,GAAG,WAAW,YAAY,IAAI,SAAS;AAAA,IAChE,UAAU,CAAC,GAAG,qBAAqB,UAAU,GAAG,IAAI;AAAA,IACpD,SAAS,MAAM,uBAAuB,UAAU;AAAA,IAChD,SAAS,CAAC,CAAC;AAAA,EACb,CAAC;AAED,QAAM,EAAE,MAAM,eAAe,CAAC,GAAG,WAAW,aAAa,IAAI,SAAS;AAAA,IACpE,UAAU,CAAC,GAAG,qBAAqB,UAAU,GAAG,KAAK;AAAA,IACrD,SAAS,MAAM,yBAAyB,UAAU;AAAA,IAClD,SAAS,CAAC,CAAC;AAAA,EACb,CAAC;AAED,QAAM,aAAa,MAAM;AACvB,gBAAY,kBAAkB,EAAE,UAAU,qBAAqB,UAAU,EAAE,CAAC;AAAA,EAC9E;AAEA,QAAM,cAAc,YAAY;AAAA,IAC9B,YAAY,CAAC,YACX,qBAAqB,EAAE,YAAY,OAAO,CAAC,OAAO,EAAE,CAAC;AAAA,IACvD,WAAW;AAAA,EACb,CAAC;AAED,QAAM,iBAAiB,YAAY;AAAA,IACjC,YAAY,CAAC,YACX,wBAAwB,EAAE,YAAY,OAAO,CAAC,OAAO,EAAE,CAAC;AAAA,IAC1D,WAAW;AAAA,EACb,CAAC;AAED,QAAM,QAA4B;AAAA,IAChC,MAAM;AAAA,MACJ,GAAG,UAAU,IAAI,CAAC,OAAO;AAAA,QACvB,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,QAAQ,EAAE,SAAS;AAAA,QACnB,SAAS;AAAA,MACX,EAAE;AAAA,MACF,GAAG,aAAa,IAAI,CAAC,OAAO;AAAA,QAC1B,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,QAAQ,EAAE,SAAS;AAAA,QACnB,SAAS;AAAA,MACX,EAAE;AAAA,IACJ;AAAA,IACA,CAAC,WAAW,YAAY;AAAA,EAC1B;AAEA,QAAM,aAAa,CAAC,OAAe;AACjC,UAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC1C,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,SAAS;AAChB,qBAAe,OAAO,EAAE;AAAA,IAC1B,OAAO;AACL,kBAAY,OAAO,EAAE;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,WAAW,eAAe;AAAA,IAC1B,WAAW,UAAU;AAAA,IACrB;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,67 @@
1
+ import "server-only";
2
+ import { api } from "../../../infra/api/client";
3
+ import { ApiError } from "../../../infra/api/types";
4
+ import { getUserContext } from "../../auth/utils/get-user-context";
5
+ import {
6
+ AccountUserSchema,
7
+ ProjectUserSchema
8
+ } from "../types";
9
+ class ProjectUsersService {
10
+ async listInProject(id_project) {
11
+ const { id_account, id_user } = await getUserContext();
12
+ const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;
13
+ const response = await api.pages.get(url);
14
+ if (response.status === 0) {
15
+ throw new ApiError(
16
+ response.message || "Erro ao listar usu\xE1rios do projeto",
17
+ "LIST_PROJECT_USERS_FAILED",
18
+ 400
19
+ );
20
+ }
21
+ return response.data.map((item) => ProjectUserSchema.parse(item));
22
+ }
23
+ async listNotInProject(id_project) {
24
+ const { id_account } = await getUserContext();
25
+ const url = `/accounts/${id_account}/users?notInProject=${id_project}`;
26
+ const response = await api.apps.get(url);
27
+ if (response.status === 0) {
28
+ throw new ApiError(
29
+ response.message || "Erro ao listar usu\xE1rios dispon\xEDveis",
30
+ "LIST_AVAILABLE_USERS_FAILED",
31
+ 400
32
+ );
33
+ }
34
+ return response.data.map((item) => AccountUserSchema.parse(item));
35
+ }
36
+ async addToProject({ id_project, users }) {
37
+ const { id_account, id_user } = await getUserContext();
38
+ const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;
39
+ const response = await api.pages.post(url, { users });
40
+ if (response.status === 0) {
41
+ throw new ApiError(
42
+ response.message || "Erro ao adicionar usu\xE1rio ao projeto",
43
+ "ADD_PROJECT_USER_FAILED",
44
+ 400
45
+ );
46
+ }
47
+ }
48
+ async removeFromProject({ id_project, users }) {
49
+ const { id_account, id_user } = await getUserContext();
50
+ const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;
51
+ const response = await api.pages.delete(url, {
52
+ body: JSON.stringify({ users })
53
+ });
54
+ if (response.status === 0) {
55
+ throw new ApiError(
56
+ response.message || "Erro ao remover usu\xE1rio do projeto",
57
+ "REMOVE_PROJECT_USER_FAILED",
58
+ 400
59
+ );
60
+ }
61
+ }
62
+ }
63
+ const projectUsersService = new ProjectUsersService();
64
+ export {
65
+ projectUsersService
66
+ };
67
+ //# sourceMappingURL=project-users.service.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/projects/services/project-users.service.ts"],"sourcesContent":["import 'server-only';\r\n\r\nimport { api } from '../../../infra/api/client';\r\nimport { ApiError } from '../../../infra/api/types';\r\nimport { getUserContext } from '../../auth/utils/get-user-context';\r\nimport {\r\n AccountUser,\r\n AccountUserSchema,\r\n AddRemoveProjectUsersParams,\r\n ProjectUser,\r\n ProjectUserSchema,\r\n} from '../types';\r\n\r\ninterface ApiListResponse<T> {\r\n data: T[];\r\n total: number;\r\n status: number;\r\n message?: string;\r\n}\r\n\r\ninterface ApiActionResponse {\r\n status: number;\r\n message?: string;\r\n}\r\n\r\nclass ProjectUsersService {\r\n async listInProject(id_project: number): Promise<ProjectUser[]> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;\r\n\r\n const response = await api.pages.get<ApiListResponse<ProjectUser>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao listar usuários do projeto',\r\n 'LIST_PROJECT_USERS_FAILED',\r\n 400\r\n );\r\n }\r\n\r\n return response.data.map((item) => ProjectUserSchema.parse(item));\r\n }\r\n\r\n async listNotInProject(id_project: number): Promise<AccountUser[]> {\r\n const { id_account } = await getUserContext();\r\n const url = `/accounts/${id_account}/users?notInProject=${id_project}`;\r\n\r\n const response = await api.apps.get<ApiListResponse<AccountUser>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao listar usuários disponíveis',\r\n 'LIST_AVAILABLE_USERS_FAILED',\r\n 400\r\n );\r\n }\r\n\r\n return response.data.map((item) => AccountUserSchema.parse(item));\r\n }\r\n\r\n async addToProject({ id_project, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;\r\n\r\n const response = await api.pages.post<ApiActionResponse>(url, { users });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao adicionar usuário ao projeto',\r\n 'ADD_PROJECT_USER_FAILED',\r\n 400\r\n );\r\n }\r\n }\r\n\r\n async removeFromProject({ id_project, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;\r\n\r\n const response = await api.pages.delete<ApiActionResponse>(url, {\r\n body: JSON.stringify({ users }),\r\n });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao remover usuário do projeto',\r\n 'REMOVE_PROJECT_USER_FAILED',\r\n 400\r\n );\r\n }\r\n }\r\n}\r\n\r\nexport const projectUsersService = new ProjectUsersService();\r\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,WAAW;AACpB,SAAS,gBAAgB;AACzB,SAAS,sBAAsB;AAC/B;AAAA,EAEE;AAAA,EAGA;AAAA,OACK;AAcP,MAAM,oBAAoB;AAAA,EACxB,MAAM,cAAc,YAA4C;AAC9D,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,UAAU;AAEzE,UAAM,WAAW,MAAM,IAAI,MAAM,IAAkC,GAAG;AAEtE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO,SAAS,KAAK,IAAI,CAAC,SAAS,kBAAkB,MAAM,IAAI,CAAC;AAAA,EAClE;AAAA,EAEA,MAAM,iBAAiB,YAA4C;AACjE,UAAM,EAAE,WAAW,IAAI,MAAM,eAAe;AAC5C,UAAM,MAAM,aAAa,UAAU,uBAAuB,UAAU;AAEpE,UAAM,WAAW,MAAM,IAAI,KAAK,IAAkC,GAAG;AAErE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO,SAAS,KAAK,IAAI,CAAC,SAAS,kBAAkB,MAAM,IAAI,CAAC;AAAA,EAClE;AAAA,EAEA,MAAM,aAAa,EAAE,YAAY,MAAM,GAA+C;AACpF,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,UAAU;AAEzE,UAAM,WAAW,MAAM,IAAI,MAAM,KAAwB,KAAK,EAAE,MAAM,CAAC;AAEvE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,EAAE,YAAY,MAAM,GAA+C;AACzF,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,UAAU;AAEzE,UAAM,WAAW,MAAM,IAAI,MAAM,OAA0B,KAAK;AAAA,MAC9D,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,IAChC,CAAC;AAED,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,sBAAsB,IAAI,oBAAoB;","names":[]}
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ const ProjectUserSchema = z.object({
3
+ id_user: z.union([z.number(), z.string()]).transform(Number),
4
+ id_account: z.union([z.number(), z.string()]).transform(Number),
5
+ id_project: z.union([z.number(), z.string()]).transform(Number),
6
+ name: z.string(),
7
+ email: z.string(),
8
+ profile: z.string().optional(),
9
+ photo: z.string().nullable().optional()
10
+ });
11
+ const AccountUserSchema = z.object({
12
+ id: z.union([z.number(), z.string()]).transform(Number),
13
+ name: z.string(),
14
+ email: z.string(),
15
+ profile: z.string().optional(),
16
+ photo: z.string().nullable().optional()
17
+ });
18
+ export {
19
+ AccountUserSchema,
20
+ ProjectUserSchema
21
+ };
22
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/modules/projects/types.ts"],"sourcesContent":["import { z } from 'zod';\r\n\r\nexport const ProjectUserSchema = z.object({\r\n id_user: z.union([z.number(), z.string()]).transform(Number),\r\n id_account: z.union([z.number(), z.string()]).transform(Number),\r\n id_project: z.union([z.number(), z.string()]).transform(Number),\r\n name: z.string(),\r\n email: z.string(),\r\n profile: z.string().optional(),\r\n photo: z.string().nullable().optional(),\r\n});\r\n\r\nexport type ProjectUser = z.infer<typeof ProjectUserSchema>;\r\n\r\nexport const AccountUserSchema = z.object({\r\n id: z.union([z.number(), z.string()]).transform(Number),\r\n name: z.string(),\r\n email: z.string(),\r\n profile: z.string().optional(),\r\n photo: z.string().nullable().optional(),\r\n});\r\n\r\nexport type AccountUser = z.infer<typeof AccountUserSchema>;\r\n\r\nexport interface AddRemoveProjectUsersParams {\r\n id_project: number;\r\n users: number[];\r\n}\r\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC3D,YAAY,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC9D,YAAY,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC9D,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACxC,CAAC;AAIM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EACtD,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACxC,CAAC;","names":[]}
package/dist/server.mjs CHANGED
@@ -8,6 +8,11 @@ import { findWhitelabel } from "./modules/whitelabel/actions/find-whitelabel.act
8
8
  import { validateSessionAction } from "./modules/auth/actions/validate-session.action";
9
9
  import { listSubscriptionsAction } from "./modules/subscriptions/actions/list-subscriptions.action";
10
10
  import { listIaCreditsAction } from "./modules/ia-credits/actions/list-ia-credits.action";
11
+ import { projectUsersService } from "./modules/projects/services/project-users.service";
12
+ import { listProjectUsersAction } from "./modules/projects/actions/list-project-users.action";
13
+ import { listAvailableUsersAction } from "./modules/projects/actions/list-available-users.action";
14
+ import { addProjectUserAction } from "./modules/projects/actions/add-project-user.action";
15
+ import { removeProjectUserAction } from "./modules/projects/actions/remove-project-user.action";
11
16
  import { accountService } from "./modules/accounts/services/account.service";
12
17
  import { twoFactorService } from "./modules/accounts/services/two-factor.service";
13
18
  import {
@@ -32,6 +37,7 @@ import { getUserContext } from "./modules/auth/utils/get-user-context";
32
37
  export {
33
38
  ApiClient,
34
39
  accountService,
40
+ addProjectUserAction,
35
41
  api,
36
42
  apiClient,
37
43
  authService,
@@ -49,8 +55,12 @@ export {
49
55
  getUserContext,
50
56
  iaCreditsService,
51
57
  listAccountUsersAction,
58
+ listAvailableUsersAction,
52
59
  listIaCreditsAction,
60
+ listProjectUsersAction,
53
61
  listSubscriptionsAction,
62
+ projectUsersService,
63
+ removeProjectUserAction,
54
64
  requestEmailChangeAction,
55
65
  requestPhoneChangeAction,
56
66
  subscriptionsService,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { listIaCreditsAction } from './modules/ia-credits/actions/list-ia-credits.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,2BAA2B;AAGpC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { listIaCreditsAction } from './modules/ia-credits/actions/list-ia-credits.action';\r\n\r\n// Project Users Services & Actions (server-only)\r\nexport { projectUsersService } from './modules/projects/services/project-users.service';\r\nexport { listProjectUsersAction } from './modules/projects/actions/list-project-users.action';\r\nexport { listAvailableUsersAction } from './modules/projects/actions/list-available-users.action';\r\nexport { addProjectUserAction } from './modules/projects/actions/add-project-user.action';\r\nexport { removeProjectUserAction } from './modules/projects/actions/remove-project-user.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,gCAAgC;AACzC,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AAGxC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.44",
3
+ "version": "1.1.48",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -10,6 +10,7 @@ import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from '.
10
10
  import { Checkbox } from '../ui/form/Checkbox';
11
11
  import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/overlay/Tooltip';
12
12
  import { cn } from '../../infra/utils/clsx';
13
+ import { useProjectUsers } from '../../modules/projects/hooks/project-users.hook';
13
14
 
14
15
  export interface UserSelectorItem {
15
16
  id: number;
@@ -18,7 +19,20 @@ export interface UserSelectorItem {
18
19
  checked: boolean;
19
20
  }
20
21
 
22
+ function UserItemSkeleton() {
23
+ return (
24
+ <div className="flex items-center justify-between px-2 py-1.5 gap-2">
25
+ <div className="flex items-center gap-2 flex-1">
26
+ <div className="size-8 rounded-full bg-gray-100 animate-pulse shrink-0" />
27
+ <div className="h-3.5 w-28 rounded bg-gray-100 animate-pulse" />
28
+ </div>
29
+ <div className="size-4 rounded bg-gray-100 animate-pulse shrink-0" />
30
+ </div>
31
+ );
32
+ }
33
+
21
34
  export interface UsersSelectorPopoverProps {
35
+ id_project?: number;
22
36
  users?: UserSelectorItem[];
23
37
  onToggleUser?: (id: number) => void;
24
38
  addRoute?: string;
@@ -29,33 +43,33 @@ export interface UsersSelectorPopoverProps {
29
43
  contentClassName?: string;
30
44
  }
31
45
 
32
- function UsersSelectorPopover({
33
- users: externalUsers,
34
- onToggleUser,
35
- addRoute = '/my-teams?createNew=true',
36
- manageRoute = '/my-teams',
37
- userCount = 5,
46
+ interface ContentProps {
47
+ users: UserSelectorItem[];
48
+ isLoading: boolean;
49
+ userCount: number;
50
+ toggleUser: (id: number) => void;
51
+ addRoute: string;
52
+ manageRoute: string;
53
+ side?: UsersSelectorPopoverProps['side'];
54
+ align?: UsersSelectorPopoverProps['align'];
55
+ contentClassName?: string;
56
+ }
57
+
58
+ function UsersSelectorPopoverContent({
59
+ users,
60
+ isLoading,
61
+ userCount,
62
+ toggleUser,
63
+ addRoute,
64
+ manageRoute,
38
65
  side,
39
66
  align,
40
67
  contentClassName,
41
- }: UsersSelectorPopoverProps) {
68
+ }: ContentProps) {
42
69
  const route = useRouter();
43
70
  const [open, setOpen] = useState(false);
44
- const [internalUsers, setInternalUsers] = useState<UserSelectorItem[]>(externalUsers ?? []);
45
71
  const [hoveredId, setHoveredId] = useState<number | null>(null);
46
72
 
47
- const users = externalUsers ?? internalUsers;
48
-
49
- const toggleUser = (id: number) => {
50
- if (onToggleUser) {
51
- onToggleUser(id);
52
- } else {
53
- setInternalUsers((prev) =>
54
- prev.map((u) => (u.id === id ? { ...u, checked: !u.checked } : u))
55
- );
56
- }
57
- };
58
-
59
73
  return (
60
74
  <Popover open={open} onOpenChange={setOpen}>
61
75
  <PopoverTrigger className="cursor-pointer">
@@ -86,7 +100,6 @@ function UsersSelectorPopover({
86
100
  <Plus size={16} />
87
101
  Adicionar
88
102
  </Button>
89
-
90
103
  <Tooltip>
91
104
  <TooltipTrigger asChild>
92
105
  <Button
@@ -105,48 +118,64 @@ function UsersSelectorPopover({
105
118
  <Command className="gap-2" value="">
106
119
  <CommandInput placeholder="Busque aqui" />
107
120
  <CommandList className="custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]">
108
- <CommandEmpty>No results found.</CommandEmpty>
109
- {users.map((user) => (
110
- <CommandItem
111
- key={user.id}
112
- autoFocus={false}
113
- onPointerEnter={() => setHoveredId(user.id)}
114
- onPointerLeave={() => setHoveredId(null)}
115
- onFocus={() => setHoveredId(user.id)}
116
- onBlur={() => setHoveredId(null)}
117
- onSelect={() => toggleUser(user.id)}
118
- className="cursor-pointer"
119
- >
120
- <div className="flex items-center justify-between w-full">
121
- <div className="flex items-center gap-2">
122
- <Image
123
- alt="Avatar"
124
- src={user.avatar}
125
- width={32}
126
- height={32}
127
- className="rounded-full"
128
- />
129
- {user.name}
130
- </div>
131
-
132
- <Tooltip open={hoveredId === user.id}>
133
- <TooltipTrigger
134
- onClick={(e) => e.stopPropagation()}
135
- onPointerDown={(e) => e.stopPropagation()}
136
- >
137
- <Checkbox
138
- checked={user.checked}
139
- onCheckedChange={() => toggleUser(user.id)}
140
- className={hoveredId === user.id ? 'border-gray-950' : ''}
141
- />
142
- </TooltipTrigger>
143
- <TooltipContent>
144
- {user.checked ? 'Remover do projeto' : 'Adicionar ao projeto'}
145
- </TooltipContent>
146
- </Tooltip>
147
- </div>
148
- </CommandItem>
149
- ))}
121
+ {isLoading ? (
122
+ <>
123
+ <UserItemSkeleton />
124
+ <UserItemSkeleton />
125
+ <UserItemSkeleton />
126
+ </>
127
+ ) : users.length === 0 ? (
128
+ <div className="flex items-center justify-center h-full">
129
+ <span className="paragraph-small-medium text-center text-gray-500">
130
+ Nenhum usuário encontrado.
131
+ </span>
132
+ </div>
133
+ ) : (
134
+ <>
135
+ <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
136
+ {users.map((user) => (
137
+ <CommandItem
138
+ key={user.id}
139
+ value={user.name}
140
+ autoFocus={false}
141
+ onPointerEnter={() => setHoveredId(user.id)}
142
+ onPointerLeave={() => setHoveredId(null)}
143
+ onFocus={() => setHoveredId(user.id)}
144
+ onBlur={() => setHoveredId(null)}
145
+ onSelect={() => toggleUser(user.id)}
146
+ className="cursor-pointer"
147
+ >
148
+ <div className="flex items-center justify-between w-full">
149
+ <div className="flex items-center gap-2">
150
+ <Image
151
+ alt="Avatar"
152
+ src={user.avatar}
153
+ width={32}
154
+ height={32}
155
+ className="rounded-full"
156
+ />
157
+ {user.name}
158
+ </div>
159
+ <Tooltip open={hoveredId === user.id}>
160
+ <TooltipTrigger
161
+ onClick={(e) => e.stopPropagation()}
162
+ onPointerDown={(e) => e.stopPropagation()}
163
+ >
164
+ <Checkbox
165
+ checked={user.checked}
166
+ onCheckedChange={() => toggleUser(user.id)}
167
+ className={hoveredId === user.id ? 'border-gray-950' : ''}
168
+ />
169
+ </TooltipTrigger>
170
+ <TooltipContent>
171
+ {user.checked ? 'Remover do projeto' : 'Adicionar ao projeto'}
172
+ </TooltipContent>
173
+ </Tooltip>
174
+ </div>
175
+ </CommandItem>
176
+ ))}
177
+ </>
178
+ )}
150
179
  </CommandList>
151
180
  </Command>
152
181
  </div>
@@ -155,4 +184,75 @@ function UsersSelectorPopover({
155
184
  );
156
185
  }
157
186
 
187
+ function ConnectedUsersSelectorPopover({
188
+ id_project,
189
+ addRoute,
190
+ manageRoute,
191
+ side,
192
+ align,
193
+ contentClassName,
194
+ }: Required<Pick<UsersSelectorPopoverProps, 'id_project'>> &
195
+ Pick<UsersSelectorPopoverProps, 'addRoute' | 'manageRoute' | 'side' | 'align' | 'contentClassName'>) {
196
+ const { users, isLoading, userCount, toggleUser } = useProjectUsers(id_project);
197
+ return (
198
+ <UsersSelectorPopoverContent
199
+ users={users}
200
+ isLoading={isLoading}
201
+ userCount={userCount}
202
+ toggleUser={toggleUser}
203
+ addRoute={addRoute ?? '/my-teams?createNew=true'}
204
+ manageRoute={manageRoute ?? '/my-teams'}
205
+ side={side}
206
+ align={align}
207
+ contentClassName={contentClassName}
208
+ />
209
+ );
210
+ }
211
+
212
+ function StaticUsersSelectorPopover({
213
+ users: externalUsers = [],
214
+ onToggleUser,
215
+ userCount = 5,
216
+ addRoute = '/my-teams?createNew=true',
217
+ manageRoute = '/my-teams',
218
+ side,
219
+ align,
220
+ contentClassName,
221
+ }: Omit<UsersSelectorPopoverProps, 'id_project'>) {
222
+ const [internalUsers, setInternalUsers] = useState<UserSelectorItem[]>(externalUsers);
223
+
224
+ const users = externalUsers.length > 0 ? externalUsers : internalUsers;
225
+
226
+ const toggleUser = (id: number) => {
227
+ if (onToggleUser) {
228
+ onToggleUser(id);
229
+ } else {
230
+ setInternalUsers((prev) =>
231
+ prev.map((u) => (u.id === id ? { ...u, checked: !u.checked } : u))
232
+ );
233
+ }
234
+ };
235
+
236
+ return (
237
+ <UsersSelectorPopoverContent
238
+ users={users}
239
+ isLoading={false}
240
+ userCount={userCount}
241
+ toggleUser={toggleUser}
242
+ addRoute={addRoute}
243
+ manageRoute={manageRoute}
244
+ side={side}
245
+ align={align}
246
+ contentClassName={contentClassName}
247
+ />
248
+ );
249
+ }
250
+
251
+ function UsersSelectorPopover(props: UsersSelectorPopoverProps) {
252
+ if (props.id_project != null) {
253
+ return <ConnectedUsersSelectorPopover {...props} id_project={props.id_project} />;
254
+ }
255
+ return <StaticUsersSelectorPopover {...props} />;
256
+ }
257
+
158
258
  export { UsersSelectorPopover };
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ export * from './providers/auth.provider';
10
10
  export * from './providers/whitelabel.provider';
11
11
 
12
12
  // Hooks
13
+ export { useProjectUsers, projectUsersQueryKey } from './modules/projects/hooks/project-users.hook';
14
+ export type { ProjectUser, AccountUser, AddRemoveProjectUsersParams } from './modules/projects/types';
13
15
  export { useUserQuery, useUserValidateSession, useInvalidateUser, useSetUserData, USER_QUERY_KEY } from './modules/auth/hooks/useUserQuery';
14
16
  export { useSubscriptions, SUBSCRIPTIONS_QUERY_KEY } from './modules/subscriptions/hooks/list-subscriptions.hook';
15
17
  export { useActiveSubscription } from './modules/subscriptions/hooks/find-active-subscription.hook';