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