@adventurelabs/scout-core 1.0.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.
Files changed (90) hide show
  1. package/README.md +90 -0
  2. package/dist/api_keys/actions.d.ts +2 -0
  3. package/dist/api_keys/actions.js +20 -0
  4. package/dist/api_keys/index.d.ts +1 -0
  5. package/dist/api_keys/index.js +1 -0
  6. package/dist/constants/annotator.d.ts +1 -0
  7. package/dist/constants/annotator.js +11 -0
  8. package/dist/constants/app.d.ts +2 -0
  9. package/dist/constants/app.js +2 -0
  10. package/dist/constants/index.d.ts +2 -0
  11. package/dist/constants/index.js +2 -0
  12. package/dist/helpers/auth.d.ts +1 -0
  13. package/dist/helpers/auth.js +8 -0
  14. package/dist/helpers/bounding_boxes.d.ts +5 -0
  15. package/dist/helpers/bounding_boxes.js +43 -0
  16. package/dist/helpers/db.d.ts +3 -0
  17. package/dist/helpers/db.js +22 -0
  18. package/dist/helpers/devices.d.ts +8 -0
  19. package/dist/helpers/devices.js +92 -0
  20. package/dist/helpers/email.d.ts +7 -0
  21. package/dist/helpers/email.js +22 -0
  22. package/dist/helpers/events.d.ts +6 -0
  23. package/dist/helpers/events.js +71 -0
  24. package/dist/helpers/finance.d.ts +9 -0
  25. package/dist/helpers/finance.js +16 -0
  26. package/dist/helpers/gps.d.ts +5 -0
  27. package/dist/helpers/gps.js +11 -0
  28. package/dist/helpers/herds.d.ts +9 -0
  29. package/dist/helpers/herds.js +80 -0
  30. package/dist/helpers/index.d.ts +17 -0
  31. package/dist/helpers/index.js +17 -0
  32. package/dist/helpers/location.d.ts +1 -0
  33. package/dist/helpers/location.js +3 -0
  34. package/dist/helpers/plans.d.ts +5 -0
  35. package/dist/helpers/plans.js +61 -0
  36. package/dist/helpers/tags.d.ts +7 -0
  37. package/dist/helpers/tags.js +158 -0
  38. package/dist/helpers/time.d.ts +3 -0
  39. package/dist/helpers/time.js +11 -0
  40. package/dist/helpers/ui.d.ts +4 -0
  41. package/dist/helpers/ui.js +12 -0
  42. package/dist/helpers/users.d.ts +6 -0
  43. package/dist/helpers/users.js +66 -0
  44. package/dist/helpers/web.d.ts +1 -0
  45. package/dist/helpers/web.js +7 -0
  46. package/dist/helpers/zones.d.ts +18 -0
  47. package/dist/helpers/zones.js +108 -0
  48. package/dist/hooks/index.d.ts +2 -0
  49. package/dist/hooks/index.js +2 -0
  50. package/dist/hooks/useScoutDbListener.d.ts +1 -0
  51. package/dist/hooks/useScoutDbListener.js +94 -0
  52. package/dist/hooks/useScoutRefresh.d.ts +7 -0
  53. package/dist/hooks/useScoutRefresh.js +45 -0
  54. package/dist/index.d.ts +38 -0
  55. package/dist/index.js +43 -0
  56. package/dist/providers/ScoutRefreshProvider.d.ts +1 -0
  57. package/dist/providers/ScoutRefreshProvider.js +8 -0
  58. package/dist/providers/index.d.ts +1 -0
  59. package/dist/providers/index.js +1 -0
  60. package/dist/store/hooks.d.ts +1 -0
  61. package/dist/store/hooks.js +3 -0
  62. package/dist/store/index.d.ts +1 -0
  63. package/dist/store/index.js +1 -0
  64. package/dist/store/scout.d.ts +103 -0
  65. package/dist/store/scout.js +196 -0
  66. package/dist/supabase/client.d.ts +1 -0
  67. package/dist/supabase/client.js +5 -0
  68. package/dist/supabase/index.d.ts +3 -0
  69. package/dist/supabase/index.js +3 -0
  70. package/dist/supabase/middleware.d.ts +2 -0
  71. package/dist/supabase/middleware.js +46 -0
  72. package/dist/supabase/server.d.ts +636 -0
  73. package/dist/supabase/server.js +22 -0
  74. package/dist/types/bounding_boxes.d.ts +27 -0
  75. package/dist/types/bounding_boxes.js +11 -0
  76. package/dist/types/db.d.ts +42 -0
  77. package/dist/types/db.js +15 -0
  78. package/dist/types/gps.d.ts +21 -0
  79. package/dist/types/gps.js +13 -0
  80. package/dist/types/herd_module.d.ts +31 -0
  81. package/dist/types/herd_module.js +82 -0
  82. package/dist/types/index.d.ts +6 -0
  83. package/dist/types/index.js +6 -0
  84. package/dist/types/requests.d.ts +18 -0
  85. package/dist/types/requests.js +25 -0
  86. package/dist/types/supabase.d.ts +754 -0
  87. package/dist/types/supabase.js +25 -0
  88. package/dist/types/ui.d.ts +7 -0
  89. package/dist/types/ui.js +8 -0
  90. package/package.json +52 -0
