diff --git a/src/components/Dashboard/common/FilePreview.jsx b/src/components/Dashboard/common/FilePreview.jsx
index 0091b66..14fcdac 100644
--- a/src/components/Dashboard/common/FilePreview.jsx
+++ b/src/components/Dashboard/common/FilePreview.jsx
@@ -12,10 +12,21 @@ const FilePreview = ({ file, style = {} }) => {
const [fileObjectUrl, setFileObjectUrl] = useState(null)
const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
const fetchPreview = useCallback(async () => {
+ if (error != null) {
+ return
+ }
setLoading(true)
+ console.log('fetching file content', file)
const objectUrl = await fetchFileContent(file, false)
+ if (objectUrl == null) {
+ setLoading(false)
+ console.error('Failed to fetch file content', file)
+ setError('Failed to fetch file content')
+ return
+ }
setFileObjectUrl(objectUrl)
setLoading(false)
}, [file, fetchFileContent])
@@ -30,6 +41,10 @@ const FilePreview = ({ file, style = {} }) => {
return
}
+ if (error != null) {
+ return
{error}
+ }
+
const isGcode = ['.g', '.gcode'].includes(
(file?.extension || '').toLowerCase()
)
diff --git a/src/components/Dashboard/common/FileUpload.jsx b/src/components/Dashboard/common/FileUpload.jsx
index e4ff122..be83fe1 100644
--- a/src/components/Dashboard/common/FileUpload.jsx
+++ b/src/components/Dashboard/common/FileUpload.jsx
@@ -7,12 +7,14 @@ import ObjectSelect from './ObjectSelect'
import FileList from './FileList'
import PlusIcon from '../../Icons/PlusIcon'
import { LoadingOutlined } from '@ant-design/icons'
+import FileIcon from '../../Icons/FileIcon'
const { Text } = Typography
const FileUpload = ({
value,
onChange,
+ minimal = false,
multiple = true,
defaultPreviewOpen = false,
showPreview = true,
@@ -96,6 +98,15 @@ const FileUpload = ({
}
}
+ if (minimal == true) {
+ return (
+
+
+ {currentFiles[0]?.name}
+
+ )
+ }
+
return (
{hasNoItems && uploading == false ? (
@@ -170,7 +181,8 @@ FileUpload.propTypes = {
multiple: PropTypes.bool,
showPreview: PropTypes.bool,
showInfo: PropTypes.bool,
- defaultPreviewOpen: PropTypes.bool
+ defaultPreviewOpen: PropTypes.bool,
+ minimal: PropTypes.bool
}
export default FileUpload
diff --git a/src/components/Dashboard/common/ObjectProperty.jsx b/src/components/Dashboard/common/ObjectProperty.jsx
index 8a7992b..1bca065 100644
--- a/src/components/Dashboard/common/ObjectProperty.jsx
+++ b/src/components/Dashboard/common/ObjectProperty.jsx
@@ -559,6 +559,7 @@ const ObjectProperty = ({
return (
{
{...changeProperty}
longId={false}
minimal={true}
+ showPreview={false}
objectData={value?.old}
maxWidth='200px'
/>
@@ -71,6 +72,7 @@ const PropertyChanges = ({ type, value }) => {
{...changeProperty}
longId={false}
minimal={true}
+ showPreview={false}
objectData={value?.new}
maxWidth='200px'
/>
diff --git a/src/components/Dashboard/context/AuthContext.jsx b/src/components/Dashboard/context/AuthContext.jsx
index 2510980..c4c796d 100644
--- a/src/components/Dashboard/context/AuthContext.jsx
+++ b/src/components/Dashboard/context/AuthContext.jsx
@@ -42,6 +42,19 @@ const AuthContext = createContext()
const { Text } = Typography
+const extractUserFromAuthData = (authData) => {
+ if (!authData || typeof authData !== 'object') return null
+ if (authData.user && typeof authData.user === 'object') return authData.user
+
+ // Some endpoints may return the "user" fields at the top-level. Only treat it
+ // as a user object if it looks like one (avoid confusing token-only payloads).
+ const looksLikeUser =
+ authData._id || authData.username || authData.email || authData.name
+ if (looksLikeUser) return authData
+
+ return null
+}
+
const AuthProvider = ({ children }) => {
const [messageApi, contextHolder] = message.useMessage()
const [notificationApi, notificationContextHolder] =
@@ -80,19 +93,6 @@ const AuthProvider = ({ children }) => {
redirectType = 'app-scheme'
}
- const extractUserFromAuthData = (authData) => {
- if (!authData || typeof authData !== 'object') return null
- if (authData.user && typeof authData.user === 'object') return authData.user
-
- // Some endpoints may return the "user" fields at the top-level. Only treat it
- // as a user object if it looks like one (avoid confusing token-only payloads).
- const looksLikeUser =
- authData._id || authData.username || authData.email || authData.name
- if (looksLikeUser) return authData
-
- return null
- }
-
const isSessionExpired = (session) => {
if (!session?.expiresAt) return true
const now = new Date()
@@ -100,6 +100,32 @@ const AuthProvider = ({ children }) => {
return expirationDate <= now
}
+ const getUserInfo = useCallback(
+ async (token, getIsCancelled = () => false) => {
+ try {
+ const response = await axios.get(`${config.backendUrl}/auth/user`, {
+ headers: { Authorization: `Bearer ${token}` }
+ })
+ if (!getIsCancelled() && response.status === 200 && response.data) {
+ const nextUser = extractUserFromAuthData(response.data)
+ if (nextUser) setUserProfile(nextUser)
+ }
+ } catch (err) {
+ if (!getIsCancelled()) {
+ logger.debug('Failed to refresh user from API:', err)
+ if (err.response?.status === 401) {
+ setAuthenticated(false)
+ setToken(null)
+ setUserProfile(null)
+ setExpiresAt(null)
+ setShowUnauthorizedModal(true)
+ }
+ }
+ }
+ },
+ []
+ )
+
const persistSession = useCallback(
async ({ token: nextToken, expiresAt: nextExpiresAt, user: nextUser }) => {
if (isElectron) {
@@ -154,6 +180,10 @@ const AuthProvider = ({ children }) => {
setUserProfile(session.user)
setExpiresAt(session.expiresAt)
setAuthenticated(true)
+
+ if (session.user) {
+ getUserInfo(session.token, () => cancelled)
+ }
} else if (!cancelled) {
setAuthenticated(false)
setUserProfile(null)
@@ -173,6 +203,10 @@ const AuthProvider = ({ children }) => {
setUserProfile(storedUser)
setExpiresAt(storedExpiresAt)
setAuthenticated(true)
+
+ if (storedToken && storedUser) {
+ getUserInfo(storedToken, () => cancelled)
+ }
}
} else if (!cancelled) {
setAuthenticated(false)
@@ -197,7 +231,7 @@ const AuthProvider = ({ children }) => {
return () => {
cancelled = true
}
- }, [isElectron, getAuthSession, clearPersistedSession])
+ }, [isElectron, getAuthSession, clearPersistedSession, getUserInfo])
// Set up cookie synchronization between tabs
useEffect(() => {