@canmingir/link 1.2.50 → 1.2.52

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link",
3
- "version": "1.2.50",
3
+ "version": "1.2.52",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "link-server": "./server/server.ts"
package/src/http/index.js CHANGED
@@ -40,6 +40,20 @@ instance.interceptors.response.use(
40
40
  async (error) => {
41
41
  publish("LOADED", { loading: false });
42
42
  const statusCode = error.response?.status;
43
+ const identityProvider = storage.get("link", "identityProvider");
44
+
45
+ if (statusCode === 500 && identityProvider === "DEMO") {
46
+ const token = storage.get("link", "accessToken");
47
+ try {
48
+ const decodedToken = jwtDecode(token);
49
+ if (decodedToken.exp * 1000 < Date.now()) {
50
+ return Promise.reject(error);
51
+ }
52
+ } catch (_) {
53
+ return Promise.reject(error);
54
+ }
55
+ }
56
+
43
57
  switch (statusCode) {
44
58
  case 400:
45
59
  publish("GLOBAL_MESSAGE_POSTED", {
@@ -78,7 +92,7 @@ instance.interceptors.response.use(
78
92
  break;
79
93
  }
80
94
  return Promise.reject(error);
81
- }
95
+ },
82
96
  );
83
97
 
84
98
  export const fetcher = (url) => instance.get(url).then((res) => res.data);
@@ -128,26 +142,41 @@ const refreshInterceptor =
128
142
  : createAuthRefreshInterceptor.default;
129
143
 
130
144
  refreshInterceptor(instance, refreshAuthLogic, {
131
- statusCodes: [401, 403],
132
- shouldRefresh: () => {
133
- const { name, base } = config();
145
+ statusCodes: [401, 403, 500],
146
+ pauseInstanceWhileRefreshing: true,
147
+ shouldRefresh: (error) => {
148
+ const { base } = config();
134
149
  const token = storage.get("link", "accessToken");
150
+ const identityProvider = storage.get("link", "identityProvider");
151
+ const statusCode = error.response?.status;
135
152
 
136
153
  if (!token) {
137
154
  window.location.href = `${window.location.origin}${base}/login`;
138
155
  return false;
139
156
  }
140
157
 
158
+ if (identityProvider === "DEMO") {
159
+ if (statusCode !== 500) {
160
+ return true;
161
+ }
162
+
163
+ try {
164
+ const decodedToken = jwtDecode(token);
165
+ return decodedToken.exp * 1000 < Date.now();
166
+ } catch (_) {
167
+ // If we can't decode, attempt refresh once.
168
+ return true;
169
+ }
170
+ }
171
+
172
+ if (statusCode === 500) {
173
+ return false;
174
+ }
175
+
141
176
  try {
142
177
  const decodedToken = jwtDecode(token);
143
178
  if (decodedToken.exp * 1000 < Date.now()) {
144
179
  return true;
145
- } else {
146
- publish("GLOBAL_MESSAGE_POSTED", {
147
- status: true,
148
- message: "UNAUTHORIZED",
149
- severity: "warning",
150
- });
151
180
  }
152
181
  } catch (err) {
153
182
  window.location.href = `${window.location.origin}${base}/login`;
package/src/http/user.js CHANGED
@@ -43,7 +43,7 @@ instance.getUserDetails = async () => {
43
43
 
44
44
  if (isExpired) {
45
45
  console.log(
46
- "Access token expired, refreshing before fetching user details..."
46
+ "Access token expired, refreshing before fetching user details...",
47
47
  );
48
48
  const { appId } = config();
49
49
  const projectId = storage.get("projectId");
@@ -54,12 +54,16 @@ instance.getUserDetails = async () => {
54
54
  appId,
55
55
  projectId,
56
56
  identityProvider,
57
+ ...(identityProvider === "DEMO" && {
58
+ username: "admin",
59
+ password: "admin",
60
+ }),
57
61
  });
58
62
 
59
63
  accessToken = data.accessToken;
60
64
  storage.set("link", "accessToken", accessToken);
61
65
  console.log(
62
- "Access token refreshed successfully, now fetching user details"
66
+ "Access token refreshed successfully, now fetching user details",
63
67
  );
64
68
  }
65
69
  } catch (error) {
@@ -86,25 +90,91 @@ instance.getUserDetails = async () => {
86
90
  };
87
91
 
88
92
  instance.getPermittedUsers = async () => {
89
- const userIds = [];
90
- const refreshToken = await storage.get("link", "refreshToken");
93
+ const { appId } = config();
94
+ const projectId = storage.get("projectId");
95
+ const identityProvider = storage.get("link", "identityProvider");
96
+
91
97
  const response = await http.get("/permissions");
92
98
 
93
- response.data.forEach((permission) => {
94
- userIds.push(permission.userId);
95
- });
96
-
97
- const users = await Promise.all(
98
- userIds.map(async (userId) => {
99
- const response = await axios.get(
100
- `https://api.github.com/user/${userId}`,
101
- { headers: { Authorization: `Bearer ${refreshToken}` } }
102
- );
103
- return response.data;
104
- })
105
- );
106
-
107
- return users;
99
+ const uniqueUserIds = [
100
+ ...new Set(
101
+ response.data
102
+ .filter((p) => p.appId === appId && p.projectId === projectId)
103
+ .map((p) => p.userId),
104
+ ),
105
+ ];
106
+
107
+ const UUID_REGEX =
108
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
109
+
110
+ if (identityProvider?.toUpperCase() === "GITHUB") {
111
+ const results = await Promise.allSettled(
112
+ uniqueUserIds.map(async (userId) => {
113
+ if (UUID_REGEX.test(userId)) {
114
+ return {
115
+ id: String(userId),
116
+ identityProvider,
117
+ name: String(userId),
118
+ displayName: null,
119
+ avatarUrl: `https://api.dicebear.com/7.x/bottts/svg?seed=${userId}`,
120
+ email: null,
121
+ };
122
+ }
123
+ const { data } = await axios.get(
124
+ `https://api.github.com/user/${userId}`,
125
+ { headers: { Accept: "application/vnd.github+json" } },
126
+ );
127
+ return {
128
+ id: String(data.id),
129
+ identityProvider: "GITHUB",
130
+ name: data.login,
131
+ displayName: data.name || null,
132
+ avatarUrl: data.avatar_url || null,
133
+ email: data.email || null,
134
+ };
135
+ }),
136
+ );
137
+
138
+ return results
139
+ .filter((r) => r.status === "fulfilled" && r.value !== null)
140
+ .map((r) => r.value);
141
+ }
142
+
143
+ if (identityProvider?.toUpperCase() === "DEMO") {
144
+ return [
145
+ {
146
+ id: "1001",
147
+ identityProvider: "DEMO",
148
+ name: "admin",
149
+ displayName: "Demo Admin",
150
+ avatarUrl: `https://api.dicebear.com/7.x/bottts/svg?seed=1001`,
151
+ email: "admin@demo.local",
152
+ },
153
+ ];
154
+ }
155
+
156
+ if (identityProvider?.toUpperCase() === "COGNITO") {
157
+ const accessToken = await storage.get("link", "accessToken");
158
+ let currentUserId = null;
159
+
160
+ try {
161
+ const decoded = jwtDecode(accessToken);
162
+ currentUserId = decoded.sub;
163
+ } catch (_) {}
164
+
165
+ return uniqueUserIds
166
+ .filter((id) => !UUID_REGEX.test(id))
167
+ .map((id) => ({
168
+ id: String(id),
169
+ identityProvider: "COGNITO",
170
+ name: id === currentUserId ? "You" : "Cognito User",
171
+ displayName: id === currentUserId ? "Cognito Admin" : null,
172
+ avatarUrl: `https://api.dicebear.com/7.x/bottts/svg?seed=${id}`,
173
+ email: null,
174
+ }));
175
+ }
176
+
177
+ return [];
108
178
  };
109
179
 
110
180
  export default instance;
@@ -46,11 +46,7 @@ export default function Header({ onOpenNav }) {
46
46
  </IconButton>
47
47
  )}
48
48
  {projectBar && <ProjectBar />}
49
- {TopBar && (
50
- <Box sx={{ flexGrow: 1, mx: 2 }}>
51
- <TopBar />
52
- </Box>
53
- )}
49
+ {TopBar && <TopBar />}
54
50
 
55
51
  <Stack
56
52
  direction="row"
@@ -26,7 +26,7 @@ export default function NavToggleButton({ sx, ...other }) {
26
26
  onClick={() =>
27
27
  settings.onUpdate(
28
28
  "themeLayout",
29
- settings.themeLayout === "vertical" ? "mini" : "vertical"
29
+ settings.themeLayout === "vertical" ? "mini" : "vertical",
30
30
  )
31
31
  }
32
32
  sx={{
@@ -34,7 +34,7 @@ export default function NavToggleButton({ sx, ...other }) {
34
34
  top: 32,
35
35
  position: "fixed",
36
36
  left: NAV.W_VERTICAL - 12,
37
- zIndex: 0,
37
+ zIndex: theme.zIndex.appBar + 1,
38
38
  border: `dashed 1px ${theme.palette.divider}`,
39
39
  ...bgBlur({ opacity: 0.48, color: theme.palette.background.default }),
40
40
  "&:hover": {
@@ -1,7 +1,7 @@
1
- import { Box } from "@mui/material";
2
-
3
1
  import React, { useId, useLayoutEffect, useMemo, useState } from "react";
4
2
 
3
+ import { Box } from "@mui/material";
4
+
5
5
  const DynamicConnector = ({
6
6
  containerEl,
7
7
  parentEl,
@@ -43,41 +43,48 @@ const DynamicConnector = ({
43
43
  const cRect = containerEl.getBoundingClientRect();
44
44
  const pRect = parentEl.getBoundingClientRect();
45
45
 
46
+ const scaleX = containerEl.offsetWidth
47
+ ? cRect.width / containerEl.offsetWidth || 1
48
+ : 1;
49
+ const scaleY = containerEl.offsetHeight
50
+ ? cRect.height / containerEl.offsetHeight || 1
51
+ : 1;
52
+
46
53
  let parentPoint;
47
54
  let childPoints = [];
48
55
 
49
56
  if (isHorizontal) {
50
57
  parentPoint = {
51
- x: pRect.right - cRect.left,
52
- y: pRect.top + pRect.height / 2 - cRect.top,
58
+ x: (pRect.right - cRect.left) / scaleX,
59
+ y: (pRect.top + pRect.height / 2 - cRect.top) / scaleY,
53
60
  };
54
61
 
55
62
  childPoints = childEls.map((el) => {
56
63
  if (!el) return { x: parentPoint.x + 100, y: parentPoint.y };
57
64
  const r = el.getBoundingClientRect();
58
65
  return {
59
- x: r.left - cRect.left,
60
- y: r.top + r.height / 2 - cRect.top,
66
+ x: (r.left - cRect.left) / scaleX,
67
+ y: (r.top + r.height / 2 - cRect.top) / scaleY,
61
68
  };
62
69
  });
63
70
  } else {
64
71
  parentPoint = {
65
- x: pRect.left + pRect.width / 2 - cRect.left,
66
- y: pRect.bottom - cRect.top,
72
+ x: (pRect.left + pRect.width / 2 - cRect.left) / scaleX,
73
+ y: (pRect.bottom - cRect.top) / scaleY,
67
74
  };
68
75
 
69
76
  childPoints = childEls.map((el) => {
70
77
  if (!el) return { x: parentPoint.x, y: parentPoint.y + 100 };
71
78
  const r = el.getBoundingClientRect();
72
79
  return {
73
- x: r.left + r.width / 2 - cRect.left,
74
- y: r.top - cRect.top,
80
+ x: (r.left + r.width / 2 - cRect.left) / scaleX,
81
+ y: (r.top - cRect.top) / scaleY,
75
82
  };
76
83
  });
77
84
  }
78
85
 
79
86
  setPoints({ parent: parentPoint, children: childPoints });
80
- setDims({ w: cRect.width, h: cRect.height });
87
+ setDims({ w: cRect.width / scaleX, h: cRect.height / scaleY });
81
88
  };
82
89
 
83
90
  update();
@@ -95,7 +102,7 @@ const DynamicConnector = ({
95
102
  gradient: `gradient-${uniqueId}`,
96
103
  arrow: `arrow-${uniqueId}`,
97
104
  }),
98
- [uniqueId]
105
+ [uniqueId],
99
106
  );
100
107
 
101
108
  const getPath = (from, to) => {
@@ -1,10 +1,10 @@
1
- import FlowNode from "./FlowNode";
2
- import { useGraphOperations } from "../hooks/useGraphOperations";
3
-
4
1
  import { Box, alpha } from "@mui/material";
5
2
  import React, { useMemo, useState } from "react";
6
3
  import { assertLinkedGraph, buildTreeFromLinked } from "../utils/flowUtils";
7
4
 
5
+ import FlowNode from "./FlowNode";
6
+ import { useGraphOperations } from "../hooks/useGraphOperations";
7
+
8
8
  export const Flow = ({
9
9
  data,
10
10
  variant = "simple",
@@ -13,6 +13,7 @@ export const Flow = ({
13
13
  editable = false,
14
14
  onChange,
15
15
  height,
16
+ initialZoom,
16
17
  }) => {
17
18
  const [floatingNodes, setFloatingNodes] = useState([]);
18
19
 
@@ -65,6 +66,8 @@ export const Flow = ({
65
66
  return (
66
67
  <Box
67
68
  sx={{
69
+ height,
70
+ flexShrink: 0,
68
71
  backgroundImage: (theme) => `
69
72
  radial-gradient(
70
73
  ${alpha(theme.palette.divider, 0.08)} 1px,
@@ -86,6 +89,7 @@ export const Flow = ({
86
89
  onConnect={editable ? handleConnect : undefined}
87
90
  floatingNodes={floatingNodes}
88
91
  height={height}
92
+ initialZoom={initialZoom}
89
93
  />
90
94
  </Box>
91
95
  );
@@ -0,0 +1,78 @@
1
+ import React, { useMemo } from "react";
2
+
3
+ import FlowBoardItem from "./FlowBoardItem";
4
+ import FlowViewport from "./FlowViewport";
5
+ import { SelectionProvider } from "../selection/SelectionContext";
6
+ import { getBaseStyleForVariant } from "../styles";
7
+
8
+ export const FlowBoard = ({
9
+ flows = [],
10
+ variant = "simple",
11
+ style,
12
+ plugin,
13
+ initialZoom = 1,
14
+ height = "100vh",
15
+ gap = 480,
16
+ onFlowPositionChange,
17
+ }) => {
18
+ const baseStyle = getBaseStyleForVariant(variant);
19
+ const selectionColor = baseStyle.selectionColor ?? "#64748b";
20
+
21
+ const positionedFlows = useMemo(() => {
22
+ const count = flows.length;
23
+ return flows.map((flow, index) => {
24
+ const fallback = {
25
+ x: (index - (count - 1) / 2) * gap,
26
+ y: 0,
27
+ };
28
+ return {
29
+ flow,
30
+ id: flow?.id ?? index,
31
+ position: flow?.position ?? fallback,
32
+ };
33
+ });
34
+ }, [flows, gap]);
35
+
36
+ const mergedNodesById = useMemo(() => {
37
+ const merged = {};
38
+ for (const { flow } of positionedFlows) {
39
+ if (flow?.nodes) Object.assign(merged, flow.nodes);
40
+ }
41
+ return merged;
42
+ }, [positionedFlows]);
43
+
44
+ return (
45
+ <SelectionProvider>
46
+ <FlowViewport
47
+ selectionColor={selectionColor}
48
+ nodesById={mergedNodesById}
49
+ variant={variant}
50
+ style={style}
51
+ plugin={plugin}
52
+ height={height}
53
+ initialZoom={initialZoom}
54
+ >
55
+ {positionedFlows.map(({ flow, id, position }) => (
56
+ <FlowBoardItem
57
+ key={id}
58
+ flow={flow}
59
+ flowId={id}
60
+ label={flow?.label}
61
+ divider={flow?.divider}
62
+ position={position}
63
+ onPositionChange={
64
+ onFlowPositionChange
65
+ ? (pos) => onFlowPositionChange(id, pos)
66
+ : undefined
67
+ }
68
+ variant={flow?.variant ?? variant}
69
+ style={style}
70
+ plugin={plugin}
71
+ />
72
+ ))}
73
+ </FlowViewport>
74
+ </SelectionProvider>
75
+ );
76
+ };
77
+
78
+ export default FlowBoard;
@@ -0,0 +1,144 @@
1
+ import React, { useEffect, useMemo, useRef, useState } from "react";
2
+ import { assertLinkedGraph, buildTreeFromLinked } from "../utils/flowUtils";
3
+
4
+ import { Box } from "@mui/material";
5
+ import FlowNodeView from "../nodes/FlowNodeView";
6
+
7
+ const FlowBoardItem = ({
8
+ flow,
9
+ flowId,
10
+ position: initialPosition,
11
+ onPositionChange,
12
+ variant,
13
+ style,
14
+ plugin,
15
+ label,
16
+ divider,
17
+ }) => {
18
+ const [position, setPosition] = useState(
19
+ () => initialPosition || { x: 0, y: 0 },
20
+ );
21
+
22
+ useEffect(() => {
23
+ if (initialPosition) setPosition({ ...initialPosition });
24
+ }, [initialPosition?.x, initialPosition?.y]);
25
+
26
+ const didDragRef = useRef(false);
27
+
28
+ const namespacedFlow = useMemo(() => {
29
+ if (flowId == null) return flow;
30
+ const prefix = `${flowId}::`;
31
+ const ns = (id) =>
32
+ typeof id === "string" && !id.startsWith(prefix) ? `${prefix}${id}` : id;
33
+
34
+ const nodes = {};
35
+ for (const [id, node] of Object.entries(flow?.nodes || {})) {
36
+ const nextArr = Array.isArray(node.next)
37
+ ? node.next.map((n) => (typeof n === "string" ? ns(n) : n))
38
+ : node.next != null
39
+ ? ns(node.next)
40
+ : undefined;
41
+ nodes[ns(id)] = {
42
+ ...node,
43
+ id: ns(id),
44
+ next: nextArr,
45
+ previous: node.previous != null ? ns(node.previous) : undefined,
46
+ };
47
+ }
48
+ const roots = Array.isArray(flow?.roots) ? flow.roots.map(ns) : flow?.roots;
49
+ return { ...flow, nodes, roots };
50
+ }, [flow, flowId]);
51
+
52
+ const { nodesById, roots } = useMemo(
53
+ () => assertLinkedGraph(namespacedFlow),
54
+ [namespacedFlow],
55
+ );
56
+
57
+ const treesData = useMemo(() => {
58
+ if (!roots?.length) return [];
59
+ return roots
60
+ .map((rootId) => buildTreeFromLinked(rootId, nodesById))
61
+ .filter(Boolean);
62
+ }, [nodesById, roots]);
63
+
64
+ const handleMouseDown = (e) => {
65
+ if (e.button !== 0) return;
66
+ if (e.target?.closest?.(".MuiCard-root") || e.target?.closest?.("button"))
67
+ return;
68
+
69
+ e.stopPropagation();
70
+ didDragRef.current = false;
71
+
72
+ const startX = e.clientX;
73
+ const startY = e.clientY;
74
+ const startPosition = { ...position };
75
+ let lastPosition = startPosition;
76
+
77
+ const onMove = (ev) => {
78
+ const dx = ev.clientX - startX;
79
+ const dy = ev.clientY - startY;
80
+ if (!didDragRef.current && (Math.abs(dx) > 3 || Math.abs(dy) > 3)) {
81
+ didDragRef.current = true;
82
+ }
83
+ lastPosition = { x: startPosition.x + dx, y: startPosition.y + dy };
84
+ setPosition(lastPosition);
85
+ };
86
+
87
+ const onUp = () => {
88
+ window.removeEventListener("mousemove", onMove);
89
+ window.removeEventListener("mouseup", onUp);
90
+ if (didDragRef.current) onPositionChange?.(lastPosition);
91
+ };
92
+
93
+ window.addEventListener("mousemove", onMove);
94
+ window.addEventListener("mouseup", onUp);
95
+ };
96
+
97
+ if (!treesData.length) return null;
98
+
99
+ return (
100
+ <Box
101
+ data-flow-id={label}
102
+ onMouseDown={handleMouseDown}
103
+ sx={{
104
+ position: "absolute",
105
+ left: "50%",
106
+ top: "50%",
107
+ transform: `translate(${position.x}px, ${position.y}px)`,
108
+ cursor: "grab",
109
+ "&:active": { cursor: "grabbing" },
110
+ }}
111
+ >
112
+ {label != null && (
113
+ <Box
114
+ sx={{
115
+ position: "absolute",
116
+ top: -28,
117
+ left: 0,
118
+ fontSize: 12,
119
+ fontWeight: 600,
120
+ opacity: 0.6,
121
+ pointerEvents: "none",
122
+ whiteSpace: "nowrap",
123
+ }}
124
+ >
125
+ {label}
126
+ </Box>
127
+ )}
128
+ {divider != null && <Box sx={{ width: "100%", mb: 1 }}>{divider}</Box>}
129
+ <Box sx={{ display: "flex", gap: 4, alignItems: "flex-start" }}>
130
+ {treesData.map((tree, idx) => (
131
+ <FlowNodeView
132
+ key={tree.id || `tree-${idx}`}
133
+ node={tree}
134
+ variant={variant}
135
+ style={style}
136
+ plugin={plugin}
137
+ />
138
+ ))}
139
+ </Box>
140
+ </Box>
141
+ );
142
+ };
143
+
144
+ export default FlowBoardItem;
@@ -17,6 +17,7 @@ const FlowNode = ({
17
17
  plugin,
18
18
  node,
19
19
  height,
20
+ initialZoom,
20
21
  ...props
21
22
  }) => {
22
23
  if (!isRoot) {
@@ -50,6 +51,7 @@ const FlowNode = ({
50
51
  style={style}
51
52
  plugin={plugin}
52
53
  height={height}
54
+ initialZoom={initialZoom}
53
55
  >
54
56
  {node && (
55
57
  <FlowNodeView
@@ -17,13 +17,14 @@ const FlowViewport = ({
17
17
  style,
18
18
  plugin,
19
19
  height = "100vh",
20
+ initialZoom = 1,
20
21
  sx = {},
21
22
  ...rest
22
23
  }) => {
23
24
  const clampZoom = (zoom) => Math.min(2.5, Math.max(0.25, zoom));
24
25
 
25
26
  const [offset, setOffset] = useState({ x: 0, y: 0 });
26
- const [zoom, setZoom] = useState(1);
27
+ const [zoom, setZoom] = useState(initialZoom);
27
28
  const [isDragging, setIsDragging] = useState(false);
28
29
  const [selectionBox, setSelectionBox] = useState(null);
29
30
  const [shouldCenter, setShouldCenter] = useState(true);
@@ -291,7 +292,7 @@ const FlowViewport = ({
291
292
  onContextMenu={(e) => e.preventDefault()}
292
293
  sx={{
293
294
  width: "100%",
294
- height: "100vh",
295
+ height: height,
295
296
  overflow: "hidden",
296
297
  bgcolor: "none",
297
298
  cursor: isDragging ? "grabbing" : "default",
@@ -1 +1,2 @@
1
1
  export { default as Flow } from "./core/Flow";
2
+ export { default as FlowBoard } from "./core/FlowBoard";
package/src/lib/index.js CHANGED
@@ -23,7 +23,7 @@ export { default as useTable } from "./useTable/useTable";
23
23
  export { default as useChart } from "./useChart/useChart";
24
24
 
25
25
  export { default as Flow } from "./Flow/core/Flow";
26
-
26
+ export { default as FlowBoard } from "./Flow/core/FlowBoard";
27
27
  export {
28
28
  HeaderCard,
29
29
  MediaAvatarCard,
@@ -1,10 +1,3 @@
1
- import Iconify from "../components/Iconify";
2
- import config from "../config/config";
3
- import { useEvent } from "@nucleoidai/react-event";
4
- import useSettings from "../hooks/useSettings";
5
- import { useSettingsContext } from "../components/settings/context";
6
- import { useUser } from "../hooks/use-user";
7
-
8
1
  import {
9
2
  Avatar,
10
3
  Box,
@@ -30,6 +23,13 @@ import {
30
23
  import { Button, Dialog, DialogActions, DialogContent } from "@mui/material";
31
24
  import React, { useEffect, useState } from "react";
32
25
 
26
+ import Iconify from "../components/Iconify";
27
+ import config from "../config/config";
28
+ import { useEvent } from "@nucleoidai/react-event";
29
+ import useSettings from "../hooks/useSettings";
30
+ import { useSettingsContext } from "../components/settings/context";
31
+ import { useUser } from "../hooks/use-user";
32
+
33
33
  let pkg = {
34
34
  name: "",
35
35
  version: "",
@@ -173,7 +173,7 @@ function SettingsDialog({ handleClose, open }) {
173
173
  color: "white",
174
174
  minHeight: 600,
175
175
  },
176
- }
176
+ },
177
177
  }}
178
178
  >
179
179
  <DialogContent>
@@ -211,16 +211,24 @@ const Permission = () => {
211
211
 
212
212
  useEffect(() => {
213
213
  getPermittedUsers();
214
+ // eslint-disable-next-line react-hooks/exhaustive-deps
214
215
  }, [event, event2]);
215
216
 
216
217
  return (
217
- <Stack direction="column" spacing={2} sx={{
218
- p: 2
219
- }}>
218
+ <Stack
219
+ direction="column"
220
+ spacing={2}
221
+ sx={{
222
+ p: 2,
223
+ }}
224
+ >
220
225
  <Typography variant="h6">Users</Typography>
221
- <Typography variant="subtitle2" sx={{
222
- color: "text.secondary"
223
- }}>
226
+ <Typography
227
+ variant="subtitle2"
228
+ sx={{
229
+ color: "text.secondary",
230
+ }}
231
+ >
224
232
  Users with access to the project.
225
233
  </Typography>
226
234
  <List>
@@ -238,7 +246,7 @@ const Permission = () => {
238
246
  }}
239
247
  >
240
248
  <ListItemAvatar>
241
- <Avatar src={user.avatar_url} alt={user.name} />
249
+ <Avatar src={user.avatarUrl} alt={user.name} />
242
250
  </ListItemAvatar>
243
251
  <ListItemText primary={user.name} />
244
252
  <ListItemSecondaryAction>
@@ -306,13 +314,20 @@ const Settings = () => {
306
314
  };
307
315
 
308
316
  return (
309
- <Stack direction="column" spacing={2} sx={{
310
- p: 2
311
- }}>
317
+ <Stack
318
+ direction="column"
319
+ spacing={2}
320
+ sx={{
321
+ p: 2,
322
+ }}
323
+ >
312
324
  <Typography variant="h6">Settings</Typography>
313
- <Typography variant="subtitle2" sx={{
314
- color: "text.secondary"
315
- }}>
325
+ <Typography
326
+ variant="subtitle2"
327
+ sx={{
328
+ color: "text.secondary",
329
+ }}
330
+ >
316
331
  Configure your application settings.
317
332
  </Typography>
318
333
  <List>
@@ -373,9 +388,13 @@ const About = () => {
373
388
  const description = pkg.description;
374
389
 
375
390
  return (
376
- <Stack direction="column" spacing={2} sx={{
377
- p: 2
378
- }}>
391
+ <Stack
392
+ direction="column"
393
+ spacing={2}
394
+ sx={{
395
+ p: 2,
396
+ }}
397
+ >
379
398
  <Box
380
399
  sx={{
381
400
  p: 3,
@@ -386,9 +405,13 @@ const About = () => {
386
405
  "linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.01))",
387
406
  }}
388
407
  >
389
- <Stack direction="row" spacing={2.5} sx={{
390
- alignItems: "center"
391
- }}>
408
+ <Stack
409
+ direction="row"
410
+ spacing={2.5}
411
+ sx={{
412
+ alignItems: "center",
413
+ }}
414
+ >
392
415
  <Avatar
393
416
  src={iconSrc}
394
417
  variant="rounded"
@@ -404,9 +427,12 @@ const About = () => {
404
427
  </Avatar>
405
428
 
406
429
  <Box sx={{ flex: 1, minWidth: 0 }}>
407
- <Typography variant="h5" sx={{
408
- fontWeight: 600
409
- }}>
430
+ <Typography
431
+ variant="h5"
432
+ sx={{
433
+ fontWeight: 600,
434
+ }}
435
+ >
410
436
  {appName.toUpperCase()}
411
437
  </Typography>
412
438
 
@@ -416,8 +442,9 @@ const About = () => {
416
442
  sx={{
417
443
  color: "text.secondary",
418
444
  mt: 0.5,
419
- maxWidth: 520
420
- }}>
445
+ maxWidth: 520,
446
+ }}
447
+ >
421
448
  {description}
422
449
  </Typography>
423
450
  )}
@@ -460,9 +487,12 @@ const About = () => {
460
487
  textAlign: "center",
461
488
  }}
462
489
  >
463
- <Typography variant="caption" sx={{
464
- color: "text.secondary"
465
- }}>
490
+ <Typography
491
+ variant="caption"
492
+ sx={{
493
+ color: "text.secondary",
494
+ }}
495
+ >
466
496
  © 2026 greycollar.ai. All rights reserved.
467
497
  </Typography>
468
498
  </Box>
@@ -473,15 +503,20 @@ const About = () => {
473
503
 
474
504
  const InfoRow = ({ label, value, link }) => {
475
505
  return (
476
- <Stack direction="row" spacing={1.5} sx={{
477
- alignItems: "center"
478
- }}>
506
+ <Stack
507
+ direction="row"
508
+ spacing={1.5}
509
+ sx={{
510
+ alignItems: "center",
511
+ }}
512
+ >
479
513
  <Typography
480
514
  variant="body2"
481
515
  sx={{
482
516
  color: "text.secondary",
483
- minWidth: 110
484
- }}>
517
+ minWidth: 110,
518
+ }}
519
+ >
485
520
  {label}
486
521
  </Typography>
487
522
  {link ? (