@vtex/faststore-plugin-buyer-portal 1.3.26 → 1.3.28

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/CHANGELOG.md +15 -1
  2. package/package.json +1 -1
  3. package/src/features/addresses/components/AddRecipientsDrawer/AddRecipientsDrawer.tsx +20 -2
  4. package/src/features/addresses/components/CreateAddressDrawer/CreateAddressDrawer.tsx +50 -4
  5. package/src/features/addresses/components/DeleteAddressDrawer/DeleteAddressDrawer.tsx +26 -2
  6. package/src/features/addresses/components/DeleteAddressLocationDrawer/DeleteAddressLocationDrawer.tsx +29 -3
  7. package/src/features/addresses/components/DeleteRecipientAddressDrawer/DeleteRecipientAddressDrawer.tsx +24 -2
  8. package/src/features/addresses/components/EditAddressDrawer/EditAddressDrawer.tsx +28 -2
  9. package/src/features/addresses/components/EditAddressLocationDrawer/EditAddressLocationDrawer.tsx +30 -4
  10. package/src/features/addresses/components/EditRecipientAddressDrawer/EditRecipientAddressDrawer.tsx +30 -2
  11. package/src/features/addresses/components/LocationsDrawer/LocationsDrawer.tsx +29 -2
  12. package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +32 -4
  13. package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +59 -4
  14. package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +28 -1
  15. package/src/features/buying-policies/components/AddBuyingPolicyDrawer/AddBuyingPolicyDrawer.tsx +48 -5
  16. package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/basic-buying-policy-drawer.scss +25 -18
  17. package/src/features/buying-policies/components/DeleteBuyingPolicyDrawer/DeleteBuyingPolicyDrawer.tsx +28 -2
  18. package/src/features/buying-policies/components/UpdateBuyingPolicyDrawer/UpdateBuyingPolicyDrawer.tsx +49 -5
  19. package/src/features/custom-fields/components/CreateCustomFieldValueDrawer/CreateCustomFieldValueDrawer.tsx +60 -5
  20. package/src/features/custom-fields/components/DeleteCustomFieldValueDrawer/DeleteCustomFieldValueDrawer.tsx +61 -5
  21. package/src/features/custom-fields/components/UpdateCustomFieldValueDrawer/UpdateCustomFieldValueDrawer.tsx +32 -3
  22. package/src/features/org-units/components/CreateOrgUnitDrawer/CreateOrgUnitDrawer.tsx +18 -0
  23. package/src/features/org-units/components/DeleteOrgUnitDrawer/DeleteOrgUnitDrawer.tsx +28 -0
  24. package/src/features/org-units/components/UpdateOrgUnitDrawer/UpdateOrgUnitDrawer.tsx +28 -0
  25. package/src/features/shared/hooks/analytics/types.ts +14 -0
  26. package/src/features/shared/hooks/analytics/useAnalytics.ts +249 -0
  27. package/src/features/shared/hooks/index.ts +1 -0
  28. package/src/features/shared/services/logger/analytics/analytics.ts +101 -0
  29. package/src/features/shared/services/logger/analytics/constants.ts +83 -0
  30. package/src/features/shared/services/logger/analytics/types.ts +108 -0
  31. package/src/features/shared/services/logger/index.ts +1 -0
  32. package/src/features/shared/utils/constants.ts +1 -1
  33. package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +24 -0
  34. package/src/features/users/components/DeleteUserDrawer/DeleteUserDrawer.tsx +23 -2
  35. package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +45 -4
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.28] - 2025-11-25
11
+
12
+ ### Added
13
+
14
+ - Setup business metrics and analytics events
15
+
16
+ ## [1.3.27] - 2025-11-25
17
+
18
+ ### Changed
19
+
20
+ - Style improvements and consistency updates to the `basic-buying-policy-drawer.scss` file.
21
+
10
22
  ## [1.3.26] - 2025-11-25
11
23
 
12
24
  ### Added
@@ -262,7 +274,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
262
274
  - Add CHANGELOG file
263
275
  - Add README file
264
276
 
