diff --git a/src/cloud/api/teams/export.ts b/src/cloud/api/teams/export.ts new file mode 100644 index 0000000000..d46004c21c --- /dev/null +++ b/src/cloud/api/teams/export.ts @@ -0,0 +1,8 @@ +import { callApiBlob } from '../../lib/client' + +export async function exportWorkspace(teamId: string) { + const data = await callApiBlob(`api/teams/${teamId}/export`, { + method: 'get', + }) + return data +} diff --git a/src/cloud/components/Application.tsx b/src/cloud/components/Application.tsx index 6101916742..10642a7e4a 100644 --- a/src/cloud/components/Application.tsx +++ b/src/cloud/components/Application.tsx @@ -30,7 +30,7 @@ import { mapUsers } from '../../design/lib/mappers/users' import { mdiCog, mdiDownload, - mdiGiftOutline, + mdiExclamationThick, mdiInbox, mdiLogoutVariant, mdiMagnify, @@ -49,8 +49,6 @@ import { import { useModal } from '../../design/lib/stores/modal' import NewDocButton from './buttons/NewDocButton' import { useCloudSidebarTree } from '../lib/hooks/sidebar/useCloudSidebarTree' -import { isTimeEligibleForDiscount } from '../lib/subscription' -import DiscountModal from './Modal/contents/DiscountModal' import { Notification as UserNotification } from '../interfaces/db/notifications' import useNotificationState from '../../design/lib/hooks/useNotificationState' import { useNotifications } from '../../design/lib/stores/notifications' @@ -64,7 +62,6 @@ import SidebarHeader from '../../design/components/organisms/Sidebar/atoms/Sideb import SidebarButtonList from '../../design/components/organisms/Sidebar/molecules/SidebarButtonList' import { getTeamLinkHref } from './Link/TeamLink' import WithPastille from '../../design/components/atoms/WithPastille' -import SidebarButton from '../../design/components/organisms/Sidebar/atoms/SidebarButton' import CloudGlobalSearch from './CloudGlobalSearch' import { useCloudSidebarSpaces } from '../lib/hooks/sidebar/useCloudSidebarSpaces' import { trackEvent } from '../api/track' @@ -78,6 +75,7 @@ import SidebarSubscriptionCTA from './Subscription/SidebarSubscriptionCTA' import { isEmpty } from 'lodash' import LoaderTopbar from '../../design/components/atoms/loaders/LoaderTopbar' import Icon from '../../design/components/atoms/Icon' +import ExportModal from './Modal/contents/ExportModal' interface ApplicationProps { className?: string @@ -101,7 +99,6 @@ const Application = ({ permissions = [], currentUserPermissions, currentUserIsCoreMember, - subscription, navigatingBetweenPage, } = usePage() const { openModal } = useModal() @@ -406,6 +403,23 @@ const Application = ({ }, id: 'sidebar__button__members', }, + { + label: 'Export your data', + icon: ( + + + + ), + variant: 'transparent', + labelClick: () => { + return openModal(, { + showCloseIcon: true, + width: 'large', + }) + }, + id: 'sidebar__button__export', + pastille: counts[team.id] ? counts[team.id] : undefined, + }, ]} > {currentUserIsCoreMember && } @@ -422,6 +436,7 @@ const Application = ({ team, translate, showSearchScreen, + openModal, ]) const sidebarFooter = useMemo(() => { @@ -450,31 +465,11 @@ const Application = ({ id: 'sidebar__button__shared', }, ]} - > - {isTimeEligibleForDiscount(team) && subscription == null ? ( - - - - } - id='sidebar__button__promo' - label={translate(lngKeys.SidebarNewUserDiscount)} - labelClick={() => { - trackEvent(MixpanelActionTrackTypes.DiscountSidebar) - return openModal(, { - showCloseIcon: true, - width: 'large', - }) - }} - /> - ) : null} - + /> ) - }, [team, translate, pathname, subscription, push, sendToElectron, openModal]) + }, [team, translate, pathname, push, sendToElectron]) return ( <> @@ -535,7 +530,6 @@ const Application = ({ } /> -
{ const { pageFolder, team, currentUserIsCoreMember } = usePage() @@ -236,6 +237,7 @@ const FolderPage = () => { + { + const { team } = usePage() + const [sending, setSending] = useState(false) + + const handleExportClick = useCallback(async () => { + if (team == null || sending) { + return + } + + setSending(true) + try { + const blob = await exportWorkspace(team.id) + + const url = window.URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `space-${team.name}-export.zip` + document.body.appendChild(a) + a.click() + a.remove() + window.URL.revokeObjectURL(url) + } catch (err) { + console.error('Failed to export space', err) + } finally { + setSending(false) + } + }, [team, sending]) + + if (team == null) { + return null + } + + return ( + +
+
Export your space data
+
+

+ The service for boostnote is planned to be retired at the end of + September. We recommend exporting your space's data so that you do + not lose any of your information. +

+

Here is an overview of what can be exported:

+
    +
  • Public & your accessible private Folders & documents hierarchy
  • +
  • Your Documents' content
  • +
  • Your Documents'attachments
  • +
+ + + + Download ZIP + + +
+ ) +} + +const Container = styled.div` + text-align: center; + .export__modal__subtitle { + span { + font-size: ${({ theme }) => theme.sizes.fonts.md}px; + display: inline-block; + margin-right: ${({ theme }) => theme.sizes.spaces.sm}px; + } + + margin-bottom: ${({ theme }) => theme.sizes.spaces.md}px; + } + + .export__modal__header { + text-align: center; + } + + .export__modal__title { + margin: 0; + margin-top: ${({ theme }) => theme.sizes.spaces.md}px; + font-size: ${({ theme }) => theme.sizes.fonts.l}px; + } + + .export__modal__header > * + .export__modal__header > * { + margin-top: ${({ theme }) => theme.sizes.spaces.df}px; + } + + .export__modal__description { + margin: ${({ theme }) => theme.sizes.spaces.df}px 0; + text-transform: uppercase; + color: ${({ theme }) => theme.colors.text.subtle}; + font-size: ${({ theme }) => theme.sizes.fonts.md}; + } + + li { + list-style: none; + } +` + +export default ExportModal diff --git a/src/cloud/components/Onboarding/FolderPageExportSection.tsx b/src/cloud/components/Onboarding/FolderPageExportSection.tsx new file mode 100644 index 0000000000..827f07948c --- /dev/null +++ b/src/cloud/components/Onboarding/FolderPageExportSection.tsx @@ -0,0 +1,86 @@ +import React from 'react' +import ColoredBlock from '../../../design/components/atoms/ColoredBlock' +import Flexbox from '../../../design/components/atoms/Flexbox' +import styled from '../../../design/lib/styled' +import Button from '../../../design/components/atoms/Button' +import { mdiExport } from '@mdi/js' +import ExportModal from '../Modal/contents/ExportModal' +import { useModal } from '../../../design/lib/stores/modal' +import { ExternalLink } from '../../../design/components/atoms/Link' + +const FolderPageExportSection = () => { + const { openModal } = useModal() + + return ( + + + +
BoostNote is getting discontinued
+ + + Learn more + +
+ +

+ We thank you for your continued support. We regret to inform you + that the service will end at the end of September. As such we + recommend for users to export their data to make sure nothing is + being lost. +

+ +
+
+
+ ) +} + +const FolderPageExportSectionContainer = styled.div` + margin: ${({ theme }) => theme.sizes.spaces.df}px + ${({ theme }) => theme.sizes.spaces.sm}px; + + .export__section__block { + input { + color: ${({ theme }) => theme.colors.text.subtle}; + } + h5 { + color: ${({ theme }) => theme.colors.text.primary}; + margin: ${({ theme }) => theme.sizes.spaces.sm}px 0; + font-size: ${({ theme }) => theme.sizes.fonts.l}px; + } + p { + color: ${({ theme }) => theme.colors.text.primary}; + font-size: ${({ theme }) => theme.sizes.fonts.df}px; + margin-bottom: ${({ theme }) => theme.sizes.spaces.sm}px; + } + .form__row__items { + > * { + margin-bottom: ${({ theme }) => theme.sizes.spaces.sm}px; + } + flex-wrap: wrap; + } + } + + .link { + color: ${({ theme }) => theme.colors.text.subtle} !important; + } +` + +export default FolderPageExportSection diff --git a/src/cloud/components/WorkspacePage/index.tsx b/src/cloud/components/WorkspacePage/index.tsx index 9a85a6b6a5..6036c36592 100644 --- a/src/cloud/components/WorkspacePage/index.tsx +++ b/src/cloud/components/WorkspacePage/index.tsx @@ -18,6 +18,7 @@ import { ViewsManager } from '../Views' import ApplicationPageLoader from '../ApplicationPageLoader' import LoaderFolderPage from '../../../design/components/atoms/loaders/LoaderFolderPage' import ViewerDisclaimer from '../ViewerDisclaimer' +import FolderPageExportSection from '../Onboarding/FolderPageExportSection' const WorkspacePage = ({ workspace: pageWorkspace, @@ -133,6 +134,7 @@ const WorkspacePage = ({ +