mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-14 20:40:17 -05:00
Merge pull request #20824 from overleaf/ii-bs5-share-modal
[web] BS5 share modal GitOrigin-RevId: 40a33e06eab720b568d31aefa021682535b6934e
This commit is contained in:
parent
6c7ee8f778
commit
4f838ccacf
44 changed files with 1229 additions and 600 deletions
|
@ -1,11 +1,13 @@
|
|||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useUserContext } from '../../../shared/context/user-context'
|
||||
import { upgradePlan } from '../../../main/account-upgrade'
|
||||
import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { useFeatureFlag } from '../../../shared/context/split-test-context'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function AddCollaboratorsUpgrade() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -21,34 +23,52 @@ export default function AddCollaboratorsUpgrade() {
|
|||
</p>
|
||||
<ul className="list-unstyled">
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="check" />}
|
||||
bs5={<MaterialIcon type="check" className="align-text-bottom" />}
|
||||
/>
|
||||
|
||||
{t('unlimited_projects')}
|
||||
</li>
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="check" />}
|
||||
bs5={<MaterialIcon type="check" className="align-text-bottom" />}
|
||||
/>
|
||||
|
||||
{t('collabs_per_proj', {
|
||||
collabcount: 'Multiple',
|
||||
})}
|
||||
</li>
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="check" />}
|
||||
bs5={<MaterialIcon type="check" className="align-text-bottom" />}
|
||||
/>
|
||||
|
||||
{t('full_doc_history')}
|
||||
</li>
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="check" />}
|
||||
bs5={<MaterialIcon type="check" className="align-text-bottom" />}
|
||||
/>
|
||||
|
||||
{t('sync_to_dropbox')}
|
||||
</li>
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="check" />}
|
||||
bs5={<MaterialIcon type="check" className="align-text-bottom" />}
|
||||
/>
|
||||
|
||||
{t('sync_to_github')}
|
||||
</li>
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="check" />}
|
||||
bs5={<MaterialIcon type="check" className="align-text-bottom" />}
|
||||
/>
|
||||
|
||||
{t('compile_larger_projects')}
|
||||
</li>
|
||||
|
@ -65,15 +85,15 @@ export default function AddCollaboratorsUpgrade() {
|
|||
: t('start_free_trial')}
|
||||
</StartFreeTrialButton>
|
||||
) : (
|
||||
<Button
|
||||
bsStyle="primary"
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
upgradePlan('project-sharing')
|
||||
setStartedFreeTrial(true)
|
||||
}}
|
||||
>
|
||||
{t('upgrade')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)}
|
||||
</p>
|
||||
{startedFreeTrial && (
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useState, useMemo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Form, FormGroup, FormControl, Button } from 'react-bootstrap'
|
||||
import { useMultipleSelection } from 'downshift'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import SelectCollaborators from './select-collaborators'
|
||||
|
@ -10,6 +9,10 @@ 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 OLForm from '@/features/ui/components/ol/ol-form'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function AddCollaborators() {
|
||||
const [privileges, setPrivileges] = useState('readAndWrite')
|
||||
|
@ -139,38 +142,39 @@ export default function AddCollaborators() {
|
|||
])
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<OLForm>
|
||||
<OLFormGroup>
|
||||
<SelectCollaborators
|
||||
loading={!nonMemberContacts}
|
||||
options={nonMemberContacts || []}
|
||||
placeholder="joe@example.com, sue@example.com, …"
|
||||
multipleSelectionProps={multipleSelectionProps}
|
||||
/>
|
||||
</FormGroup>
|
||||
</OLFormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<OLFormGroup>
|
||||
<div className="pull-right">
|
||||
<FormControl
|
||||
componentClass="select"
|
||||
<OLFormSelect
|
||||
className="privileges"
|
||||
bsSize="sm"
|
||||
value={privileges}
|
||||
onChange={event => setPrivileges(event.target.value)}
|
||||
bs3Props={{
|
||||
bsSize: 'sm',
|
||||
}}
|
||||
>
|
||||
<option value="readAndWrite">{t('can_edit')}</option>
|
||||
<option value="readOnly">{t('read_only')}</option>
|
||||
</FormControl>
|
||||
</OLFormSelect>
|
||||
<span> </span>
|
||||
<ClickableElementEnhancer
|
||||
as={Button}
|
||||
as={OLButton}
|
||||
onClick={handleSubmit}
|
||||
bsStyle="primary"
|
||||
variant="primary"
|
||||
>
|
||||
{t('share')}
|
||||
</ClickableElementEnhancer>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</OLFormGroup>
|
||||
</OLForm>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,11 +4,17 @@ import { useTranslation } from 'react-i18next'
|
|||
import { useShareProjectContext } from './share-project-modal'
|
||||
import TransferOwnershipModal from './transfer-ownership-modal'
|
||||
import { removeMemberFromProject, updateMember } from '../utils/api'
|
||||
import { Button, Col, Form, FormControl, FormGroup } from 'react-bootstrap'
|
||||
import Tooltip from '../../../shared/components/tooltip'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { sendMB } from '../../../infrastructure/event-tracking'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
|
||||
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 BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
export default function EditMember({ member }) {
|
||||
const [privileges, setPrivileges] = useState(member.privileges)
|
||||
|
@ -53,20 +59,24 @@ export default function EditMember({ member }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Form horizontal id="share-project-form" onSubmit={handleSubmit}>
|
||||
<FormGroup className="project-member row">
|
||||
<Col xs={7}>
|
||||
<FormControl.Static>{member.email}</FormControl.Static>
|
||||
</Col>
|
||||
<form
|
||||
id="share-project-form"
|
||||
className={bsVersion({ bs3: 'form-horizontal' })}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<OLFormGroup className="project-member row">
|
||||
<OLCol xs={7} className={bsVersion({ bs3: 'pt-1', bs5: 'pt-2' })}>
|
||||
{member.email}
|
||||
</OLCol>
|
||||
|
||||
<Col xs={3}>
|
||||
<OLCol xs={3}>
|
||||
<SelectPrivilege
|
||||
value={privileges}
|
||||
handleChange={event => setPrivileges(event.target.value)}
|
||||
/>
|
||||
</Col>
|
||||
</OLCol>
|
||||
|
||||
<Col xs={2}>
|
||||
<OLCol xs={2}>
|
||||
{privileges === member.privileges ? (
|
||||
<RemoveMemberAction member={member} />
|
||||
) : (
|
||||
|
@ -74,9 +84,9 @@ export default function EditMember({ member }) {
|
|||
handleReset={() => setPrivileges(member.privileges)}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</OLCol>
|
||||
</OLFormGroup>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
EditMember.propTypes = {
|
||||
|
@ -91,17 +101,18 @@ function SelectPrivilege({ value, handleChange }) {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
componentClass="select"
|
||||
<OLFormSelect
|
||||
className="privileges"
|
||||
bsSize="sm"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
bs3Props={{
|
||||
bsSize: 'sm',
|
||||
}}
|
||||
>
|
||||
<option value="owner">{t('owner')}</option>
|
||||
<option value="readAndWrite">{t('can_edit')}</option>
|
||||
<option value="readOnly">{t('read_only')}</option>
|
||||
</FormControl>
|
||||
</OLFormSelect>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -134,23 +145,25 @@ function RemoveMemberAction({ member }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<FormControl.Static className="text-center">
|
||||
<Tooltip
|
||||
<div className="text-center">
|
||||
<OLTooltip
|
||||
id="remove-collaborator"
|
||||
description={t('remove_collaborator')}
|
||||
overlayProps={{ placement: 'bottom' }}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
onClick={handleClick}
|
||||
className="remove-button"
|
||||
aria-label={t('remove_collaborator')}
|
||||
>
|
||||
<Icon type="times" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</FormControl.Static>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="times" />}
|
||||
bs5={<MaterialIcon type="clear" />}
|
||||
/>
|
||||
</OLButton>
|
||||
</OLTooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -167,15 +180,19 @@ function ChangePrivilegesActions({ handleReset }) {
|
|||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<Button type="submit" bsSize="sm" bsStyle="primary">
|
||||
<OLButton type="submit" size="sm" variant="primary">
|
||||
{t('change_or_cancel-change')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<div className="text-sm">
|
||||
{t('change_or_cancel-or')}
|
||||
|
||||
<Button type="button" className="btn-inline-link" onClick={handleReset}>
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={handleReset}
|
||||
>
|
||||
{t('change_or_cancel-cancel')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -2,19 +2,25 @@ import { useCallback } from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { Button, Col, Row } from 'react-bootstrap'
|
||||
import Tooltip from '../../../shared/components/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MemberPrivileges from './member-privileges'
|
||||
import { resendInvite, revokeInvite } from '../utils/api'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { sendMB } from '../../../infrastructure/event-tracking'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
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 BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export default function Invite({ invite, isProjectOwner }) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Row className="project-invite">
|
||||
<Col xs={7}>
|
||||
<OLRow className="project-invite">
|
||||
<OLCol xs={7}>
|
||||
<div>{invite.email}</div>
|
||||
|
||||
<div className="small">
|
||||
|
@ -22,18 +28,18 @@ export default function Invite({ invite, isProjectOwner }) {
|
|||
.
|
||||
{isProjectOwner && <ResendInvite invite={invite} />}
|
||||
</div>
|
||||
</Col>
|
||||
</OLCol>
|
||||
|
||||
<Col xs={3} className="text-left">
|
||||
<OLCol xs={3} className="text-start">
|
||||
<MemberPrivileges privileges={invite.privileges} />
|
||||
</Col>
|
||||
</OLCol>
|
||||
|
||||
{isProjectOwner && (
|
||||
<Col xs={2} className="text-center">
|
||||
<OLCol xs={2} className="text-center">
|
||||
<RevokeInvite invite={invite} />
|
||||
</Col>
|
||||
</OLCol>
|
||||
)}
|
||||
</Row>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -71,15 +77,15 @@ function ResendInvite({ invite }) {
|
|||
)
|
||||
|
||||
return (
|
||||
<Button
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={handleClick}
|
||||
disabled={inFlight}
|
||||
// ref={buttonRef}
|
||||
>
|
||||
{t('resend')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -109,21 +115,26 @@ function RevokeInvite({ invite }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
id="revoke-invite"
|
||||
description={t('revoke_invite')}
|
||||
overlayProps={{ placement: 'bottom' }}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
onClick={handleClick}
|
||||
aria-label={t('revoke')}
|
||||
className="btn-inline-link"
|
||||
className={classnames(
|
||||
'btn-inline-link',
|
||||
bsVersion({ bs5: 'text-decoration-none' })
|
||||
)}
|
||||
>
|
||||
<Icon type="times" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="times" />}
|
||||
bs5={<MaterialIcon type="clear" />}
|
||||
/>
|
||||
</OLButton>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { useCallback, useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button, Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '../../../shared/components/tooltip'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import { setProjectAccessLevel } from '../utils/api'
|
||||
|
@ -15,6 +13,12 @@ import { getJSON } from '../../../infrastructure/fetch-json'
|
|||
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
export default function LinkSharing() {
|
||||
const [inflight, setInflight] = useState(false)
|
||||
|
@ -84,13 +88,12 @@ export default function LinkSharing() {
|
|||
function PrivateSharing({ setAccessLevel, inflight, projectId }) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol xs={12} className="text-center">
|
||||
{t('link_sharing_is_off')}
|
||||
<span> </span>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={() => {
|
||||
setAccessLevel('tokenBased')
|
||||
|
@ -99,11 +102,11 @@ function PrivateSharing({ setAccessLevel, inflight, projectId }) {
|
|||
disabled={inflight}
|
||||
>
|
||||
{t('turn_on_link_sharing')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<span> </span>
|
||||
<LinkSharingInfo />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -128,22 +131,22 @@ function TokenBasedSharing({ setAccessLevel, inflight }) {
|
|||
}, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol xs={12} className="text-center">
|
||||
<strong>{t('link_sharing_is_on')}</strong>
|
||||
<span> </span>
|
||||
<Button
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={() => setAccessLevel('private')}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('turn_off_link_sharing')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<span> </span>
|
||||
<LinkSharingInfo />
|
||||
</Col>
|
||||
<Col xs={12} className="access-token-display-area">
|
||||
</OLCol>
|
||||
<OLCol xs={12} className="access-token-display-area">
|
||||
<div className="access-token-wrapper">
|
||||
<strong>{t('anyone_with_link_can_edit')}</strong>
|
||||
<AccessToken
|
||||
|
@ -162,8 +165,8 @@ function TokenBasedSharing({ setAccessLevel, inflight }) {
|
|||
tooltipId="tooltip-copy-link-ro"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -176,26 +179,25 @@ function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol xs={12} className="text-center">
|
||||
<strong>
|
||||
{accessLevel === 'readAndWrite' && t('this_project_is_public')}
|
||||
{accessLevel === 'readOnly' && t('this_project_is_public_read_only')}
|
||||
</strong>
|
||||
<span> </span>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={() => setAccessLevel('private')}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('make_private')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<span> </span>
|
||||
<LinkSharingInfo />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -220,8 +222,8 @@ export function ReadOnlyTokenLink() {
|
|||
}, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="access-token-display-area">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol className="access-token-display-area">
|
||||
<div className="access-token-wrapper">
|
||||
<strong>{t('anyone_with_link_can_view')}</strong>
|
||||
<AccessToken
|
||||
|
@ -231,8 +233,8 @@ export function ReadOnlyTokenLink() {
|
|||
tooltipId="tooltip-copy-link-ro"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -275,7 +277,7 @@ function LinkSharingInfo() {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
id="link-sharing-info"
|
||||
description={t('learn_more_about_link_sharing')}
|
||||
>
|
||||
|
@ -284,8 +286,11 @@ function LinkSharingInfo() {
|
|||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<Icon type="question-circle" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="question-circle" />}
|
||||
bs5={<MaterialIcon type="help" className="align-middle" />}
|
||||
/>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
|
||||
export default function OwnerInfo() {
|
||||
const { t } = useTranslation()
|
||||
const { owner } = useProjectContext()
|
||||
|
||||
return (
|
||||
<Row className="project-member">
|
||||
<Col xs={7}>{owner?.email}</Col>
|
||||
<Col xs={3} className="text-left">
|
||||
<OLRow className="project-member">
|
||||
<OLCol xs={7}>{owner?.email}</OLCol>
|
||||
<OLCol xs={3} className="text-start">
|
||||
{t('owner')}
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import { upgradePlan } from '../../../../main/account-upgrade'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useUserContext } from '@/shared/context/user-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
type AccessLevelsChangedProps = {
|
||||
somePendingEditorsResolved: boolean
|
||||
|
@ -50,20 +50,20 @@ export default function AccessLevelsChanged({
|
|||
{t('upgrade')}
|
||||
</StartFreeTrialButton>
|
||||
) : (
|
||||
<Button
|
||||
bsSize="sm"
|
||||
className="btn-secondary"
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
upgradePlan('project-sharing')
|
||||
}}
|
||||
>
|
||||
{t('upgrade')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)}
|
||||
<Button
|
||||
<OLButton
|
||||
variant="link"
|
||||
size="sm"
|
||||
href="https://www.overleaf.com/blog/changes-to-project-sharing"
|
||||
bsSize="sm"
|
||||
className="btn-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => {
|
||||
|
@ -75,7 +75,7 @@ export default function AccessLevelsChanged({
|
|||
}}
|
||||
>
|
||||
{t('read_more')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import { upgradePlan } from '../../../../main/account-upgrade'
|
||||
import { linkSharingEnforcementDate } from '../../utils/link-sharing'
|
||||
|
@ -7,6 +6,7 @@ import { useProjectContext } from '@/shared/context/project-context'
|
|||
import { useUserContext } from '@/shared/context/user-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function AddCollaboratorsUpgrade() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -40,20 +40,20 @@ export default function AddCollaboratorsUpgrade() {
|
|||
{t('upgrade')}
|
||||
</StartFreeTrialButton>
|
||||
) : (
|
||||
<Button
|
||||
bsSize="sm"
|
||||
className="btn-secondary"
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
upgradePlan('project-sharing')
|
||||
}}
|
||||
>
|
||||
{t('upgrade')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)}
|
||||
<Button
|
||||
<OLButton
|
||||
variant="link"
|
||||
size="sm"
|
||||
href="https://www.overleaf.com/blog/changes-to-project-sharing"
|
||||
bsSize="sm"
|
||||
className="btn-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => {
|
||||
|
@ -65,7 +65,7 @@ export default function AddCollaboratorsUpgrade() {
|
|||
}}
|
||||
>
|
||||
{t('read_more')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect, useState, useMemo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Form, FormGroup, FormControl, Button } from 'react-bootstrap'
|
||||
import { useMultipleSelection } from 'downshift'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import SelectCollaborators from './select-collaborators'
|
||||
|
@ -11,6 +10,10 @@ 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 OLFormSelect from '@/features/ui/components/ol/ol-form-select'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function AddCollaborators({ readOnly }) {
|
||||
const [privileges, setPrivileges] = useState('readAndWrite')
|
||||
|
@ -146,41 +149,42 @@ export default function AddCollaborators({ readOnly }) {
|
|||
])
|
||||
|
||||
return (
|
||||
<Form className="add-collabs">
|
||||
<FormGroup>
|
||||
<OLForm className="add-collabs">
|
||||
<OLFormGroup>
|
||||
<SelectCollaborators
|
||||
loading={!nonMemberContacts}
|
||||
options={nonMemberContacts || []}
|
||||
placeholder="Email, comma separated"
|
||||
multipleSelectionProps={multipleSelectionProps}
|
||||
/>
|
||||
</FormGroup>
|
||||
</OLFormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<OLFormGroup>
|
||||
<div className="pull-right">
|
||||
<FormControl
|
||||
componentClass="select"
|
||||
<OLFormSelect
|
||||
className="privileges"
|
||||
bsSize="sm"
|
||||
value={privileges}
|
||||
onChange={event => setPrivileges(event.target.value)}
|
||||
bs3Props={{
|
||||
bsSize: 'sm',
|
||||
}}
|
||||
>
|
||||
<option disabled={readOnly} value="readAndWrite">
|
||||
{t('can_edit')}
|
||||
</option>
|
||||
<option value="readOnly">{t('can_view')}</option>
|
||||
</FormControl>
|
||||
</OLFormSelect>
|
||||
<span> </span>
|
||||
<ClickableElementEnhancer
|
||||
as={Button}
|
||||
as={OLButton}
|
||||
onClick={handleSubmit}
|
||||
bsStyle="primary"
|
||||
variant="primary"
|
||||
>
|
||||
{t('invite')}
|
||||
</ClickableElementEnhancer>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</OLFormGroup>
|
||||
</OLForm>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import { upgradePlan } from '@/main/account-upgrade'
|
||||
|
@ -6,6 +5,7 @@ import { useProjectContext } from '@/shared/context/project-context'
|
|||
import { useUserContext } from '@/shared/context/user-context'
|
||||
import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function CollaboratorsLimitUpgrade() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -44,15 +44,14 @@ export default function CollaboratorsLimitUpgrade() {
|
|||
{t('upgrade')}
|
||||
</StartFreeTrialButton>
|
||||
) : (
|
||||
<Button
|
||||
bsSize="medium"
|
||||
className="btn-premium"
|
||||
<OLButton
|
||||
variant="premium"
|
||||
onClick={() => {
|
||||
upgradePlan('project-sharing')
|
||||
}}
|
||||
>
|
||||
{t('upgrade')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
@ -78,15 +77,15 @@ export default function CollaboratorsLimitUpgrade() {
|
|||
{t('upgrade')}
|
||||
</StartFreeTrialButton>
|
||||
) : (
|
||||
<Button
|
||||
bsSize="sm"
|
||||
className="btn-secondary"
|
||||
<OLButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
upgradePlan('project-sharing')
|
||||
}}
|
||||
>
|
||||
{t('upgrade')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useState, useEffect, useMemo, MouseEventHandler } from 'react'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import TransferOwnershipModal from './transfer-ownership-modal'
|
||||
import { removeMemberFromProject, updateMember } from '../../utils/api'
|
||||
import { Button, Col, Form, FormGroup } from 'react-bootstrap'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
|
@ -12,6 +11,13 @@ import { Select } from '@/shared/components/select'
|
|||
import type { ProjectContextMember } from '@/shared/context/types/project-context'
|
||||
import { PermissionsLevel } from '@/features/ide-react/types/permissions'
|
||||
import { linkSharingEnforcementDate } from '../../utils/link-sharing'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type PermissionsOption = PermissionsLevel | 'removeAccess' | 'downgraded'
|
||||
|
||||
|
@ -115,22 +121,44 @@ export default function EditMember({
|
|||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
horizontal
|
||||
<form
|
||||
className={bsVersion({ bs3: 'form-horizontal' })}
|
||||
id="share-project-form"
|
||||
onSubmit={e => {
|
||||
e.preventDefault()
|
||||
commitPrivilegeChange(privileges)
|
||||
}}
|
||||
>
|
||||
<FormGroup className="project-member">
|
||||
<Col xs={7}>
|
||||
<OLFormGroup
|
||||
className={classnames('project-member', bsVersion({ bs5: 'row' }))}
|
||||
>
|
||||
<OLCol xs={7}>
|
||||
<div className="project-member-email-icon">
|
||||
<Icon
|
||||
type={
|
||||
shouldWarnMember() || member.pendingEditor ? 'warning' : 'user'
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<Icon
|
||||
type={
|
||||
shouldWarnMember() || member.pendingEditor
|
||||
? 'warning'
|
||||
: 'user'
|
||||
}
|
||||
fw
|
||||
/>
|
||||
}
|
||||
bs5={
|
||||
<MaterialIcon
|
||||
type={
|
||||
shouldWarnMember() || member.pendingEditor
|
||||
? 'warning'
|
||||
: 'person'
|
||||
}
|
||||
className={
|
||||
shouldWarnMember() || member.pendingEditor
|
||||
? 'text-warning'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
}
|
||||
fw
|
||||
/>
|
||||
<div className="email-warning">
|
||||
{member.email}
|
||||
|
@ -146,18 +174,23 @@ export default function EditMember({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</OLCol>
|
||||
|
||||
<Col xs={1}>
|
||||
<OLCol xs={2}>
|
||||
{privileges !== member.privileges && privilegeChangePending && (
|
||||
<ChangePrivilegesActions
|
||||
handleReset={() => setPrivileges(member.privileges)}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</OLCol>
|
||||
|
||||
<Col xs={4} className="project-member-select">
|
||||
{hasBeenDowngraded && <Icon type="warning" fw />}
|
||||
<OLCol xs={3} className="project-member-select">
|
||||
{hasBeenDowngraded && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="warning" fw />}
|
||||
bs5={<MaterialIcon type="warning" className="text-warning" />}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectPrivilege
|
||||
value={privileges}
|
||||
|
@ -169,9 +202,9 @@ export default function EditMember({
|
|||
hasBeenDowngraded={hasBeenDowngraded}
|
||||
canAddCollaborators={canAddCollaborators}
|
||||
/>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</OLCol>
|
||||
</OLFormGroup>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
EditMember.propTypes = {
|
||||
|
@ -262,8 +295,9 @@ function SelectPrivilege({
|
|||
}
|
||||
|
||||
type ChangePrivilegesActionsProps = {
|
||||
handleReset: MouseEventHandler<Button>
|
||||
handleReset: React.ComponentProps<typeof OLButton>['onClick']
|
||||
}
|
||||
|
||||
function ChangePrivilegesActions({
|
||||
handleReset,
|
||||
}: ChangePrivilegesActionsProps) {
|
||||
|
@ -271,15 +305,19 @@ function ChangePrivilegesActions({
|
|||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<Button type="submit" bsSize="sm" bsStyle="primary">
|
||||
<OLButton type="submit" size="sm" variant="primary">
|
||||
{t('change_or_cancel-change')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<div className="text-sm">
|
||||
{t('change_or_cancel-or')}
|
||||
|
||||
<Button type="button" className="btn-inline-link" onClick={handleReset}>
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={handleReset}
|
||||
>
|
||||
{t('change_or_cancel-cancel')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { Button, Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { linkSharingEnforcementDate } from '../../utils/link-sharing'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
|
||||
type EditorOverLimitModalContentProps = {
|
||||
handleHide: () => void
|
||||
|
@ -14,22 +20,21 @@ export default function EditorOverLimitModalContent({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('do_you_need_edit_access')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('do_you_need_edit_access')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
{t('this_project_has_more_than_max_collabs', {
|
||||
linkSharingDate: linkSharingEnforcementDate,
|
||||
})}
|
||||
</p>
|
||||
<p>{t('to_keep_edit_access')}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
href="/blog/changes-to-project-sharing"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
@ -41,9 +46,9 @@ export default function EditorOverLimitModalContent({
|
|||
}}
|
||||
>
|
||||
{t('learn_more')}
|
||||
</Button>
|
||||
<Button
|
||||
className="btn-primary"
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
sendMB('notification-click', {
|
||||
name: 'link-sharing-collaborator-limit',
|
||||
|
@ -53,8 +58,8 @@ export default function EditorOverLimitModalContent({
|
|||
}}
|
||||
>
|
||||
{t('ok')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
import EditorOverLimitModalContent from './editor-over-limit-modal-content'
|
||||
import customLocalStorage from '@/infrastructure/local-storage'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLModal from '@/features/ui/components/ol/ol-modal'
|
||||
|
||||
const EditorOverLimitModal = () => {
|
||||
const [show, setShow] = useState(false)
|
||||
|
@ -55,7 +55,7 @@ const EditorOverLimitModal = () => {
|
|||
}, [features, isProjectOwner, members, permissionsLevel, projectId])
|
||||
|
||||
return show ? (
|
||||
<AccessibleModal
|
||||
<OLModal
|
||||
animation
|
||||
show={show}
|
||||
onHide={() => {
|
||||
|
@ -67,7 +67,7 @@ const EditorOverLimitModal = () => {
|
|||
id="editor-over-limit-modal"
|
||||
>
|
||||
<EditorOverLimitModalContent handleHide={handleHide} />
|
||||
</AccessibleModal>
|
||||
</OLModal>
|
||||
) : null
|
||||
}
|
||||
|
||||
|
|
|
@ -2,37 +2,43 @@ import { useCallback } from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import { Button, Col, Row } from 'react-bootstrap'
|
||||
import Tooltip from '@/shared/components/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MemberPrivileges from './member-privileges'
|
||||
import { resendInvite, revokeInvite } from '../../utils/api'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
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 BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export default function Invite({ invite, isProjectOwner }) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Row className="project-invite">
|
||||
<Col xs={8}>
|
||||
<OLRow className="project-invite">
|
||||
<OLCol xs={8}>
|
||||
<div>{invite.email}</div>
|
||||
<div className="small">
|
||||
{t('invite_not_accepted')}
|
||||
.
|
||||
{isProjectOwner && <ResendInvite invite={invite} />}
|
||||
</div>
|
||||
</Col>
|
||||
</OLCol>
|
||||
|
||||
<Col xs={3} className="text-right">
|
||||
<OLCol xs={3} className="text-end">
|
||||
<MemberPrivileges privileges={invite.privileges} />
|
||||
</Col>
|
||||
</OLCol>
|
||||
|
||||
{isProjectOwner && (
|
||||
<Col xs={1} className="text-center">
|
||||
<OLCol xs={1} className="text-center">
|
||||
<RevokeInvite invite={invite} />
|
||||
</Col>
|
||||
</OLCol>
|
||||
)}
|
||||
</Row>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -70,15 +76,15 @@ function ResendInvite({ invite }) {
|
|||
)
|
||||
|
||||
return (
|
||||
<Button
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={handleClick}
|
||||
disabled={inFlight}
|
||||
// ref={buttonRef}
|
||||
>
|
||||
{t('resend')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -108,21 +114,26 @@ function RevokeInvite({ invite }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
id="revoke-invite"
|
||||
description={t('revoke_invite')}
|
||||
overlayProps={{ placement: 'bottom' }}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
onClick={handleClick}
|
||||
aria-label={t('revoke')}
|
||||
className="btn-inline-link"
|
||||
className={classnames(
|
||||
'btn-inline-link',
|
||||
bsVersion({ bs5: 'text-decoration-none' })
|
||||
)}
|
||||
>
|
||||
<Icon type="times" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="times" />}
|
||||
bs5={<MaterialIcon type="clear" />}
|
||||
/>
|
||||
</OLButton>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { useCallback, useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button, Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/shared/components/tooltip'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import { useShareProjectContext } from './share-project-modal'
|
||||
import { setProjectAccessLevel } from '../../utils/api'
|
||||
|
@ -15,6 +13,12 @@ import { getJSON } from '../../../../infrastructure/fetch-json'
|
|||
import useAbortController from '@/shared/hooks/use-abort-controller'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
export default function LinkSharing() {
|
||||
const [inflight, setInflight] = useState(false)
|
||||
|
@ -88,13 +92,12 @@ export default function LinkSharing() {
|
|||
function PrivateSharing({ setAccessLevel, inflight, projectId, setShowLinks }) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol xs={12} className="text-center">
|
||||
<strong>{t('link_sharing_is_off_short')}</strong>
|
||||
<span> </span>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={() => {
|
||||
setAccessLevel('tokenBased')
|
||||
|
@ -104,11 +107,11 @@ function PrivateSharing({ setAccessLevel, inflight, projectId, setShowLinks }) {
|
|||
disabled={inflight}
|
||||
>
|
||||
{t('turn_on_link_sharing')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<span> </span>
|
||||
<LinkSharingInfo />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -139,53 +142,58 @@ function TokenBasedSharing({
|
|||
}, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol xs={12} className="text-center">
|
||||
<strong>{t('link_sharing_is_on')}</strong>
|
||||
<span> </span>
|
||||
<Button
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={() => setAccessLevel('private')}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('turn_off_link_sharing')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<span> </span>
|
||||
<LinkSharingInfo />
|
||||
<Button
|
||||
bsStyle="link"
|
||||
className="btn-chevron"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-chevron align-middle"
|
||||
onClick={() => setShowLinks(!showLinks)}
|
||||
>
|
||||
<Icon type={showLinks ? 'chevron-up' : 'chevron-down'} fw />
|
||||
</Button>
|
||||
</Col>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type={showLinks ? 'chevron-up' : 'chevron-down'} fw />}
|
||||
bs5={
|
||||
<MaterialIcon
|
||||
type={showLinks ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</OLButton>
|
||||
</OLCol>
|
||||
{showLinks && (
|
||||
<>
|
||||
<Col xs={12} className="access-token-display-area">
|
||||
<div className="access-token-wrapper">
|
||||
<strong>{t('anyone_with_link_can_edit')}</strong>
|
||||
<AccessToken
|
||||
token={tokens?.readAndWrite}
|
||||
tokenHashPrefix={tokens?.readAndWriteHashPrefix}
|
||||
path="/"
|
||||
tooltipId="tooltip-copy-link-rw"
|
||||
/>
|
||||
</div>
|
||||
<div className="access-token-wrapper">
|
||||
<strong>{t('anyone_with_link_can_view')}</strong>
|
||||
<AccessToken
|
||||
token={tokens?.readOnly}
|
||||
tokenHashPrefix={tokens?.readOnlyHashPrefix}
|
||||
path="/read/"
|
||||
tooltipId="tooltip-copy-link-ro"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</>
|
||||
<OLCol xs={12} className="access-token-display-area">
|
||||
<div className="access-token-wrapper">
|
||||
<strong>{t('anyone_with_link_can_edit')}</strong>
|
||||
<AccessToken
|
||||
token={tokens?.readAndWrite}
|
||||
tokenHashPrefix={tokens?.readAndWriteHashPrefix}
|
||||
path="/"
|
||||
tooltipId="tooltip-copy-link-rw"
|
||||
/>
|
||||
</div>
|
||||
<div className="access-token-wrapper">
|
||||
<strong>{t('anyone_with_link_can_view')}</strong>
|
||||
<AccessToken
|
||||
token={tokens?.readOnly}
|
||||
tokenHashPrefix={tokens?.readOnlyHashPrefix}
|
||||
path="/read/"
|
||||
tooltipId="tooltip-copy-link-ro"
|
||||
/>
|
||||
</div>
|
||||
</OLCol>
|
||||
)}
|
||||
</Row>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -200,26 +208,25 @@ function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol xs={12} className="text-center">
|
||||
<strong>
|
||||
{accessLevel === 'readAndWrite' && t('this_project_is_public')}
|
||||
{accessLevel === 'readOnly' && t('this_project_is_public_read_only')}
|
||||
</strong>
|
||||
<span> </span>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="link"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={() => setAccessLevel('private')}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('make_private')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
<span> </span>
|
||||
<LinkSharingInfo />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -244,8 +251,8 @@ export function ReadOnlyTokenLink() {
|
|||
}, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
<Col xs={12} className="access-token-display-area">
|
||||
<OLRow className="public-access-level">
|
||||
<OLCol className="access-token-display-area">
|
||||
<div className="access-token-wrapper">
|
||||
<strong>{t('anyone_with_link_can_view')}</strong>
|
||||
<AccessToken
|
||||
|
@ -255,8 +262,8 @@ export function ReadOnlyTokenLink() {
|
|||
tooltipId="tooltip-copy-link-ro"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -299,7 +306,7 @@ function LinkSharingInfo() {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
id="link-sharing-info"
|
||||
description={t('learn_more_about_link_sharing')}
|
||||
>
|
||||
|
@ -308,8 +315,11 @@ function LinkSharingInfo() {
|
|||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<Icon type="question-circle" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="question-circle" />}
|
||||
bs5={<MaterialIcon type="help" className="align-middle" />}
|
||||
/>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
export default function OwnerInfo() {
|
||||
const { t } = useTranslation()
|
||||
const { owner } = useProjectContext()
|
||||
|
||||
return (
|
||||
<Row className="project-member">
|
||||
<Col xs={8}>
|
||||
<OLRow className="project-member">
|
||||
<OLCol xs={8}>
|
||||
<div className="project-member-email-icon">
|
||||
<Icon type="user" fw />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="user" fw />}
|
||||
bs5={<MaterialIcon type="person" />}
|
||||
/>
|
||||
<div className="email-warning">{owner?.email}</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={4} className="text-right">
|
||||
</OLCol>
|
||||
<OLCol xs={4} className="text-end">
|
||||
{t('owner')}
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,12 @@ import { useCombobox } from 'downshift'
|
|||
import classnames from 'classnames'
|
||||
|
||||
import Icon from '@/shared/components/icon'
|
||||
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 BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
|
||||
// Unicode characters in these Unicode groups:
|
||||
// "General Punctuation — Spaces"
|
||||
|
@ -156,98 +162,123 @@ export default function SelectCollaborators({
|
|||
{t('add_people')}
|
||||
|
||||
</strong>
|
||||
{loading && <Icon type="refresh" spin />}
|
||||
{loading && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="refresh" spin />}
|
||||
bs5={
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
|
||||
<div className="host">
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<div {...getComboboxProps()} className="tags" onClick={focusInput}>
|
||||
<div className="tags-main">
|
||||
{selectedItems.map((selectedItem, index) => (
|
||||
<SelectedItem
|
||||
key={`selected-item-${index}`}
|
||||
removeSelectedItem={removeSelectedItem}
|
||||
selectedItem={selectedItem}
|
||||
focusInput={focusInput}
|
||||
index={index}
|
||||
getSelectedItemProps={getSelectedItemProps}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
{...getComboboxProps()}
|
||||
className="tags form-control"
|
||||
onClick={focusInput}
|
||||
>
|
||||
{selectedItems.map((selectedItem, index) => (
|
||||
<SelectedItem
|
||||
key={`selected-item-${index}`}
|
||||
removeSelectedItem={removeSelectedItem}
|
||||
selectedItem={selectedItem}
|
||||
focusInput={focusInput}
|
||||
index={index}
|
||||
getSelectedItemProps={getSelectedItemProps}
|
||||
/>
|
||||
))}
|
||||
|
||||
<input
|
||||
{...getInputProps(
|
||||
getDropdownProps({
|
||||
className: classnames({
|
||||
input: true,
|
||||
'invalid-tag': !isValidInput,
|
||||
}),
|
||||
type: 'email',
|
||||
placeholder,
|
||||
size: inputValue.length
|
||||
? inputValue.length + 5
|
||||
: placeholder.length,
|
||||
ref: inputRef,
|
||||
// preventKeyAction: showDropdown,
|
||||
onBlur: () => {
|
||||
addNewItem(inputValue, false)
|
||||
},
|
||||
onChange: e => {
|
||||
setInputValue(e.target.value)
|
||||
},
|
||||
onClick: () => focusInput,
|
||||
onKeyDown: event => {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
// Enter: always prevent form submission
|
||||
<input
|
||||
{...getInputProps(
|
||||
getDropdownProps({
|
||||
className: classnames('input', {
|
||||
'invalid-tag': !isValidInput,
|
||||
}),
|
||||
type: 'email',
|
||||
placeholder,
|
||||
size: inputValue.length
|
||||
? inputValue.length + 5
|
||||
: placeholder.length,
|
||||
ref: inputRef,
|
||||
// preventKeyAction: showDropdown,
|
||||
onBlur: () => {
|
||||
addNewItem(inputValue, false)
|
||||
},
|
||||
onChange: e => {
|
||||
setInputValue(e.target.value)
|
||||
},
|
||||
onClick: () => focusInput,
|
||||
onKeyDown: event => {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
// Enter: always prevent form submission
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
break
|
||||
|
||||
case 'Tab':
|
||||
// Tab: if the dropdown isn't open, try to create a new item using inputValue and prevent blur if successful
|
||||
if (!isOpen && addNewItem(inputValue)) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
break
|
||||
}
|
||||
break
|
||||
|
||||
case 'Tab':
|
||||
// Tab: if the dropdown isn't open, try to create a new item using inputValue and prevent blur if successful
|
||||
if (!isOpen && addNewItem(inputValue)) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
break
|
||||
case ',':
|
||||
// comma: try to create a new item using inputValue
|
||||
event.preventDefault()
|
||||
addNewItem(inputValue)
|
||||
break
|
||||
}
|
||||
},
|
||||
onPaste: event => {
|
||||
const data =
|
||||
// modern browsers
|
||||
event.clipboardData?.getData('text/plain') ??
|
||||
// IE11
|
||||
window.clipboardData?.getData('text')
|
||||
|
||||
case ',':
|
||||
// comma: try to create a new item using inputValue
|
||||
event.preventDefault()
|
||||
addNewItem(inputValue)
|
||||
break
|
||||
}
|
||||
},
|
||||
onPaste: event => {
|
||||
const data =
|
||||
// modern browsers
|
||||
event.clipboardData?.getData('text/plain') ??
|
||||
// IE11
|
||||
window.clipboardData?.getData('text')
|
||||
if (data) {
|
||||
const emails = data
|
||||
.split(/[\r\n,; ]+/)
|
||||
.filter(item => item.includes('@'))
|
||||
|
||||
if (data) {
|
||||
const emails = data
|
||||
.split(/[\r\n,; ]+/)
|
||||
.filter(item => item.includes('@'))
|
||||
if (emails.length) {
|
||||
// pasted comma-separated email addresses
|
||||
event.preventDefault()
|
||||
|
||||
if (emails.length) {
|
||||
// pasted comma-separated email addresses
|
||||
event.preventDefault()
|
||||
|
||||
for (const email of emails) {
|
||||
addNewItem(email)
|
||||
}
|
||||
for (const email of emails) {
|
||||
addNewItem(email)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
},
|
||||
})
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={classnames({ autocomplete: isOpen })}>
|
||||
<ul {...getMenuProps()} className="suggestion-list">
|
||||
<div
|
||||
className={bsVersion({ bs3: classnames({ autocomplete: isOpen }) })}
|
||||
>
|
||||
<ul
|
||||
{...getMenuProps()}
|
||||
className={classnames(
|
||||
bsVersion({
|
||||
bs3: 'suggestion-list',
|
||||
bs5: classnames('dropdown-menu select-dropdown-menu', {
|
||||
show: isOpen,
|
||||
}),
|
||||
})
|
||||
)}
|
||||
>
|
||||
{isOpen &&
|
||||
filteredOptions.map((item, index) => (
|
||||
<Option
|
||||
|
@ -280,15 +311,36 @@ SelectCollaborators.propTypes = {
|
|||
function Option({ selected, item, getItemProps, index }) {
|
||||
return (
|
||||
<li
|
||||
className={classnames('suggestion-item', { selected })}
|
||||
className={bsVersion({
|
||||
bs3: classnames('suggestion-item', { selected }),
|
||||
})}
|
||||
{...getItemProps({ item, index })}
|
||||
>
|
||||
<Icon type="user" fw />
|
||||
|
||||
{item.display}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<>
|
||||
<Icon type="user" fw />
|
||||
|
||||
{item.display}
|
||||
</>
|
||||
}
|
||||
bs5={
|
||||
<DropdownItem
|
||||
as="span"
|
||||
role={undefined}
|
||||
leadingIcon="person"
|
||||
className={classnames({
|
||||
active: selected,
|
||||
})}
|
||||
>
|
||||
{item.display}
|
||||
</DropdownItem>
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
Option.propTypes = {
|
||||
selected: PropTypes.bool.isRequired,
|
||||
item: PropTypes.shape({
|
||||
|
@ -318,23 +370,44 @@ function SelectedItem({
|
|||
)
|
||||
|
||||
return (
|
||||
<span
|
||||
className="tag-item"
|
||||
{...getSelectedItemProps({ selectedItem, index })}
|
||||
>
|
||||
<Icon type="user" fw />
|
||||
<span>{selectedItem.display}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="remove-button btn-inline-link"
|
||||
aria-label={t('remove')}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon type="close" fw />
|
||||
</button>
|
||||
</span>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<span
|
||||
className="tag-item"
|
||||
{...getSelectedItemProps({ selectedItem, index })}
|
||||
>
|
||||
<Icon type="user" fw />
|
||||
<span>{selectedItem.display}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="remove-button btn-inline-link"
|
||||
aria-label={t('remove')}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon type="close" fw />
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
bs5={
|
||||
<Tag
|
||||
prepend={
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="user" fw />}
|
||||
bs5={<MaterialIcon type="person" />}
|
||||
/>
|
||||
}
|
||||
closeBtnProps={{
|
||||
onClick: handleClick,
|
||||
}}
|
||||
{...getSelectedItemProps({ selectedItem, index })}
|
||||
>
|
||||
{selectedItem.display}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
SelectedItem.propTypes = {
|
||||
focusInput: PropTypes.func.isRequired,
|
||||
removeSelectedItem: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import { PublicAccessLevel } from '../../../../../../types/public-access-level'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
|
||||
export default function SendInvitesNotice() {
|
||||
const { publicAccessLevel } = useProjectContext()
|
||||
|
@ -25,11 +26,11 @@ export default function SendInvitesNotice() {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
<Row className="public-access-level public-access-level--notice">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level public-access-level-notice">
|
||||
<OLCol className="text-center">
|
||||
<AccessLevel level={publicAccessLevel} />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Row } from 'react-bootstrap'
|
||||
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({
|
||||
canAddCollaborators,
|
||||
|
@ -12,7 +12,7 @@ export default function SendInvites({
|
|||
somePendingEditorsResolved,
|
||||
}) {
|
||||
return (
|
||||
<Row className="invite-controls">
|
||||
<OLRow className="invite-controls">
|
||||
{hasExceededCollaboratorLimit && !haveAnyEditorsBeenDowngraded && (
|
||||
<AddCollaboratorsUpgrade />
|
||||
)}
|
||||
|
@ -27,7 +27,7 @@ export default function SendInvites({
|
|||
!hasExceededCollaboratorLimit &&
|
||||
!haveAnyEditorsBeenDowngraded && <CollaboratorsLimitUpgrade />}
|
||||
<AddCollaborators readOnly={!canAddCollaborators} />
|
||||
</Row>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import { Button, Modal, Grid } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import { lazy, Suspense } from 'react'
|
||||
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
||||
import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
|
||||
const ReadOnlyTokenLink = lazy(() =>
|
||||
import('./link-sharing').then(({ ReadOnlyTokenLink }) => ({
|
||||
|
@ -36,13 +45,13 @@ export default function ShareProjectModalContent({
|
|||
const { isRestrictedTokenMember } = useEditorContext()
|
||||
|
||||
return (
|
||||
<AccessibleModal show={show} onHide={cancel} animation={animation}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('share_project')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModal show={show} onHide={cancel} animation={animation}>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('share_project')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body className="modal-body-share modal-link-share-new">
|
||||
<Grid fluid>
|
||||
<OLModalBody className="modal-body-share modal-link-share-new">
|
||||
<div className="container-fluid">
|
||||
<Suspense fallback={<FullSizeLoadingSpinner minHeight="15rem" />}>
|
||||
{isRestrictedTokenMember ? (
|
||||
<ReadOnlyTokenLink />
|
||||
|
@ -50,33 +59,43 @@ export default function ShareProjectModalContent({
|
|||
<ShareModalBody />
|
||||
)}
|
||||
</Suspense>
|
||||
</Grid>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer className="modal-footer-share">
|
||||
<div className="modal-footer-left">
|
||||
{inFlight && <Icon type="refresh" spin />}
|
||||
{error && (
|
||||
<span className="text-danger error">
|
||||
<ErrorMessage error={error} />
|
||||
</span>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={<ErrorMessage error={error} />}
|
||||
className="mb-0 mt-3"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</OLModalBody>
|
||||
|
||||
<OLModalFooter>
|
||||
<div className={bsVersion({ bs3: 'pull-left', bs5: 'me-auto' })}>
|
||||
{inFlight && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="refresh" spin />}
|
||||
bs5={
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="modal-footer-right">
|
||||
<ClickableElementEnhancer
|
||||
onClick={cancel}
|
||||
as={Button}
|
||||
type="button"
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
disabled={inFlight}
|
||||
>
|
||||
{t('close')}
|
||||
</ClickableElementEnhancer>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
<ClickableElementEnhancer
|
||||
onClick={cancel}
|
||||
as={OLButton}
|
||||
variant="secondary"
|
||||
disabled={inFlight}
|
||||
>
|
||||
{t('close')}
|
||||
</ClickableElementEnhancer>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import { useState } from 'react'
|
||||
import { Modal, Button } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import PropTypes from 'prop-types'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import { transferProjectOwnership } from '../../utils/api'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
|
||||
export default function TransferOwnershipModal({ member, cancel }) {
|
||||
const { t } = useTranslation()
|
||||
|
@ -32,11 +41,11 @@ export default function TransferOwnershipModal({ member, cancel }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal show onHide={cancel}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('change_project_owner')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<OLModal show onHide={cancel}>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('change_project_owner')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="project_ownership_transfer_confirmation_1"
|
||||
|
@ -47,37 +56,38 @@ export default function TransferOwnershipModal({ member, cancel }) {
|
|||
/>
|
||||
</p>
|
||||
<p>{t('project_ownership_transfer_confirmation_2')}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<div className="modal-footer-left">
|
||||
{inflight && <Icon type="refresh" spin />}
|
||||
{error && (
|
||||
<span className="text-danger">
|
||||
{t('generic_something_went_wrong')}
|
||||
</span>
|
||||
{error && (
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={t('generic_something_went_wrong')}
|
||||
className="mb-0 mt-3"
|
||||
/>
|
||||
)}
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<div className={bsVersion({ bs3: 'pull-left', bs5: 'me-auto' })}>
|
||||
{inflight && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="refresh" spin />}
|
||||
bs5={
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-footer-right">
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
onClick={cancel}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="primary"
|
||||
onClick={confirm}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('change_owner')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
<OLButton variant="secondary" onClick={cancel} disabled={inflight}>
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
<OLButton variant="primary" onClick={confirm} disabled={inflight}>
|
||||
{t('change_owner')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
TransferOwnershipModal.propTypes = {
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import MemberPrivileges from './member-privileges'
|
||||
import Icon from '@/shared/components/icon'
|
||||
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 BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
export default function ViewMember({ member }) {
|
||||
return (
|
||||
<Row className="project-member">
|
||||
<Col xs={8}>
|
||||
<OLRow className="project-member">
|
||||
<OLCol xs={8}>
|
||||
<div className="project-member-email-icon">
|
||||
<Icon type="user" fw />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="user" fw />}
|
||||
bs5={<MaterialIcon type="person" />}
|
||||
/>
|
||||
<div className="email-warning">{member.email}</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={4} className="text-right">
|
||||
</OLCol>
|
||||
<OLCol xs={4} className="text-end">
|
||||
<MemberPrivileges privileges={member.privileges} />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { Button, Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
|
||||
type ViewOnlyAccessModalContentProps = {
|
||||
handleHide: () => void
|
||||
|
@ -13,18 +19,17 @@ export default function ViewOnlyAccessModalContent({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('view_only_access')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('view_only_access')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<OLModalBody>
|
||||
<p>{t('this_project_already_has_maximum_editors')}</p>
|
||||
<p>{t('please_ask_the_project_owner_to_upgrade_more_editors')}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
href="/blog/changes-to-project-sharing"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
@ -36,9 +41,9 @@ export default function ViewOnlyAccessModalContent({
|
|||
}}
|
||||
>
|
||||
{t('learn_more')}
|
||||
</Button>
|
||||
<Button
|
||||
className="btn-primary"
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
sendMB('notification-click', {
|
||||
name: 'link-sharing-collaborator-limit',
|
||||
|
@ -48,8 +53,8 @@ export default function ViewOnlyAccessModalContent({
|
|||
}}
|
||||
>
|
||||
{t('ok')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
import ViewOnlyAccessModalContent from './view-only-access-modal-content'
|
||||
import customLocalStorage from '@/infrastructure/local-storage'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLModal from '@/features/ui/components/ol/ol-modal'
|
||||
|
||||
const ViewOnlyAccessModal = () => {
|
||||
const [show, setShow] = useState(false)
|
||||
|
@ -60,7 +60,7 @@ const ViewOnlyAccessModal = () => {
|
|||
])
|
||||
|
||||
return show ? (
|
||||
<AccessibleModal
|
||||
<OLModal
|
||||
animation
|
||||
show={show}
|
||||
onHide={() => {
|
||||
|
@ -72,7 +72,7 @@ const ViewOnlyAccessModal = () => {
|
|||
id="editor-over-limit-modal"
|
||||
>
|
||||
<ViewOnlyAccessModalContent handleHide={handleHide} />
|
||||
</AccessibleModal>
|
||||
</OLModal>
|
||||
) : null
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,12 @@ import { useCombobox } from 'downshift'
|
|||
import classnames from 'classnames'
|
||||
|
||||
import Icon from '../../../shared/components/icon'
|
||||
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 BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
|
||||
// Unicode characters in these Unicode groups:
|
||||
// "General Punctuation — Spaces"
|
||||
|
@ -154,12 +160,28 @@ export default function SelectCollaborators({
|
|||
<label className="small" {...getLabelProps()}>
|
||||
{t('share_with_your_collabs')}
|
||||
|
||||
{loading && <Icon type="refresh" spin />}
|
||||
{loading && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="refresh" spin />}
|
||||
bs5={
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
|
||||
<div className="host">
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<div {...getComboboxProps()} className="tags" onClick={focusInput}>
|
||||
<div
|
||||
{...getComboboxProps()}
|
||||
className="tags form-control"
|
||||
onClick={focusInput}
|
||||
>
|
||||
{selectedItems.map((selectedItem, index) => (
|
||||
<SelectedItem
|
||||
key={`selected-item-${index}`}
|
||||
|
@ -174,8 +196,7 @@ export default function SelectCollaborators({
|
|||
<input
|
||||
{...getInputProps(
|
||||
getDropdownProps({
|
||||
className: classnames({
|
||||
input: true,
|
||||
className: classnames('input', {
|
||||
'invalid-tag': !isValidInput,
|
||||
}),
|
||||
type: 'email',
|
||||
|
@ -241,8 +262,20 @@ export default function SelectCollaborators({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className={classnames({ autocomplete: isOpen })}>
|
||||
<ul {...getMenuProps()} className="suggestion-list">
|
||||
<div
|
||||
className={bsVersion({ bs3: classnames({ autocomplete: isOpen }) })}
|
||||
>
|
||||
<ul
|
||||
{...getMenuProps()}
|
||||
className={classnames(
|
||||
bsVersion({
|
||||
bs3: 'suggestion-list',
|
||||
bs5: classnames('dropdown-menu select-dropdown-menu', {
|
||||
show: isOpen,
|
||||
}),
|
||||
})
|
||||
)}
|
||||
>
|
||||
{isOpen &&
|
||||
filteredOptions.map((item, index) => (
|
||||
<Option
|
||||
|
@ -275,15 +308,36 @@ SelectCollaborators.propTypes = {
|
|||
function Option({ selected, item, getItemProps, index }) {
|
||||
return (
|
||||
<li
|
||||
className={classnames('suggestion-item', { selected })}
|
||||
className={bsVersion({
|
||||
bs3: classnames('suggestion-item', { selected }),
|
||||
})}
|
||||
{...getItemProps({ item, index })}
|
||||
>
|
||||
<Icon type="user" fw />
|
||||
|
||||
{item.display}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<>
|
||||
<Icon type="user" fw />
|
||||
|
||||
{item.display}
|
||||
</>
|
||||
}
|
||||
bs5={
|
||||
<DropdownItem
|
||||
as="span"
|
||||
role={undefined}
|
||||
leadingIcon="person"
|
||||
className={classnames({
|
||||
active: selected,
|
||||
})}
|
||||
>
|
||||
{item.display}
|
||||
</DropdownItem>
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
Option.propTypes = {
|
||||
selected: PropTypes.bool.isRequired,
|
||||
item: PropTypes.shape({
|
||||
|
@ -313,23 +367,44 @@ function SelectedItem({
|
|||
)
|
||||
|
||||
return (
|
||||
<span
|
||||
className="tag-item"
|
||||
{...getSelectedItemProps({ selectedItem, index })}
|
||||
>
|
||||
<Icon type="user" fw />
|
||||
<span>{selectedItem.display}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="remove-button btn-inline-link"
|
||||
aria-label={t('remove')}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon type="close" fw />
|
||||
</button>
|
||||
</span>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<span
|
||||
className="tag-item"
|
||||
{...getSelectedItemProps({ selectedItem, index })}
|
||||
>
|
||||
<Icon type="user" fw />
|
||||
<span>{selectedItem.display}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="remove-button btn-inline-link"
|
||||
aria-label={t('remove')}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon type="close" fw />
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
bs5={
|
||||
<Tag
|
||||
prepend={
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="user" fw />}
|
||||
bs5={<MaterialIcon type="person" />}
|
||||
/>
|
||||
}
|
||||
closeBtnProps={{
|
||||
onClick: handleClick,
|
||||
}}
|
||||
{...getSelectedItemProps({ selectedItem, index })}
|
||||
>
|
||||
{selectedItem.display}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
SelectedItem.propTypes = {
|
||||
focusInput: PropTypes.func.isRequired,
|
||||
removeSelectedItem: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import { Col, Row } from 'react-bootstrap'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
|
||||
export default function SendInvitesNotice() {
|
||||
const { publicAccessLevel } = useProjectContext()
|
||||
|
||||
return (
|
||||
<Row className="public-access-level public-access-level--notice">
|
||||
<Col xs={12} className="text-center">
|
||||
<OLRow className="public-access-level public-access-level-notice">
|
||||
<OLCol className="text-center">
|
||||
<AccessLevel level={publicAccessLevel} />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Row } from 'react-bootstrap'
|
||||
import AddCollaborators from './add-collaborators'
|
||||
import AddCollaboratorsUpgrade from './add-collaborators-upgrade'
|
||||
import PropTypes from 'prop-types'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
|
||||
export default function SendInvites({ canAddCollaborators }) {
|
||||
return (
|
||||
<Row className="invite-controls">
|
||||
<OLRow className="invite-controls">
|
||||
{canAddCollaborators ? <AddCollaborators /> : <AddCollaboratorsUpgrade />}
|
||||
</Row>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import { Button, Modal, Grid } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import AccessibleModal from '../../../shared/components/accessible-modal'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import { lazy, Suspense } from 'react'
|
||||
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
||||
import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
|
||||
const ReadOnlyTokenLink = lazy(() =>
|
||||
import('./link-sharing').then(({ ReadOnlyTokenLink }) => ({
|
||||
|
@ -36,13 +45,13 @@ export default function ShareProjectModalContent({
|
|||
const { isRestrictedTokenMember } = useEditorContext()
|
||||
|
||||
return (
|
||||
<AccessibleModal show={show} onHide={cancel} animation={animation}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('share_project')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModal show={show} onHide={cancel} animation={animation}>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('share_project')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body className="modal-body-share">
|
||||
<Grid fluid>
|
||||
<OLModalBody className="modal-body-share">
|
||||
<div className="container-fluid">
|
||||
<Suspense fallback={<FullSizeLoadingSpinner minHeight="15rem" />}>
|
||||
{isRestrictedTokenMember ? (
|
||||
<ReadOnlyTokenLink />
|
||||
|
@ -50,33 +59,43 @@ export default function ShareProjectModalContent({
|
|||
<ShareModalBody />
|
||||
)}
|
||||
</Suspense>
|
||||
</Grid>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer className="modal-footer-share">
|
||||
<div className="modal-footer-left">
|
||||
{inFlight && <Icon type="refresh" spin />}
|
||||
{error && (
|
||||
<span className="text-danger error">
|
||||
<ErrorMessage error={error} />
|
||||
</span>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={<ErrorMessage error={error} />}
|
||||
className="mb-0 mt-3"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</OLModalBody>
|
||||
|
||||
<OLModalFooter>
|
||||
<div className={bsVersion({ bs3: 'pull-left', bs5: 'me-auto' })}>
|
||||
{inFlight && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="refresh" spin />}
|
||||
bs5={
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="modal-footer-right">
|
||||
<ClickableElementEnhancer
|
||||
onClick={cancel}
|
||||
as={Button}
|
||||
type="button"
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
disabled={inFlight}
|
||||
>
|
||||
{t('close')}
|
||||
</ClickableElementEnhancer>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
<ClickableElementEnhancer
|
||||
onClick={cancel}
|
||||
as={OLButton}
|
||||
variant="secondary"
|
||||
disabled={inFlight}
|
||||
>
|
||||
{t('close')}
|
||||
</ClickableElementEnhancer>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import { useState } from 'react'
|
||||
import { Modal, Button } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import PropTypes from 'prop-types'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { transferProjectOwnership } from '../utils/api'
|
||||
import AccessibleModal from '../../../shared/components/accessible-modal'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { useLocation } from '../../../shared/hooks/use-location'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
|
||||
export default function TransferOwnershipModal({ member, cancel }) {
|
||||
const { t } = useTranslation()
|
||||
|
@ -32,11 +41,11 @@ export default function TransferOwnershipModal({ member, cancel }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal show onHide={cancel}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('change_project_owner')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<OLModal show onHide={cancel}>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('change_project_owner')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="project_ownership_transfer_confirmation_1"
|
||||
|
@ -47,37 +56,38 @@ export default function TransferOwnershipModal({ member, cancel }) {
|
|||
/>
|
||||
</p>
|
||||
<p>{t('project_ownership_transfer_confirmation_2')}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<div className="modal-footer-left">
|
||||
{inflight && <Icon type="refresh" spin />}
|
||||
{error && (
|
||||
<span className="text-danger">
|
||||
{t('generic_something_went_wrong')}
|
||||
</span>
|
||||
{error && (
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={t('generic_something_went_wrong')}
|
||||
className="mb-0 mt-3"
|
||||
/>
|
||||
)}
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<div className={bsVersion({ bs3: 'pull-left', bs5: 'me-auto' })}>
|
||||
{inflight && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="refresh" spin />}
|
||||
bs5={
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-footer-right">
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
onClick={cancel}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
bsStyle="primary"
|
||||
onClick={confirm}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('change_owner')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
<OLButton variant="secondary" onClick={cancel} disabled={inflight}>
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
<OLButton variant="primary" onClick={confirm} disabled={inflight}>
|
||||
{t('change_owner')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
TransferOwnershipModal.propTypes = {
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import MemberPrivileges from './member-privileges'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
|
||||
export default function ViewMember({ member }) {
|
||||
return (
|
||||
<Row className="project-member">
|
||||
<Col xs={7}>{member.email}</Col>
|
||||
<Col xs={3}>
|
||||
<OLRow className="project-member">
|
||||
<OLCol xs={7}>{member.email}</OLCol>
|
||||
<OLCol xs={3}>
|
||||
<MemberPrivileges privileges={member.privileges} />
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,15 @@ import type { IconButtonProps } from '@/features/ui/components/types/icon-button
|
|||
|
||||
const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||
(
|
||||
{ accessibilityLabel, icon, isLoading = false, size = 'default', ...props },
|
||||
{ accessibilityLabel, icon, isLoading = false, size, className, ...props },
|
||||
ref
|
||||
) => {
|
||||
const iconButtonClassName = `icon-button-${size}`
|
||||
const iconSizeClassName = size === 'large' ? 'icon-large' : 'icon-small'
|
||||
const iconButtonClassName = classNames(className, {
|
||||
'icon-button': !size,
|
||||
'icon-button-small': size === 'sm',
|
||||
'icon-button-large': size === 'lg',
|
||||
})
|
||||
const iconSizeClassName = size === 'lg' ? 'icon-large' : 'icon-small'
|
||||
const materialIconClassName = classNames(iconSizeClassName, {
|
||||
'button-content-hidden': isLoading,
|
||||
})
|
||||
|
|
|
@ -38,6 +38,7 @@ export function bs3ButtonProps(props: ButtonProps) {
|
|||
target: props.target,
|
||||
rel: props.rel,
|
||||
onClick: props.onClick,
|
||||
onMouseDown: props.onMouseDown as BS3ButtonProps['onMouseDown'],
|
||||
type: props.type,
|
||||
draggable: props.draggable,
|
||||
download: props.download,
|
||||
|
|
|
@ -11,8 +11,8 @@ function OLCol(props: OLColProps) {
|
|||
|
||||
const getBs3Sizes = (obj: typeof rest) => {
|
||||
const bs5ToBs3SizeMap = {
|
||||
xs: undefined,
|
||||
sm: undefined,
|
||||
xs: 'xs',
|
||||
sm: 'xs',
|
||||
md: 'sm',
|
||||
lg: 'md',
|
||||
xl: 'lg',
|
||||
|
|
|
@ -14,6 +14,7 @@ export type ButtonProps = {
|
|||
isLoading?: boolean
|
||||
loadingLabel?: string
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
||||
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>
|
||||
onMouseOver?: React.MouseEventHandler<HTMLButtonElement>
|
||||
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
|
||||
onFocus?: React.FocusEventHandler<HTMLButtonElement>
|
||||
|
|
|
@ -73,6 +73,7 @@ const IconButton: FC<{
|
|||
<OLIconButton
|
||||
onClick={handleClick}
|
||||
variant="link"
|
||||
size="sm"
|
||||
accessibilityLabel={t('copy')}
|
||||
className="copy-button"
|
||||
bs3Props={{ bsSize: 'xsmall' }}
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.public-access-level.public-access-level--notice {
|
||||
.public-access-level.public-access-level-notice {
|
||||
background-color: @gray-lightest;
|
||||
border-bottom: none;
|
||||
margin-top: @margin-md;
|
||||
|
@ -167,12 +167,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.modal-footer-share {
|
||||
.modal-footer-left {
|
||||
max-width: 70%;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-button:focus-within {
|
||||
outline: none;
|
||||
|
|
|
@ -39,3 +39,9 @@
|
|||
animation-name: bounce;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.recaptcha-branding {
|
||||
padding: var(--spacing-05) var(--spacing-05) 0 var(--spacing-05);
|
||||
text-align: center;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
|
|
@ -26,3 +26,4 @@
|
|||
@import 'select';
|
||||
@import 'link';
|
||||
@import 'pagination';
|
||||
@import 'loading-spinner';
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
padding: var(--spacing-01);
|
||||
}
|
||||
|
||||
.icon-button-default {
|
||||
.icon-button {
|
||||
padding: var(--spacing-04);
|
||||
}
|
||||
|
||||
|
|
|
@ -184,3 +184,8 @@
|
|||
margin-left: var(--spacing-04);
|
||||
}
|
||||
}
|
||||
|
||||
%input-focus-style {
|
||||
border-color: $input-focus-border-color;
|
||||
box-shadow: $form-check-input-focus-box-shadow;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.full-size-loading-spinner-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
|
@ -15,5 +15,7 @@
|
|||
@import 'subscription';
|
||||
@import 'editor/pdf';
|
||||
@import 'editor/compile-button';
|
||||
@import 'editor/share';
|
||||
@import 'editor/tags-input';
|
||||
@import 'website-redesign';
|
||||
@import 'group-settings';
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
.modal-body-share {
|
||||
h3 {
|
||||
border-bottom: 1px solid var(--neutral-30);
|
||||
padding-bottom: calc(var(--line-height-03) / 4);
|
||||
margin: 0;
|
||||
font-size: var(--font-size-03);
|
||||
}
|
||||
|
||||
.project-member.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.project-member .remove-button {
|
||||
font-size: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.project-invite,
|
||||
.public-access-level {
|
||||
font-size: var(--font-size-02);
|
||||
padding: calc(var(--line-height-03) / 2) 0;
|
||||
border-bottom: 1px solid var(--neutral-30);
|
||||
}
|
||||
|
||||
.public-access-level {
|
||||
margin-top: calc(var(--line-height-03) / 4);
|
||||
font-size: var(--font-size-02);
|
||||
padding-bottom: var(--spacing-07);
|
||||
|
||||
.access-token-display-area {
|
||||
margin-top: calc(var(--line-height-03) / 4);
|
||||
|
||||
.access-token-wrapper {
|
||||
padding-top: calc(var(--line-height-03) / 4);
|
||||
|
||||
.access-token {
|
||||
margin-top: calc(var(--line-height-03) / 4);
|
||||
background-color: var(--neutral-10);
|
||||
border: 1px solid var(--neutral-30);
|
||||
padding: var(--spacing-03) var(--spacing-05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
code {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-chevron {
|
||||
padding: 0 calc(var(--line-height-03) / 2);
|
||||
text-decoration: none;
|
||||
color: var(--neutral-70);
|
||||
}
|
||||
}
|
||||
|
||||
.public-access-level.public-access-level-notice {
|
||||
background-color: var(--neutral-10);
|
||||
border-bottom: none;
|
||||
margin-top: var(--spacing-07);
|
||||
padding-top: var(--spacing-07);
|
||||
}
|
||||
|
||||
.project-member,
|
||||
.project-invite {
|
||||
&:hover {
|
||||
background-color: var(--neutral-10);
|
||||
}
|
||||
}
|
||||
|
||||
.project-member {
|
||||
padding: calc(var(--line-height-03) / 2) 0;
|
||||
font-size: var(--font-size-03);
|
||||
|
||||
.select-wrapper {
|
||||
width: 100px;
|
||||
margin-left: auto;
|
||||
|
||||
.select-trigger {
|
||||
padding: 0 var(--spacing-05);
|
||||
color: var(--neutral-70);
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
li:last-child .dropdown-item {
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-member-email-icon {
|
||||
display: grid;
|
||||
grid-template-columns: 2em auto;
|
||||
align-items: center;
|
||||
padding-bottom: var(--spacing-03);
|
||||
|
||||
.subtitle {
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-member .text-left,
|
||||
.project-invite .text-left {
|
||||
padding-left: var(--spacing-08);
|
||||
}
|
||||
|
||||
.invite-controls {
|
||||
.small {
|
||||
padding: var(--spacing-01);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
padding: calc(var(--line-height-03) / 2);
|
||||
background-color: var(--neutral-10);
|
||||
margin-top: calc(var(--line-height-03) / 2);
|
||||
|
||||
form {
|
||||
.form-group {
|
||||
margin-bottom: calc(var(--line-height-03) / 2);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.privileges {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.tags-new .privileges {
|
||||
background: transparent;
|
||||
width: auto;
|
||||
height: 30px;
|
||||
font-size: var(--font-size-02);
|
||||
border: none;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.add-collaborators-upgrade {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-08);
|
||||
|
||||
.upgrade-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-07);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copy-button:focus-within {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.modal-link-share-new {
|
||||
.invite-controls {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.public-access-level {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.invite-warning {
|
||||
margin-bottom: calc(var(--line-height-03) / 2);
|
||||
}
|
||||
|
||||
.project-member-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
.tags-input *,
|
||||
.tags-input *::before,
|
||||
.tags-input *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tags-input label.small {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.tags-input .host {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tags-input .host:active,
|
||||
.tags-input[disabled] .host:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tags-input .tags {
|
||||
appearance: textfield;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
cursor: text;
|
||||
background-color: var(--white);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.badge-tag {
|
||||
margin: var(--spacing-01);
|
||||
|
||||
.badge-tag-content {
|
||||
max-width: initial;
|
||||
|
||||
.badge-content {
|
||||
white-space: initial;
|
||||
word-break: break-word;
|
||||
text-align: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tags-input .tags:focus-within {
|
||||
@extend %input-focus-style;
|
||||
}
|
||||
|
||||
.tags-input .tags .input {
|
||||
border: 0;
|
||||
outline: none;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&::placeholder {
|
||||
color: $input-placeholder-color;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-input .tags .input.invalid-tag {
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
.tags-input .tags .input::-ms-clear {
|
||||
display: none;
|
||||
}
|
Loading…
Reference in a new issue