mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #15319 from overleaf/ii-ide-page-prototype-share-modal
Share modal for React IDE page GitOrigin-RevId: f72f824abdcb5a135c354e3ccc35912b2097673f
This commit is contained in:
parent
541e7e315c
commit
9b6f83dfd4
14 changed files with 277 additions and 138 deletions
|
@ -927,6 +927,7 @@
|
||||||
"remove_or_replace_figure": "",
|
"remove_or_replace_figure": "",
|
||||||
"remove_secondary_email_addresses": "",
|
"remove_secondary_email_addresses": "",
|
||||||
"remove_tag": "",
|
"remove_tag": "",
|
||||||
|
"removed_from_project": "",
|
||||||
"removing": "",
|
"removing": "",
|
||||||
"rename": "",
|
"rename": "",
|
||||||
"rename_project": "",
|
"rename_project": "",
|
||||||
|
@ -1372,6 +1373,7 @@
|
||||||
"you_have_added_x_of_group_size_y": "",
|
"you_have_added_x_of_group_size_y": "",
|
||||||
"you_have_been_invited_to_transfer_management_of_your_account": "",
|
"you_have_been_invited_to_transfer_management_of_your_account": "",
|
||||||
"you_have_been_invited_to_transfer_management_of_your_account_to": "",
|
"you_have_been_invited_to_transfer_management_of_your_account_to": "",
|
||||||
|
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "",
|
||||||
"you_may_be_able_to_prevent_a_compile_timeout": "",
|
"you_may_be_able_to_prevent_a_compile_timeout": "",
|
||||||
"you_need_to_configure_your_sso_settings": "",
|
"you_need_to_configure_your_sso_settings": "",
|
||||||
"you_will_be_able_to_reassign_subscription": "",
|
"you_will_be_able_to_reassign_subscription": "",
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { useState, useCallback } from 'react'
|
||||||
|
import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
|
||||||
|
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
||||||
|
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||||
|
import EditorNavigationToolbarRoot from '@/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root'
|
||||||
|
import ShareProjectModal from '@/features/share-project-modal/components/share-project-modal'
|
||||||
|
|
||||||
|
function EditorNavigationToolbar() {
|
||||||
|
const [showShareModal, setShowShareModal] = useState(false)
|
||||||
|
const { onlineUsersArray } = useOnlineUsersContext()
|
||||||
|
const { openDoc } = useEditorManagerContext()
|
||||||
|
|
||||||
|
const handleOpenShareModal = () => {
|
||||||
|
eventTracking.sendMBOnce('ide-open-share-modal-once')
|
||||||
|
setShowShareModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleHideShareModal = useCallback(() => {
|
||||||
|
setShowShareModal(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EditorNavigationToolbarRoot
|
||||||
|
// @ts-ignore
|
||||||
|
onlineUsersArray={onlineUsersArray}
|
||||||
|
openDoc={openDoc}
|
||||||
|
openShareProjectModal={handleOpenShareModal}
|
||||||
|
/>
|
||||||
|
<ShareProjectModal
|
||||||
|
show={showShareModal}
|
||||||
|
handleHide={handleHideShareModal}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditorNavigationToolbar
|
|
@ -1,63 +0,0 @@
|
||||||
import React, { useCallback } from 'react'
|
|
||||||
import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button'
|
|
||||||
import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button'
|
|
||||||
import LayoutDropdownButton from '@/features/editor-navigation-toolbar/components/layout-dropdown-button'
|
|
||||||
import MenuButton from '@/features/editor-navigation-toolbar/components/menu-button'
|
|
||||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
|
||||||
import { sendMB } from '@/infrastructure/event-tracking'
|
|
||||||
import OnlineUsersWidget from '@/features/editor-navigation-toolbar/components/online-users-widget'
|
|
||||||
import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
|
|
||||||
|
|
||||||
type HeaderProps = {
|
|
||||||
chatIsOpen: boolean
|
|
||||||
setChatIsOpen: (chatIsOpen: boolean) => void
|
|
||||||
historyIsOpen: boolean
|
|
||||||
setHistoryIsOpen: (historyIsOpen: boolean) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Header({
|
|
||||||
chatIsOpen,
|
|
||||||
setChatIsOpen,
|
|
||||||
historyIsOpen,
|
|
||||||
setHistoryIsOpen,
|
|
||||||
}: HeaderProps) {
|
|
||||||
const { setLeftMenuShown } = useLayoutContext()
|
|
||||||
const { onlineUsersArray } = useOnlineUsersContext()
|
|
||||||
|
|
||||||
function toggleChatOpen() {
|
|
||||||
setChatIsOpen(!chatIsOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleHistoryOpen() {
|
|
||||||
setHistoryIsOpen(!historyIsOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleShowLeftMenuClick = useCallback(() => {
|
|
||||||
sendMB('navigation-clicked-menu')
|
|
||||||
setLeftMenuShown(value => !value)
|
|
||||||
}, [setLeftMenuShown])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<header className="toolbar toolbar-header">
|
|
||||||
<div className="toolbar-left">
|
|
||||||
<MenuButton onClick={handleShowLeftMenuClick} />
|
|
||||||
</div>
|
|
||||||
<div className="toolbar-right">
|
|
||||||
<OnlineUsersWidget
|
|
||||||
onlineUsers={onlineUsersArray}
|
|
||||||
goToUser={() => alert('Not implemented')}
|
|
||||||
/>
|
|
||||||
<LayoutDropdownButton />
|
|
||||||
<HistoryToggleButton
|
|
||||||
historyIsOpen={historyIsOpen}
|
|
||||||
onClick={toggleHistoryOpen}
|
|
||||||
/>
|
|
||||||
<ChatToggleButton
|
|
||||||
chatIsOpen={chatIsOpen}
|
|
||||||
onClick={toggleChatOpen}
|
|
||||||
unreadMessageCount={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -7,14 +7,16 @@ import PlaceholderChat from '@/features/ide-react/components/layout/placeholder/
|
||||||
import PlaceholderHistory from '@/features/ide-react/components/layout/placeholder/placeholder-history'
|
import PlaceholderHistory from '@/features/ide-react/components/layout/placeholder/placeholder-history'
|
||||||
import MainLayout from '@/features/ide-react/components/layout/main-layout'
|
import MainLayout from '@/features/ide-react/components/layout/main-layout'
|
||||||
import { EditorAndSidebar } from '@/features/ide-react/components/editor-and-sidebar'
|
import { EditorAndSidebar } from '@/features/ide-react/components/editor-and-sidebar'
|
||||||
import Header from '@/features/ide-react/components/header'
|
|
||||||
import EditorLeftMenu from '@/features/editor-left-menu/components/editor-left-menu'
|
import EditorLeftMenu from '@/features/editor-left-menu/components/editor-left-menu'
|
||||||
|
import EditorNavigationToolbar from '@/features/ide-react/components/editor-navigation-toolbar'
|
||||||
import { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking'
|
import { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking'
|
||||||
|
import useSocketListeners from '@/features/ide-react/hooks/use-socket-listeners'
|
||||||
|
|
||||||
// This is filled with placeholder content while the real content is migrated
|
// This is filled with placeholder content while the real content is migrated
|
||||||
// away from Angular
|
// away from Angular
|
||||||
export default function IdePage() {
|
export default function IdePage() {
|
||||||
useLayoutEventTracking()
|
useLayoutEventTracking()
|
||||||
|
useSocketListeners()
|
||||||
|
|
||||||
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
|
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
|
||||||
const { registerUserActivity } = useConnectionContext()
|
const { registerUserActivity } = useConnectionContext()
|
||||||
|
@ -32,24 +34,8 @@ export default function IdePage() {
|
||||||
return () => document.body.removeEventListener('click', listener)
|
return () => document.body.removeEventListener('click', listener)
|
||||||
}, [listener])
|
}, [listener])
|
||||||
|
|
||||||
const { chatIsOpen, setChatIsOpen, view, setView } = useLayoutContext()
|
const { chatIsOpen, view } = useLayoutContext()
|
||||||
const historyIsOpen = view === 'history'
|
const historyIsOpen = view === 'history'
|
||||||
const setHistoryIsOpen = useCallback(
|
|
||||||
(historyIsOpen: boolean) => {
|
|
||||||
setView(historyIsOpen ? 'history' : 'editor')
|
|
||||||
},
|
|
||||||
[setView]
|
|
||||||
)
|
|
||||||
|
|
||||||
const headerContent = (
|
|
||||||
<Header
|
|
||||||
chatIsOpen={chatIsOpen}
|
|
||||||
setChatIsOpen={setChatIsOpen}
|
|
||||||
historyIsOpen={historyIsOpen}
|
|
||||||
setHistoryIsOpen={setHistoryIsOpen}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
const chatContent = <PlaceholderChat />
|
|
||||||
|
|
||||||
const mainContent = historyIsOpen ? (
|
const mainContent = historyIsOpen ? (
|
||||||
<PlaceholderHistory
|
<PlaceholderHistory
|
||||||
|
@ -70,8 +56,8 @@ export default function IdePage() {
|
||||||
<Alerts />
|
<Alerts />
|
||||||
<EditorLeftMenu />
|
<EditorLeftMenu />
|
||||||
<MainLayout
|
<MainLayout
|
||||||
headerContent={headerContent}
|
headerContent={<EditorNavigationToolbar />}
|
||||||
chatContent={chatContent}
|
chatContent={<PlaceholderChat />}
|
||||||
mainContent={mainContent}
|
mainContent={mainContent}
|
||||||
chatIsOpen={chatIsOpen}
|
chatIsOpen={chatIsOpen}
|
||||||
shouldPersistLayout
|
shouldPersistLayout
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button'
|
import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button'
|
||||||
|
import ShareProjectButton from '@/features/editor-navigation-toolbar/components/share-project-button'
|
||||||
import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button'
|
import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button'
|
||||||
import LayoutDropdownButton from '@/features/editor-navigation-toolbar/components/layout-dropdown-button'
|
import LayoutDropdownButton from '@/features/editor-navigation-toolbar/components/layout-dropdown-button'
|
||||||
|
|
||||||
|
@ -16,6 +17,8 @@ export default function PlaceholderHeader({
|
||||||
historyIsOpen,
|
historyIsOpen,
|
||||||
setHistoryIsOpen,
|
setHistoryIsOpen,
|
||||||
}: PlaceholderHeaderProps) {
|
}: PlaceholderHeaderProps) {
|
||||||
|
function handleOpenShareModal() {}
|
||||||
|
|
||||||
function toggleChatOpen() {
|
function toggleChatOpen() {
|
||||||
setChatIsOpen(!chatIsOpen)
|
setChatIsOpen(!chatIsOpen)
|
||||||
}
|
}
|
||||||
|
@ -28,11 +31,12 @@ export default function PlaceholderHeader({
|
||||||
<header className="toolbar toolbar-header">
|
<header className="toolbar toolbar-header">
|
||||||
<div className="toolbar-left">Header placeholder</div>
|
<div className="toolbar-left">Header placeholder</div>
|
||||||
<div className="toolbar-right">
|
<div className="toolbar-right">
|
||||||
<LayoutDropdownButton />
|
<ShareProjectButton onClick={handleOpenShareModal} />
|
||||||
<HistoryToggleButton
|
<HistoryToggleButton
|
||||||
historyIsOpen={historyIsOpen}
|
historyIsOpen={historyIsOpen}
|
||||||
onClick={toggleHistoryOpen}
|
onClick={toggleHistoryOpen}
|
||||||
/>
|
/>
|
||||||
|
<LayoutDropdownButton />
|
||||||
<ChatToggleButton
|
<ChatToggleButton
|
||||||
chatIsOpen={chatIsOpen}
|
chatIsOpen={chatIsOpen}
|
||||||
onClick={toggleChatOpen}
|
onClick={toggleChatOpen}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Modal } from 'react-bootstrap'
|
||||||
|
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||||
|
|
||||||
|
export type GenericMessageModalOwnProps = {
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericMessageModalProps = React.ComponentProps<typeof AccessibleModal> &
|
||||||
|
GenericMessageModalOwnProps
|
||||||
|
|
||||||
|
function GenericMessageModal({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
...modalProps
|
||||||
|
}: GenericMessageModalProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccessibleModal {...modalProps}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>{title}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
|
||||||
|
<Modal.Body className="modal-body-share">{message}</Modal.Body>
|
||||||
|
|
||||||
|
<Modal.Footer>
|
||||||
|
<button className="btn btn-info" onClick={() => modalProps.onHide()}>
|
||||||
|
{t('ok')}
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</AccessibleModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GenericMessageModal
|
|
@ -18,7 +18,6 @@ import { JoinProjectPayload } from '@/features/ide-react/connection/join-project
|
||||||
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
||||||
import { getMockIde } from '@/shared/context/mock/mock-ide'
|
import { getMockIde } from '@/shared/context/mock/mock-ide'
|
||||||
import { populateEditorScope } from '@/features/ide-react/context/editor-manager-context'
|
import { populateEditorScope } from '@/features/ide-react/context/editor-manager-context'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
|
||||||
import { postJSON } from '@/infrastructure/fetch-json'
|
import { postJSON } from '@/infrastructure/fetch-json'
|
||||||
import { EventLog } from '@/features/ide-react/editor/event-log'
|
import { EventLog } from '@/features/ide-react/editor/event-log'
|
||||||
import { populateSettingsScope } from '@/features/ide-react/scope-adapters/settings-adapter'
|
import { populateSettingsScope } from '@/features/ide-react/scope-adapters/settings-adapter'
|
||||||
|
@ -41,10 +40,6 @@ const IdeReactContext = createContext<IdeReactContextValue | undefined>(
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
function showGenericMessageModal(title: string, message: string) {
|
|
||||||
debugConsole.log('*** showGenericMessageModal ***', title, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateIdeReactScope(store: ReactScopeValueStore) {
|
function populateIdeReactScope(store: ReactScopeValueStore) {
|
||||||
store.set('sync_tex_error', false)
|
store.set('sync_tex_error', false)
|
||||||
store.set('settings', window.userSettings)
|
store.set('settings', window.userSettings)
|
||||||
|
@ -156,7 +151,6 @@ export const IdeReactProvider: FC = ({ children }) => {
|
||||||
return {
|
return {
|
||||||
...getMockIde(),
|
...getMockIde(),
|
||||||
socket,
|
socket,
|
||||||
showGenericMessageModal,
|
|
||||||
reportError,
|
reportError,
|
||||||
// TODO: MIGRATION: Remove this once it's no longer used
|
// TODO: MIGRATION: Remove this once it's no longer used
|
||||||
fileTreeManager: {
|
fileTreeManager: {
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
FC,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import GenericMessageModal, {
|
||||||
|
GenericMessageModalOwnProps,
|
||||||
|
} from '@/features/ide-react/components/modals/generic-message-modal'
|
||||||
|
|
||||||
|
type ModalsContextValue = {
|
||||||
|
showGenericMessageModal: (
|
||||||
|
title: GenericMessageModalOwnProps['title'],
|
||||||
|
message: GenericMessageModalOwnProps['message']
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalsContext = createContext<ModalsContextValue | undefined>(undefined)
|
||||||
|
|
||||||
|
export const ModalsContextProvider: FC = ({ children }) => {
|
||||||
|
const [showGenericModal, setShowGenericModal] = useState(false)
|
||||||
|
const [genericMessageModalData, setGenericMessageModalData] =
|
||||||
|
useState<GenericMessageModalOwnProps>({ title: '', message: '' })
|
||||||
|
|
||||||
|
const handleHideGenericModal = useCallback(() => {
|
||||||
|
setShowGenericModal(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const showGenericMessageModal = useCallback(
|
||||||
|
(
|
||||||
|
title: GenericMessageModalOwnProps['title'],
|
||||||
|
message: GenericMessageModalOwnProps['message']
|
||||||
|
) => {
|
||||||
|
setGenericMessageModalData({ title, message })
|
||||||
|
setShowGenericModal(true)
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const value = useMemo<ModalsContextValue>(
|
||||||
|
() => ({
|
||||||
|
showGenericMessageModal,
|
||||||
|
}),
|
||||||
|
[showGenericMessageModal]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalsContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
<GenericMessageModal
|
||||||
|
show={showGenericModal}
|
||||||
|
onHide={handleHideGenericModal}
|
||||||
|
{...genericMessageModalData}
|
||||||
|
/>
|
||||||
|
</ModalsContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useModalsContext(): ModalsContextValue {
|
||||||
|
const context = useContext(ModalsContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useModalsContext is only available inside ModalsContextProvider'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import { OnlineUsersProvider } from '@/features/ide-react/context/online-users-c
|
||||||
import { MetadataProvider } from '@/features/ide-react/context/metadata-context'
|
import { MetadataProvider } from '@/features/ide-react/context/metadata-context'
|
||||||
import { ReferencesProvider } from '@/features/ide-react/context/references-context'
|
import { ReferencesProvider } from '@/features/ide-react/context/references-context'
|
||||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||||
|
import { ModalsContextProvider } from '@/features/ide-react/context/modals-context'
|
||||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||||
|
|
||||||
export const ReactContextRoot: FC = ({ children }) => {
|
export const ReactContextRoot: FC = ({ children }) => {
|
||||||
|
@ -38,7 +39,9 @@ export const ReactContextRoot: FC = ({ children }) => {
|
||||||
<EditorManagerProvider>
|
<EditorManagerProvider>
|
||||||
<OnlineUsersProvider>
|
<OnlineUsersProvider>
|
||||||
<MetadataProvider>
|
<MetadataProvider>
|
||||||
{children}
|
<ModalsContextProvider>
|
||||||
|
{children}
|
||||||
|
</ModalsContextProvider>
|
||||||
</MetadataProvider>
|
</MetadataProvider>
|
||||||
</OnlineUsersProvider>
|
</OnlineUsersProvider>
|
||||||
</EditorManagerProvider>
|
</EditorManagerProvider>
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
|
||||||
|
import {
|
||||||
|
listProjectInvites,
|
||||||
|
listProjectMembers,
|
||||||
|
} from '@/features/share-project-modal/utils/api'
|
||||||
|
import useScopeValue from '@/shared/hooks/use-scope-value'
|
||||||
|
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
||||||
|
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||||
|
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||||
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
|
||||||
|
function useSocketListeners() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { socket } = useConnectionContext()
|
||||||
|
const { projectId } = useIdeReactContext()
|
||||||
|
const { showGenericMessageModal } = useModalsContext()
|
||||||
|
const [, setPublicAccessLevel] = useScopeValue('project.publicAccesLevel')
|
||||||
|
const [, setProjectMembers] = useScopeValue('project.members')
|
||||||
|
const [, setProjectInvites] = useScopeValue('project.invites')
|
||||||
|
|
||||||
|
useSocketListener(socket, 'project:access:revoked', () => {
|
||||||
|
showGenericMessageModal(
|
||||||
|
t('removed_from_project'),
|
||||||
|
t(
|
||||||
|
'you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
useSocketListener(socket, 'project:publicAccessLevel:changed', data => {
|
||||||
|
if (data.newAccessLevel) {
|
||||||
|
setPublicAccessLevel(data.newAccessLevel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
useSocketListener(socket, 'project:membership:changed', data => {
|
||||||
|
if (data.members) {
|
||||||
|
listProjectMembers(projectId)
|
||||||
|
.then(({ members }) => {
|
||||||
|
if (members) {
|
||||||
|
setProjectMembers(members)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
debugConsole.error('Error fetching members for project', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.invites) {
|
||||||
|
listProjectInvites(projectId)
|
||||||
|
.then(({ invites }) => {
|
||||||
|
if (invites) {
|
||||||
|
setProjectInvites(invites)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
debugConsole.error('Error fetching invites for project', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSocketListeners
|
|
@ -2,7 +2,6 @@ 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 AccessibleModal from '../../../shared/components/accessible-modal'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
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'
|
||||||
|
@ -16,18 +15,24 @@ const ReadOnlyTokenLink = lazy(() =>
|
||||||
|
|
||||||
const ShareModalBody = lazy(() => import('./share-modal-body'))
|
const ShareModalBody = lazy(() => import('./share-modal-body'))
|
||||||
|
|
||||||
|
type ShareProjectModalContentProps = {
|
||||||
|
cancel: () => void
|
||||||
|
show: boolean
|
||||||
|
animation: boolean
|
||||||
|
inFlight: boolean
|
||||||
|
error: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export default function ShareProjectModalContent({
|
export default function ShareProjectModalContent({
|
||||||
show,
|
show,
|
||||||
cancel,
|
cancel,
|
||||||
animation,
|
animation,
|
||||||
inFlight,
|
inFlight,
|
||||||
error,
|
error,
|
||||||
}) {
|
}: ShareProjectModalContentProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { isRestrictedTokenMember } = useEditorContext({
|
const { isRestrictedTokenMember } = useEditorContext()
|
||||||
isRestrictedTokenMember: PropTypes.bool,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleModal show={show} onHide={cancel} animation={animation}>
|
<AccessibleModal show={show} onHide={cancel} animation={animation}>
|
||||||
|
@ -72,37 +77,26 @@ export default function ShareProjectModalContent({
|
||||||
</AccessibleModal>
|
</AccessibleModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ShareProjectModalContent.propTypes = {
|
|
||||||
cancel: PropTypes.func.isRequired,
|
|
||||||
show: PropTypes.bool,
|
|
||||||
animation: PropTypes.bool,
|
|
||||||
inFlight: PropTypes.bool,
|
|
||||||
error: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
function ErrorMessage({ error }) {
|
function ErrorMessage({ error }: Pick<ShareProjectModalContentProps, 'error'>) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case 'cannot_invite_non_user':
|
case 'cannot_invite_non_user':
|
||||||
return t('cannot_invite_non_user')
|
return <>{t('cannot_invite_non_user')}</>
|
||||||
|
|
||||||
case 'cannot_verify_user_not_robot':
|
case 'cannot_verify_user_not_robot':
|
||||||
return t('cannot_verify_user_not_robot')
|
return <>{t('cannot_verify_user_not_robot')}</>
|
||||||
|
|
||||||
case 'cannot_invite_self':
|
case 'cannot_invite_self':
|
||||||
return t('cannot_invite_self')
|
return <>{t('cannot_invite_self')}</>
|
||||||
|
|
||||||
case 'invalid_email':
|
case 'invalid_email':
|
||||||
return t('invalid_email')
|
return <>{t('invalid_email')}</>
|
||||||
|
|
||||||
case 'too_many_requests':
|
case 'too_many_requests':
|
||||||
return t('too_many_requests')
|
return <>{t('too_many_requests')}</>
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return t('generic_something_went_wrong')
|
return <>{t('generic_something_went_wrong')}</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ErrorMessage.propTypes = {
|
|
||||||
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]).isRequired,
|
|
||||||
}
|
|
|
@ -14,19 +14,23 @@ import {
|
||||||
import { useSplitTestContext } from '../../../shared/context/split-test-context'
|
import { useSplitTestContext } from '../../../shared/context/split-test-context'
|
||||||
import { sendMB } from '../../../infrastructure/event-tracking'
|
import { sendMB } from '../../../infrastructure/event-tracking'
|
||||||
|
|
||||||
const ShareProjectContext = createContext()
|
type ShareProjectContextValue = {
|
||||||
|
updateProject: (data: unknown) => void
|
||||||
ShareProjectContext.Provider.propTypes = {
|
monitorRequest: <T extends Promise<unknown>>(request: () => T) => T
|
||||||
value: PropTypes.shape({
|
inFlight: boolean
|
||||||
updateProject: PropTypes.func.isRequired,
|
setInFlight: React.Dispatch<
|
||||||
monitorRequest: PropTypes.func.isRequired,
|
React.SetStateAction<ShareProjectContextValue['inFlight']>
|
||||||
inFlight: PropTypes.bool,
|
>
|
||||||
setInFlight: PropTypes.func,
|
error: string | undefined
|
||||||
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
setError: React.Dispatch<
|
||||||
setError: PropTypes.func,
|
React.SetStateAction<ShareProjectContextValue['error']>
|
||||||
}),
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ShareProjectContext = createContext<ShareProjectContextValue | undefined>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
export function useShareProjectContext() {
|
export function useShareProjectContext() {
|
||||||
const context = useContext(ShareProjectContext)
|
const context = useContext(ShareProjectContext)
|
||||||
|
|
||||||
|
@ -39,13 +43,20 @@ export function useShareProjectContext() {
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShareProjectModalProps = {
|
||||||
|
handleHide: () => void
|
||||||
|
show: boolean
|
||||||
|
animation?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
const ShareProjectModal = React.memo(function ShareProjectModal({
|
const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||||
handleHide,
|
handleHide,
|
||||||
show,
|
show,
|
||||||
animation = true,
|
animation = true,
|
||||||
}) {
|
}: ShareProjectModalProps) {
|
||||||
const [inFlight, setInFlight] = useState(false)
|
const [inFlight, setInFlight] =
|
||||||
const [error, setError] = useState()
|
useState<ShareProjectContextValue['inFlight']>(false)
|
||||||
|
const [error, setError] = useState<ShareProjectContextValue['error']>()
|
||||||
|
|
||||||
const project = useProjectContext(projectShape)
|
const project = useProjectContext(projectShape)
|
||||||
|
|
||||||
|
@ -84,7 +95,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||||
|
|
||||||
const promise = request()
|
const promise = request()
|
||||||
|
|
||||||
promise.catch(error => {
|
promise.catch((error: { data?: Record<string, string> }) => {
|
||||||
setError(
|
setError(
|
||||||
error.data?.errorReason ||
|
error.data?.errorReason ||
|
||||||
error.data?.error ||
|
error.data?.error ||
|
||||||
|
@ -130,10 +141,5 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||||
</ShareProjectContext.Provider>
|
</ShareProjectContext.Provider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
ShareProjectModal.propTypes = {
|
|
||||||
animation: PropTypes.bool,
|
|
||||||
handleHide: PropTypes.func.isRequired,
|
|
||||||
show: PropTypes.bool.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ShareProjectModal
|
export default ShareProjectModal
|
|
@ -8,10 +8,11 @@ import { debugConsole } from '@/utils/debugging'
|
||||||
|
|
||||||
App.component(
|
App.component(
|
||||||
'shareProjectModal',
|
'shareProjectModal',
|
||||||
react2angular(
|
react2angular(rootContext.use(ShareProjectModal), [
|
||||||
rootContext.use(ShareProjectModal),
|
'animation',
|
||||||
Object.keys(ShareProjectModal.propTypes)
|
'handleHide',
|
||||||
)
|
'show',
|
||||||
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
export default App.controller('ReactShareProjectModalController', [
|
export default App.controller('ReactShareProjectModalController', [
|
||||||
|
|
|
@ -1445,6 +1445,7 @@
|
||||||
"remove_secondary_email_addresses": "Remove any secondary email addresses associated with your account. <0>Remove them in account settings.</0>",
|
"remove_secondary_email_addresses": "Remove any secondary email addresses associated with your account. <0>Remove them in account settings.</0>",
|
||||||
"remove_tag": "Remove tag __tagName__",
|
"remove_tag": "Remove tag __tagName__",
|
||||||
"removed": "removed",
|
"removed": "removed",
|
||||||
|
"removed_from_project": "Removed from project",
|
||||||
"removing": "Removing",
|
"removing": "Removing",
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"rename_project": "Rename Project",
|
"rename_project": "Rename Project",
|
||||||
|
@ -2052,6 +2053,7 @@
|
||||||
"you_have_added_x_of_group_size_y": "You have added <0>__addedUsersSize__</0> of <1>__groupSize__</1> available members",
|
"you_have_added_x_of_group_size_y": "You have added <0>__addedUsersSize__</0> of <1>__groupSize__</1> available members",
|
||||||
"you_have_been_invited_to_transfer_management_of_your_account": "You have been invited to transfer management of your account.",
|
"you_have_been_invited_to_transfer_management_of_your_account": "You have been invited to transfer management of your account.",
|
||||||
"you_have_been_invited_to_transfer_management_of_your_account_to": "You have been invited to transfer management of your account to __groupName__.",
|
"you_have_been_invited_to_transfer_management_of_your_account_to": "You have been invited to transfer management of your account to __groupName__.",
|
||||||
|
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.",
|
||||||
"you_introed_high_number": " You’ve introduced <0>__numberOfPeople__</0> people to __appName__. Good job!",
|
"you_introed_high_number": " You’ve introduced <0>__numberOfPeople__</0> people to __appName__. Good job!",
|
||||||
"you_introed_small_number": " You’ve introduced <0>__numberOfPeople__</0> person to __appName__. Good job, but can you get some more?",
|
"you_introed_small_number": " You’ve introduced <0>__numberOfPeople__</0> person to __appName__. Good job, but can you get some more?",
|
||||||
"you_may_be_able_to_prevent_a_compile_timeout": "You may be able to prevent a compile timeout using the following tips.",
|
"you_may_be_able_to_prevent_a_compile_timeout": "You may be able to prevent a compile timeout using the following tips.",
|
||||||
|
|
Loading…
Reference in a new issue