diff --git a/ui/src/client/fetcher.ts b/ui/src/client/fetcher.ts
index 66c0579c3..12d893e0f 100644
--- a/ui/src/client/fetcher.ts
+++ b/ui/src/client/fetcher.ts
@@ -108,15 +108,15 @@ async function handleResponse(
if (response.status === 401 && redirect) {
window.location.href = '/sign-in'
}
+ let error
+ try {
+ error = await response.json()
+ } catch {
+ error = 'Oops! something went wrong.'
+ }
if (showError) {
- let message
- try {
- message = errorToString(await response.json())
- } catch {
- message = 'Oops! something went wrong.'
- }
- store.dispatch(errorOccurred(message))
- throw new Error(message)
+ store.dispatch(errorOccurred(errorToString(error)))
}
+ throw error
}
}
diff --git a/ui/src/components/sharing/index.tsx b/ui/src/components/sharing/index.tsx
index 39b084874..6823259be 100644
--- a/ui/src/components/sharing/index.tsx
+++ b/ui/src/components/sharing/index.tsx
@@ -23,24 +23,23 @@ import {
} from '@chakra-ui/react'
import cx from 'classnames'
import FileAPI from '@/client/api/file'
-import { geOwnerPermission } from '@/client/api/permission'
import { swrConfig } from '@/client/options'
import { useAppDispatch, useAppSelector } from '@/store/hook'
import { sharingModalDidClose } from '@/store/ui/files'
-import SharingGroupOverview from './sharing-group-overview'
-import SharingUserOverview from './sharing-user-overview'
+import SharingGroupForm from './sharing-group-form'
+import SharingUserForm from './sharing-user-form'
const Sharing = () => {
const dispatch = useAppDispatch()
const selection = useAppSelector((state) => state.ui.files.selection)
const isModalOpen = useAppSelector((state) => state.ui.files.isShareModalOpen)
- const { data: file } = FileAPI.useGet(selection[0], swrConfig())
+ const isSingleSelection = selection.length === 1
const { data: userPermissions } = FileAPI.useGetUserPermissions(
- file && geOwnerPermission(file.permission) ? file.id : undefined,
+ isSingleSelection ? selection[0] : undefined,
swrConfig(),
)
const { data: groupPermissions } = FileAPI.useGetGroupPermissions(
- file && geOwnerPermission(file.permission) ? file.id : undefined,
+ isSingleSelection ? selection[0] : undefined,
swrConfig(),
)
@@ -69,7 +68,7 @@ const Sharing = () => {
className={cx('flex', 'flex-row', 'items-center', 'gap-0.5')}
>
Users
- {file && userPermissions && userPermissions.length > 0 ? (
+ {userPermissions && userPermissions.length > 0 ? (
{userPermissions.length}
@@ -81,7 +80,7 @@ const Sharing = () => {
className={cx('flex', 'flex-row', 'items-center', 'gap-0.5')}
>
Groups
- {file && groupPermissions && groupPermissions.length > 0 ? (
+ {groupPermissions && groupPermissions.length > 0 ? (
{groupPermissions.length}
@@ -91,10 +90,10 @@ const Sharing = () => {
-
+
-
+
diff --git a/ui/src/components/sharing/sharing-form-skeleton.tsx b/ui/src/components/sharing/sharing-form-skeleton.tsx
index b6b6e40a6..bb747a11e 100644
--- a/ui/src/components/sharing/sharing-form-skeleton.tsx
+++ b/ui/src/components/sharing/sharing-form-skeleton.tsx
@@ -12,9 +12,9 @@ import cx from 'classnames'
const SharingFormSkeleton = () => (
-
-
-
+
+
+
)
diff --git a/ui/src/components/sharing/sharing-group-overview.tsx b/ui/src/components/sharing/sharing-group-form.tsx
similarity index 68%
rename from ui/src/components/sharing/sharing-group-overview.tsx
rename to ui/src/components/sharing/sharing-group-form.tsx
index d5384f8ca..2d06d2274 100644
--- a/ui/src/components/sharing/sharing-group-overview.tsx
+++ b/ui/src/components/sharing/sharing-group-form.tsx
@@ -10,17 +10,20 @@
import { useCallback, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { Button } from '@chakra-ui/react'
-import { IconAdd, IconCheck, SectionPlaceholder, Select } from '@koupr/ui'
+import {
+ IconAdd,
+ IconCheck,
+ SectionError,
+ SectionPlaceholder,
+ Select,
+} from '@koupr/ui'
import { OptionBase } from 'chakra-react-select'
import cx from 'classnames'
import FileAPI from '@/client/api/file'
import GroupAPI, { Group } from '@/client/api/group'
-import {
- geEditorPermission,
- geOwnerPermission,
- PermissionType,
-} from '@/client/api/permission'
+import { geEditorPermission, PermissionType } from '@/client/api/permission'
import WorkspaceAPI from '@/client/api/workspace'
+import { errorToString } from '@/client/error'
import { swrConfig } from '@/client/options'
import GroupSelector from '@/components/common/group-selector'
import { useAppDispatch, useAppSelector } from '@/store/hook'
@@ -33,51 +36,63 @@ interface PermissionTypeOption extends OptionBase {
label: string
}
-const SharingGroupOverview = () => {
- const { id: workspaceId, fileId } = useParams()
+const SharingGroupForm = () => {
+ const { id: workspaceId } = useParams()
const dispatch = useAppDispatch()
const selection = useAppSelector((state) => state.ui.files.selection)
const mutateFiles = useAppSelector((state) => state.ui.files.mutate)
const [isGranting, setIsGranting] = useState(false)
const [group, setGroup] = useState()
const [permission, setPermission] = useState()
- const { data: workspace } = WorkspaceAPI.useGet(workspaceId)
- const { data: file } = FileAPI.useGet(selection[0], swrConfig())
- const { data: groups } = GroupAPI.useList(
+ const {
+ data: workspace,
+ error: workspaceError,
+ isLoading: isWorkspaceLoading,
+ } = WorkspaceAPI.useGet(workspaceId, swrConfig())
+ const {
+ data: groups,
+ error: groupsError,
+ isLoading: isGroupsLoading,
+ } = GroupAPI.useList(
{
organizationId: workspace?.organization.id,
},
swrConfig(),
)
+ const isSingleSelection = selection.length === 1
const { mutate: mutatePermissions } = FileAPI.useGetGroupPermissions(
- file && geOwnerPermission(file.permission) ? file.id : undefined,
+ isSingleSelection ? selection[0] : undefined,
)
- const isSingleSelection = selection.length === 1
+ const isWorkspaceError = !workspace && workspaceError
+ const isWorkspaceReady = workspace && !workspaceError
+ const isGroupsError = !groups && groupsError
+ const isGroupsEmpty = groups && !groupsError && groups.totalElements === 0
+ const isGroupsReady = groups && !groupsError && groups.totalElements > 0
const handleGrantPermission = useCallback(async () => {
- if (group && permission) {
- try {
- setIsGranting(true)
- await FileAPI.grantGroupPermission({
- ids: selection,
- groupId: group.id,
- permission,
- })
- await mutateFiles?.()
- if (isSingleSelection) {
- await mutatePermissions()
- }
- setGroup(undefined)
- setIsGranting(false)
- if (!isSingleSelection) {
- dispatch(sharingModalDidClose())
- }
- } catch {
- setIsGranting(false)
+ if (!group || !permission) {
+ return
+ }
+ try {
+ setIsGranting(true)
+ await FileAPI.grantGroupPermission({
+ ids: selection,
+ groupId: group.id,
+ permission,
+ })
+ await mutateFiles?.()
+ if (isSingleSelection) {
+ await mutatePermissions()
+ }
+ setGroup(undefined)
+ setIsGranting(false)
+ if (!isSingleSelection) {
+ dispatch(sharingModalDidClose())
}
+ } catch {
+ setIsGranting(false)
}
}, [
- fileId,
selection,
group,
permission,
@@ -89,8 +104,34 @@ const SharingGroupOverview = () => {
return (
- {!groups ?
: null}
- {groups && groups.totalElements > 0 ? (
+ {isWorkspaceLoading || isGroupsLoading ?
: null}
+ {isWorkspaceError || isGroupsError ? (
+
+ ) : null}
+ {isWorkspaceReady && isGroupsEmpty ? (
+
+ {workspace &&
+ geEditorPermission(workspace.organization.permission) ? (
+ }
+ to={`/new/group?org=${workspace.organization.id}`}
+ >
+ New Group
+
+ ) : null}
+ >
+ }
+ height="auto"
+ />
+ ) : null}
+ {isWorkspaceReady && isGroupsReady ? (
{
) : null}
- {groups && groups.totalElements === 0 ? (
-
- {workspace &&
- geEditorPermission(workspace.organization.permission) ? (
- }
- to={`/new/group?org=${workspace.organization.id}`}
- >
- New Group
-
- ) : null}
- >
- }
- height="auto"
- />
- ) : null}
{isSingleSelection ? (
<>
@@ -152,4 +173,4 @@ const SharingGroupOverview = () => {
)
}
-export default SharingGroupOverview
+export default SharingGroupForm
diff --git a/ui/src/components/sharing/sharing-group-permissions.tsx b/ui/src/components/sharing/sharing-group-permissions.tsx
index 0129ef568..78365dcd0 100644
--- a/ui/src/components/sharing/sharing-group-permissions.tsx
+++ b/ui/src/components/sharing/sharing-group-permissions.tsx
@@ -8,7 +8,6 @@
// by the GNU Affero General Public License v3.0 only, included in the file
// AGPL-3.0-only in the root of this repository.
import { useCallback, useState } from 'react'
-import { useParams } from 'react-router-dom'
import {
Table,
Thead,
@@ -20,25 +19,34 @@ import {
Badge,
Avatar,
} from '@chakra-ui/react'
-import { IconDelete, SectionPlaceholder, SectionSpinner, Text } from '@koupr/ui'
+import {
+ IconDelete,
+ SectionError,
+ SectionPlaceholder,
+ SectionSpinner,
+ Text,
+} from '@koupr/ui'
import cx from 'classnames'
import FileAPI, { GroupPermission } from '@/client/api/file'
-import { geOwnerPermission } from '@/client/api/permission'
+import { errorToString } from '@/client/error'
import { swrConfig } from '@/client/options'
import { useAppSelector } from '@/store/hook'
const SharingGroupPermissions = () => {
- const { fileId } = useParams()
const selection = useAppSelector((state) => state.ui.files.selection)
const mutateFiles = useAppSelector((state) => state.ui.files.mutate)
const [revokedPermission, setRevokedPermission] = useState()
- const { data: file } = FileAPI.useGet(selection[0], swrConfig())
- const { data: permissions, mutate: mutatePermissions } =
- FileAPI.useGetGroupPermissions(
- file && geOwnerPermission(file.permission) ? file.id : undefined,
- swrConfig(),
- )
- const isSingleSelection = selection.length === 1
+ const {
+ data: permissions,
+ error: permissionsError,
+ isLoading: isPermissionsLoading,
+ mutate: mutatePermissions,
+ } = FileAPI.useGetGroupPermissions(selection[0], swrConfig())
+ const isPermissionsError = !permissions && permissionsError
+ const isPermissionsEmpty =
+ permissions && !permissionsError && permissions.length === 0
+ const isPermissionsReady =
+ permissions && !permissionsError && permissions.length > 0
const handleRevokePermission = useCallback(
async (permission: GroupPermission) => {
@@ -49,23 +57,24 @@ const SharingGroupPermissions = () => {
groupId: permission.group.id,
})
await mutateFiles?.()
- if (isSingleSelection) {
- await mutatePermissions()
- }
+ await mutatePermissions()
} finally {
setRevokedPermission(undefined)
}
},
- [fileId, selection, isSingleSelection, mutateFiles, mutatePermissions],
+ [selection, mutateFiles, mutatePermissions],
)
return (
<>
- {!permissions ? : null}
- {permissions && permissions.length === 0 ? (
+ {isPermissionsLoading ? : null}
+ {isPermissionsError ? (
+
+ ) : null}
+ {isPermissionsEmpty ? (
) : null}
- {permissions && permissions.length > 0 ? (
+ {isPermissionsReady ? (
diff --git a/ui/src/components/sharing/sharing-user-overview.tsx b/ui/src/components/sharing/sharing-user-form.tsx
similarity index 77%
rename from ui/src/components/sharing/sharing-user-overview.tsx
rename to ui/src/components/sharing/sharing-user-form.tsx
index 7f83fef1d..21149141b 100644
--- a/ui/src/components/sharing/sharing-user-overview.tsx
+++ b/ui/src/components/sharing/sharing-user-form.tsx
@@ -10,17 +10,20 @@
import { useCallback, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { Button } from '@chakra-ui/react'
-import { IconCheck, IconPersonAdd, SectionPlaceholder, Select } from '@koupr/ui'
+import {
+ IconCheck,
+ IconPersonAdd,
+ SectionError,
+ SectionPlaceholder,
+ Select,
+} from '@koupr/ui'
import { OptionBase } from 'chakra-react-select'
import cx from 'classnames'
import FileAPI from '@/client/api/file'
-import {
- geEditorPermission,
- geOwnerPermission,
- PermissionType,
-} from '@/client/api/permission'
+import { geEditorPermission, PermissionType } from '@/client/api/permission'
import UserAPI, { User } from '@/client/api/user'
import WorkspaceAPI from '@/client/api/workspace'
+import { errorToString } from '@/client/error'
import { swrConfig } from '@/client/options'
import UserSelector from '@/components/common/user-selector'
import { useAppDispatch, useAppSelector } from '@/store/hook'
@@ -34,8 +37,8 @@ interface PermissionTypeOption extends OptionBase {
label: string
}
-const SharingUserOverview = () => {
- const { id: workspaceId, fileId } = useParams()
+const SharingUserForm = () => {
+ const { id: workspaceId } = useParams()
const navigate = useNavigate()
const dispatch = useAppDispatch()
const selection = useAppSelector((state) => state.ui.files.selection)
@@ -43,19 +46,31 @@ const SharingUserOverview = () => {
const [isGranting, setIsGranting] = useState(false)
const [user, setUser] = useState()
const [permission, setPermission] = useState()
- const { data: workspace } = WorkspaceAPI.useGet(workspaceId)
- const { data: file } = FileAPI.useGet(selection[0], swrConfig())
- const { data: users } = UserAPI.useList(
+ const {
+ data: workspace,
+ error: workspaceError,
+ isLoading: isWorkspaceLoading,
+ } = WorkspaceAPI.useGet(workspaceId, swrConfig())
+ const {
+ data: users,
+ error: usersError,
+ isLoading: isUsersLoading,
+ } = UserAPI.useList(
{
organizationId: workspace?.organization.id,
excludeMe: true,
},
swrConfig(),
)
- const { mutate: mutatePermissions } = FileAPI.useGetUserPermissions(
- file && geOwnerPermission(file.permission) ? file.id : undefined,
- )
const isSingleSelection = selection.length === 1
+ const { mutate: mutatePermissions } = FileAPI.useGetGroupPermissions(
+ isSingleSelection ? selection[0] : undefined,
+ )
+ const isWorkspaceError = !workspace && workspaceError
+ const isWorkspaceReady = workspace && !workspaceError
+ const isUsersError = !users && usersError
+ const isUsersEmpty = users && !usersError && users.totalElements === 0
+ const isUsersReady = users && !usersError && users.totalElements > 0
const handleGrantPermission = useCallback(async () => {
if (!user || !permission) {
@@ -81,7 +96,6 @@ const SharingUserOverview = () => {
setIsGranting(false)
}
}, [
- fileId,
selection,
user,
permission,
@@ -101,8 +115,14 @@ const SharingUserOverview = () => {
return (
- {!users ?
: null}
- {users && users.totalElements === 0 ? (
+ {isWorkspaceLoading || isUsersLoading ?
: null}
+ {isWorkspaceError || isUsersError ? (
+
+ ) : null}
+ {isWorkspaceReady && isUsersEmpty ? (
{
height="auto"
/>
) : null}
- {users && users.totalElements > 0 ? (
+ {isWorkspaceReady && isUsersReady ? (
{
)
}
-export default SharingUserOverview
+export default SharingUserForm
diff --git a/ui/src/components/sharing/sharing-user-permissions.tsx b/ui/src/components/sharing/sharing-user-permissions.tsx
index 4e53ba7ab..7a6c94ebb 100644
--- a/ui/src/components/sharing/sharing-user-permissions.tsx
+++ b/ui/src/components/sharing/sharing-user-permissions.tsx
@@ -20,11 +20,17 @@ import {
Badge,
Avatar,
} from '@chakra-ui/react'
-import { IconDelete, SectionPlaceholder, SectionSpinner, Text } from '@koupr/ui'
+import {
+ IconDelete,
+ SectionError,
+ SectionPlaceholder,
+ SectionSpinner,
+ Text,
+} from '@koupr/ui'
import cx from 'classnames'
import FileAPI, { UserPermission } from '@/client/api/file'
-import { geOwnerPermission } from '@/client/api/permission'
import WorkspaceAPI from '@/client/api/workspace'
+import { errorToString } from '@/client/error'
import IdPUserAPI from '@/client/idp/user'
import { swrConfig } from '@/client/options'
import { getPictureUrlById } from '@/lib/helpers/picture'
@@ -35,14 +41,19 @@ const SharingUserPermissions = () => {
const selection = useAppSelector((state) => state.ui.files.selection)
const mutateFiles = useAppSelector((state) => state.ui.files.mutate)
const [revokedPermission, setRevokedPermission] = useState()
- const { data: workspace } = WorkspaceAPI.useGet(workspaceId)
- const { data: file } = FileAPI.useGet(selection[0], swrConfig())
+ const { data: workspace } = WorkspaceAPI.useGet(workspaceId, swrConfig())
const { data: me } = IdPUserAPI.useGet()
- const { data: permissions, mutate: mutatePermissions } =
- FileAPI.useGetUserPermissions(
- file && geOwnerPermission(file.permission) ? file.id : undefined,
- swrConfig(),
- )
+ const {
+ data: permissions,
+ error: permissionsError,
+ isLoading: isPermissionsLoading,
+ mutate: mutatePermissions,
+ } = FileAPI.useGetUserPermissions(selection[0], swrConfig())
+ const isPermissionsError = !permissions && permissionsError
+ const isPermissionsEmpty =
+ permissions && !permissionsError && permissions.length === 0
+ const isPermissionsReady =
+ permissions && !permissionsError && permissions.length > 0
const handleRevokePermission = useCallback(
async (permission: UserPermission) => {
@@ -63,11 +74,14 @@ const SharingUserPermissions = () => {
return (
<>
- {!permissions ? : null}
- {permissions && permissions.length === 0 ? (
+ {isPermissionsLoading ? : null}
+ {isPermissionsError ? (
+
+ ) : null}
+ {isPermissionsEmpty ? (
) : null}
- {permissions && permissions.length > 0 ? (
+ {isPermissionsReady ? (