@titus-system/syncdesk 0.3.1 → 0.4.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # CHANGELOG
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ ### Changed
12
+
13
+ ### Deprecated
14
+
15
+ ### Removed
16
+
17
+ ### Fixed
18
+
19
+ ### Security
20
+
21
+ ### Dev Notes
package/README.md CHANGED
@@ -14,6 +14,8 @@ npm install @titus-system/syncdesk
14
14
 
15
15
  ### Install locally
16
16
 
17
+ Alternatively, you can install locally bypassing the npm registry.
18
+
17
19
  ```sh
18
20
  npm run build
19
21
  npm pack
@@ -31,7 +33,8 @@ npm install /path/to/your/library-1.0.0.tgz
31
33
  # You may have to login to npm registry first with `npm login`
32
34
  npm login
33
35
 
34
- npm run build && npm publish
36
+ npm run build
37
+ npm publish --access public
35
38
  ```
36
39
 
37
40
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@titus-system/syncdesk",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "",
@@ -1,12 +1,18 @@
1
1
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
2
  import { apiClient, ApiResponse } from "../../api";
3
3
  import {
4
+ AdminRegisterUserRequest,
5
+ ChangePasswordRequest,
6
+ ForgotPasswordRequest,
7
+ ForgotPasswordResponse,
4
8
  LoginResponse,
5
9
  RegisterUserRequest,
10
+ ResetPasswordRequest,
6
11
  UserCreatedResponse,
7
12
  UserLoginRequest,
8
13
  UserWithRoles,
9
14
  } from "../types/auth";
15
+ import { User } from "../../users/types/user";
10
16
 
11
17
  const PATH = "auth";
12
18
 
@@ -93,3 +99,61 @@ export const useLogout = () => {
93
99
  },
94
100
  });
95
101
  };
102
+
103
+ /**
104
+ * Register a new user as admin.
105
+ */
106
+ export const useAdminRegister = () => {
107
+ const queryClient = useQueryClient();
108
+
109
+ return useMutation({
110
+ mutationFn: async (userData: AdminRegisterUserRequest): Promise<User> => {
111
+ const response = await apiClient.post<ApiResponse<User>>(
112
+ `${PATH}/admin/register`,
113
+ userData,
114
+ );
115
+ return response.data.data;
116
+ },
117
+ onSuccess: () => {
118
+ queryClient.invalidateQueries({ queryKey: ["users"] });
119
+ },
120
+ });
121
+ };
122
+
123
+ /**
124
+ * Change the password of the current user.
125
+ */
126
+ export const useChangePassword = () => {
127
+ return useMutation({
128
+ mutationFn: async (data: ChangePasswordRequest): Promise<void> => {
129
+ await apiClient.post(`${PATH}/change-password`, data);
130
+ },
131
+ });
132
+ };
133
+
134
+ /**
135
+ * Request a password reset email.
136
+ */
137
+ export const useForgotPassword = () => {
138
+ return useMutation({
139
+ mutationFn: async (
140
+ data: ForgotPasswordRequest,
141
+ ): Promise<ForgotPasswordResponse> => {
142
+ const response = await apiClient.post<
143
+ ApiResponse<ForgotPasswordResponse>
144
+ >(`${PATH}/forgot-password`, data);
145
+ return response.data.data;
146
+ },
147
+ });
148
+ };
149
+
150
+ /**
151
+ * Reset a user's password using a valid reset token.
152
+ */
153
+ export const useResetPassword = () => {
154
+ return useMutation({
155
+ mutationFn: async (data: ResetPasswordRequest): Promise<void> => {
156
+ await apiClient.post(`${PATH}/reset-password`, data);
157
+ },
158
+ });
159
+ };
@@ -3,6 +3,8 @@ export type OAuthProvider = "local" | "google" | "microsoft";
3
3
  export interface LoginResponse {
4
4
  access_token: string;
5
5
  refresh_token: string;
6
+ must_change_password?: boolean;
7
+ must_accept_terms?: boolean;
6
8
  }
7
9
 
