Skip to content

Commit 96579ad

Browse files
committed
add temporary export modal in sidebar
1 parent 3a4e1a0 commit 96579ad

File tree

4 files changed

+165
-24
lines changed

4 files changed

+165
-24
lines changed

src/cloud/api/teams/export.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { callApiBlob } from '../../lib/client'
2+
3+
export async function exportWorkspace(teamId: string) {
4+
const data = await callApiBlob(`api/teams/${teamId}/export`, {
5+
method: 'get',
6+
})
7+
return data
8+
}

src/cloud/components/Application.tsx

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { mapUsers } from '../../design/lib/mappers/users'
3030
import {
3131
mdiCog,
3232
mdiDownload,
33-
mdiGiftOutline,
33+
mdiExclamationThick,
3434
mdiInbox,
3535
mdiLogoutVariant,
3636
mdiMagnify,
@@ -49,8 +49,6 @@ import {
4949
import { useModal } from '../../design/lib/stores/modal'
5050
import NewDocButton from './buttons/NewDocButton'
5151
import { useCloudSidebarTree } from '../lib/hooks/sidebar/useCloudSidebarTree'
52-
import { isTimeEligibleForDiscount } from '../lib/subscription'
53-
import DiscountModal from './Modal/contents/DiscountModal'
5452
import { Notification as UserNotification } from '../interfaces/db/notifications'
5553
import useNotificationState from '../../design/lib/hooks/useNotificationState'
5654
import { useNotifications } from '../../design/lib/stores/notifications'
@@ -78,6 +76,7 @@ import SidebarSubscriptionCTA from './Subscription/SidebarSubscriptionCTA'
7876
import { isEmpty } from 'lodash'
7977
import LoaderTopbar from '../../design/components/atoms/loaders/LoaderTopbar'
8078
import Icon from '../../design/components/atoms/Icon'
79+
import ExportModal from './Modal/contents/ExportModal'
8180

8281
interface ApplicationProps {
8382
className?: string
@@ -101,7 +100,6 @@ const Application = ({
101100
permissions = [],
102101
currentUserPermissions,
103102
currentUserIsCoreMember,
104-
subscription,
105103
navigatingBetweenPage,
106104
} = usePage()
107105
const { openModal } = useModal()
@@ -451,30 +449,27 @@ const Application = ({
451449
},
452450
]}
453451
>
454-
{isTimeEligibleForDiscount(team) && subscription == null ? (
455-
<SidebarButton
456-
variant='subtle'
457-
icon={
458-
<WithPastille>
459-
<Icon size={16} path={mdiGiftOutline} />
460-
</WithPastille>
461-
}
462-
id='sidebar__button__promo'
463-
label={translate(lngKeys.SidebarNewUserDiscount)}
464-
labelClick={() => {
465-
trackEvent(MixpanelActionTrackTypes.DiscountSidebar)
466-
return openModal(<DiscountModal />, {
467-
showCloseIcon: true,
468-
width: 'large',
469-
})
470-
}}
471-
/>
472-
) : null}
452+
<SidebarButton
453+
variant='subtle'
454+
icon={
455+
<WithPastille>
456+
<Icon size={16} path={mdiExclamationThick} />
457+
</WithPastille>
458+
}
459+
id='sidebar__button__promo'
460+
label={'Export your data'}
461+
labelClick={() => {
462+
return openModal(<ExportModal />, {
463+
showCloseIcon: true,
464+
width: 'large',
465+
})
466+
}}
467+
/>
473468
</SidebarButtonList>
474469
<SidebarSubscriptionCTA />
475470
</>
476471
)
477-
}, [team, translate, pathname, subscription, push, sendToElectron, openModal])
472+
}, [team, translate, pathname, push, sendToElectron, openModal])
478473

