mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
Merge pull request #33717 from overleaf/as-new-link-sharing-pass-groups
[web] Pass project owner link sharing settings to frontend GitOrigin-RevId: ff000f38ef36a3d448a2934d0c5963139852eee2
This commit is contained in:
@@ -572,9 +572,20 @@ const _ProjectController = {
|
||||
)
|
||||
).isMember)()
|
||||
: false,
|
||||
activeGroupSubscriptions:
|
||||
SubscriptionLocator.promises.getUserActiveGroupSubscriptions(userId, {
|
||||
_id: 1,
|
||||
teamName: 1,
|
||||
sharingPermissions: 1,
|
||||
}),
|
||||
})
|
||||
|
||||
const { project, userValues, userIsMemberOfGroupSubscription } = responses
|
||||
const {
|
||||
project,
|
||||
userValues,
|
||||
userIsMemberOfGroupSubscription,
|
||||
activeGroupSubscriptions,
|
||||
} = responses
|
||||
|
||||
await Promise.all([
|
||||
InactiveProjectManager.promises.reactivateProjectIfRequired(project),
|
||||
@@ -959,6 +970,7 @@ const _ProjectController = {
|
||||
),
|
||||
isMemberOfGroupSubscription: userIsMemberOfGroupSubscription,
|
||||
hasInstitutionLicence: userHasInstitutionLicence,
|
||||
activeGroupSubscriptions,
|
||||
},
|
||||
initialLoadingScreenTheme,
|
||||
userSettings,
|
||||
|
||||
@@ -180,6 +180,27 @@ const SubscriptionLocator = {
|
||||
}
|
||||
},
|
||||
|
||||
async getUserActiveGroupSubscriptions(userOrId, projection = {}) {
|
||||
if (!Features.hasFeature('saas')) return []
|
||||
|
||||
const userId = SubscriptionLocator._getUserId(userOrId)
|
||||
return await Subscription.find(
|
||||
{
|
||||
groupPlan: true,
|
||||
$and: [
|
||||
{ $or: [{ admin_id: userId }, { member_ids: userId }] },
|
||||
{
|
||||
$or: [
|
||||
{ 'recurlyStatus.state': 'active' },
|
||||
{ 'paymentProvider.state': 'active' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
projection
|
||||
).exec()
|
||||
},
|
||||
|
||||
async getUserSubscriptionStatus(userId) {
|
||||
let usersSubscription = { personal: false, group: false }
|
||||
|
||||
|
||||
@@ -161,6 +161,7 @@
|
||||
"anyone": "",
|
||||
"anyone_in_x": "",
|
||||
"anyone_in_x_with_the_link": "",
|
||||
"anyone_in_your_group_with_the_link": "",
|
||||
"anyone_with_link_can_edit": "",
|
||||
"anyone_with_link_can_view": "",
|
||||
"anyone_with_the_link": "",
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export default function CopySharingLinkButton() {
|
||||
const isCopyBtnEnabled =
|
||||
Boolean(navigator.clipboard?.writeText) &&
|
||||
Boolean(sharingLinkData?.token) &&
|
||||
(projectAccess === 'anyoneInXyzWithTheLink' ||
|
||||
(projectAccess?.startsWith('anyoneInXyzWithTheLink') ||
|
||||
projectAccess === 'anyoneWithTheLink')
|
||||
|
||||
const handleCopyClick = () => {
|
||||
|
||||
+54
-26
@@ -28,6 +28,7 @@ import {
|
||||
useShareProjectContext,
|
||||
} from '@/features/share-project-modal/components/share-project-modal'
|
||||
import { ExcludeStrict } from '@ol-types/utils'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
type ProjectAccessProps = {
|
||||
setIsInvitedPeopleScreen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
@@ -48,8 +49,7 @@ function ProjectAccess({
|
||||
null
|
||||
)
|
||||
const { isProjectOwner } = useEditorContext()
|
||||
// TODO get the group name from backend
|
||||
const groupName = 'your group'
|
||||
const { activeGroupSubscriptions } = getMeta('ol-user')
|
||||
|
||||
const {
|
||||
monitorRequest,
|
||||
@@ -69,13 +69,11 @@ function ProjectAccess({
|
||||
let reqBody: Pick<SharingLinkData, 'privileges' | 'subscriptionId'>
|
||||
if (newAccess === 'onlyInvitedPeople') {
|
||||
reqBody = { privileges: false }
|
||||
} else if (
|
||||
newAccess === 'anyoneInXyzWithTheLink' &&
|
||||
sharingLinkData?.subscriptionId
|
||||
) {
|
||||
} else if (newAccess.startsWith('anyoneInXyzWithTheLink')) {
|
||||
const newSubscriptionId = newAccess.split('.')[1]
|
||||
reqBody = {
|
||||
privileges: privileges || 'readOnly',
|
||||
subscriptionId: sharingLinkData.subscriptionId,
|
||||
subscriptionId: newSubscriptionId,
|
||||
}
|
||||
} else if (newAccess === 'anyoneWithTheLink') {
|
||||
reqBody = { privileges: privileges || 'readOnly' }
|
||||
@@ -125,14 +123,34 @@ function ProjectAccess({
|
||||
})
|
||||
}
|
||||
|
||||
const getGroupLinkText = (id?: string) => {
|
||||
if (
|
||||
!id ||
|
||||
!activeGroupSubscriptions ||
|
||||
activeGroupSubscriptions.length === 0
|
||||
) {
|
||||
return ''
|
||||
}
|
||||
const subscription = activeGroupSubscriptions.find(sub => sub._id === id)
|
||||
if (subscription?.teamName) {
|
||||
return t('anyone_in_x_with_the_link', {
|
||||
groupName: subscription.teamName,
|
||||
})
|
||||
} else {
|
||||
return t('anyone_in_your_group_with_the_link')
|
||||
}
|
||||
}
|
||||
|
||||
const getProjectAccessDropdownToggleText = () => {
|
||||
switch (projectAccess) {
|
||||
if (!projectAccess) return ''
|
||||
const [accessType, subscriptionId] = projectAccess.split('.')
|
||||
switch (accessType) {
|
||||
case 'legacyLinkSharing':
|
||||
return t('via_sharing_links_legacy')
|
||||
case 'onlyInvitedPeople':
|
||||
return t('only_invited_people')
|
||||
case 'anyoneInXyzWithTheLink':
|
||||
return t('anyone_in_x_with_the_link', { groupName })
|
||||
return getGroupLinkText(subscriptionId)
|
||||
case 'anyoneWithTheLink':
|
||||
return t('anyone_with_the_link')
|
||||
default:
|
||||
@@ -167,7 +185,7 @@ function ProjectAccess({
|
||||
{projectAccess === 'onlyInvitedPeople' && (
|
||||
<MaterialIcon type="lock" unfilled />
|
||||
)}
|
||||
{projectAccess === 'anyoneInXyzWithTheLink' && (
|
||||
{projectAccess.startsWith('anyoneInXyzWithTheLink') && (
|
||||
<MaterialIcon type="domain" unfilled />
|
||||
)}
|
||||
{projectAccess === 'anyoneWithTheLink' && (
|
||||
@@ -217,23 +235,33 @@ function ProjectAccess({
|
||||
{t('only_invited_people')}
|
||||
</DropdownItem>
|
||||
</DropdownListItem>
|
||||
{sharingLinkData?.subscriptionId && (
|
||||
<DropdownListItem className="d-flex align-items-center">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
eventKey="anyoneInXyzWithTheLink"
|
||||
leadingIcon={<MaterialIcon type="domain" unfilled />}
|
||||
trailingIcon={
|
||||
projectAccess === 'anyoneInXyzWithTheLink'
|
||||
? 'check'
|
||||
: undefined
|
||||
}
|
||||
active={projectAccess === 'anyoneInXyzWithTheLink'}
|
||||
{activeGroupSubscriptions &&
|
||||
activeGroupSubscriptions.map(subscription => (
|
||||
<DropdownListItem
|
||||
className="d-flex align-items-center"
|
||||
key={subscription._id}
|
||||
>
|
||||
{t('anyone_in_x_with_the_link', { groupName })}
|
||||
</DropdownItem>
|
||||
</DropdownListItem>
|
||||
)}
|
||||
<DropdownItem
|
||||
as="button"
|
||||
eventKey={`anyoneInXyzWithTheLink.${subscription._id}`}
|
||||
leadingIcon={<MaterialIcon type="domain" unfilled />}
|
||||
trailingIcon={
|
||||
projectAccess ===
|
||||
`anyoneInXyzWithTheLink.${subscription._id}`
|
||||
? 'check'
|
||||
: undefined
|
||||
}
|
||||
active={
|
||||
projectAccess ===
|
||||
`anyoneInXyzWithTheLink.${subscription._id}`
|
||||
}
|
||||
>
|
||||
{t('anyone_in_x_with_the_link', {
|
||||
groupName: subscription.teamName || 'your group',
|
||||
})}
|
||||
</DropdownItem>
|
||||
</DropdownListItem>
|
||||
))}
|
||||
<DropdownListItem className="d-flex align-items-center gap-2">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ function RemoveSharingLinksModal({
|
||||
'this_change_will_permanently_remove_your_original_links_you_can_still_share_new_link'
|
||||
)
|
||||
} else if (
|
||||
pendingAccess === 'anyoneInXyzWithTheLink' ||
|
||||
pendingAccess.startsWith('anyoneInXyzWithTheLink') ||
|
||||
pendingAccess === 'anyoneWithTheLink'
|
||||
) {
|
||||
confirmationModalBodyText = t(
|
||||
|
||||
+2
-1
@@ -137,8 +137,9 @@ function ShareProjectModalContentInner({
|
||||
{isSharingUpdatesEnabled ? (
|
||||
<>
|
||||
{!isInvitedPeopleScreen &&
|
||||
projectAccess &&
|
||||
(projectAccess === 'onlyInvitedPeople' ||
|
||||
projectAccess === 'anyoneInXyzWithTheLink' ||
|
||||
projectAccess.startsWith('anyoneInXyzWithTheLink') ||
|
||||
projectAccess === 'anyoneWithTheLink') && (
|
||||
<CopySharingLinkButton />
|
||||
)}
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ import {
|
||||
export type ProjectAccessType =
|
||||
| 'legacyLinkSharing'
|
||||
| 'onlyInvitedPeople'
|
||||
| 'anyoneInXyzWithTheLink'
|
||||
| `anyoneInXyzWithTheLink.${string}`
|
||||
| 'anyoneWithTheLink'
|
||||
|
||||
export type ShareProjectContextValue = {
|
||||
@@ -161,7 +161,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
if (!data.privileges) {
|
||||
setProjectAccess('onlyInvitedPeople')
|
||||
} else if (data.subscriptionId) {
|
||||
setProjectAccess('anyoneInXyzWithTheLink')
|
||||
setProjectAccess(`anyoneInXyzWithTheLink.${data.subscriptionId}`)
|
||||
} else {
|
||||
setProjectAccess('anyoneWithTheLink')
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@
|
||||
"anyone": "Anyone",
|
||||
"anyone_in_x": "Anyone in __groupName__",
|
||||
"anyone_in_x_with_the_link": "Anyone in __groupName__ with the link",
|
||||
"anyone_in_your_group_with_the_link": "Anyone in your group with the link",
|
||||
"anyone_with_link_can_edit": "Anyone with this link can edit this project",
|
||||
"anyone_with_link_can_view": "Anyone with this link can view this project",
|
||||
"anyone_with_the_link": "Anyone with the link",
|
||||
|
||||
@@ -9,10 +9,11 @@ import fetchMock from 'fetch-mock'
|
||||
import ChatPane from '../../../../../frontend/js/features/chat/components/chat-pane'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import { stubMathJax, tearDownMathJaxStubs } from './stubs'
|
||||
import { UserId } from '@ol-types/user'
|
||||
|
||||
describe('<ChatPane />', function () {
|
||||
const user = {
|
||||
id: 'fake_user',
|
||||
id: 'fake_user' as UserId,
|
||||
first_name: 'fake_user_first_name',
|
||||
email: 'fake@example.com',
|
||||
}
|
||||
|
||||
+11
-7
@@ -253,7 +253,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
...createContextProps({ publicAccessLevel: 'tokenBased', invites }),
|
||||
user: {
|
||||
id: 'non-project-owner',
|
||||
id: 'non-project-owner' as UserId,
|
||||
email: 'non-project-owner@example.com',
|
||||
},
|
||||
})
|
||||
@@ -283,7 +283,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
...createContextProps({ publicAccessLevel: 'private', invites }),
|
||||
user: {
|
||||
id: 'non-project-owner',
|
||||
id: 'non-project-owner' as UserId,
|
||||
email: 'non-project-owner@example.com',
|
||||
},
|
||||
})
|
||||
@@ -1057,10 +1057,14 @@ describe('<ShareProjectModal/>', function () {
|
||||
subscriptionId: 'sub-123',
|
||||
})
|
||||
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'private' })
|
||||
)
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
...createContextProps({ publicAccessLevel: 'private' }),
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
activeGroupSubscriptions: [{ _id: 'sub-123' }],
|
||||
},
|
||||
})
|
||||
|
||||
await screen.findByText('Anyone in your group with the link')
|
||||
})
|
||||
@@ -1331,7 +1335,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
...createContextProps(),
|
||||
user: {
|
||||
id: 'non-project-owner',
|
||||
id: 'non-project-owner' as UserId,
|
||||
email: 'non-project-owner@example.com',
|
||||
},
|
||||
})
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@ import { TestContainer } from '../helpers/test-container'
|
||||
import { FC } from 'react'
|
||||
import { MetadataContext } from '@/features/ide-react/context/metadata-context'
|
||||
import { ReferencesContext } from '@/features/ide-react/context/references-context'
|
||||
import { UserId } from '@ol-types/user'
|
||||
|
||||
describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
beforeEach(function () {
|
||||
@@ -764,7 +765,7 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
|
||||
window.metaAttributesCache.set('ol-showSymbolPalette', true)
|
||||
const user = {
|
||||
id: '123abd',
|
||||
id: '123abd' as UserId,
|
||||
email: 'testuser@example.com',
|
||||
}
|
||||
cy.mount(
|
||||
@@ -794,7 +795,7 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
|
||||
window.metaAttributesCache.set('ol-showSymbolPalette', false)
|
||||
const user = {
|
||||
id: '123abd',
|
||||
id: '123abd' as UserId,
|
||||
email: 'testuser@example.com',
|
||||
}
|
||||
cy.mount(
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
ProjectMetadata,
|
||||
ProjectUpdate,
|
||||
} from '@/shared/context/types/project-metadata'
|
||||
import { UserId } from '../../../types/user'
|
||||
import { User, UserId } from '../../../types/user'
|
||||
import { ProjectCompiler } from '../../../types/project-settings'
|
||||
import { ReferencesContext } from '@/features/ide-react/context/references-context'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
@@ -70,12 +70,14 @@ const defaultUserSettings = {
|
||||
} satisfies UserSettings
|
||||
|
||||
export type EditorProvidersProps = {
|
||||
user?: {
|
||||
id: string
|
||||
email: string
|
||||
signUpDate?: string
|
||||
isProfessionalGroupPlan?: boolean
|
||||
}
|
||||
user?: Pick<
|
||||
User,
|
||||
| 'id'
|
||||
| 'email'
|
||||
| 'signUpDate'
|
||||
| 'activeGroupSubscriptions'
|
||||
| 'isProfessionalGroupPlan'
|
||||
>
|
||||
projectId?: string
|
||||
projectName?: string
|
||||
projectOwner?: ProjectMetadata['owner']
|
||||
|
||||
@@ -71,6 +71,7 @@ describe('ProjectController', function () {
|
||||
ctx.SubscriptionLocator = {
|
||||
promises: {
|
||||
getUsersSubscription: sinon.stub().resolves(),
|
||||
getUserActiveGroupSubscriptions: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
ctx.SubscriptionController = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SharingPermissions } from '../modules/sharing-permissions/app/src/types'
|
||||
import { Brand } from './helpers/brand'
|
||||
|
||||
export type RefProviders = {
|
||||
@@ -64,6 +65,11 @@ export type User = {
|
||||
isMemberOfGroupSubscription?: boolean
|
||||
isProfessionalGroupPlan?: boolean
|
||||
hasInstitutionLicence?: boolean
|
||||
activeGroupSubscriptions?: {
|
||||
_id: string
|
||||
teamName?: string
|
||||
sharingPermissions?: SharingPermissions
|
||||
}[]
|
||||
}
|
||||
|
||||
export type LoggedOutUser = {
|
||||
|
||||
Reference in New Issue
Block a user