8
10
  export interface UserCreatedResponse {
@@ -20,11 +22,35 @@ export interface UserLoginRequest {
20
22
 
21
23
  export interface RegisterUserRequest {
22
24
  email: string;
23
- name: string;
24
- username: string;
25
+ name?: string;
26
+ username?: string;
25
27
  password: string;
26
28
  }
27
29
 
30
+ export interface AdminRegisterUserRequest {
31
+ email: string;
32
+ name?: string | null;
33
+ role_ids?: number[];
34
+ }
35
+
36
+ export interface ChangePasswordRequest {
37
+ current_password: string;
38
+ new_password: string;
39
+ }
40
+
41
+ export interface ForgotPasswordRequest {
42
+ email: string;
43
+ }
44
+
45
+ export interface ResetPasswordRequest {
46
+ token: string;
47
+ new_password: string;
48
+ }
49
+
50
+ export interface ForgotPasswordResponse {
51
+ message: string;
52
+ }
53
+
28
54
  // I am making a safe assumption for your /me route based on "user_with_roles"
29
55
  export interface UserWithRoles {
30
56
  id: string;
@@ -6,6 +6,8 @@ import type {
6
6
  ReadyResponse,
7
7
  } from "../types/health";
8
8
 
9
+ const PATH = "";
10
+
9
11
  /**
10
12
  * Ping the server to check for basic connectivity.
11
13
  */
@@ -13,7 +15,9 @@ export const usePing = () => {
13
15
  return useQuery({
14
16
  queryKey: ["health", "ping"],
15
17
  queryFn: async (): Promise<PingResponse> => {
16
- const response = await apiClient.get<ApiResponse<PingResponse>>("/ping");
18
+ const response = await apiClient.get<ApiResponse<PingResponse>>(
19
+ `${PATH}/ping`,
20
+ );
17
21
  return response.data.data;
18
22
  },
19
23
  });
@@ -26,8 +30,9 @@ export const useHealth = () => {
26
30
  return useQuery({
27
31
  queryKey: ["health", "status"],
28
32
  queryFn: async (): Promise<HealthResponse> => {
29
- const response =
30
- await apiClient.get<ApiResponse<HealthResponse>>("/health");
33
+ const response = await apiClient.get<ApiResponse<HealthResponse>>(
34
+ `${PATH}/health`,
35
+ );
31
36
  return response.data.data;
32
37
  },
33
38
  });
@@ -40,8 +45,9 @@ export const useReady = () => {
40
45
  return useQuery({
41
46
  queryKey: ["health", "ready"],
42
47
  queryFn: async (): Promise<ReadyResponse> => {
43
- const response =
44
- await apiClient.get<ApiResponse<ReadyResponse>>("/ready");
48
+ const response = await apiClient.get<ApiResponse<ReadyResponse>>(
49
+ `${PATH}/ready`,
50
+ );
45
51
  return response.data.data;
46
52
  },
47
53
  });
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./roles";
5
5
  export * from "./permissions";
6
6
  export * from "./live_chat";
7
7
  export * from "./health";
8
+ export * from "./ticket";
8
9
 
9
10
  export { config } from "./config";
10
11
  export type { LibraryConfig } from "./config";
@@ -9,12 +9,15 @@ import type {
9
9
  AddPermissionRolesDTO,
10
10
  } from "../types/permission";
11
11
 
12
+ const PATH = "/permissions";
13
+
12
14
  export function usePermissions() {
13
15
  return useQuery<Permission[]>({
14
16
  queryKey: ["permissions"],
15
17
  queryFn: async () => {
16
- const response =
17
- await apiClient.get<ApiResponse<Permission[]>>("/permissions/");
18
+ const response = await apiClient.get<ApiResponse<Permission[]>>(
19
+ `${PATH}/`,
20
+ );
18
21
  return response.data.data;
19
22
  },
20
23
  });
@@ -25,7 +28,7 @@ export function usePermission(id: number) {
25
28
  queryKey: ["permissions", id],
26
29
  queryFn: async () => {
27
30
  const response = await apiClient.get<ApiResponse<Permission>>(
28
- `/permissions/${id}`,
31
+ `${PATH}/${id}`,
29
32
  );
30
33
  return response.data.data;
31
34
  },
@@ -38,7 +41,7 @@ export function useCreatePermission() {
38
41
  return useMutation<Permission, Error, CreatePermissionDTO>({
39
42
  mutationFn: async (dto) => {
40
43
  const response = await apiClient.post<ApiResponse<Permission>>(
41
- "/permissions/",
44
+ `${PATH}/`,
42
45
  dto,
43
46
  );
44
47
  return response.data.data;
@@ -58,7 +61,7 @@ export function useReplacePermission() {
58
61
  >({
59
62
  mutationFn: async ({ id, dto }) => {
60
63
  const response = await apiClient.put<ApiResponse<Permission>>(
61
- `/permissions/${id}`,
64
+ `${PATH}/${id}`,
62
65
  dto,
63
66
  );
64
67
  return response.data.data;
@@ -79,7 +82,7 @@ export function useUpdatePermission() {
79
82
  >({
80
83
  mutationFn: async ({ id, dto }) => {
81
84
  const response = await apiClient.patch<ApiResponse<Permission>>(
82
- `/permissions/${id}`,
85
+ `${PATH}/${id}`,
83
86
  dto,
84
87
  );
85
88
  return response.data.data;
@@ -96,7 +99,7 @@ export function useDeletePermission() {
96
99
  return useMutation<Permission, Error, number>({
97
100
  mutationFn: async (id) => {
98
101
  const response = await apiClient.delete<ApiResponse<Permission>>(
99
- `/permissions/${id}`,
102
+ `${PATH}/${id}`,
100
103
  );
101
104
  return response.data.data;
102
105
  },
@@ -112,7 +115,7 @@ export function usePermissionRoles(id: number) {
112
115
  queryKey: ["permissions", id, "roles"],
113
116
  queryFn: async () => {
114
117
  const response = await apiClient.get<ApiResponse<Permission>>(
115
- `/permissions/${id}/roles`,
118
+ `${PATH}/${id}/roles`,
116
119
  );
117
120
  return response.data.data;
118
121
  },
@@ -129,7 +132,7 @@ export function useAddPermissionRoles() {
129
132
  >({
130
133
  mutationFn: async ({ id, dto }) => {
131
134
  const response = await apiClient.post<ApiResponse<Permission>>(
132
- `/permissions/${id}/roles`,
135
+ `${PATH}/${id}/roles`,
133
136
  dto,
134
137
  );
135
138
  return response.data.data;
@@ -1,12 +1,20 @@
1
1
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
2
  import { apiClient, ApiResponse } from "../../api";
3
- import type { Role, CreateRoleDTO, ReplaceRoleDTO, UpdateRoleDTO, AddRolePermissionsDTO } from "../types/role";
3
+ import type {
4
+ Role,
5
+ CreateRoleDTO,
6
+ ReplaceRoleDTO,
7
+ UpdateRoleDTO,
8
+ AddRolePermissionsDTO,
9
+ } from "../types/role";
10
+
11
+ const PATH = "/roles";
4
12
 
5
13
  export function useRoles() {
6
14
  return useQuery<Role[]>({
7
15
  queryKey: ["roles"],
8
16
  queryFn: async () => {
9
- const response = await apiClient.get<ApiResponse<Role[]>>("/roles/");
17
+ const response = await apiClient.get<ApiResponse<Role[]>>(`${PATH}/`);
10
18
  return response.data.data;
11
19
  },
12
20
  });
@@ -16,7 +24,7 @@ export function useRole(id: number) {
16
24
  return useQuery<Role>({
17
25
  queryKey: ["roles", id],
18
26
  queryFn: async () => {
19
- const response = await apiClient.get<ApiResponse<Role>>(`/roles/${id}`);
27
+ const response = await apiClient.get<ApiResponse<Role>>(`${PATH}/${id}`);
20
28
  return response.data.data;
21
29
  },
22
30
  enabled: !!id,
@@ -27,7 +35,7 @@ export function useCreateRole() {
27
35
  const queryClient = useQueryClient();
28
36
  return useMutation<Role, Error, CreateRoleDTO>({
29
37
  mutationFn: async (dto) => {
30
- const response = await apiClient.post<ApiResponse<Role>>("/roles/", dto);
38
+ const response = await apiClient.post<ApiResponse<Role>>(`${PATH}/`, dto);
31
39
  return response.data.data;
32
40
  },
33
41
  onSuccess: () => {
@@ -40,7 +48,10 @@ export function useReplaceRole() {
40
48
  const queryClient = useQueryClient();
41
49
  return useMutation<Role, Error, { id: number; dto: ReplaceRoleDTO }>({
42
50
  mutationFn: async ({ id, dto }) => {
43
- const response = await apiClient.put<ApiResponse<Role>>(`/roles/${id}`, dto);
51
+ const response = await apiClient.put<ApiResponse<Role>>(
52
+ `${PATH}/${id}`,
53
+ dto,
54
+ );
44
55
  return response.data.data;
45
56
  },
46
57
  onSuccess: (_, { id }) => {
@@ -54,7 +65,10 @@ export function useUpdateRole() {
54
65
  const queryClient = useQueryClient();
55
66
  return useMutation<Role, Error, { id: number; dto: UpdateRoleDTO }>({
56
67
  mutationFn: async ({ id, dto }) => {
57
- const response = await apiClient.patch<ApiResponse<Role>>(`/roles/${id}`, dto);
68
+ const response = await apiClient.patch<ApiResponse<Role>>(
69
+ `${PATH}/${id}`,
70
+ dto,
71
+ );
58
72
  return response.data.data;
59
73
  },
60
74
  onSuccess: (_, { id }) => {
@@ -68,7 +82,9 @@ export function useDeleteRole() {
68
82
  const queryClient = useQueryClient();
69
83
  return useMutation<Role, Error, number>({
70
84
  mutationFn: async (id) => {
71
- const response = await apiClient.delete<ApiResponse<Role>>(`/roles/${id}`);
85
+ const response = await apiClient.delete<ApiResponse<Role>>(
86
+ `${PATH}/${id}`,
87
+ );
72
88
  return response.data.data;
73
89
  },
74
90
  onSuccess: (_, id) => {
@@ -82,7 +98,9 @@ export function useRolePermissions(id: number) {
82
98
  return useQuery<Role>({
83
99
  queryKey: ["roles", id, "permissions"],
84
100
  queryFn: async () => {
85
- const response = await apiClient.get<ApiResponse<Role>>(`/roles/${id}/permissions`);
101
+ const response = await apiClient.get<ApiResponse<Role>>(
102
+ `${PATH}/${id}/permissions`,
103
+ );
86
104
  return response.data.data;
87
105
  },
88
106
  enabled: !!id,
@@ -93,7 +111,10 @@ export function useAddRolePermissions() {
93
111
  const queryClient = useQueryClient();
94
112
  return useMutation<Role, Error, { id: number; dto: AddRolePermissionsDTO }>({
95
113
  mutationFn: async ({ id, dto }) => {
96
- const response = await apiClient.post<ApiResponse<Role>>(`/roles/${id}/permissions`, dto);
114
+ const response = await apiClient.post<ApiResponse<Role>>(
115
+ `${PATH}/${id}/permissions`,
116
+ dto,
117
+ );
97
118
  return response.data.data;
98
119
  },
99
120
  onSuccess: (_, { id }) => {
@@ -0,0 +1,77 @@
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { apiClient, ApiResponse } from "../../api";
3
+ import {
4
+ CreateTicketRequest,
5
+ CreateTicketResponse,
6
+ TicketResponse,
7
+ TicketSearchFilters,
8
+ UpdateTicketStatusRequest,
9
+ UpdateTicketStatusResponse,
10
+ } from "../types/ticket";
11
+
12
+ const PATH = "/tickets";
13
+
14
+ // Query keys
15
+ export const TICKET_KEYS = {
16
+ all: ["tickets"] as const,
17
+ list: (filters: TicketSearchFilters) =>
18
+ [...TICKET_KEYS.all, "list", filters] as const,
19
+ };
20
+
21
+ /** Get all tickets. */
22
+ export const useTickets = (filters: TicketSearchFilters = {}) => {
23
+ return useQuery({
24
+ queryKey: TICKET_KEYS.list(filters),
25
+ queryFn: async (): Promise<TicketResponse[]> => {
26
+ const response = await apiClient.get<ApiResponse<TicketResponse[]>>(
27
+ `${PATH}/`,
28
+ { params: filters },
29
+ );
30
+ return response.data.data;
31
+ },
32
+ });
33
+ };
34
+
35
+ /** Create a new ticket. */
36
+ export const useCreateTicket = () => {
37
+ const queryClient = useQueryClient();
38
+
39
+ return useMutation({
40
+ mutationFn: async (
41
+ payload: CreateTicketRequest,
42
+ ): Promise<CreateTicketResponse> => {
43
+ const response = await apiClient.post<ApiResponse<CreateTicketResponse>>(
44
+ `${PATH}/`,
45
+ payload,
46
+ );
47
+ return response.data.data;
48
+ },
49
+ onSuccess: () => {
50
+ // Invalidate tickets list queries on success
51
+ queryClient.invalidateQueries({ queryKey: TICKET_KEYS.all });
52
+ },
53
+ });
54
+ };
55
+
56
+ /** Update a ticket's status. */
57
+ export const useUpdateTicketStatus = () => {
58
+ const queryClient = useQueryClient();
59
+
60
+ return useMutation({
61
+ mutationFn: async ({
62
+ ticketId,
63
+ payload,
64
+ }: {
65
+ ticketId: string;
66
+ payload: UpdateTicketStatusRequest;
67
+ }): Promise<UpdateTicketStatusResponse> => {
68
+ const response = await apiClient.patch<
69
+ ApiResponse<UpdateTicketStatusResponse>
70
+ >(`${PATH}/${ticketId}/status`, payload);
71
+ return response.data.data;
72
+ },
73
+ onSuccess: () => {
74
+ queryClient.invalidateQueries({ queryKey: TICKET_KEYS.all });
75
+ },
76
+ });
77
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./types/ticket";
2
+ export * from "./hooks/useTickets";
@@ -0,0 +1,88 @@
1
+ export type TicketType = "issue" | "access" | "new_feature";
2
+ export type TicketCriticality = "high" | "medium" | "low";
3
+ export type TicketStatus =
4
+ | "open"
5
+ | "in_progress"
6
+ | "waiting_for_provider"
7
+ | "waiting_for_validation"
8
+ | "finished";
9
+
10
+ export interface CreateTicketRequest {
11
+ triage_id: string; // PydanticObjectId as string
12
+ type: TicketType;
13
+ criticality: TicketCriticality;
14
+ product: string;
15
+ description: string;
16
+ chat_ids: string[]; // List of PydanticObjectId as string
17
+ client_id: string; // UUID as string
18
+ }
19
+
20
+ export interface CreateTicketResponse {
21
+ id: string;
22
+ status: TicketStatus;
23
+ creation_date: string; // ISO datetime
24
+ }
25
+
26
+ export interface TicketSearchFilters {
27
+ ticket_id?: string;
28
+ client_id?: string;
29
+ triage_id?: string;
30
+ status?: TicketStatus;
31
+ criticality?: TicketCriticality;
32
+ type?: TicketType;
33
+ product?: string;
34
+ }
35
+
36
+ export interface TicketCompanyResponse {
37
+ id: string;
38
+ name: string;
39
+ }
40
+
41
+ export interface TicketClientResponse {
42
+ id: string;
43
+ name: string;
44
+ email: string;
45
+ company: TicketCompanyResponse;
46
+ }
47
+
48
+ export interface TicketHistoryResponse {
49
+ agent_id: string;
50
+ name: string;
51
+ level: string;
52
+ assignment_date: string;
53
+ exit_date: string;
54
+ transfer_reason: string;
55
+ }
56
+
57
+ export interface TicketCommentResponse {
58
+ comment_id: string;
59
+ author: string;
60
+ text: string;
61
+ date: string;
62
+ internal: boolean;
63
+ }
64
+
65
+ export interface TicketResponse {
66
+ id: string;
67
+ triage_id: string;
68
+ type: TicketType;
69
+ criticality: TicketCriticality;
70
+ product: string;
71
+ status: TicketStatus;
72
+ creation_date: string;
73
+ description: string;
74
+ chat_ids: string[];
75
+ agent_history: TicketHistoryResponse[];
76
+ client: TicketClientResponse;
77
+ comments: TicketCommentResponse[];
78
+ }
79
+
80
+ export interface UpdateTicketStatusRequest {
81
+ status: TicketStatus;
82
+ }
83
+
84
+ export interface UpdateTicketStatusResponse {
85
+ id: string;
86
+ previous_status: TicketStatus;
87
+ current_status: TicketStatus;
88
+ }