Enhance FilePreview and FileUpload components with error handling and minimal display option. Refactor AuthContext to improve user info retrieval and session management.
This commit is contained in:
parent
845b330242
commit
52fd0ebd63
@ -12,10 +12,21 @@ const FilePreview = ({ file, style = {} }) => {
|
|||||||
|
|
||||||
const [fileObjectUrl, setFileObjectUrl] = useState(null)
|
const [fileObjectUrl, setFileObjectUrl] = useState(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
|
||||||
const fetchPreview = useCallback(async () => {
|
const fetchPreview = useCallback(async () => {
|
||||||
|
if (error != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
console.log('fetching file content', file)
|
||||||
const objectUrl = await fetchFileContent(file, false)
|
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)
|
setFileObjectUrl(objectUrl)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, [file, fetchFileContent])
|
}, [file, fetchFileContent])
|
||||||
@ -30,6 +41,10 @@ const FilePreview = ({ file, style = {} }) => {
|
|||||||
return <LoadingPlaceholder message={'Loading file preview...'} />
|
return <LoadingPlaceholder message={'Loading file preview...'} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
return <div style={{ color: 'red' }}>{error}</div>
|
||||||
|
}
|
||||||
|
|
||||||
const isGcode = ['.g', '.gcode'].includes(
|
const isGcode = ['.g', '.gcode'].includes(
|
||||||
(file?.extension || '').toLowerCase()
|
(file?.extension || '').toLowerCase()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -7,12 +7,14 @@ import ObjectSelect from './ObjectSelect'
|
|||||||
import FileList from './FileList'
|
import FileList from './FileList'
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import FileIcon from '../../Icons/FileIcon'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const FileUpload = ({
|
const FileUpload = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
minimal = false,
|
||||||
multiple = true,
|
multiple = true,
|
||||||
defaultPreviewOpen = false,
|
defaultPreviewOpen = false,
|
||||||
showPreview = true,
|
showPreview = true,
|
||||||
@ -96,6 +98,15 @@ const FileUpload = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (minimal == true) {
|
||||||
|
return (
|
||||||
|
<Flex gap={'small'} vertical>
|
||||||
|
<FileIcon file={currentFiles[0]} style={{ fontSize: '24px' }} />
|
||||||
|
<Text>{currentFiles[0]?.name}</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={'small'} vertical>
|
<Flex gap={'small'} vertical>
|
||||||
{hasNoItems && uploading == false ? (
|
{hasNoItems && uploading == false ? (
|
||||||
@ -170,7 +181,8 @@ FileUpload.propTypes = {
|
|||||||
multiple: PropTypes.bool,
|
multiple: PropTypes.bool,
|
||||||
showPreview: PropTypes.bool,
|
showPreview: PropTypes.bool,
|
||||||
showInfo: PropTypes.bool,
|
showInfo: PropTypes.bool,
|
||||||
defaultPreviewOpen: PropTypes.bool
|
defaultPreviewOpen: PropTypes.bool,
|
||||||
|
minimal: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileUpload
|
export default FileUpload
|
||||||
|
|||||||
@ -559,6 +559,7 @@ const ObjectProperty = ({
|
|||||||
return (
|
return (
|
||||||
<FileList
|
<FileList
|
||||||
files={value}
|
files={value}
|
||||||
|
minimal={minimal}
|
||||||
multiple={false}
|
multiple={false}
|
||||||
card={false}
|
card={false}
|
||||||
defaultPreviewOpen={previewOpen}
|
defaultPreviewOpen={previewOpen}
|
||||||
@ -572,6 +573,7 @@ const ObjectProperty = ({
|
|||||||
return (
|
return (
|
||||||
<FileList
|
<FileList
|
||||||
files={value}
|
files={value}
|
||||||
|
minimal={minimal}
|
||||||
multiple={true}
|
multiple={true}
|
||||||
defaultPreviewOpen={previewOpen}
|
defaultPreviewOpen={previewOpen}
|
||||||
showPreview={showPreview}
|
showPreview={showPreview}
|
||||||
@ -801,6 +803,7 @@ const ObjectProperty = ({
|
|||||||
return (
|
return (
|
||||||
<FileUpload
|
<FileUpload
|
||||||
multiple={false}
|
multiple={false}
|
||||||
|
minimal={minimal}
|
||||||
defaultPreviewOpen={previewOpen}
|
defaultPreviewOpen={previewOpen}
|
||||||
showPreview={showPreview}
|
showPreview={showPreview}
|
||||||
showInfo={showHyperlink}
|
showInfo={showHyperlink}
|
||||||
@ -811,6 +814,7 @@ const ObjectProperty = ({
|
|||||||
return (
|
return (
|
||||||
<FileUpload
|
<FileUpload
|
||||||
multiple={true}
|
multiple={true}
|
||||||
|
minimal={minimal}
|
||||||
defaultPreviewOpen={previewOpen}
|
defaultPreviewOpen={previewOpen}
|
||||||
showPreview={showPreview}
|
showPreview={showPreview}
|
||||||
showInfo={showHyperlink}
|
showInfo={showHyperlink}
|
||||||
|
|||||||
@ -57,6 +57,7 @@ const PropertyChanges = ({ type, value }) => {
|
|||||||
{...changeProperty}
|
{...changeProperty}
|
||||||
longId={false}
|
longId={false}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
|
showPreview={false}
|
||||||
objectData={value?.old}
|
objectData={value?.old}
|
||||||
maxWidth='200px'
|
maxWidth='200px'
|
||||||
/>
|
/>
|
||||||
@ -71,6 +72,7 @@ const PropertyChanges = ({ type, value }) => {
|
|||||||
{...changeProperty}
|
{...changeProperty}
|
||||||
longId={false}
|
longId={false}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
|
showPreview={false}
|
||||||
objectData={value?.new}
|
objectData={value?.new}
|
||||||
maxWidth='200px'
|
maxWidth='200px'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -42,6 +42,19 @@ const AuthContext = createContext()
|
|||||||
|
|
||||||
const { Text } = Typography
|
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 AuthProvider = ({ children }) => {
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
const [notificationApi, notificationContextHolder] =
|
const [notificationApi, notificationContextHolder] =
|
||||||
@ -80,19 +93,6 @@ const AuthProvider = ({ children }) => {
|
|||||||
redirectType = 'app-scheme'
|
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) => {
|
const isSessionExpired = (session) => {
|
||||||
if (!session?.expiresAt) return true
|
if (!session?.expiresAt) return true
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@ -100,6 +100,32 @@ const AuthProvider = ({ children }) => {
|
|||||||
return expirationDate <= now
|
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(
|
const persistSession = useCallback(
|
||||||
async ({ token: nextToken, expiresAt: nextExpiresAt, user: nextUser }) => {
|
async ({ token: nextToken, expiresAt: nextExpiresAt, user: nextUser }) => {
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
@ -154,6 +180,10 @@ const AuthProvider = ({ children }) => {
|
|||||||
setUserProfile(session.user)
|
setUserProfile(session.user)
|
||||||
setExpiresAt(session.expiresAt)
|
setExpiresAt(session.expiresAt)
|
||||||
setAuthenticated(true)
|
setAuthenticated(true)
|
||||||
|
|
||||||
|
if (session.user) {
|
||||||
|
getUserInfo(session.token, () => cancelled)
|
||||||
|
}
|
||||||
} else if (!cancelled) {
|
} else if (!cancelled) {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
@ -173,6 +203,10 @@ const AuthProvider = ({ children }) => {
|
|||||||
setUserProfile(storedUser)
|
setUserProfile(storedUser)
|
||||||
setExpiresAt(storedExpiresAt)
|
setExpiresAt(storedExpiresAt)
|
||||||
setAuthenticated(true)
|
setAuthenticated(true)
|
||||||
|
|
||||||
|
if (storedToken && storedUser) {
|
||||||
|
getUserInfo(storedToken, () => cancelled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (!cancelled) {
|
} else if (!cancelled) {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
@ -197,7 +231,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [isElectron, getAuthSession, clearPersistedSession])
|
}, [isElectron, getAuthSession, clearPersistedSession, getUserInfo])
|
||||||
|
|
||||||
// Set up cookie synchronization between tabs
|
// Set up cookie synchronization between tabs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user