mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-27 02:51:57 +02:00
Merge pull request #25550 from overleaf/dp-share-modal-proptypes
Remove proptypes from ShareProjectModal GitOrigin-RevId: b95fed5007f72e4a57a65b1d08d8fcc9579b3630
This commit is contained in:
@@ -2,20 +2,19 @@ import { useEffect, useState, useMemo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMultipleSelection } from 'downshift'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import SelectCollaborators from './select-collaborators'
|
||||
import SelectCollaborators, { ContactItem } from './select-collaborators'
|
||||
import { resendInvite, sendInvite } from '../utils/api'
|
||||
import { useUserContacts } from '../hooks/use-user-contacts'
|
||||
import useIsMounted from '@/shared/hooks/use-is-mounted'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
|
||||
import PropTypes from 'prop-types'
|
||||
import OLForm from '@/features/ui/components/ol/ol-form'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import { Select } from '@/shared/components/select'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function AddCollaborators({ readOnly }) {
|
||||
export default function AddCollaborators({ readOnly }: { readOnly?: boolean }) {
|
||||
const [privileges, setPrivileges] = useState('readAndWrite')
|
||||
|
||||
const isMounted = useIsMounted()
|
||||
@@ -43,7 +42,7 @@ export default function AddCollaborators({ readOnly }) {
|
||||
)
|
||||
}, [contacts, currentMemberEmails])
|
||||
|
||||
const multipleSelectionProps = useMultipleSelection({
|
||||
const multipleSelectionProps = useMultipleSelection<ContactItem>({
|
||||
initialActiveIndex: 0,
|
||||
initialSelectedItems: [],
|
||||
})
|
||||
@@ -129,7 +128,7 @@ export default function AddCollaborators({ readOnly }) {
|
||||
? previousViewersAmount + 1
|
||||
: previousViewersAmount,
|
||||
})
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
setInFlight(false)
|
||||
setError(
|
||||
error.data?.errorReason ||
|
||||
@@ -213,13 +212,17 @@ export default function AddCollaborators({ readOnly }) {
|
||||
dataTestId="add-collaborator-select"
|
||||
items={privilegeOptions}
|
||||
itemToKey={item => item.key}
|
||||
itemToString={item => item.label}
|
||||
itemToSubtitle={item => item.description || ''}
|
||||
itemToDisabled={item => readOnly && item.key !== 'readOnly'}
|
||||
itemToString={item => item?.label || ''}
|
||||
itemToSubtitle={item => item?.description || ''}
|
||||
itemToDisabled={item => !!(readOnly && item?.key !== 'readOnly')}
|
||||
selected={privilegeOptions.find(
|
||||
option => option.key === privileges
|
||||
)}
|
||||
onSelectedItemChanged={item => setPrivileges(item.key)}
|
||||
onSelectedItemChanged={item => {
|
||||
if (item) {
|
||||
setPrivileges(item.key)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ClickableElementEnhancer
|
||||
as={OLButton}
|
||||
@@ -233,7 +236,3 @@ export default function AddCollaborators({ readOnly }) {
|
||||
</OLForm>
|
||||
)
|
||||
}
|
||||
|
||||
AddCollaborators.propTypes = {
|
||||
readOnly: PropTypes.bool,
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import TransferOwnershipModal from './transfer-ownership-modal'
|
||||
@@ -227,15 +226,6 @@ export default function EditMember({
|
||||
</form>
|
||||
)
|
||||
}
|
||||
EditMember.propTypes = {
|
||||
member: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
privileges: PropTypes.string.isRequired,
|
||||
}),
|
||||
hasExceededCollaboratorLimit: PropTypes.bool.isRequired,
|
||||
canAddCollaborators: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
type SelectPrivilegeProps = {
|
||||
value: string
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MemberPrivileges from './member-privileges'
|
||||
@@ -11,8 +10,15 @@ import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { ProjectContextMember } from '@/shared/context/types/project-context'
|
||||
|
||||
export default function Invite({ invite, isProjectOwner }) {
|
||||
export default function Invite({
|
||||
invite,
|
||||
isProjectOwner,
|
||||
}: {
|
||||
invite: ProjectContextMember
|
||||
isProjectOwner: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<OLRow className="project-invite">
|
||||
@@ -38,12 +44,7 @@ export default function Invite({ invite, isProjectOwner }) {
|
||||
)
|
||||
}
|
||||
|
||||
Invite.propTypes = {
|
||||
invite: PropTypes.object.isRequired,
|
||||
isProjectOwner: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
function ResendInvite({ invite }) {
|
||||
function ResendInvite({ invite }: { invite: ProjectContextMember }) {
|
||||
const { t } = useTranslation()
|
||||
const { monitorRequest, setError, inFlight } = useShareProjectContext()
|
||||
const { _id: projectId } = useProjectContext()
|
||||
@@ -66,7 +67,9 @@ function ResendInvite({ invite }) {
|
||||
// if (buttonRef.current) {
|
||||
// buttonRef.current.blur()
|
||||
// }
|
||||
document.activeElement.blur()
|
||||
if (document.activeElement) {
|
||||
;(document.activeElement as HTMLElement).blur()
|
||||
}
|
||||
}),
|
||||
[invite, monitorRequest, projectId, setError]
|
||||
)
|
||||
@@ -84,16 +87,12 @@ function ResendInvite({ invite }) {
|
||||
)
|
||||
}
|
||||
|
||||
ResendInvite.propTypes = {
|
||||
invite: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
function RevokeInvite({ invite }) {
|
||||
function RevokeInvite({ invite }: { invite: ProjectContextMember }) {
|
||||
const { t } = useTranslation()
|
||||
const { updateProject, monitorRequest } = useShareProjectContext()
|
||||
const { _id: projectId, invites, members } = useProjectContext()
|
||||
|
||||
function handleClick(event) {
|
||||
function handleClick(event: React.MouseEvent) {
|
||||
event.preventDefault()
|
||||
|
||||
monitorRequest(() => revokeInvite(projectId, invite)).then(() => {
|
||||
@@ -126,7 +125,3 @@ function RevokeInvite({ invite }) {
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
RevokeInvite.propTypes = {
|
||||
invite: PropTypes.object.isRequired,
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import { setProjectAccessLevel } from '../utils/api'
|
||||
@@ -18,6 +17,16 @@ import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
type Tokens = {
|
||||
readAndWrite: string
|
||||
readAndWriteHashPrefix: string
|
||||
readAndWritePrefix: string
|
||||
readOnly: string
|
||||
readOnlyHashPrefix: string
|
||||
}
|
||||
|
||||
type AccessLevel = 'private' | 'tokenBased' | 'readAndWrite' | 'readOnly'
|
||||
|
||||
export default function LinkSharing() {
|
||||
const [inflight, setInflight] = useState(false)
|
||||
const [showLinks, setShowLinks] = useState(true)
|
||||
@@ -28,7 +37,7 @@ export default function LinkSharing() {
|
||||
|
||||
// set the access level of a project
|
||||
const setAccessLevel = useCallback(
|
||||
newPublicAccessLevel => {
|
||||
(newPublicAccessLevel: string) => {
|
||||
setInflight(true)
|
||||
sendMB('link-sharing-click-off', {
|
||||
project_id: projectId,
|
||||
@@ -75,6 +84,7 @@ export default function LinkSharing() {
|
||||
case 'readAndWrite':
|
||||
case 'readOnly':
|
||||
return (
|
||||
// TODO: do we even need this anymore?
|
||||
<LegacySharing
|
||||
setAccessLevel={setAccessLevel}
|
||||
accessLevel={publicAccessLevel}
|
||||
@@ -87,7 +97,17 @@ export default function LinkSharing() {
|
||||
}
|
||||
}
|
||||
|
||||
function PrivateSharing({ setAccessLevel, inflight, projectId, setShowLinks }) {
|
||||
function PrivateSharing({
|
||||
setAccessLevel,
|
||||
inflight,
|
||||
projectId,
|
||||
setShowLinks,
|
||||
}: {
|
||||
setAccessLevel: (level: AccessLevel) => void
|
||||
inflight: boolean
|
||||
projectId: string
|
||||
setShowLinks: (show: boolean) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<OLRow className="public-access-level">
|
||||
@@ -113,23 +133,21 @@ function PrivateSharing({ setAccessLevel, inflight, projectId, setShowLinks }) {
|
||||
)
|
||||
}
|
||||
|
||||
PrivateSharing.propTypes = {
|
||||
setAccessLevel: PropTypes.func.isRequired,
|
||||
inflight: PropTypes.bool,
|
||||
projectId: PropTypes.string,
|
||||
setShowLinks: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
function TokenBasedSharing({
|
||||
setAccessLevel,
|
||||
inflight,
|
||||
setShowLinks,
|
||||
showLinks,
|
||||
}: {
|
||||
setAccessLevel: (level: AccessLevel) => void
|
||||
inflight: boolean
|
||||
setShowLinks: (show: boolean) => void
|
||||
showLinks: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { _id: projectId } = useProjectContext()
|
||||
|
||||
const [tokens, setTokens] = useState(null)
|
||||
const [tokens, setTokens] = useState<Tokens | null>(null)
|
||||
|
||||
const { signal } = useAbortController()
|
||||
|
||||
@@ -190,14 +208,15 @@ function TokenBasedSharing({
|
||||
)
|
||||
}
|
||||
|
||||
TokenBasedSharing.propTypes = {
|
||||
setAccessLevel: PropTypes.func.isRequired,
|
||||
inflight: PropTypes.bool,
|
||||
setShowLinks: PropTypes.func.isRequired,
|
||||
showLinks: PropTypes.bool,
|
||||
}
|
||||
|
||||
function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
|
||||
function LegacySharing({
|
||||
accessLevel,
|
||||
setAccessLevel,
|
||||
inflight,
|
||||
}: {
|
||||
accessLevel: AccessLevel
|
||||
setAccessLevel: (level: AccessLevel) => void
|
||||
inflight: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
@@ -223,17 +242,11 @@ function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
|
||||
)
|
||||
}
|
||||
|
||||
LegacySharing.propTypes = {
|
||||
accessLevel: PropTypes.string.isRequired,
|
||||
setAccessLevel: PropTypes.func.isRequired,
|
||||
inflight: PropTypes.bool,
|
||||
}
|
||||
|
||||
export function ReadOnlyTokenLink() {
|
||||
const { t } = useTranslation()
|
||||
const { _id: projectId } = useProjectContext()
|
||||
|
||||
const [tokens, setTokens] = useState(null)
|
||||
const [tokens, setTokens] = useState<Tokens | null>(null)
|
||||
|
||||
const { signal } = useAbortController()
|
||||
|
||||
@@ -260,7 +273,17 @@ export function ReadOnlyTokenLink() {
|
||||
)
|
||||
}
|
||||
|
||||
function AccessToken({ token, tokenHashPrefix, path, tooltipId }) {
|
||||
function AccessToken({
|
||||
token,
|
||||
tokenHashPrefix,
|
||||
path,
|
||||
tooltipId,
|
||||
}: {
|
||||
token?: string
|
||||
tokenHashPrefix?: string
|
||||
path: string
|
||||
tooltipId: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isAdmin } = useUserContext()
|
||||
|
||||
@@ -288,13 +311,6 @@ function AccessToken({ token, tokenHashPrefix, path, tooltipId }) {
|
||||
)
|
||||
}
|
||||
|
||||
AccessToken.propTypes = {
|
||||
token: PropTypes.string,
|
||||
tokenHashPrefix: PropTypes.string,
|
||||
tooltipId: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
function LinkSharingInfo() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { ProjectContextMember } from '@/shared/context/types/project-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function MemberPrivileges({ privileges }) {
|
||||
export default function MemberPrivileges({
|
||||
privileges,
|
||||
}: {
|
||||
privileges: ProjectContextMember['privileges']
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
switch (privileges) {
|
||||
@@ -18,6 +22,3 @@ export default function MemberPrivileges({ privileges }) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
MemberPrivileges.propTypes = {
|
||||
privileges: PropTypes.string.isRequired,
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
import { useEffect, useMemo, useState, useRef, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { matchSorter } from 'match-sorter'
|
||||
import { useCombobox } from 'downshift'
|
||||
import { useCombobox, UseMultipleSelectionReturnValue } from 'downshift'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import Tag from '@/features/ui/components/bootstrap-5/tag'
|
||||
import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { Contact } from '../utils/types'
|
||||
|
||||
export type ContactItem = {
|
||||
email: string
|
||||
display: string
|
||||
type: string
|
||||
}
|
||||
|
||||
// Unicode characters in these Unicode groups:
|
||||
// "General Punctuation — Spaces"
|
||||
@@ -21,6 +27,11 @@ export default function SelectCollaborators({
|
||||
options,
|
||||
placeholder,
|
||||
multipleSelectionProps,
|
||||
}: {
|
||||
loading: boolean
|
||||
options: Contact[]
|
||||
placeholder: string
|
||||
multipleSelectionProps: UseMultipleSelectionReturnValue<ContactItem>
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
@@ -58,12 +69,14 @@ export default function SelectCollaborators({
|
||||
})
|
||||
}, [unselectedOptions, inputValue])
|
||||
|
||||
const inputRef = useRef(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const focusInput = useCallback(() => {
|
||||
if (inputRef.current) {
|
||||
window.setTimeout(() => {
|
||||
inputRef.current.focus()
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, 10)
|
||||
}
|
||||
}, [inputRef])
|
||||
@@ -80,7 +93,7 @@ export default function SelectCollaborators({
|
||||
return true
|
||||
}, [inputValue, selectedItems])
|
||||
|
||||
function stateReducer(state, actionAndChanges) {
|
||||
function stateReducer(_: unknown, actionAndChanges: any) {
|
||||
const { type, changes } = actionAndChanges
|
||||
// force selected item to be null so that adding, removing, then re-adding the same collaborator is recognised as a selection change
|
||||
if (type === useCombobox.stateChangeTypes.InputChange) {
|
||||
@@ -101,7 +114,7 @@ export default function SelectCollaborators({
|
||||
inputValue,
|
||||
defaultHighlightedIndex: 0,
|
||||
items: filteredOptions,
|
||||
itemToString: item => item && item.name,
|
||||
itemToString: item => (item && item.name) || '',
|
||||
stateReducer,
|
||||
onStateChange: ({ inputValue, type, selectedItem }) => {
|
||||
switch (type) {
|
||||
@@ -119,7 +132,7 @@ export default function SelectCollaborators({
|
||||
})
|
||||
|
||||
const addNewItem = useCallback(
|
||||
(_email, focus = true) => {
|
||||
(_email: string, focus = true) => {
|
||||
const email = _email.replace(matchAllSpaces, '')
|
||||
|
||||
if (
|
||||
@@ -200,7 +213,7 @@ export default function SelectCollaborators({
|
||||
addNewItem(inputValue, false)
|
||||
},
|
||||
onChange: e => {
|
||||
setInputValue(e.target.value)
|
||||
setInputValue((e.target as HTMLInputElement).value)
|
||||
},
|
||||
onClick: () => focusInput,
|
||||
onKeyDown: event => {
|
||||
@@ -230,7 +243,7 @@ export default function SelectCollaborators({
|
||||
const data =
|
||||
// modern browsers
|
||||
event.clipboardData?.getData('text/plain') ??
|
||||
// IE11
|
||||
// @ts-ignore IE11
|
||||
window.clipboardData?.getData('text')
|
||||
|
||||
if (data) {
|
||||
@@ -276,20 +289,18 @@ export default function SelectCollaborators({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
SelectCollaborators.propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
options: PropTypes.array.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
multipleSelectionProps: PropTypes.shape({
|
||||
getSelectedItemProps: PropTypes.func.isRequired,
|
||||
getDropdownProps: PropTypes.func.isRequired,
|
||||
addSelectedItem: PropTypes.func.isRequired,
|
||||
removeSelectedItem: PropTypes.func.isRequired,
|
||||
selectedItems: PropTypes.array.isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
function Option({ selected, item, getItemProps, index }) {
|
||||
function Option({
|
||||
selected,
|
||||
item,
|
||||
getItemProps,
|
||||
index,
|
||||
}: {
|
||||
selected: boolean
|
||||
item: Contact
|
||||
getItemProps: (any: any) => any
|
||||
index: number
|
||||
}) {
|
||||
return (
|
||||
<li {...getItemProps({ item, index })}>
|
||||
<DropdownItem
|
||||
@@ -306,24 +317,21 @@ function Option({ selected, item, getItemProps, index }) {
|
||||
)
|
||||
}
|
||||
|
||||
Option.propTypes = {
|
||||
selected: PropTypes.bool.isRequired,
|
||||
item: PropTypes.shape({
|
||||
display: PropTypes.string.isRequired,
|
||||
}),
|
||||
index: PropTypes.number.isRequired,
|
||||
getItemProps: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
function SelectedItem({
|
||||
removeSelectedItem,
|
||||
selectedItem,
|
||||
focusInput,
|
||||
getSelectedItemProps,
|
||||
index,
|
||||
}: {
|
||||
removeSelectedItem: (item: ContactItem) => void
|
||||
selectedItem: ContactItem
|
||||
focusInput: () => void
|
||||
getSelectedItemProps: (any: any) => any
|
||||
index: number
|
||||
}) {
|
||||
const handleClick = useCallback(
|
||||
event => {
|
||||
(event: React.MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
removeSelectedItem(selectedItem)
|
||||
@@ -344,13 +352,3 @@ function SelectedItem({
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
SelectedItem.propTypes = {
|
||||
focusInput: PropTypes.func.isRequired,
|
||||
removeSelectedItem: PropTypes.func.isRequired,
|
||||
selectedItem: PropTypes.shape({
|
||||
display: PropTypes.string.isRequired,
|
||||
}),
|
||||
getSelectedItemProps: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import AddCollaborators from './add-collaborators'
|
||||
import AddCollaboratorsUpgrade from './add-collaborators-upgrade'
|
||||
import CollaboratorsLimitUpgrade from './collaborators-limit-upgrade'
|
||||
import AccessLevelsChanged from './access-levels-changed'
|
||||
import PropTypes from 'prop-types'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
|
||||
export default function SendInvites({
|
||||
@@ -10,6 +9,11 @@ export default function SendInvites({
|
||||
hasExceededCollaboratorLimit,
|
||||
haveAnyEditorsBeenDowngraded,
|
||||
somePendingEditorsResolved,
|
||||
}: {
|
||||
canAddCollaborators: boolean
|
||||
hasExceededCollaboratorLimit: boolean
|
||||
haveAnyEditorsBeenDowngraded: boolean
|
||||
somePendingEditorsResolved: boolean
|
||||
}) {
|
||||
return (
|
||||
<OLRow className="invite-controls">
|
||||
@@ -30,10 +34,3 @@ export default function SendInvites({
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
SendInvites.propTypes = {
|
||||
canAddCollaborators: PropTypes.bool,
|
||||
hasExceededCollaboratorLimit: PropTypes.bool,
|
||||
haveAnyEditorsBeenDowngraded: PropTypes.bool,
|
||||
somePendingEditorsResolved: PropTypes.bool,
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import PropTypes from 'prop-types'
|
||||
import { transferProjectOwnership } from '../utils/api'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
@@ -13,8 +12,15 @@ import OLModal, {
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { ProjectContextMember } from '@/shared/context/types/project-context'
|
||||
|
||||
export default function TransferOwnershipModal({ member, cancel }) {
|
||||
export default function TransferOwnershipModal({
|
||||
member,
|
||||
cancel,
|
||||
}: {
|
||||
member: ProjectContextMember
|
||||
cancel: () => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [inflight, setInflight] = useState(false)
|
||||
@@ -82,7 +88,3 @@ export default function TransferOwnershipModal({ member, cancel }) {
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
TransferOwnershipModal.propTypes = {
|
||||
member: PropTypes.object.isRequired,
|
||||
cancel: PropTypes.func.isRequired,
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import MemberPrivileges from './member-privileges'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { ProjectContextMember } from '@/shared/context/types/project-context'
|
||||
|
||||
export default function ViewMember({ member }) {
|
||||
export default function ViewMember({
|
||||
member,
|
||||
}: {
|
||||
member: ProjectContextMember
|
||||
}) {
|
||||
return (
|
||||
<OLRow className="project-member">
|
||||
<OLCol xs={8}>
|
||||
@@ -19,11 +23,3 @@ export default function ViewMember({ member }) {
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
ViewMember.propTypes = {
|
||||
member: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
privileges: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getJSON } from '../../../infrastructure/fetch-json'
|
||||
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
import { Contact } from '../utils/types'
|
||||
|
||||
export function useUserContacts() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [data, setData] = useState(null)
|
||||
const [data, setData] = useState<Contact[] | null>(null)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const { signal } = useAbortController()
|
||||
@@ -21,7 +22,7 @@ export function useUserContacts() {
|
||||
return { loading, data, error }
|
||||
}
|
||||
|
||||
function buildContact(contact) {
|
||||
function buildContact(contact: Omit<Contact, 'name' | 'display'>): Contact {
|
||||
const [emailPrefix] = contact.email.split('@')
|
||||
|
||||
// the name is not just the default "email prefix as first name"
|
||||
@@ -0,0 +1,9 @@
|
||||
export type Contact = {
|
||||
id: string
|
||||
display: string
|
||||
email: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
name: string
|
||||
type: 'user'
|
||||
}
|
||||
Reference in New Issue
Block a user