265
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.26...HEAD
277
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.28...HEAD
266
278
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
267
279
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
268
280
  [1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
@@ -277,6 +289,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
277
289
 
278
290
  # <<<<<<< HEAD
279
291
 
292
+ [1.3.28]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.27...v1.3.28
293
+ [1.3.27]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.26...v1.3.27
280
294
  [1.3.26]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.25...v1.3.26
281
295
  [1.3.25]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.24...v1.3.25
282
296
  [1.3.24]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.23...v1.3.24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.3.26",
3
+ "version": "1.3.28",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -6,7 +6,8 @@ import { useUI } from "@faststore/ui";
6
6
 
7
7
  import { RecipientsForm } from "..";
8
8
  import { type BasicDrawerProps, BasicDrawer } from "../../../shared/components";
9
- import { useBuyerPortal } from "../../../shared/hooks";
9
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
10
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
10
11
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
11
12
  import { useAddRecipientsToAddress } from "../../hooks/useAddRecipientsToAddress";
12
13
 
@@ -30,12 +31,24 @@ export const AddRecipientsDrawer = ({
30
31
  const router = useRouter();
31
32
  const { pushToast } = useUI();
32
33
  const { currentOrgUnit } = useBuyerPortal();
34
+ const { trackEvent, trackEntityCreateError } = useAnalytics({
35
+ entityType: "recipient",
36
+ defaultTimerName: "recipient_creation",
37
+ shouldTrackDefaultTimer: true,
38
+ });
33
39
 
34
40
  const { addRecipientsToAddress, isAddRecipientToAddressLoading } =
35
41
  useAddRecipientsToAddress({
36
42
  onSuccess: () => {
37
43
  const hasTabParam = router.query?.tab === "recipient-tab";
38
44
 
45
+ trackEvent(ANALYTICS_EVENTS.RECIPIENT_CREATED, {
46
+ recipients_count: recipients.length,
47
+ recipient_names: recipients.map((r) => r.recipientName),
48
+ address_id: addressId ?? router.query.addressId,
49
+ org_unit_id: currentOrgUnit?.id,
50
+ });
51
+
39
52
  pushToast({
40
53
  message: "Recipients added successfully ",
41
54
  status: "INFO",
@@ -63,7 +76,12 @@ export const AddRecipientsDrawer = ({
63
76
  refetchRecipients?.();
64
77
  }
65
78
  },
66
- onError: () => {
79
+ onError: (error) => {
80
+ trackEntityCreateError(ANALYTICS_EVENTS.RECIPIENT_CREATE_ERROR, error, {
81
+ address_id: addressId ?? router.query.addressId,
82
+ org_unit_id: currentOrgUnit?.id,
83
+ });
84
+
67
85
  pushToast({
68
86
  message: "An error occurred while adding recipients",
69
87
  status: "ERROR",
@@ -12,7 +12,12 @@ import {
12
12
  import { TabBar } from "../../../shared/components/Tab/TabBar";
13
13
  import { TabContent } from "../../../shared/components/Tab/TabContent";
14
14
  import { TabOption } from "../../../shared/components/Tab/TabOption";
15
- import { useAddToScope, useBuyerPortal } from "../../../shared/hooks";
15
+ import {
16
+ useAddToScope,
17
+ useAnalytics,
18
+ useBuyerPortal,
19
+ } from "../../../shared/hooks";
20
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
16
21
  import { useCreateNewAddress } from "../../hooks";
17
22
  import { LocationInput } from "../../types/AddressData";
18
23
 
@@ -32,6 +37,13 @@ export const CreateAddressDrawer = ({
32
37
  const [isTouched, setIsTouched] = useState(false);
33
38
  const [useExistingAddress, setUseExistingAddress] = useState(false);
34
39
  const { currentOrgUnit } = useBuyerPortal();
40
+ const { trackEvent, trackEntityCreated, trackEntityCreateError } =
41
+ useAnalytics({
42
+ entityType: "address",
43
+ defaultTimerName: "address_creation",
44
+ shouldTrackDefaultTimer: true,
45
+ });
46
+
35
47
  const [completedAddress, setCompletedAddress] = useState<AddressData>(
36
48
  {} as AddressData
37
49
  );
@@ -52,8 +64,33 @@ export const CreateAddressDrawer = ({
52
64
  const [locations, setLocations] = useState<LocationInput[]>([{ name: "" }]);
53
65
  const [recipients, setRecipients] = useState<RecipientInput[]>([]);
54
66
  const [invalidAddress, setInvalidAddress] = useState(false);
67
+ const [currentTab, setCurrentTab] = useState<string>("address-tab");
68
+
69
+ const handleTabChange = (tabId: string) => {
70
+ if (currentTab !== tabId) {
71
+ trackEvent(ANALYTICS_EVENTS.ADDRESS_STEP_TIMING, {
72
+ flow_name: "address_creation",
73
+ from_step: currentTab,
74
+ to_step: tabId,
75
+ step_name: tabId,
76
+ org_unit_id: currentOrgUnit?.id,
77
+ });
78
+ setCurrentTab(tabId);
79
+ }
80
+ };
55
81
 
56
82
  const handleCreateNewAddressSuccess = () => {
83
+ trackEntityCreated(ANALYTICS_EVENTS.ADDRESS_CREATED, "address", {
84
+ address_name: address.name,
85
+ address_country: address.country,
86
+ address_city: address.city,
87
+ address_types: address.types,
88
+ locations_count: locations.filter((l) => l.name).length,
89
+ recipients_count: recipients.length,
90
+ use_existing_address: useExistingAddress,
91
+ org_unit_id: currentOrgUnit?.id,
92
+ });
93
+
57
94
  pushToast({
58
95
  message: "Address added successfully",
59
96
  status: "INFO",
@@ -64,7 +101,11 @@ export const CreateAddressDrawer = ({
64
101
 
65
102
  const { createNewAddress, isCreateNewAddressLoading } = useCreateNewAddress({
66
103
  onSuccess: handleCreateNewAddressSuccess,
67
- onError: () => {
104
+ onError: (error) => {
105
+ trackEntityCreateError(ANALYTICS_EVENTS.ADDRESS_CREATE_ERROR, error, {
106
+ org_unit_id: currentOrgUnit?.id,
107
+ });
108
+
68
109
  pushToast({
69
110
  message: "An error occurred while creating the address",
70
111
  status: "ERROR",
@@ -74,7 +115,12 @@ export const CreateAddressDrawer = ({
74
115
 
75
116
  const { addToScope } = useAddToScope({
76
117
  onSuccess: handleCreateNewAddressSuccess,
77
- onError: () => {
118
+ onError: (error) => {
119
+ trackEntityCreateError(ANALYTICS_EVENTS.ADDRESS_CREATE_ERROR, error, {
120
+ org_unit_id: currentOrgUnit?.id,
121
+ use_existing_address: true,
122
+ });
123
+
78
124
  pushToast({
79
125
  message: "An error occurred while creating the address",
80
126
  status: "ERROR",
@@ -124,7 +170,7 @@ export const CreateAddressDrawer = ({
124
170
  return (
125
171
  <BasicDrawer data-fs-bp-create-address-drawer close={close} {...props}>
126
172
  <BasicDrawer.Heading title="Add address" onClose={close} />
127
- <Tab defaultTabId="address-tab">
173
+ <Tab defaultTabId="address-tab" onChange={handleTabChange}>
128
174
  <TabBar>
129
175
  <TabOption id="address-tab">Details</TabOption>
130
176
  <TabOption id="location-tab">Locations</TabOption>
@@ -7,7 +7,8 @@ import {
7
7
  BasicDrawer,
8
8
  InputText,
9
9
  } from "../../../shared/components";
10
- import { useBuyerPortal } from "../../../shared/hooks";
10
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
11
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
11
12
  import { useDeleteAddress } from "../../hooks";
12
13
 
13
14
  export type DeleteAddressDrawerProps = Omit<BasicDrawerProps, "children"> & {
@@ -26,9 +27,22 @@ export const DeleteAddressDrawer = ({
26
27
  }: DeleteAddressDrawerProps) => {
27
28
  const { pushToast } = useUI();
28
29
  const { currentOrgUnit } = useBuyerPortal();
30
+ const { trackEvent } = useAnalytics({
31
+ entityType: "address",
32
+ entityId: addressId,
33
+ defaultTimerName: "address_delete",
34
+ shouldTrackDefaultTimer: true,
35
+ });
29
36
  const [addressNameConfirmation, setAddressNameConfirmation] = useState("");
30
37
 
31
38
  const handleDeleteSuccess = () => {
39
+ trackEvent(ANALYTICS_EVENTS.ADDRESS_DELETED, {
40
+ address_id: addressId,
41
+ address_name: addressName,
42
+ org_unit_id: currentOrgUnit?.id,
43
+ entity_type: "address",
44
+ });
45
+
32
46
  pushToast({
33
47
  message: "Address deleted successfully",
34
48
  status: "INFO",
@@ -39,7 +53,17 @@ export const DeleteAddressDrawer = ({
39
53
 
40
54
  const { deleteAddress, isDeleteAddressLoading } = useDeleteAddress({
41
55
  onSuccess: handleDeleteSuccess,
42
- onError: () => {
56
+ onError: (error) => {
57
+ const errorMessage = typeof error === "string" ? error : error.message;
58
+
59
+ trackEvent(ANALYTICS_EVENTS.ADDRESS_DELETE_ERROR, {
60
+ entity_type: "address",
61
+ entity_id: addressId,
62
+ address_name: addressName,
63
+ org_unit_id: currentOrgUnit?.id,
64
+ error_message: errorMessage,
65
+ });
66
+
43
67
  pushToast({
44
68
  message: "An error occurred while removing the address from the unit",
45
69
  status: "ERROR",
@@ -3,7 +3,8 @@ import { useState } from "react";
3
3
  import { useUI } from "@faststore/ui";
4
4
 
5
5
  import { BasicDrawer, BasicDrawerProps } from "../../../shared/components";
6
- import { useBuyerPortal } from "../../../shared/hooks";
6
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
7
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
7
8
  import { useAddressLocationDelete } from "../../hooks";
8
9
  import { LocationField } from "../LocationForm/LocationField/LocationField";
9
10
 
@@ -28,10 +29,24 @@ export const DeleteAddressLocationDrawer = ({
28
29
  const [confirmationValue, setConfirmationValue] = useState({ name: "" });
29
30
  const deleteIsDisable = confirmationValue.name !== location.value;
30
31
  const { currentOrgUnit } = useBuyerPortal();
32
+ const { trackEvent } = useAnalytics({
33
+ entityType: "location",
34
+ entityId: location.id,
35
+ defaultTimerName: "location_delete",
36
+ shouldTrackDefaultTimer: true,
37
+ });
31
38
 
32
39
  const { deleteLocationMutate, deleteLocationLoading } =
33
40
  useAddressLocationDelete({
34
41
  onSuccess: () => {
42
+ trackEvent(ANALYTICS_EVENTS.LOCATION_DELETED, {
43
+ location_id: location.id,
44
+ location_name: location.value,
45
+ address_id: location.auxId,
46
+ org_unit_id: currentOrgUnit?.id,
47
+ entity_type: "location",
48
+ });
49
+
35
50
  pushToast({
36
51
  message: "Location removed successfully",
37
52
  status: "INFO",
@@ -40,11 +55,22 @@ export const DeleteAddressLocationDrawer = ({
40
55
  onDeleteSuccess?.();
41
56
  close();
42
57
  },
43
- onError: () =>
58
+ onError: (error) => {
59
+ const errorMessage = typeof error === "string" ? error : error.message;
60
+
61
+ trackEvent(ANALYTICS_EVENTS.LOCATION_DELETE_ERROR, {
62
+ entity_type: "location",
63
+ entity_id: location.id,
64
+ location_name: location.value,
65
+ org_unit_id: currentOrgUnit?.id,
66
+ error_message: errorMessage,
67
+ });
68
+
44
69
  pushToast({
45
70
  message: "An error occurred while removing location",
46
71
  status: "ERROR",
47
- }),
72
+ });
73
+ },
48
74
  });
49
75
 
50
76
  const handleDeleteClick = () => {
@@ -9,7 +9,8 @@ import {
9
9
  BasicDrawer,
10
10
  InputText,
11
11
  } from "../../../shared/components";
12
- import { useBuyerPortal } from "../../../shared/hooks";
12
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
13
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
13
14
  import { useDeleteRecipientAddress } from "../../hooks/useDeleteRecipientAddress";
14
15
 
15
16
  export type DeleteRecipientAddressDrawerProps = Omit<
@@ -32,10 +33,24 @@ export const DeleteRecipientAddressDrawer = ({
32
33
  const { pushToast } = useUI();
33
34
  const router = useRouter();
34
35
  const { currentOrgUnit } = useBuyerPortal();
36
+ const { trackEvent } = useAnalytics({
37
+ entityType: "recipient",
38
+ entityId: recipientAddressId,
39
+ defaultTimerName: "recipient_delete",
40
+ shouldTrackDefaultTimer: true,
41
+ });
35
42
  const [recipientNameConfirmation, setRecipientNameConfirmation] =
36
43
  useState("");
37
44
 
38
45
  const handleDeleteSuccess = () => {
46
+ trackEvent(ANALYTICS_EVENTS.RECIPIENT_DELETED, {
47
+ recipient_id: recipientAddressId,
48
+ recipient_name: recipientAddressName,
49
+ address_id: router.query.addressId as string,
50
+ org_unit_id: currentOrgUnit?.id,
51
+ entity_type: "recipient",
52
+ });
53
+
39
54
  pushToast({
40
55
  message: "Recipient deleted successfully",
41
56
  status: "INFO",
@@ -47,7 +62,14 @@ export const DeleteRecipientAddressDrawer = ({
47
62
  const { deleteRecipientAddress, isDeleteRecipientAddressLoading } =
48
63
  useDeleteRecipientAddress({
49
64
  onSuccess: handleDeleteSuccess,
50
- onError: () => {
65
+ onError: (error) => {
66
+ trackEvent(ANALYTICS_EVENTS.RECIPIENT_DELETE_ERROR, {
67
+ entity_id: recipientAddressId,
68
+ recipient_name: recipientAddressName,
69
+ error_message: error instanceof Error ? error.message : String(error),
70
+ org_unit_id: currentOrgUnit?.id,
71
+ });
72
+
51
73
  pushToast({
52
74
  message:
53
75
  "An error occurred while removing the recipient from address",
@@ -4,7 +4,8 @@ import { useUI } from "@faststore/ui";
4
4
 
5
5
  import { AddressForm } from "..";
6
6
  import { type BasicDrawerProps, BasicDrawer } from "../../../shared/components";
7
- import { useBuyerPortal } from "../../../shared/hooks";
7
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
8
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
8
9
  import { useEditAddress } from "../../hooks";
9
10
 
10
11
  import type { AddressInput, AddressData } from "../../types";
@@ -24,6 +25,12 @@ export const EditAddressDrawer = ({
24
25
  const { pushToast } = useUI();
25
26
  const [isTouched, setIsTouched] = useState(false);
26
27
  const { currentOrgUnit } = useBuyerPortal();
28
+ const { trackEntityEdited, trackEntityEditError } = useAnalytics({
29
+ entityType: "address",
30
+ entityId: currentAddress.id,
31
+ defaultTimerName: "address_edit",
32
+ shouldTrackDefaultTimer: true,
33
+ });
27
34
 
28
35
  const [address, setAddress] = useState<AddressInput>({
29
36
  ...currentAddress,
@@ -33,6 +40,15 @@ export const EditAddressDrawer = ({
33
40
  const [, setInvalidAddress] = useState(false);
34
41
 
35
42
  const handleUpdatedAddressSuccess = () => {
43
+ trackEntityEdited(ANALYTICS_EVENTS.ADDRESS_EDITED, {
44
+ address_id: currentAddress.id,
45
+ address_name: address.name,
46
+ address_country: address.country,
47
+ address_city: address.city,
48
+ address_types: address.types,
49
+ org_unit_id: currentOrgUnit?.id,
50
+ });
51
+
36
52
  pushToast({
37
53
  message: "Address updated successfully",
38
54
  status: "INFO",
@@ -43,7 +59,17 @@ export const EditAddressDrawer = ({
43
59
 
44
60
  const { editAddress, isEditAddressLoading } = useEditAddress({
45
61
  onSuccess: handleUpdatedAddressSuccess,
46
- onError: () => {
62
+ onError: (error) => {
63
+ trackEntityEditError(
64
+ ANALYTICS_EVENTS.ADDRESS_EDIT_ERROR,
65
+ "address",
66
+ currentAddress.id ?? "",
67
+ error,
68
+ {
69
+ org_unit_id: currentOrgUnit?.id,
70
+ }
71
+ );
72
+
47
73
  pushToast({
48
74
  message: "An error occurred while updating the address",
49
75
  status: "ERROR",
@@ -1,9 +1,10 @@
1
- import { useEffect, useState } from "react";
1
+ import { useState, useEffect } from "react";
2
2
 
3
3
  import { CheckboxField, useUI } from "@faststore/ui";
4
4
 
5
5
  import { BasicDrawer, BasicDrawerProps } from "../../../shared/components";
6
- import { useBuyerPortal } from "../../../shared/hooks";
6
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
7
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
7
8
  import { LOCAL_STORAGE_LOCATION_EDIT_KEY } from "../../../shared/utils/constants";
8
9
  import { useAddressLocationEdit } from "../../hooks";
9
10
  import { LocationField } from "../LocationForm/LocationField/LocationField";
@@ -38,9 +39,23 @@ export const EditAddressLocationDrawer = ({
38
39
  const saveIsDisable = location.value === field.name;
39
40
  const drawerTitle = confirmView ? "Confirm location update" : "Edit location";
40
41
  const { currentOrgUnit } = useBuyerPortal();
42
+ const { trackEntityEdited, trackEntityEditError } = useAnalytics({
43
+ entityType: "location",
44
+ entityId: location.id,
45
+ defaultTimerName: "location_edit",
46
+ shouldTrackDefaultTimer: true,
47
+ });
41
48
 
42
49
  const { editLocationMutate, editLocationLoading } = useAddressLocationEdit({
43
50
  onSuccess: () => {
51
+ trackEntityEdited(ANALYTICS_EVENTS.LOCATION_EDITED, {
52
+ location_id: location.id,
53
+ location_old_name: location.value,
54
+ location_new_name: field.name,
55
+ address_id: location.auxId,
56
+ org_unit_id: currentOrgUnit?.id,
57
+ });
58
+
44
59
  pushToast({
45
60
  message: "Location edited successfully",
46
61
  status: "INFO",
@@ -49,11 +64,22 @@ export const EditAddressLocationDrawer = ({
49
64
  onEditSuccess();
50
65
  close();
51
66
  },
52
- onError: () =>
67
+ onError: (error) => {
68
+ trackEntityEditError(
69
+ ANALYTICS_EVENTS.LOCATION_EDIT_ERROR,
70
+ "location",
71
+ location.id,
72
+ error,
73
+ {
74
+ org_unit_id: currentOrgUnit?.id,
75
+ }
76
+ );
77
+
53
78
  pushToast({
54
79
  message: "An error occurred while editing the location",
55
80
  status: "ERROR",
56
- }),
81
+ });
82
+ },
57
83
  });
58
84
 
59
85
  const handleSaveClick = () => {
@@ -5,7 +5,8 @@ import { useRouter } from "next/router";
5
5
  import { CheckboxField, useUI } from "@faststore/ui";
6
6
 
7
7
  import { type BasicDrawerProps, BasicDrawer } from "../../../shared/components";
8
- import { useBuyerPortal } from "../../../shared/hooks";
8
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
9
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
9
10
  import { LOCAL_STORAGE_RECIPIENT_EDIT_KEY } from "../../../shared/utils/constants";
10
11
  import { useEditRecipientsToAddress } from "../../hooks/useEditRecipientsToAddress";
11
12
  import { type RecipientData, RecipientInput } from "../../types";
@@ -29,6 +30,13 @@ export const EditRecipientAddressDrawer = ({
29
30
  const { pushToast } = useUI();
30
31
  const router = useRouter();
31
32
  const { currentOrgUnit } = useBuyerPortal();
33
+ const { trackEntityEdited, trackEntityEditError } = useAnalytics({
34
+ entityType: "recipient",
35
+ entityId: recipient.id,
36
+ defaultTimerName: "recipient_edit",
37
+ shouldTrackDefaultTimer: true,
38
+ });
39
+
32
40
  const initialRecipientName = recipient.firstName + " " + recipient.lastName;
33
41
  const [newRecipient, setNewRecipient] = useState({
34
42
  name: initialRecipientName,
@@ -45,6 +53,16 @@ export const EditRecipientAddressDrawer = ({
45
53
  }, []);
46
54
 
47
55
  const handleEditSuccess = () => {
56
+ trackEntityEdited(ANALYTICS_EVENTS.RECIPIENT_EDITED, {
57
+ recipient_id: recipient.id,
58
+ recipient_old_name: initialRecipientName,
59
+ recipient_new_name: newRecipient.name,
60
+ recipient_old_phone: recipient.phone,
61
+ recipient_new_phone: newRecipient.phone,
62
+ address_id: router.query.addressId as string,
63
+ org_unit_id: currentOrgUnit?.id,
64
+ });
65
+
48
66
  pushToast({
49
67
  message: "Recipient edited successfully",
50
68
  status: "INFO",
@@ -56,7 +74,17 @@ export const EditRecipientAddressDrawer = ({
56
74
  const { editRecipientsToAddress, isEditRecipientToAddressLoading } =
57
75
  useEditRecipientsToAddress({
58
76
  onSuccess: handleEditSuccess,
59
- onError: () => {
77
+ onError: (error) => {
78
+ trackEntityEditError(
79
+ ANALYTICS_EVENTS.RECIPIENT_EDIT_ERROR,
80
+ "recipient",
81
+ recipient.id,
82
+ error,
83
+ {
84
+ org_unit_id: currentOrgUnit?.id,
85
+ }
86
+ );
87
+
60
88
  pushToast({
61
89
  message: "An error occurred while editing the recipient",
62
90
  status: "ERROR",
@@ -6,7 +6,8 @@ import { useUI } from "@faststore/ui";
6
6
 
7
7
  import { LocationForm } from "../";
8
8
  import { type BasicDrawerProps, BasicDrawer } from "../../../shared/components";
9
- import { useBuyerPortal } from "../../../shared/hooks";
9
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
10
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
10
11
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
11
12
  import { useAddLocationsToAddress } from "../../hooks";
12
13
 
@@ -28,13 +29,34 @@ export const LocationDrawer = ({
28
29
  const [locations, setLocations] = useState<LocationInput[]>([{ name: "" }]);
29
30
  const { pushToast } = useUI();
30
31
  const router = useRouter();
32
+ const { trackEvent, trackEntityCreated, trackEntityCreateError } =
33
+ useAnalytics({
34
+ entityType: "location",
35
+ defaultTimerName: "location_creation",
36
+ shouldTrackDefaultTimer: true,
37
+ });
31
38
 
32
39
  const saveIsDisabled = locations.filter(({ name }) => !!name).length === 0;
33
40
 
34
41
  const { addLocationsMutate, addLocationsLoading } = useAddLocationsToAddress({
35
42
  onSuccess: () => {
43
+ const validLocations = locations.filter(({ name }) => !!name);
36
44
  const hasTabParam = router.query?.tab === "location-tab";
37
45
 
46
+ trackEvent(ANALYTICS_EVENTS.LOCATION_CREATED, {
47
+ locations_count: validLocations.length,
48
+ location_names: validLocations.map((l) => l.name),
49
+ address_id: addressId ?? router.query.addressId,
50
+ org_unit_id: currentOrgUnit?.id,
51
+ });
52
+
53
+ trackEntityCreated(ANALYTICS_EVENTS.LOCATION_CREATED, "location", {
54
+ locations_count: validLocations.length,
55
+ location_names: validLocations.map((l) => l.name),
56
+ address_id: addressId ?? router.query.addressId,
57
+ org_unit_id: currentOrgUnit?.id,
58
+ });
59
+
38
60
  pushToast({
39
61
  message: "Locations added successfully ",
40
62
  status: "INFO",
@@ -62,7 +84,12 @@ export const LocationDrawer = ({
62
84
  onAddLocationSuccess?.();
63
85
  }
64
86
  },
65
- onError: () => {
87
+ onError: (error) => {
88
+ trackEntityCreateError(ANALYTICS_EVENTS.LOCATION_CREATE_ERROR, error, {
89
+ address_id: addressId ?? router.query.addressId,
90
+ org_unit_id: currentOrgUnit?.id,
91
+ });
92
+
66
93
  pushToast({
67
94
  message: "An error occurred while adding locations",
68
95
  status: "ERROR",
@@ -10,7 +10,8 @@ import {
10
10
  ErrorMessage,
11
11
  InputText,
12
12
  } from "../../../shared/components";
13
- import { useDebounce } from "../../../shared/hooks";
13
+ import { useAnalytics, useDebounce } from "../../../shared/hooks";
14
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
14
15
  import { useDeleteBudget } from "../../hooks/useDeleteBudget";
15
16
 
16
17
  export interface BudgetDeleteDrawerProps
@@ -33,6 +34,12 @@ export function BudgetDeleteDrawer({
33
34
  }: BudgetDeleteDrawerProps) {
34
35
  const { pushToast } = useUI();
35
36
  const router = useRouter();
37
+ const { trackEvent } = useAnalytics({
38
+ entityType: "budget",
39
+ entityId: budgetId,
40
+ defaultTimerName: "budget_delete",
41
+ shouldTrackDefaultTimer: true,
42
+ });
36
43
 
37
44
  const [inputValue, setInputValue] = useState<string>("");
38
45
  const [isTouched, setIsTouched] = useState<boolean>(false);
@@ -47,21 +54,42 @@ export function BudgetDeleteDrawer({
47
54
  return debouncedValue && debouncedValue !== budgetName
48
55
  ? "Budget name is invalid"
49
56
  : null;
50
- }, [debouncedValue, isTouched]);
57
+ }, [debouncedValue, isTouched, inputValue, budgetName]);
51
58
 
52
59
  const { mutate: deleteBudget, isLoading: isLoadingDeleteBudget } =
53
60
  useDeleteBudget({
54
61
  options: {
55
62
  onSuccess: handleDeleteSuccess,
56
- onError: () =>
63
+ onError: (error: Error) => {
64
+ const errorMessage =
65
+ typeof error === "string" ? error : error.message;
66
+
67
+ trackEvent(ANALYTICS_EVENTS.BUDGET_DELETE_ERROR, {
68
+ entity_type: "budget",
69
+ entity_id: budgetId,
70
+ budget_name: budgetName,
71
+ org_unit_id: orgUnitId,
72
+ contract_id: contractId,
73
+ error_message: errorMessage,
74
+ });
75
+
57
76
  pushToast({
58
77
  status: "ERROR",
59
78
  message: "An error occurred while removing the budget",
60
- }),
79
+ });
80
+ },
61
81
  },
62
82
  });
63
83
 
64
84
  function handleDeleteSuccess() {
85
+ trackEvent(ANALYTICS_EVENTS.BUDGET_DELETED, {
86
+ budget_id: budgetId,
87
+ budget_name: budgetName,
88
+ org_unit_id: orgUnitId,
89
+ contract_id: contractId,
90
+ entity_type: "budget",
91
+ });
92
+
65
93
  pushToast({
66
94
  status: "INFO",
67
95
  message: "Budget deleted successfully",