479474
return (
480475
<>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React, { useCallback, useState } from 'react'
2+
import styled from '../../../../design/lib/styled'
3+
import { usePage } from '../../../lib/stores/pageStore'
4+
import { LoadingButton } from '../../../../design/components/atoms/Button'
5+
import { exportWorkspace } from '../../../api/teams/export'
6+
import Flexbox from '../../../../design/components/atoms/Flexbox'
7+
8+
const ExportModal = () => {
9+
const { team } = usePage()
10+
const [sending, setSending] = useState(false)
11+
12+
const handleExportClick = useCallback(async () => {
13+
if (team == null || sending) {
14+
return
15+
}
16+
17+
setSending(true)
18+
try {
19+
const blob = await exportWorkspace(team.id)
20+
21+
const url = window.URL.createObjectURL(blob)
22+
const a = document.createElement('a')
23+
a.href = url
24+
a.download = `workspace-export.zip`
25+
document.body.appendChild(a)
26+
a.click()
27+
a.remove()
28+
window.URL.revokeObjectURL(url)
29+
} catch (err) {
30+
console.error('Failed to export workspace', err)
31+
} finally {
32+
setSending(false)
33+
}
34+
}, [team, sending])
35+
36+
if (team == null) {
37+
return null
38+
}
39+
40+
return (
41+
<Container className='export__modal'>
42+
<header className='export__modal__header'>
43+
<div className='export__modal__title'>Export your workspace data</div>
44+
</header>
45+
<p className='export__modal__description'>
46+
The service for boostnote is planned to be retired at the end of
47+
September. We recommend exporting your workspace&apos;s data so that you
48+
do not lose any of your information.
49+
</p>
50+
<p>Here is an overview of what can be exported:</p>
51+
<ul>
52+
<li>Folders & documents hierarchy</li>
53+
<li>Documents&apos; markdown content</li>
54+
<li>Documents&apos;ttachments</li>
55+
</ul>
56+
57+
<Flexbox justifyContent='center'>
58+
<LoadingButton
59+
disabled={sending || team == null}
60+
spinning={sending}
61+
onClick={handleExportClick}
62+
>
63+
Download ZIP Export
64+
</LoadingButton>
65+
</Flexbox>
66+
</Container>
67+
)
68+
}
69+
70+
const Container = styled.div`
71+
.export__modal__subtitle {
72+
span {
73+
font-size: ${({ theme }) => theme.sizes.fonts.md}px;
74+
display: inline-block;
75+
margin-right: ${({ theme }) => theme.sizes.spaces.sm}px;
76+
}
77+
78+
margin-bottom: ${({ theme }) => theme.sizes.spaces.md}px;
79+
}
80+
81+
.export__modal__header {
82+
text-align: center;
83+
}
84+
85+
.export__modal__title {
86+
margin: 0;
87+
margin-top: ${({ theme }) => theme.sizes.spaces.md}px;
88+
font-size: ${({ theme }) => theme.sizes.fonts.l}px;
89+
}
90+
91+
.export__modal__header > * + .export__modal__header > * {
92+
margin-top: ${({ theme }) => theme.sizes.spaces.df}px;
93+
}
94+
95+
.export__modal__description {
96+
margin: ${({ theme }) => theme.sizes.spaces.df}px 0;
97+
text-transform: uppercase;
98+
color: ${({ theme }) => theme.colors.text.subtle};
99+
font-size: ${({ theme }) => theme.sizes.fonts.md};
100+
}
101+
`
102+
103+
export default ExportModal

src/cloud/lib/client.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,38 @@ export async function callPdfApi(
102102
retry: 0,
103103
}).blob()
104104
}
105+
106+
export async function callApiBlob(
107+
pathname: string,
108+
{
109+
method = 'get',
110+
search,
111+
headers = {},
112+
signal,
113+
body,
114+
}: CallCloudJsonApiParameter = {}
115+
) {
116+
const mergedHeaders = {
117+
...headers,
118+
}
119+
const accessToken = getAccessToken()
120+
if (
121+
usingLegacyElectron &&
122+
usingElectron &&
123+
accessToken != null &&
124+
mergedHeaders['Authorization'] == null
125+
) {
126+
mergedHeaders['Authorization'] = `Bearer ${accessToken}`
127+
}
128+
return ky(pathname.startsWith('/') ? pathname.substring(1) : pathname, {
129+
prefixUrl: boostHubBaseUrl,
130+
headers: mergedHeaders,
131+
method,
132+
searchParams: search,
133+
signal,
134+
body,
135+
timeout: 60 * 1000,
136+
credentials: usingElectron ? undefined : 'include',
137+
retry: 0,
138+
}).blob()
139+
}

0 commit comments

Comments
 (0)