@@ -0,0 +1,61 @@
1
+ "use server";
2
+ import { newServerClient } from "../supabase/server";
3
+ import { EnumWebResponse, IWebResponse, } from "../types/requests";
4
+ // function that fetches the plans our db given a herd id
5
+ export async function server_get_plans_by_herd(herd_id) {
6
+ const supabase = await newServerClient();
7
+ const { data, error } = await supabase
8
+ .from("plans")
9
+ .select("*")
10
+ .eq("herd_id", herd_id);
11
+ if (error) {
12
+ return {
13
+ status: EnumWebResponse.ERROR,
14
+ msg: error.message,
15
+ data: null,
16
+ };
17
+ }
18
+ else {
19
+ return IWebResponse.success(data).to_compatible();
20
+ }
21
+ }
22
+ // function that uploads plan to our db
23
+ export async function server_create_plans(plans) {
24
+ // loop through plans and format
25
+ let formatted_plans = plans.map((plan) => {
26
+ let formatted_plan = { ...plan };
27
+ delete formatted_plan.id;
28
+ delete formatted_plan.inserted_at;
29
+ return formatted_plan;
30
+ });
31
+ const supabase = await newServerClient();
32
+ // insert data and return the response
33
+ const { data, error } = await supabase
34
+ .from("plans")
35
+ .insert(formatted_plans)
36
+ .select("*");
37
+ if (error) {
38
+ return {
39
+ status: EnumWebResponse.ERROR,
40
+ msg: error.message,
41
+ data: null,
42
+ };
43
+ }
44
+ else {
45
+ return IWebResponse.success(data).to_compatible();
46
+ }
47
+ }
48
+ export async function server_delete_plans_by_ids(plan_ids) {
49
+ const supabase = await newServerClient();
50
+ const { error } = await supabase.from("plans").delete().in("id", plan_ids);
51
+ if (error) {
52
+ return {
53
+ status: EnumWebResponse.ERROR,
54
+ msg: error.message,
55
+ data: false,
56
+ };
57
+ }
58
+ else {
59
+ return IWebResponse.success(true).to_compatible();
60
+ }
61
+ }
@@ -0,0 +1,7 @@
1
+ import { IEventWithTags, ITag } from "../types/db";
2
+ import { IWebResponseCompatible } from "../types/requests";
3
+ export declare function server_create_tags(tags: ITag[]): Promise<IWebResponseCompatible<ITag[]>>;
4
+ export declare function server_delete_tags_by_ids(tag_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
5
+ export declare function server_get_more_events_with_tags_by_herd(herd_id: number, offset: number, page_count?: number): Promise<IWebResponseCompatible<IEventWithTags[]>>;
6
+ export declare function server_get_events_and_tags_for_device(device_id: number, limit?: number): Promise<IWebResponseCompatible<IEventWithTags[]>>;
7
+ export declare function get_event_and_tags_by_event_id(event_id: number): Promise<IWebResponseCompatible<IEventWithTags>>;
@@ -0,0 +1,158 @@
1
+ "use server";
2
+ // add tag to db
3
+ import { newServerClient } from "../supabase/server";
4
+ import { EnumWebResponse, IWebResponse, } from "../types/requests";
5
+ export async function server_create_tags(tags) {
6
+ const supabase = await newServerClient();
7
+ // remove id key from tags
8
+ const formatted_tags = tags.map((tag) => {
9
+ const { id, ...rest } = tag;
10
+ return {
11
+ ...rest,
12
+ observation_type: rest.observation_type,
13
+ };
14
+ });
15
+ const { data, error } = await supabase
16
+ .from("tags")
17
+ .insert(formatted_tags)
18
+ .select("*");
19
+ if (error) {
20
+ return {
21
+ status: EnumWebResponse.ERROR,
22
+ msg: error.message,
23
+ data: null,
24
+ };
25
+ }
26
+ else {
27
+ return IWebResponse.success(data).to_compatible();
28
+ }
29
+ }
30
+ export async function server_delete_tags_by_ids(tag_ids) {
31
+ const supabase = await newServerClient();
32
+ const { error } = await supabase.from("tags").delete().in("id", tag_ids);
33
+ if (error) {
34
+ return {
35
+ status: EnumWebResponse.ERROR,
36
+ msg: error.message,
37
+ data: false,
38
+ };
39
+ }
40
+ else {
41
+ return IWebResponse.success(true).to_compatible();
42
+ }
43
+ }
44
+ // export async function server_get_events_with_tags_by_herd(
45
+ // herd_id: number
46
+ // ): Promise<IWebResponseCompatible<IEventWithTags[]>> {
47
+ // const supabase = await newServerClient();
48
+ // const { data, error } = await supabase
49
+ // .from("events")
50
+ // .select(
51
+ // `
52
+ // *,
53
+ // tags: tags (*)
54
+ // `
55
+ // )
56
+ // .eq("devices.herd_id", herd_id)
57
+ // .order("timestamp_observation", { ascending: false });
58
+ // if (error) {
59
+ // return {
60
+ // status: EnumWebResponse.ERROR,
61
+ // msg: error.message,
62
+ // data: [],
63
+ // };
64
+ // }
65
+ // return IWebResponse.success(data).to_compatible();
66
+ // }
67
+ export async function server_get_more_events_with_tags_by_herd(herd_id, offset, page_count = 10) {
68
+ const from = offset * page_count;
69
+ const to = from + page_count - 1;
70
+ const supabase = await newServerClient();
71
+ // make rpc call to get_events_with_tags_for_herd(herd_id, offset, limit)
72
+ const { data, error } = await supabase.rpc("get_events_and_tags_for_herd", {
73
+ herd_id_caller: herd_id,
74
+ offset_caller: from,
75
+ limit_caller: page_count,
76
+ });
77
+ if (error) {
78
+ console.warn("Error fetching events with tags by herd:", error.message);
79
+ return {
80
+ status: EnumWebResponse.ERROR,
81
+ msg: error.message,
82
+ data: [],
83
+ };
84
+ }
85
+ // iterate through data if tags contains null, remove it
86
+ const filtered_data = data.map((event) => {
87
+ if (!event.tags)
88
+ return event;
89
+ event.tags = event.tags.filter((tag) => tag !== null);
90
+ return event;
91
+ });
92
+ return IWebResponse.success(filtered_data).to_compatible();
93
+ }
94
+ export async function server_get_events_and_tags_for_device(device_id, limit = 3) {
95
+ const supabase = await newServerClient();
96
+ // make rpc call to get_events_with_tags_for_device(device_id, limit)
97
+ const { data, error } = await supabase.rpc("get_events_and_tags_for_device", {
98
+ device_id_caller: device_id,
99
+ limit_caller: limit,
100
+ });
101
+ if (error) {
102
+ console.warn("Error fetching recent events with tags by device:", error.message);
103
+ return {
104
+ status: EnumWebResponse.ERROR,
105
+ msg: error.message,
106
+ data: [],
107
+ };
108
+ }
109
+ return IWebResponse.success(data).to_compatible();
110
+ }
111
+ export async function get_event_and_tags_by_event_id(event_id) {
112
+ const supabase = await newServerClient();
113
+ // use actual sql query to get event and tags instead of rpc
114
+ const { data, error } = await supabase
115
+ .from("events")
116
+ .select(`
117
+ *,
118
+ tags: tags (*)
119
+ `)
120
+ .eq("id", event_id);
121
+ if (error) {
122
+ console.warn("Error fetching event with tags by event id:", error.message);
123
+ return {
124
+ status: EnumWebResponse.ERROR,
125
+ msg: error.message,
126
+ data: null,
127
+ };
128
+ }
129
+ if (!data[0]) {
130
+ return {
131
+ status: EnumWebResponse.ERROR,
132
+ msg: "Event not found",
133
+ data: null,
134
+ };
135
+ }
136
+ // Transform location to latitude/longitude
137
+ const transformedData = {
138
+ id: data[0].id,
139
+ inserted_at: data[0].inserted_at,
140
+ message: data[0].message,
141
+ media_url: data[0].media_url,
142
+ latitude: data[0].location
143
+ ? data[0].location.coordinates[1]
144
+ : null,
145
+ longitude: data[0].location
146
+ ? data[0].location.coordinates[0]
147
+ : null,
148
+ altitude: data[0].altitude,
149
+ heading: data[0].heading,
150
+ media_type: data[0].media_type,
151
+ device_id: data[0].device_id,
152
+ timestamp_observation: data[0].timestamp_observation,
153
+ is_public: data[0].is_public,
154
+ tags: data[0].tags || [],
155
+ earthranger_url: data[0].earthranger_url,
156
+ };
157
+ return IWebResponse.success(transformedData).to_compatible();
158
+ }
@@ -0,0 +1,3 @@
1
+ export declare function convertSecondsSinceEpochToDate(secondsSinceEpoch: number): Date;
2
+ export declare function convertIsoStringToDate(isoString: string): Date;
3
+ export declare function convertDateToTimeString(date: Date): string;
@@ -0,0 +1,11 @@
1
+ export function convertSecondsSinceEpochToDate(secondsSinceEpoch) {
2
+ return new Date(secondsSinceEpoch * 1000);
3
+ }
4
+ // convert iso ISO 8601 string to date
5
+ export function convertIsoStringToDate(isoString) {
6
+ return new Date(isoString);
7
+ }
8
+ // convert date to time string of format "HH:MM:SS, DD/MM/YYYY"
9
+ export function convertDateToTimeString(date) {
10
+ return `${date.toLocaleTimeString()}, ${date.toLocaleDateString()} UTC`;
11
+ }
@@ -0,0 +1,4 @@
1
+ import { Role } from "../types/db";
2
+ export declare function formatRoleCasing(role: Role): string;
3
+ export declare function formatUsernameInitials(username: string): string;
4
+ export declare function roundToXDecimals(num: number, decimals: number): number;
@@ -0,0 +1,12 @@
1
+ export function formatRoleCasing(role) {
2
+ return role.charAt(0).toUpperCase() + role.slice(1);
3
+ }
4
+ export function formatUsernameInitials(username) {
5
+ if (!username) {
6
+ return "";
7
+ }
8
+ return username.slice(0, 2).toUpperCase();
9
+ }
10
+ export function roundToXDecimals(num, decimals) {
11
+ return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
12
+ }
@@ -0,0 +1,6 @@
1
+ import { IUser, IUserAndRole, IUserRolePerHerd, Role } from "../types/db";
2
+ import { IWebResponseCompatible } from "../types/requests";
3
+ export declare function server_get_user_roles(herd_id: number): Promise<IWebResponseCompatible<IUserRolePerHerd[]>>;
4
+ export declare function server_get_user(): Promise<IWebResponseCompatible<IUser | null>>;
5
+ export declare function server_get_users_with_herd_access(herd_id: number): Promise<IWebResponseCompatible<IUserAndRole[] | null>>;
6
+ export declare function server_upsert_user_with_role(herd_id: number, username: string, role: Role): Promise<IWebResponseCompatible<IUserAndRole | null>>;
@@ -0,0 +1,66 @@
1
+ "use server";
2
+ import { newServerClient } from "../supabase/server";
3
+ import { IWebResponse, EnumWebResponse, } from "../types/requests";
4
+ export async function server_get_user_roles(herd_id) {
5
+ const supabase = await newServerClient();
6
+ // fetch user role for herd
7
+ const { data, error } = await supabase
8
+ .from("users_roles_per_herd")
9
+ .select("*")
10
+ .eq("herd_id", herd_id);
11
+ if (!data) {
12
+ return {
13
+ status: EnumWebResponse.ERROR,
14
+ msg: `No user role found for herd ${herd_id}`,
15
+ data: null,
16
+ };
17
+ }
18
+ // TODO: DETERMINE WHEN TO PASS ERROR
19
+ let response = IWebResponse.success(data);
20
+ return response.to_compatible();
21
+ }
22
+ export async function server_get_user() {
23
+ const supabase = await newServerClient();
24
+ const { data } = await supabase.auth.getUser();
25
+ const new_user = data.user;
26
+ return IWebResponse.success(new_user).to_compatible();
27
+ }
28
+ export async function server_get_users_with_herd_access(herd_id) {
29
+ const supabase = await newServerClient();
30
+ const { data, error } = await supabase
31
+ .from("users_roles_per_herd")
32
+ .select("user:users(*), role")
33
+ .eq("herd_id", herd_id);
34
+ if (error) {
35
+ return IWebResponse.error(error.message).to_compatible();
36
+ }
37
+ return IWebResponse.success(data).to_compatible();
38
+ }
39
+ export async function server_upsert_user_with_role(herd_id, username, role) {
40
+ const supabase = await newServerClient();
41
+ // first try to get user by username
42
+ const { data: user, error: user_error } = await supabase
43
+ .from("users")
44
+ .select("*")
45
+ .eq("username", username)
46
+ .single();
47
+ if (user_error) {
48
+ return IWebResponse.error("Unable to fetch user with provided username").to_compatible();
49
+ }
50
+ if (!user) {
51
+ return IWebResponse.error("User not found").to_compatible();
52
+ }
53
+ const newRoleForDb = {
54
+ user_id: user.id,
55
+ herd_id: herd_id,
56
+ role: role,
57
+ };
58
+ // upddate or insert user role
59
+ const { data, error } = await supabase
60
+ .from("users_roles_per_herd")
61
+ .upsert(newRoleForDb);
62
+ if (error) {
63
+ return IWebResponse.error("Unable to update or add user with provided username. Ensure you have sufficient permissions.").to_compatible();
64
+ }
65
+ return IWebResponse.success(data).to_compatible();
66
+ }
@@ -0,0 +1 @@
1
+ export declare function getOrigin(): string;
@@ -0,0 +1,7 @@
1
+ export function getOrigin() {
2
+ let url = process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
3
+ "http://localhost:3000";
4
+ // Make sure to include `https://` when not localhost.
5
+ url = url.startsWith("http") ? url : `https://${url}`;
6
+ return url;
7
+ }
@@ -0,0 +1,18 @@
1
+ import { IZoneWithActions } from "../types/db";
2
+ import { IWebResponseCompatible } from "../types/requests";
3
+ /**
4
+ * Get more zones and actions for a herd
5
+ * @param herd_id - The ID of the herd to get zones and actions for
6
+ * @param offset - The offset to start the query from
7
+ * @param page_count - The number of zones and actions to return
8
+ * @returns A list of zones and actions
9
+ * @throws An error if the zones or actions are not fetched
10
+ */
11
+ export declare function server_get_more_zones_and_actions_for_herd(herd_id: number, offset: number, page_count?: number): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
12
+ /**
13
+ * Create zones and actions for a herd
14
+ * @param zones - The zones to create
15
+ * @returns A list of zones and actions
16
+ * @throws An error if the zones or actions are not created
17
+ */
18
+ export declare function server_create_zones_with_actions(zones: IZoneWithActions[]): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
@@ -0,0 +1,108 @@
1
+ import { newServerClient } from "../supabase/server";
2
+ import { EnumWebResponse, IWebResponse, } from "../types/requests";
3
+ /**
4
+ * Get more zones and actions for a herd
5
+ * @param herd_id - The ID of the herd to get zones and actions for
6
+ * @param offset - The offset to start the query from
7
+ * @param page_count - The number of zones and actions to return
8
+ * @returns A list of zones and actions
9
+ * @throws An error if the zones or actions are not fetched
10
+ */
11
+ export async function server_get_more_zones_and_actions_for_herd(herd_id, offset, page_count = 10) {
12
+ const from = offset * page_count;
13
+ const to = from + page_count - 1;
14
+ const supabase = await newServerClient();
15
+ // make rpc call to get_events_with_tags_for_herd(herd_id, offset, limit)
16
+ const { data, error } = await supabase.rpc("get_zones_and_actions_for_herd", {
17
+ herd_id_caller: herd_id,
18
+ offset_caller: from,
19
+ limit_caller: page_count,
20
+ });
21
+ if (error) {
22
+ console.warn("Error fetching zones and actions for herd:", error.message);
23
+ return {
24
+ status: EnumWebResponse.ERROR,
25
+ msg: error.message,
26
+ data: [],
27
+ };
28
+ }
29
+ return IWebResponse.success(data.map((zone) => ({
30
+ ...zone,
31
+ actions: zone.actions ?? [],
32
+ }))).to_compatible();
33
+ }
34
+ /**
35
+ * Create zones and actions for a herd
36
+ * @param zones - The zones to create
37
+ * @returns A list of zones and actions
38
+ * @throws An error if the zones or actions are not created
39
+ */
40
+ export async function server_create_zones_with_actions(zones) {
41
+ // loop through plans and format
42
+ let actions = zones.flatMap((zone) => zone.actions);
43
+ let formatted_zones = zones.map((zone) => {
44
+ let formatted_zone = { ...zone };
45
+ delete formatted_zone.id;
46
+ delete formatted_zone.inserted_at;
47
+ delete formatted_zone.actions;
48
+ return formatted_zone;
49
+ });
50
+ let formatted_actions = actions.map((action) => {
51
+ let formatted_action = { ...action };
52
+ delete formatted_action.id;
53
+ delete formatted_action.zone_id;
54
+ delete formatted_action.inserted_at;
55
+ return formatted_action;
56
+ });
57
+ const supabase = await newServerClient();
58
+ // insert data and return the response
59
+ const { data, error } = await supabase
60
+ .from("zones")
61
+ .insert(formatted_zones)
62
+ .select("*");
63
+ if (error) {
64
+ let msg = `Error creating zones: ${error.message}`;
65
+ return {
66
+ status: EnumWebResponse.ERROR,
67
+ msg: msg,
68
+ data: null,
69
+ };
70
+ }
71
+ // get zone id
72
+ let zone_ids = data.map((zone) => {
73
+ return zone.id;
74
+ });
75
+ // if zone ids length is zero, return error
76
+ if (zone_ids.length === 0) {
77
+ return {
78
+ status: EnumWebResponse.ERROR,
79
+ msg: "No zones created",
80
+ data: null,
81
+ };
82
+ }
83
+ // add zone id to formatted actions
84
+ formatted_actions.forEach((action) => {
85
+ action.zone_id = zone_ids[0];
86
+ });
87
+ // insert actions
88
+ const { data: data_actions, error: error_actions } = await supabase
89
+ .from("actions")
90
+ .insert(formatted_actions)
91
+ .select("*");
92
+ if (error_actions) {
93
+ let msg = `Error creating actions: ${error_actions.message}`;
94
+ return {
95
+ status: EnumWebResponse.ERROR,
96
+ msg: msg,
97
+ data: null,
98
+ };
99
+ }
100
+ // merge data and data_actions
101
+ const merged_data = data.map((zone) => {
102
+ return {
103
+ ...zone,
104
+ actions: data_actions.filter((action) => action.zone_id === zone.id),
105
+ };
106
+ });
107
+ return IWebResponse.success(merged_data).to_compatible();
108
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./useScoutDbListener";
2
+ export * from "./useScoutRefresh";
@@ -0,0 +1,2 @@
1
+ export * from "./useScoutDbListener";
2
+ export * from "./useScoutRefresh";
@@ -0,0 +1 @@
1
+ export declare function useScoutDbListener(): void;
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { useAppDispatch } from "../store/hooks";
3
+ import { useEffect, useRef } from "react";
4
+ import { addDevice, addPlan, addTag, deleteDevice, deletePlan, deleteTag, updateDevice, updatePlan, updateTag, } from "../store/scout";
5
+ import { createBrowserClient } from "@supabase/ssr";
6
+ import { get_device_by_id } from "../helpers/devices";
7
+ import { EnumWebResponse } from "../types/requests";
8
+ export function useScoutDbListener() {
9
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
10
+ const anon_key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
11
+ const supabase = useRef(null);
12
+ const dispatch = useAppDispatch();
13
+ function handleTagInserts(payload) {
14
+ dispatch(addTag(payload.new));
15
+ }
16
+ function handleTagDeletes(payload) {
17
+ dispatch(deleteTag(payload.old));
18
+ }
19
+ function handleTagUpdates(payload) {
20
+ dispatch(updateTag(payload.new));
21
+ }
22
+ async function handleDeviceInserts(payload) {
23
+ const response_device = await get_device_by_id(payload.new.id);
24
+ if (response_device.status == EnumWebResponse.SUCCESS &&
25
+ response_device.data) {
26
+ dispatch(addDevice(response_device.data));
27
+ }
28
+ else {
29
+ console.error("error getting device by id", payload);
30
+ }
31
+ }
32
+ function handleDeviceDeletes(payload) {
33
+ dispatch(deleteDevice(payload.old));
34
+ }
35
+ async function handleDeviceUpdates(payload) {
36
+ const response_device = await get_device_by_id(payload.new.id);
37
+ if (response_device.status == EnumWebResponse.SUCCESS &&
38
+ response_device.data) {
39
+ dispatch(updateDevice(response_device.data));
40
+ }
41
+ else {
42
+ console.error("error getting device by id", payload);
43
+ }
44
+ }
45
+ function handlePlanInserts(payload) {
46
+ dispatch(addPlan(payload.new));
47
+ }
48
+ function handlePlanDeletes(payload) {
49
+ dispatch(deletePlan(payload.old));
50
+ }
51
+ function handlePlanUpdates(payload) {
52
+ dispatch(updatePlan(payload.new));
53
+ }
54
+ useEffect(() => {
55
+ const newSupabase = createBrowserClient(url, anon_key);
56
+ newSupabase
57
+ .channel("plans_insert")
58
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "plans" }, handlePlanInserts)
59
+ .subscribe();
60
+ newSupabase
61
+ .channel("plans_delete")
62
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "plans" }, handlePlanDeletes)
63
+ .subscribe();
64
+ newSupabase
65
+ .channel("plans_update")
66
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "plans" }, handlePlanUpdates)
67
+ .subscribe();
68
+ newSupabase
69
+ .channel("devices_delete")
70
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "devices" }, handleDeviceDeletes)
71
+ .subscribe();
72
+ newSupabase
73
+ .channel("devices_insert")
74
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "devices" }, handleDeviceInserts)
75
+ .subscribe();
76
+ newSupabase
77
+ .channel("devices_update")
78
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "devices" }, handleDeviceUpdates)
79
+ .subscribe();
80
+ newSupabase
81
+ .channel("tags_insert")
82
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
83
+ .subscribe();
84
+ newSupabase
85
+ .channel("tags_delete")
86
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
87
+ .subscribe();
88
+ newSupabase
89
+ .channel("tags_update")
90
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
91
+ .subscribe();
92
+ supabase.current = newSupabase;
93
+ }, []);
94
+ }
@@ -0,0 +1,7 @@
1
+ export interface UseScoutRefreshOptions {
2
+ autoRefresh?: boolean;
3
+ onRefreshComplete?: () => void;
4
+ }
5
+ export declare function useScoutRefresh(options?: UseScoutRefreshOptions): {
6
+ handleRefresh: () => Promise<void>;
7
+ };
@@ -0,0 +1,45 @@
1
+ import { useEffect } from "react";
2
+ import { useAppDispatch } from "../store/hooks";
3
+ import { EnumScoutStateStatus, setActiveHerdId, setHerdModules, setStatus, setUser, } from "../store/scout";
4
+ import { server_load_herd_modules } from "../helpers/herds";
5
+ import { server_get_user } from "../helpers/users";
6
+ export function useScoutRefresh(options = {}) {
7
+ const { autoRefresh = true, onRefreshComplete } = options;
8
+ const dispatch = useAppDispatch();
9
+ const handleRefresh = async () => {
10
+ dispatch(setStatus(EnumScoutStateStatus.LOADING));
11
+ try {
12
+ const compatible_new_herd_modules = await server_load_herd_modules();
13
+ const res_new_user = await server_get_user();
14
+ dispatch(setHerdModules(compatible_new_herd_modules));
15
+ dispatch(setUser(res_new_user.data));
16
+ // Check local storage for a last selected herd
17
+ if (localStorage.getItem("last_selected_herd")) {
18
+ const found_herd = compatible_new_herd_modules.find((hm) => hm.herd.id.toString() === localStorage.getItem("last_selected_herd"))?.herd;
19
+ // If herd is found then set it
20
+ if (found_herd) {
21
+ dispatch(setActiveHerdId(found_herd.id.toString()));
22
+ }
23
+ }
24
+ // If there is no last selected herd then select the first one
25
+ else if (compatible_new_herd_modules.length > 0) {
26
+ localStorage.setItem("last_selected_herd", compatible_new_herd_modules[0].herd.id.toString());
27
+ dispatch(setActiveHerdId(compatible_new_herd_modules[0].herd.id.toString()));
28
+ }
29
+ dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
30
+ onRefreshComplete?.();
31
+ }
32
+ catch (error) {
33
+ console.error("Error refreshing scout data:", error);
34
+ dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
35
+ }
36
+ };
37
+ useEffect(() => {
38
+ if (autoRefresh) {
39
+ handleRefresh();
40
+ }
41
+ }, [autoRefresh]);
42
+ return {
43
+ handleRefresh,
44
+ };
45
+ }